c++中的SFINAE和enable_if,如果经常写模板的话,应该听到看到这些东西.
SFINAE
首先介绍下SFINAE,参考官方介绍,中文是“模板替换失败不是一种错误”,看个代码示例
class Test {
using newType = int;
};
// 1
template<typename T>
void f(T arg) {
cout<<"old type"<<endl;
}
// 2
template <typename T>
void f(T::newType arg) {
cout<<"new type"<<endl;
}
f<int>(10); // 输出 old type
f<Test>(10); // 输出new type
这里,当将函数 f 用int类型实例化后,他可以匹配第一个函数模板,但无法匹配第二个函数模板,因为int内部没有一个叫newType的类型,int::newType是不合法的,但是这里不会报错,这其实就是SFINAE的核心。虽然第二个模板匹配失败了,但他可以匹配第一个模板函数,所以模板替换失败了不是一种错误,会让编译器去寻找下一个匹配的模板,这里的f<Test>,两个都可以匹配,但第二个模板匹配的更加具体,所以优先级高于第一个。
SFINAE要配合enable_if的,才能显示效果,下面说说 enable_if.
enable_if
enable_if是c++标准库实现的,内部实现类似于这样:
// 1
template<bool U, class T = void>
struct enable_if {};
// 2
template<class T>
struct enable_if<true, T> { using type = T; };
第二个结构体模板,其实就是第一个的偏特化版本,对第一个模板参数为true的时候做了一种偏特化。
举个使用实例:
typename std::enable_if<true, int>::type t; //编译通过, t是int类型
typename std::enable_if<false, int>::type t2; //编译失败,因为struct enable_if中没有type类型
这里其实还有一点type traits的概念,中文叫 “类型萃取”,这个在c++模板元编程中,以及c++ 的STL中,都是非常常见的用法,这里不再介绍了。
从上面的实例可以看到,如果第一个模板参数是bool, 那么编译可以成功,如果false,编译就是失败了,联合SFINANE,就可以做很多了,比如:
// std:;is_inergral
template <typename T>
typename std::enable_if<std::is_integral<T>::value>::type test(T value)
{
std::cout<<"T is int"<<std::endl;
}
// 注意 这里是 !std::is_intergral, 是 非
template <typename T>
typename std::enable_if<!std::is_integral<T>::value>::type test(T value)
{
std::cout<<"T is not int"<<std::endl;
}
test("abc"); // 输出 T is not int
test(1); // 输出T is int
因为"abc"是字符串,但是第一个模板函数要求是int, 所以enable_if的第一个模板参数是false, 那么就导致enable_if中没有type类型,导致编译不过,但由于SFINAE机制,不会报错,所以去寻找下一个可匹配的模板函数。
SFINAE,enable_if在模板中很常见,是元编程的基础技术。这里抛出引玉 :)