C++之SFINAE机制和模板元编程
替换失败不是一个错误SFINAE(Substitution Failure Is Not An Error)是C++模板编程中的一个重要概念。
它指的是在模板实例化过程中,如果某个模板参数替换导致编译错误,编译器不会立即报错,而是会尝试其
他可能的模板实例化。
模板支持推导和替换任意类型参数, 但每种类型支持的操作并非都相同, 因此一份模板函数不可能解决所有情况.
当一份标准模板函数无法解决特定情况时,按条件匹配进入特定模板函数就变成了不可或缺.
enable_if
enable_if 利用了模板匹配的技巧和struct结构, 巧妙的将条件匹配分割成两种情况,
一种是true的情况: 为结构绑定一个type
一种是false的情况: 采取留空策略
template <bool _Test, class _Ty = void> struct enable_if {}; // no member "type" when !_Test // 这个实现叫做部分具体化(partial specialization), 即: 第一个参数 (<true>) 采用具体值. // 这个模板函数简要但是浓缩了几个规则: // 当调用时传递的参数是true时, 一定会进入这个函数. // 当调用时传递的参数是true且不提供其他参数时, <class _Ty> 会把自动合并上面一个enable_if的 <class _Ty = void>; // 当调用时传递的参数是true且提供其他参数时, _Ty 会替换成传递参数的类型(放弃void). template <class _Ty> struct enable_if<true, _Ty> { // type is _Ty for _Test using type = _Ty; };
enable_if_t
enable_if_t 强制使用 enable_if 的 ::type 来触发 SFINAE 规则, 如果失败则跳过当前匹配进入下一个匹配.template <bool _Test, class _Ty = void> using enable_if_t = typename enable_if<_Test, _Ty>::type;
enable_if_t 节省掉了 typename 和 ::type , 是因为它背后使用 using 帮我们多写了typename 和 ::type (即: 强行重定义了类型).
#include <iostream> using std::cout; using std::endl; // 省略了 typename // 省略了 ::type template<typename T, enable_if_t<is_integral<T>::value, int> = 0> void example(T t) { cout << "template<typename T = book::author_type > void example();" << endl; } int main(void) { example(10); return 0; }
限制模板函数的参数类型
enable_if<is_integral<T>::value, int> 等同于 enable_if<true, int> 或者 enable_if<false, int> ,
如果第一个参数是 true , 那么 enable_if 这个结构就有一个 type 成员, 这时调用 enable_if::type 就是OK的.
如果第一个参数是 false, 那么 enable_if 这个结构就没有一个 type 成员, 这时调用 enable_if::type 就是失败的, 但是不会报错而是跳过当前匹配.
enable_if 的第二个参数提供了一个int, 所以 enable_if::type 其实就是一个 int, 也就是说
<typename T, typename enable_if<is_integral<T>::value, int>::type = 0> 等同于
<typename T, typename int = 0> 等同于
<typename T, int = 0>
#include <iostream> #include <type_traits> using namespace std; template<typename T, typename enable_if<is_integral<T>::value, int>::type = 0> void example(T t) { cout << "template<typename T = book::author_type > void example();" << endl; } int main(void) { example(10); return 0; }
后面那个 = 0 是个什么东西?
由于 enable_if 第二个类型设定了默认为 void 类型,
typename enable_if::type 等同于 typename enable_if::void, 本身不会报错,
由于 enable_if 或 enable_if_t 都是采取 匿名类型参数 形式来完成判断行为,
由于 匿名类型参数 必须要有一个默认值,
因此 typename enable_if::void = 0 是失败的, 它会导致程序跳过这个匹配.
因此 大部分的STL、TR1 的代码都会在使用 enable_if 或者 enable_if_t 时, 为第二个类型提供一个 int, 然后设定一个 0 为默认值.
#include <iostream> #include <type_traits> template <class T> typename std::enable_if<std::is_integral<T>::value, bool>::type is_odd (T i) { return bool(i % 2); } template < class T, class = typename std::enable_if<std::is_integral<T>::value>::type> bool is_even (T i) { return !bool(i % 2); } int main() { short int i = 1; std::cout << std::boolalpha; std::cout << "i is odd: " << is_odd(i) << std::endl; std::cout << "i is even: " << is_even(i) << std::endl; return 0; }
模板类型偏特化
在使用模板编程时,可以利用std::enable_if的特性根据模板参数的不同特性进行不同的类型选择。如下所示,我们可以实现一个检测变量是否为智能指针的实现:
#include <iostream> #include <type_traits> #include <memory> template <typename T> struct is_smart_pointer_helper : public std::false_type {}; template <typename T> struct is_smart_pointer_helper<std::shared_ptr<T>> : public std::true_type {}; template <typename T> struct is_smart_pointer_helper<std::unique_ptr<T>> : public std::true_type {}; template <typename T> struct is_smart_pointer_helper<std::weak_ptr<T>> : public std::true_type {}; template <typename T> struct is_smart_pointer : public is_smart_pointer_helper<typename std::remove_cv<T>::type> {}; template <typename T> typename std::enable_if<is_smart_pointer<T>::value, void>::type check_smart_pointer(const T &t) { std::cout << "is smart pointer" << std::endl; } template <typename T> typename std::enable_if < !is_smart_pointer<T>::value, void >::type check_smart_pointer(const T &t) { std::cout << "not smart pointer" << std::endl; } int main() { int *p(new int(2)); std::shared_ptr<int> pp(new int(2)); std::unique_ptr<int> upp(new int(4)); check_smart_pointer(p); check_smart_pointer(pp); check_smart_pointer(upp); return 0; }
参考:极光火狐狸CPP
浙公网安备 33010602011771号