c++ 模板参数为原始C字符串文字推断错误,但对std::string_view有效

0md85ypi  于 2023-03-05  发布在  其他
关注(0)|答案(2)|浏览(141)

我正在用C设计一个PEG解析器,该解析器应该同时支持std::string_viewstd::span<Token>作为令牌流输入。
在代码中,我看到一个模板类只能由一些代码片段(如auto p2 = lit_(std::string_view("world"));)示例化,而不能由代码auto p1 = lit_("world");示例化。我的意思是模板推导不允许从C字符串常量转换为std::string_view。这里是一个简化的代码来演示这个问题,它应该由C
20构建。

#include <span>
#include <string_view>
#include <vector>

struct Token
{
};

template <typename T>
struct Viewer;

// explicit specialization for T = Token
template <>
struct Viewer<Token>
{
    using type = std::span<Token>; // std::span or derived class
};

// explicit specialization for T = char
template <>
struct Viewer<char>
{
    using type = std::string_view;
};

// alias template
template <typename T> using ViewerT = typename Viewer<T>::type;

template <typename Base, typename T>
struct parser_base {
    using v = ViewerT<T>;
    using charType = T;
};

// literal string match, for std::string_view, it could match a string
// for std::span<Token>, it will match a stream of Tokens defined by the span<Token>
template<typename V>
struct lit_ final : public parser_base<lit_<V>, typename V::value_type> {
    /// @brief Construct a lit_ parser.
    /// @param[in] str The string literal to parse.
    //template<typename V>
    constexpr lit_(V str) noexcept
        : str(str)
    {}

private:
    V str;
};

int main()
{

    //auto p1 = lit_("world");  // build error if uncommented
    auto p2 = lit_(std::string_view("world"));

    Token a;
    std::vector<Token> tokens;
    tokens.push_back(a);
    tokens.push_back(a);
    tokens.push_back(a);
    std::span<Token> match(tokens.begin(), tokens.size());
    auto p3 = lit_(match);  //
    return 0;
}

它演示了charstd::string_view)的流或Tokenstd::span<Token>)的流都可以构造为lit_(字面量)。
有没有解决这个问题的办法?
谢谢。

wfauudbj

wfauudbj1#

模板参数推导不考虑隐式转换,这是正确的。
但是,您可以使用C++17 class template argument deduction

// (Place this after your class lit_ definition)

// If constructed with a const char*, assume template is a std::string_view.
lit_(const char*) -> lit_<std::string_view>;

Now your commented line compiles .

cgvd09ve

cgvd09ve2#

您可以使构造函数模板接受各种类型,这些类型可以转换为std::string_viewstd::span<Token>,并为构造函数提供一个 constrained 演绎指南

// literal string match, for std::string_view, it could match a string
// for std::span<Token>, it will match a stream of Tokens defined by the span<Token>
template<typename V>
struct lit_ final : public parser_base<lit_<V>, typename V::value_type> {
    /// @brief Construct a lit_ parser.
    /// @param[in] str The string literal to parse.
    template<typename R>
    constexpr lit_(R&& str) noexcept
        : str(std::forward<R>(str))
    {}

private:
    V str;
};

template<std::ranges::borrowed_range R>
  requires std::convertible_to<R, std::string_view>
lit_(R&&) -> lit_<std::string_view>;

template<std::ranges::borrowed_range R>
  requires std::convertible_to<R, std::span<Token>>
lit_(R&&) -> lit_<std::span<Token>>;

这使得lit_接受任何可以转换为std::string_viewstd::span<Token>的参数:

auto l1 = lit_("world"); // ok
auto l2 = lit_(std::string_view("world")); // ok
// auto l3 = lit_(std::string("world")); // error, rvalue string does not model borrowed_range

std::vector<Token> v;
Token arr[4] = {};
auto l4 = lit_(v);    // ok
auto l5 = lit_(arr);  // ok

相关问题