文章目录
背景
在元编程过程中,我们经常会使用enable_if这个特性,enable_if是基于C++中的SFINEA(Substitution failure is not anerror,中文直译即是“匹配失败不是错误”)实现的,SFINEA的意思是在实例化过程中,比如有三个模板,但凡能匹配到一个正确的,另外两个模板在实例化过程中即使报错,编译器也认为没问题。
enable_if
enable_if定义在头文件<type_traits>中,简单的实现如下:
template<bool B, class T = void>
struct enable_if {};
template<class T>
struct enable_if<true, T> { typedef T type; };
使用场景
场景一:类模板偏特化
简单来说,就是对类模板中的某个参数类型进行特化处理。具体的代码使用如下:
#include <iostream>
template<class T, class Enable = void>
class A {
public:
A() { std::cout << "primary template\r\n"; }
}; // primary template
template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
public:
A() { std::cout << "partial specialization\r\n"; }
}; // specialization for floating point types
int main() {
std::shared_ptr<A<int>> a1 = std::make_shared<A<int>>();//primary
auto a2 = std::make_shared<A<double>>();//specialization
}
场景二:函数参数(不建议这么写,可以参考这里写法)
考虑一个场景,我们写了软件统计景区一个月的人数,由于黄山和黑山客流量根本不是一个数量级,所以景区买的服务器也不一样,比如黄山的磁盘可以存64bit的大小,而黑山景区的磁盘只有16bit的大小,所以这个函数参数要支持uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t。如果写成<typename T>的话,部分粗心工作人员可能会传入小数,那么结果就完全炸裂,为此需要把T限制为std::is_integral::value == true, 所以enable_if的作用就是限制参数类型的,这样可以让代码在编译器就给出报错信息,避免运行时挂掉出错,有点类似于static_assert:
struct A{};
template<typename T>
struct Traits{
static const bool is_basic = true;
};
template<>
struct Traits<A>{
static const bool is_basic = false;
};
template<typename T>
void f(T a, typename user_enable_if<Traits<T>::is_basic, void>::type* dump= 0){
cout<<"a basic type"<<endl;
}
template<typename T>
void f(T a, typename user_enable_if<!Traits<T>::is_basic, void>::type* dump= 0){
cout<<"a class type"<<endl;
}
int main(){
A a;
f(1);//a basic type
f(a);//a class type
}
需要注意的是下面写法会报错,因为出现了两个合适的模板,自己写的时候要注意:
template<typename T1, typename T2>
void show(T1 par_1, T2 par_2)
{
std::cout<<par_1<<std::endl;
std::cout<<par_2<<std::endl;
};
template<typename T1, typename T2, typename dummpy=std::enable_if_t<sizeof(T2)==8>>
void show(T1 par_1, T2 par_2)
{
std::cout<<"enable if"<<std::endl;
};
场景三:函数返回值
和上面的使用类似
struct A{};
template<typename T>
struct Traits{
static const bool is_basic = true;
};
template<>
struct Traits<A>{
static const bool is_basic = false;
};
template<typename T>
typename user_enable_if<Traits<T>::is_basic, T>::type f(T a){
cout<<"a basic type"<<endl;
return a;
}
template<typename T>
typename user_enable_if<!Traits<T>::is_basic, T>::type f(T a){
cout<<"a class type"<<endl;
return a;
}
int main(){
A a;
f(1);
f(a);
}
这里需要注意两问题,第一、struct中如果想用元模板编程,数值类型必须要用static修饰,不然没办法直接用A::is_basic来调取结果;第二、强烈建议是constexpr来代替const, 因为constexpr更加能说明是在编译器做的相关操作,这个值是一个编译期就确定的。