如何为返回类型未知的可调用对象启用C++模板偏特化?
实现支持字符串字面量与任意可调用对象的Parser组合子偏特化
针对你开发解析器组合子库的需求,我们可以基于C++11的特性,分两步完成模板偏特化,既不依赖std::function,又能让is_convertible<T, parser<T>>作为合法解析器的判断谓词。
一、字符串字面量的偏特化实现
首先处理字符串字面量的场景,我们可以同时支持std::string和const char*两种输入,让使用更顺手:
#include <string> #include <type_traits> // 假设你有自定义的optional和parse_stream类型 template <typename T> class optional; struct parse_stream { bool starts_with(const std::string& s) const; // 实现流前缀检查逻辑 void consume(std::size_t n); // 实现消费流中指定长度字符的逻辑 }; // 基础parser模板 template <typename P, typename allow=void> struct parser; // 偏特化支持std::string template <> struct parser<std::string> { using result_type = std::string; // 支持从std::string或const char*直接构造 parser(const std::string& s) : target_(s) {} parser(const char* s) : target_(s) {} optional<result_type> operator()(parse_stream stream) const { if (stream.starts_with(target_)) { stream.consume(target_.size()); return target_; } return optional<result_type>(); // 返回空值表示匹配失败 } private: std::string target_; }; // 额外偏特化支持const char*字面量(满足is_convertible<const char*, parser<const char*>>) template <> struct parser<const char*> { using result_type = std::string; parser(const char* s) : impl_(s) {} optional<result_type> operator()(parse_stream stream) const { return impl_(stream); } private: parser<std::string> impl_; };
这样无论是std::string对象,还是直接写"hello"这类字面量,都能直接转换成对应的parser实例,满足第一个需求。
二、任意可调用对象的偏特化(不依赖std::function)
核心问题是如何匹配返回类型未知但签名符合要求的可调用对象。我们用SFINAE+模板元编程来检测可调用对象的签名是否满足:接收parse_stream,返回optional<Result>(Result为任意类型)。
第一步:定义签名检测Trait
先实现一个Trait,用来判断某个类型是否是合法的解析器可调用对象:
// 基础Trait:默认不是合法解析器可调用对象 template <typename F, typename = void> struct is_valid_parser_callable : std::false_type {}; // C++11没有std::is_callable,我们自己实现一个替代版 template <typename F, typename Arg, typename = void> struct is_callable_with : std::false_type {}; template <typename F, typename Arg> struct is_callable_with<F, Arg, typename std::enable_if< std::is_same< decltype(std::declval<F>()(std::declval<Arg>())), typename std::result_of<F(Arg)>::type >::value >::type> : std::true_type {}; // 特化Trait:当F可以被parse_stream调用,且返回值是optional<R>时,标记为合法 template <typename F> struct is_valid_parser_callable<F, typename std::enable_if< is_callable_with<F, parse_stream>::value, typename std::enable_if< std::is_same< decltype(std::declval<F>()(std::declval<parse_stream>())), optional<typename std::result_of<F(parse_stream)>::type> >::value >::type >::type> : std::true_type {};
第二步:基于Trait的Parser偏特化
利用SFINAE,让parser模板匹配所有符合条件的可调用对象:
// 偏特化:匹配所有合法的解析器可调用对象 template <typename F> struct parser<F, typename std::enable_if<is_valid_parser_callable<F>::value>::type> { // 提取可调用对象的返回类型 using result_type = typename std::result_of<F(parse_stream)>::type; // 直接存储原可调用对象,避免std::function的额外开销 parser(F func) : func_(std::move(func)) {} // 转发调用到存储的可调用对象 optional<result_type> operator()(parse_stream stream) const { return func_(stream); } private: F func_; };
这样一来,普通函数、lambda表达式、自定义Functor,只要签名符合optional<R>(parse_stream),都会自动匹配这个偏特化,且保持原生类型的效率。
三、验证谓词is_convertible<T, parser<T>>的有效性
现在验证设计是否满足你的需求:
- 对于
std::string:is_convertible<std::string, parser<std::string>>为true,因为parser<std::string>有对应的构造函数。 - 对于
const char*:is_convertible<const char*, parser<const char*>>为true,因为我们偏特化了parser<const char*>。 - 对于合法的lambda:
auto int_parser = [](parse_stream s) -> optional<int> { // 自定义整数解析逻辑 return optional<int>(42); };is_convertible<decltype(int_parser), parser<decltype(int_parser)>>为true,因为偏特化版本有接受F的构造函数。
补充说明
- 如果你使用自定义
optional类型,只需替换代码中的optional为你的类型即可。 - 如果可调用对象的参数是
const parse_stream&或parse_stream&,只需调整Trait中的parse_stream为对应的引用类型。 - C++11的
std::result_of对lambda和Functor的支持稳定,只要参数类型严格匹配即可。
内容的提问来源于stack exchange,提问作者gct
相关产品推荐
相关产品推荐

