C++ enable_if 探究

在C++11中充斥着大量的enable_if, 那么enable_if是什么呢,为何要引入它呢,它有什么作用呢,,,,

在这之前,我们先了解下C++模板推导的机理。

1. 前序:C++模板推导机理

模板推导过程中,编译器会根据具体调用时的类型,来进行模板推导,并找出最适合的一个模板,注意是最适合的,所以编译器需要把所有对应的模板全部看一遍,才能知道哪个是最适合的。

比如:

//1
void func(int a, int b){
   cout << "int and int"<<endl;
}
//2
template<typename T1, typename T2>
void func(T1 a, T2 b){
	cout << "T1 and T2" <<endl;
}
//3
template<typename T>
void func(T a, T b){
	cout <<"T and T" <<endl;
}
//4
template<>
void func<bool, int>(bool a, int b){
	cout << "bool and int <>" <<endl;
}
//5
template<>
void func<int, int>(int a, int b){
	cout << "int and int <>" <<endl;
}
//6
template<bool,  typename T>
void func(bool a, T b){
	cout << "bool and T" <<endl;
}

int main(){
    func(5, 6);                     // int and int
    func(5U, 6);                    // T1 and T2
    func(5U, 6U);                   //T and T
    func(false, 6U);                // T1 and T2
    func(false, 6);                 // bool and int <>
    func<false, int>(false, 6U);    // bool and T
    return 0;
}

C++在函数模板推导之前,会根据形参类型,首先查看有没有形参匹配的非模板函数void func(int a, int b),如果匹配,则直接会调用此函数。如果不匹配,则会开始查找模板。首先会找到个最泛化的模板版本template<typename T1, typename T2> void func(T1 a, T2 b),再找特化的版本template<> void func<bool, int>(bool a, int b) , 对于template<typename T> void func(T a, T b)可以理解为是泛化版本中满足连个形参相同的一个函数定义。template<bool, typename T> void func(bool a, T b),只是利用了函数模板传参而已(template<bool a, typename T>, 我省列了a)。

  • func(5,6)会直接找非模板版本,此时找到了void func(int a, int b),那么就不会接着往下找模板版本了(包括模板的特化版本,这也是为何输出是int and int而非 int and int <>的原因)。

  • func(5U,6)第一个形参unsigned int与void func(int a, int b)不匹配 ,此时便会寻找模板,在模板中,发现template<typename T1, typename T2> void func(T1 a, T2 b)匹配。而其他的模板都不匹配。

  • func(5U, 6U),同理,会发现,模板中两个匹配分别为template<typename T1, typename T2> void func(T1 a, T2 b)template<typename T> void func(T a, T b),但显然后一个要求更苛刻(两个形参类型相同)所以这里就选择了后一个template<typename T> void func(T a, T b)。(注意,此处并非偏特化,c++函数不支持偏特化,类模板才支持)。

  • func(false, 6U),同理,template<typename T1, typename T2> void func(T1 a, T2 b)满足。template<> void func<bool, int>(bool a, int b)特化为了bool和int,第二个参数不是unsigned int, 所以不满足。

  • func(false, 6),同理,template<typename T1, typename T2> void func(T1 a, T2 b)满足,template<> void func<bool, int>(bool a, int b)也满足,但是后者是特化版本,所以调用了template<> void func<bool, int>(bool a, int b)

  • func<false, int>(false, 6U),此处显示声明了带参模板类型为<false, unsigned int>, 由于带参的模板函数只有template<bool, typename T> void func(bool a, T b), 此时会调用此函数,并且6U会隐式转化为int类型。

现在函数模板的推导调用顺序已经清楚了。那么出现了一个新问题:

在C++模板类中由于有泛型模板、特化、偏特化,那么就会出现很多的模板定义,当这些模板类作为形参传递给模板函数时,由于函数模板推导需要把所有的模板进行参数替换匹配一遍,那么就可能会出现一个问题,也就是有些模板函数会出现错误,如果编译器进行报错的话,那么就会使模板的编程变得特别复杂,那么如何才能解决这个问题呢。由此引入了SFINAE机制(Substitution Failure Is Not An Error)。

