最近整理代码时发现了有人常会使用std::enable_if_t
,据说这个是C++14才支持的写法,因此再次勾起了我的整理欲。但要是熟悉std::enable_if
的话其实也没啥太大难度,自认为这种使用方式主要提供了一种通过模板偏特化来实现的类型筛选机制,某些情况下在设计复杂工程的泛化处理时能提供一些方便。但能力有限,目前我还没有发现哪些非常典型的使用场景能大幅提升性能。
不过整理之前感觉有必要先引入一个很重要的概念:SFINAE
,这是英文Substitution failure is not an error的缩写,意思是匹配失败不是错误。这句话的意思是:我们使用模板函数时编译器会根据传入的参数来推导适配最合适的模板函数,在某些情况下,推导过程会发现某一个或者某几个模板函数推导起来其实是无法编译通过的,但只要有一个可以正确推导并编译出来,则那些推导得到的可能产生编译错误的模板函数就并不会引发编译错误,即匹配失败不是错误。下面举个栗子:
struct testA
{
int data;
testA(int val) :data(val){};
};
struct testB :testA
{
typedef double value;
testB(int val) :testA(val){};
};
template<typename T>
typename T::value add(T t1, T t2)
{
return t1.data + t2.data;
}
int add(testA t1, testA t2)
{
return t1.data + t2.data;
}
从代码编写角度来说,乍一看总感觉模板函数存在有问题:在未明确输入类型是testA
还是testB
时就贸然使用了其中的value
类型,如果是其他场景一般会引起编译器的报错,理论上着实也不甚严谨。 但依托于模板特化的运作机制,编译器进行类型推导时会尝试适配该模板,SFINAE
特性会引导编译器在遭遇特化失败后放弃该模板并转向其他函数,且不会报错。
下面时执行结果:
int _tmain(int argc, _TCHAR* argv[]){
testA a(1), b(2);
testB c(3), d(4);
add(a, b);
add(c, d);
return 0;
}
当然,我这个小例子中使用了一些对于结构体struct的派生继承,其实对于C++来说,对于struct的使用也已经提升到了新的层面,其中的使用场景我一会再总结个小文章吧,点击前往。
现在开始描述下std::enable_if
的使用方式吧,std::enable_if
顾名思义,满足条件时类型有效。作为选择类型的小工具,其广泛的应用在 C++ 的模板元编程(meta programming)中。基本实现方式大约为:
template<bool B, class T = void>
struct enable_if {};
template<class T>
struct enable_if<true, T> { typedef T type; };
一个是普通版本的模板类定义,一个偏特化版本的模板类定义。
主要在于第一个参数是否为true
,当第一个模板参数为false
的时候并不会定义type
,只有在第一模板参数为true
的时候才会定义type
。
typename std::enable_if<true, int>::type t;
typename std::enable_if<true>::type;
typename std::enable_if<false>::type;
typename std::enable_if<false, int>::type t2;
网上扒过来了一个用于偏特化的小例子:
template <typename T>
typename std::enable_if<std::is_trivial<T>::value>::type SFINAE_test(T value)
{
std::cout<<"T is trival"<<std::endl;
}
template <typename T>
typename std::enable_if<!std::is_trivial<T>::value>::type SFINAE_test(T value)
{
std::cout<<"T is none trival"<<std::endl;
}
下面是一个用于校验函数模板参数类型的小例子:
template <typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
is_odd(T t) {
return bool(t%2);
}
template <typename T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
bool is_even(T t) {
return !is_odd(t);
}
到这里对于enable_if_t
就更通俗易懂了:
template <bool _Test, class _Ty = void>
using enable_if_t = typename enable_if<_Test, _Ty>::type;
可以直接拿来作为类型来使用,以下是我从网上找的一些简单事例。
1、作为函数参数或返回值:
template<typename T>
struct Check1
{
template<typename U = T>
U read(typename std::enable_if_t<std::is_same_v<U, int> >* = nullptr) {
return 42;
}
template<typename U = T>
typename std::enable_if_t<std::is_same_v<U, double>, U> read() {
return 3.14;
}
}
作为模板参数:
template<typename T>
struct Check2
{
template<typename U = T, typename std::enable_if_t<std::is_same_v<U, int>, int> = 0>
U read() {
return 42;
}
template<typename U = T, typename std::enable_if_t<std::is_same_v<U, double>>* = nullptr>
U read() {
return 3.14;
}
};
类型偏特化:
template<typename T, typename = void>
struct zoo;
template<typename T>
struct zoo<T, std::enable_if_t<std::is_integral_v<T>>>
{
};
最后查阅到据说C++ 20中通过concepts又做了一些简化:
#ifdef __cpp_lib_concepts
#include <concepts>
#endif
template<std::integral T>
void display_concepts_1(T num)
{
}
void display_concepts(std::integral auto num)
{
}
void display_concepts(std::floating_point auto num)
{
}
等价于:
template<typename T, typename std::enable_if_t<std::is_integral_v<T>>* = nullptr>
void display_1(T num)
{
}
template<typename T>
void display(typename std::enable_if_t<std::is_integral_v<T>>* = nullptr)
{
}
template<typename T>
void display(typename std::enable_if_t<std::is_floating_point_v<T>>* = nullptr)
{
}
就先写到这里吧,期待下次遇到有意思的问题继续记录。
参考文章:
[1]: https://blog.csdn.net/jeffasd/article/details/84667090
[2]: https://blog.csdn.net/kpengk/article/details/119979733
[3]: https://www.jianshu.com/p/45a2410d4085