C++模板参数推导问题:C字符串字面量无法转为std::string_view
解决C++20 PEG解析器中lit_模板无法推导C字符串字面量的问题
问题核心是当传入C字符串字面量(如"world")时,模板参数V会被推导为const char[6],而非预期的std::string_view,导致与现有Viewer体系不兼容。以下是两种可行的解决方案:
方案1:使用类模板推导指引(CTAD)+ 模板构造函数
通过添加推导指引告诉编译器如何将原始输入类型映射到目标V类型,同时修改构造函数支持隐式转换:
#include <span> #include <string_view> #include <vector> struct Token { }; template <typename T> struct Viewer; template <> struct Viewer<Token> { using type = std::span<Token>; }; template <> struct Viewer<char> { using type = std::string_view; }; template <typename T> using ViewerT = typename Viewer<T>::type; template <typename Base, typename T> struct parser_base { using v = ViewerT<T>; using charType = T; }; template<typename V> struct lit_ final : public parser_base<lit_<V>, typename V::value_type> { // 模板构造函数:接受可转换为V的任意类型 template <typename U> constexpr lit_(U&& str) noexcept : str(std::forward<U>(str)) {} private: V str; }; // 推导指引:C字符串字面量 → lit_<std::string_view> template <size_t N> lit_(const char (&)[N]) -> lit_<std::string_view>; // 推导指引:const char* → lit_<std::string_view> lit_(const char*) -> lit_<std::string_view>; // 推导指引:Token数组 → lit_<std::span<Token>> template <size_t N> lit_(Token (&)[N]) -> lit_<std::span<Token>>; // 可选:支持直接传入std::vector<Token> lit_(std::vector<Token>&) -> lit_<std::span<Token>>; int main() { auto p1 = lit_("world"); // 现在可正常编译 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); Token arr[3] = {}; auto p4 = lit_(arr); // 自动推导为lit_<std::span<Token>> return 0; }
原理说明
- 模板构造函数允许接受任意可隐式转换为
V的类型,比如const char[]可以转换为std::string_view。 - 推导指引明确告诉编译器,当传入C字符串、Token数组等类型时,直接推导
V为对应的std::string_view或std::span<Token>,跳过原始数组类型的推导。
方案2:使用工厂函数封装类型推导
如果希望用户调用更简洁,可以定义一个工厂函数lit,自动完成类型映射:
#include <span> #include <string_view> #include <vector> #include <type_traits> struct Token { }; template <typename T> struct Viewer; template <> struct Viewer<Token> { using type = std::span<Token>; }; template <> struct Viewer<char> { using type = std::string_view; }; template <typename T> using ViewerT = typename Viewer<T>::type; template <typename Base, typename T> struct parser_base { using v = ViewerT<T>; using charType = T; }; template<typename V> struct lit_ final : public parser_base<lit_<V>, typename V::value_type> { constexpr lit_(V str) noexcept : str(str) {} private: V str; }; // 工厂函数:自动推导目标V类型 template <typename U> constexpr auto lit(U&& str) { using RawType = std::remove_cvref_t<U>; if constexpr (std::is_array_v<RawType> || std::is_same_v<RawType, const char*>) { return lit_<std::string_view>(std::forward<U>(str)); } else if constexpr (std::is_convertible_v<U, std::span<Token>>) { return lit_<std::span<Token>>(std::forward<U>(str)); } else { // 回退到直接推导原始类型 return lit_(std::forward<U>(str)); } } int main() { auto p1 = lit("world"); // 自动生成lit_<std::string_view> 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; }
原理说明
- 工厂函数通过
constexpr if分支判断输入类型,将C字符串、Token数组等自动转换为对应的lit_实例,用户无需手动指定模板参数。
内容的提问来源于stack exchange,提问作者ollydbg
相关产品推荐
相关产品推荐

