std::enable_if的原理和用法

std::enable_if的原理

在很多模板编程中,我们经常看到std::enable_if的使用,其实std::enable_if的源代码很简单。

  // enable_if的原始模板定义,_Tp默认为void。
  template<bool, typename _Tp = void>
    struct enable_if
    { };

  // enable_if的一个模板特化,表示当传入的bool参数为true时,定义enable_if的一个类型别名type。
  template<typename _Tp>
    struct enable_if<true, _Tp>
    { typedef _Tp type; };

总结一句话就是enable_if<false>时没有enable_if<>::type定义,enable_if<true>时存在enable_if<>::type定义且默认type为void

std::enable_if<true, int>::type b;  // 相当于int b;

前面提到当bool参数为为false时,就没有enable_if<>::type定义,如果没有type定义还强制使用,那么会发生什么呢?自然是编译错误:

std::enable_if<false, int>::type b;  // error: ‘type’ is not a member of ‘std::enable_if<false, int>’

SFINE替换失败并非错误

Substitution failure is not an error (SFINAE) 。
SFINAE是我们网上经常看到的词语,它表示的是C++语言在模板参数匹配失败时不认为这是一个编译错误。具体说,当创建一个重载函数的候选集时,某些(或全部)候选函数是用模板实参替换(可能的推导)模板形参的模板实例化结果。如果某个模板的实参替换时失败,编译器将在候选集中删除该模板,而不是当作一个编译错误从而中断编译过程。像下面这个例子就不是在模板匹配阶段产生的错误,所以会在编译报错:

std::enable_if<false, int>::type b;  // 编译失败的原因是这个错误不是发生在 模板参数匹配 的过程

因此std::enbale_if通常用在模板匹配中才有意义。

std::enable_if的用法

std::enable_if的最常用用法就是对模板类的模板类型T进行一些检查,比如有下面一个模板函数,我们希望当输入的T是整型数据类型和浮点数据类型的时候匹配不同的函数,该如何实现呢?

template <typename T>
void foo(T value) {
    std::cout << "aaaaa" << std::endl;
}

可能会第一时间想到模板特化,或者函数重载的方式;但问题是整型数据类型有很多(int,short…),浮点数据类型也有多个(float,double);那就意味着要写很多个这样的函数重载或者模板匹配:

// 模板特化,不能覆盖所有类型,且不优雅
template <>
void foo(int value) {
    std::cout << "aaaaa" << std::endl;
}

template <>
void foo(float value) {
    std::cout << "aaaaa" << std::endl;
}

// 其他数据类型的函数模板特化
....

可以看到上面的方式不太优雅,此刻std::enable_if就排上用场了,搭配std::is_integral和std::is_floating_point即可:其中std::enable_if_t就是std::enable_if<…>::type的重定义。

#include <type_traits>
#include <iostream>

template <typename T>
std::enable_if_t<std::is_integral<T>::value> foo(T value) {
    std::cout << "这是一个整型类型" << std::endl;
}

template <typename T>
std::enable_if_t<std::is_floating_point<T>::value> foo(T value) {
    std::cout << "这是一个浮点类型" << std::endl;
}

int main() {
    foo(42); 
    foo(3.14); 
    return 0;
}

所以这里可以看到std::is_integral::value是实现这个需求的关键,只有当传入的数据类型T是整型的时候,std::is_integral::value=true,enable_if::type才被定义,因此匹配成功。
注意点一:
除了上面的写法,还可以这么写:写法①

template <typename T>
void foo(T value, typename std::enable_if_t<std::is_integral<T>::value>* = nullptr) {
    std::cout << "这是一个整型类型" << std::endl;
}

template <typename T>
void foo(T value, typename std::enable_if_t<std::is_floating_point<T>::value>* = nullptr) {
    std::cout << "这是一个浮点类型" << std::endl;
}

也可以这样写:写法②

template <typename T, typename std::enable_if_t<std::is_integral<T>::value>* = nullptr>
void foo(T value) {
    std::cout << "这是一个整型类型" << std::endl;
}

template <typename T, typename std::enable_if_t<std::is_floating_point<T>::value>* = nullptr>
void foo(T value) {
    std::cout << "这是一个浮点类型" << std::endl;
}

但是不能这样写:写法③

// error: redefinition of ‘template<class T, class> void foo(T)’
// 在编译前就把这两个模板函数识别为相同的模板定义
template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
void foo(T value) {
    std::cout << "这是一个整型类型" << std::endl;
}

template <typename T, typename = std::enable_if_t<std::is_floating_point<T>::value>>
void foo(T value) {
    std::cout << "这是一个浮点类型" << std::endl;
}

但是为什么可以写法①和②没有问题?不解,有大佬说int*,char* , double* 在模板签名里看就是不同的类型了,不懂
注意点二:
上面的实现都是基于不同的模板定义实现,而不是模板特化。为什么提这点,因为很容易理解偏了把这两个想成是模板特化,但实际就是不同的模板定义。下面这种就会报错:

template <typename T>
void foo(T value) {
    std::cout << "这是一个嘿嘿嘿" << std::endl;
}

template <typename T, typename std::enable_if_t<std::is_integral<T>::value>* = nullptr>
void foo(T value) {
    std::cout << "这是一个整型类型" << std::endl;
}

int main() {
    foo(42); 
    return 0;
}

说到模板特化,这里也提一嘴,模板函数不能部分特化,模板类可以部分特化:

// 成功:模板里可以部分特化:
template <typename T, typename F>
class A{
};

template <typename F>
class A<int, F>{
};

// error:  non-class, non-variable partial specialization ‘B<int, F>’ is not allowed
template <typename T, typename F>
void B(){
};
template <typename F>
void B<int, F>(){
};
  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值