2. SFINAE

且看这个例子:

int add(int a,  int b){
    return a+b;
}
template<typename T>
typename T::value_type add(const T& a, const T& b){
    return a+b;
}
int main(){
    add(6U, 5U); //result:11, called the first function, because of the second would fail.
}

add(6U, 5U)的形参是unsigned int不满足int add(int a, int b),会进行调用模板的版本,进行模板推导,但是显然unsigned int::value_type 是错误的,unsigned int 内没有value_type,但是编译器没有报错,并且输出了结果为11。这是因为在推导模板过程发现了错误,摒弃了模版template<typename T> typename T::value_type add(const T& a, const T& b),转而调用int add(int a, int b)并对形参进行了隐式转换(unsigned int --> int)。

在最新的C++11标准中,阐述了当某些模板替换失败时,比如下面的例子,那么不进行报错,编译器会忽略这个在替换过程中出现错误的模板版本,转而看其他的可选模板。此标准的陈述如下:

If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.

如果所有的模板都出错被丢弃而导致都没有匹配上怎么办,答案就是会编译报错

3. enable_if

有了上述的铺垫,我们现在再看看enable_if。

3.1 什么是enable_if.

源码中的定义如下:

// Primary template.
/// Define a member typedef @c type only if a boolean constant is true.
template<bool, typename _Tp = void>
    struct enable_if
    { };

// Partial specialization for true.
template<typename _Tp>
    struct enable_if<true, _Tp>
    { typedef _Tp type; };

其实 enable_if 是可用用来帮助编译器在编译期间进行模板选择的struct。利用了c++的traits编程技法。

这两个版本的定义中,第一个是一个一般的泛化模板定义,第二个是偏特化定义。当模板的第一个参数是true时,就会调用偏特化的版本,而不是第一个泛化的版本。

enable_if<true, T> //用的是偏特化的版本
enable_if<false, T> // 用的是泛化的

3.2 enable_if的使用方法

下面为enable_if的一种使用方法:

//定义一个person类
class person{};

//定义一个dog类
class dog{};

//用来判断是否是person类
template<typename T>
struct is_person{
   static const bool value = false;
} ;

template<>
struct is_person<person>{
   static const bool value = true;
};

//当T为person时,被调用
template<typename T, typename std::enable_if<is_person<T>::value, T> :: type* = nullptr>
void func(T t){
    // do something;
    cout << "person stuff"<<endl;
}

void func(int a){
    cout << "int type" <<endl;
}

//当T不是person时,被调用
template<typename T, typename std::enable_if<!is_person<T>::value, T> :: type* = nullptr>
void func(T t){
    // do something;
    cout << "not person stuff"<<endl;
}

int main(){
    func(5);  		// int type
    func(person());     // person stuff
    func(dog());	// not person stuff
}

这个例子中,我们定义了is_person结构体,其person的特化版本内的value = true,泛化版本value = false,由此结合enable_if的定义,以及前面介绍的SFINAE,我们不难得出上面的输出结果。因为enable_if<true, T>::type是没有错误的,但enable_if<false, T>内并无type,所以会出错丢弃对应的出错模板,采用其他模板。

enable_if可能看起来使用比较麻烦,C++14引入了一个enable_if_t ,其定义如下:

template <bool B, typename T = void>
using enable_if_t = typename enable_if<B, T>::type;

所以可以用enable_if_t来替换上面的enable_if。其实感觉并没有书写简化多少。

4. 总结

C++基于自身的SFIANE法则,利用函数模板在推导过程中把出错的模板摒弃掉,进而选择最匹配的模板板门进行调用。enable_if为程序员提供了那些模板在编译期间被摒弃掉,那些在特定的模板参数类型下要使用。从而选择正确的模板进行编译匹配。

原文链接: https://www.cnblogs.com/yb-blogs/p/13702717.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    C++ enable_if 探究

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/202999

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月12日 下午9:21
下一篇 2023年2月12日 下午9:21

相关推荐