模板参数推断原始 C 字符串文字的错误,但适用于 std::string_view

template argument deduce error for raw C string literal but works for std::string_view

提问人:ollydbg23 提问时间:2/26/2023 更新时间:2/26/2023 访问量:99

问:

我正在用 C++ 设计一个 PEG 解析器,解析器应该同时支持 和 作为令牌流输入。std::string_viewstd::span<Token>

在代码中,我看到一个模板类只能由一些代码片段实例化,例如 ,而不能由代码实例化。我的意思是模板推导不允许从 C 字符串文字转换为 . 这里有一个简化的代码来演示这个问题,它应该由 C++20 构建。auto p2 = lit_(std::string_view("world"));auto p1 = lit_("world");std::string_view

#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_viewTokenstd::span<Token>lit_

关于如何解决这个问题的任何想法?

谢谢。

C++ C++20 字符串视图 标准跨度

评论

0赞 ALX23z 2/26/2023
我相信您可以添加推导指南,说明从字符数组/指针构造会导致 .(也许,您需要添加另一个构造函数才能正常工作)。lit_<std::string_view>
0赞 ollydbg23 2/26/2023
嗨,@ALX23z谢谢,你是对的,Draw 的回答也给了我解决方案。

答:

2赞 Drew Dormann 2/26/2023 #1

您说得对,模板参数推导不考虑隐式转换。

但是,您可以使用 C++17 类模板参数推导

// (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>;

现在,您的注释行已编译

评论

0赞 ollydbg23 2/26/2023
嗨,谢谢你帮助我。虽然我是 C++ 的长期用户,但这是我第一次看到 .如果我没记错的话,PEG 解析器代码最初只支持解析 ,而在那个时候,我可以直接使用 C 文字字符串构造 ,这意味着隐式转换有效。但后来我通过添加对 的支持来扩展,我猜编译器不知道它是否应该隐式转换为 或 .User-defined deduction guidesstd::string_viewlit_<std::string_view>std::span<Token>string_viewspan<Token>
1赞 康桓瑋 2/26/2023 #2

可以使构造函数模板接受可以转换为 或 的各种类型,并为构造函数提供约束推导std::string_viewstd::span<Token>

// 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>>;

这使得接受任何可以转换为 a 的参数以及: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

评论

0赞 ollydbg23 2/26/2023
哇!这太棒了,谢谢。顺便说一句:我只看到一些像这样的代码,我从来没有看到过放在里面。高级 C++ 功能(也许是 C++ 20),尤其是与模板相关的功能对我来说就像一种新的编程语言,我会找一些时间来学习它们。template<typename T>std::ranges::borrowed_range<>