如何为模板的依赖式返回类型命名且避免重复定义?
借助std::enable_if或std::conditional,我们可以实现返回类型依赖于模板参数的模板函数,比如cppreference上std::enable_if的简化示例:
template<class T> typename std::enable_if<std::is_trivially_default_constructible<T>::value>::type construct(T*) { std::cout << "default constructing trivially default constructible T\n"; }
现在的问题是:如何在函数体内定义一个可直接指代该返回类型的别名?
首先要明确一个坑:正如cppreference相关文档的注意事项所提示的,不能直接把条件表达式放到函数模板的默认参数里,否则会触发重定义错误:
default template arguments are not accounted for in function template equivalence
比如下面的代码就是错误的:
/* WRONG */ struct T { enum { int_t, float_t } type; template<typename Integer, typename = std::enable_if_t<std::is_integral<Integer>::value>> T(Integer) : type(int_t) {} template<typename Floating, typename = std::enable_if_t<std::is_floating_point<Floating>::value>> T(Floating) : type(float_t) {} // 错误:被视为重定义 };
常规的做法是在函数体内用using声明重复一遍类型定义,但这意味着要维护两份完全相同的逻辑,而且如果是嵌套的std::conditional_t,可读性会很差(哪怕比传统CRTP好一些)。
有没有不用重复定义就能为该类型命名的方法?
可行方案
1. 提取公共类型特征模板
把返回类型的判断逻辑提取到一个独立的模板中,这样既可以在返回类型处使用,也能在函数体内复用:
template<class T> struct ConstructReturnType { using type = typename std::enable_if<std::is_trivially_default_constructible<T>::value>::type; }; template<class T> typename ConstructReturnType<T>::type construct(T*) { using ReturnType = typename ConstructReturnType<T>::type; // 现在可以在函数体内使用ReturnType std::cout << "default constructing trivially default constructible T\n"; }
这种方式把逻辑集中在一处,避免了重复定义,可读性也更好。
2. C++14及以后:用auto返回类型推导+decltype
如果你的编译器支持C++14及以上,可以用返回类型推导简化写法,同时结合decltype在函数体内获取返回类型:
template<class T> auto construct(T*) -> typename std::enable_if<std::is_trivially_default_constructible<T>::value>::type { using ReturnType = decltype(construct<T>(nullptr)); std::cout << "default constructing trivially default constructible T\n"; }
注意这里的construct<T>(nullptr)只是用于推导类型,不会实际执行函数调用。
3. C++20及以后:用概念(Concepts)简化写法
C++20引入的概念可以彻底替代std::enable_if的写法,同时类型别名的定义也更直观:
template<std::trivially_default_constructible T> void construct(T*) { using ReturnType = void; // 针对更复杂的依赖类型场景: // using ReturnType = std::conditional_t<std::is_trivially_default_constructible<T>::value, void, SomeOtherType>; std::cout << "default constructing trivially default constructible T\n"; }
用概念的写法不仅更简洁,还能避免之前的重定义问题,代码可读性大幅提升。
内容的提问来源于stack exchange,提问作者Zsar

