C++ SFINAE简介和std::enable_if_t的简单使用

最近整理代码时发现了有人常会使用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)	// Definition #1
{
	return t1.data + t2.data;
}
int add(testA t1, testA t2)	// Definition #2
{
	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);	// Call #2. Without error (even though there is no testA::value) thanks to SFINAE.
	add(c, d);	// Call #1
	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; //正确,type等同于int
typename std::enable_if<true>::type; //可以通过编译,没有实际用处,推导的模板是偏特化版本,第一模板参数是true,第二模板参数是通常版本中定义的默认类型即void,但是一般也用不上它。
typename std::enable_if<false>::type; //无法通过编译,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
{
    //如果T的类型是int,则定义函数 int read(void* = nullptr)
	template<typename U = T>
	U read(typename std::enable_if_t<std::is_same_v<U, int> >* = nullptr) {
		return 42;
	}
    
    //如果T的类型是double,则定义函数 double read()
	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
{
    //如果T的类型是int,则定义函数 int read()
	template<typename U = T, typename std::enable_if_t<std::is_same_v<U, int>, int> = 0>
	U read() {
		return 42;
	}
    
    //如果T的类型是double,则定义函数 double read()
	template<typename U = T, typename std::enable_if_t<std::is_same_v<U, double>>* = nullptr>
	U read() {
		return 3.14;
	}
};

类型偏特化:

// T是其它类型
template<typename T, typename = void>
struct zoo;

// 如果T是整型(我从网上扒代码时原文这里写的浮点,我觉得应该是整型吧,
//这里的std::is_integral_v应该是C++新标准里的特性,但也类似于std::is_integral<T>::value,据说在C++17标准之后了。)
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

// 如果T是整数类型
template<std::integral T>
void display_concepts_1(T num)
{
}

// 如果T是整数类型
void display_concepts(std::integral auto num)
{
}

// 如果T是浮点数类型
void display_concepts(std::floating_point auto num)
{
}

等价于:

// 如果T是整数类型
template<typename T, typename std::enable_if_t<std::is_integral_v<T>>* = nullptr>
void display_1(T num)
{
}

// 如果T是整数类型
template<typename T>
void display(typename std::enable_if_t<std::is_integral_v<T>>* = nullptr)
{
}

// 如果T是浮点数类型
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

  • 18
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值