自学内容网 自学内容网

【标准库的典型内容】std::enable_if

一、 SFINAE

在介绍 s t d : : e n a b l e _ i f std::enable\_if std::enable_if之前,先介绍一个概念: S F I N A E SFINAE SFINAE,全称是: S u b s t i t u t i o n   F a i l u r e   i s   n o t   a n   E r r o r Substitution\ Failure \ is \ not\ an\ Error Substitution Failure is not an Error,即“替换失败不是一个错误”。

S F I N A E SFINAE SFINAE可以看做是 C + + C++ C++语言的一种特性或模板设计中要遵循的一个重要原则,非常重要,务必要理解好。


现在,我们通过一个小样例来了解一下 S F I N A E SFINAE SFINAE,参考下方代码:


template<typename T>
typename T::size_type mydouble(const T& t) {
return t[0] * 2;
}

比如,这里我们写了一个函数模板,返回类型是 T : : s i z e _ t y p e T::size\_type T::size_type,这就要求这个返回类型必须是个类类型,而且其中必须有个类名叫作 s i z e _ t y p e size\_type size_type

如果此时我们调用

mydouble(15);

编译器将报错:
在这里插入图片描述
显然,这里编译器并不认为 m y d o u b l e mydouble mydouble这个函数模板有问题,而是找不到匹配的模板实例。因此,这就叫作 S F I N A E SFINAE SFINAE,即“替换失败不是一个错误”。


如果我们使用正确的类类型呢:

std::vector<int>vec;
vec.push_back(1);
std::cout << mydouble(vec) << "\n";

此时将编译通过,因为在 s t d : : v e c t o r std::vector std::vector中存在一个类型名 s i z e _ t y p e size\_type size_type,因此 T : : s i z e _ t y p e T::size\_type T::size_type合法。


二、 s t d : : e n a b l e _ i f std::enable\_if std::enable_if的认识与使用

2.1 s t d : : e n a b l e _ i f std::enable\_if std::enable_if的基本认识

我们先看看,在标准库中是怎么实现的 s t d : : e n a b l e _ i f std::enable\_if std::enable_if

//struct template enable_if

template<bool _Test, class _Ty = void> //泛化版本
struct enable_if {

};

template<class _Ty> //偏特化版本
struct enable_if<true, _Ty> {
using type = _Ty;

};

首先, s t d : : e n a b l e _ i f std::enable\_if std::enable_if是一个结构体模板,有一个特化版本。
当传入的第一个 b o o l bool bool类型的参数为 t r u e true true时,将调用特化版本,在特化版本的结构体中存在一个类型为 t y p e type type

这就是基于 S F I N A E SFINAE SFINAE特性实现的一个类似于编译期间条件分支的作用。


2.2 s t d : : e n a b l e _ i f std::enable\_if std::enable_if的使用

2.2.1 基本使用

下面是一个最简单的使用,参考下方代码:

void Test2() {
//条件为真,存在type成员
std::enable_if<(3 > 2)>::type* mypoint1 = nullptr; //调用了偏特化版本

//条件为假,不存在type类型成员
std::enable_if<(3 < 2)>::type* mypoint2 = nullptr; //调用了偏特化版本

}

对于上面的第一个情况,由于 ( 3 > 2 ) = t r u e (3>2) = true (3>2)=true,因此将调用偏特化版本,而偏特化版本存在 t y p e type type类型名,因此能够顺利编译通过。

而第二种情况,由于 ( 3 < 2 ) = f a l s e (3<2)=false (3<2)=false,因此将调用泛化版本,而泛化版本中不存在这么一个 t y p e type type类型名,因此无法通过编译。


2.2.2 用于函数模板

由于 s t d : : e n a b l e _ i f < > : : t y p e std::enable\_if<>::type std::enable_if<>::type是一个类型,因此它可以作为返回值,通过条件来判断是否存在这么一个返回值类型。 参考下方代码:

//std::enable_if用于函数模板中
template<typename T>
typename std::enable_if < (sizeof(T) > 2) > ::type funceb() {
std::cout << "类型大小大于2个字节,调用函数模板!\n";
}

void Test3() {
funceb<int>(); //成功

funceb<char>(); //失败,char为一个字节
}

这里 s i z e o f ( ) sizeof() sizeof()可以在编译期间确定,因此我们在调用模板的时候就能知道类型的大小了,也就可以知道是否可以调用到泛化版本还是偏特化版本了。

显然, c h a r char char类型大小为 1 1 1字节,所以无法实例化出存在 t y p e type type的模板,因此就会编译失败,而 i n t int int类型是 4 4 4字节,因此是可以的。


s t d : : e n a b l e _ i f std::enable\_if std::enable_if用于函数模板的时候,也可以存在形参。参考下方代码:

//也可以有参数
template<typename T>
std::enable_if_t<(sizeof(T) == 4), T> funceb3(T&& t) {
t++;
return t;
}

void Test5() {
int t = funceb3(10);

std::cout << t << "\n";
}

注意写法,这个 T T T是放在第一个 b o o l bool bool类型之后的,就像前面代码所示,默认为 v o i d void void

在这里插入图片描述


2.2.3 类型简写

s t d : : e n a b l e _ i f std::enable\_if std::enable_if可以通过类型别名来简化代码,而标准库为我们实现了这个方法,我们可以通过 s t d : : e n a b l e _ i f _ t std::enable\_if\_t std::enable_if_t来作为其的别名。 参考下方代码:

//泛化版本
template<bool _Test, class _Ty = void> //泛化版本
struct enable_if {

};

//偏特化版本
template<bool _Test, class _Ty = void>
using enable_if_t = typename enable_if<_Test, _Ty>::type;

由于 t y p e n a m e e n a b l e _ i f < _ T e s t , _ T y > : : t y p e typename enable\_if<\_Test,\_Ty>::type typenameenable_if<_Test,_Ty>::type是一个类型,所以使用 u s i n g using using来取别名是可以的。

因此,我们可以这样简写:

//std::enable_if的简写
template<typename T>
std::enable_if_t<(sizeof(T) > 2)> funceb2() {

}

2.2.4 用于类模板中

在之前,我们遇到了一个问题:在拷贝构造的时候,在类中拷贝构造函数没有被调用,而是转而调用构造函数模板,那么这个问题在这里就可以使用 s t d : : e n a b l e _ i f std::enable\_if std::enable_if来解决,回顾问题:完美转发
这里,参考下方代码:

/使用std::enable_if解决拷贝构造函数无法被调用

class Human {
//使用别名来简化代码
template<typename T>
using StrProType = std::enable_if_t<std::is_convertible<T, std::string>::value>;
private:
std::string m_name;
public:
//完美转发构造函数模板
template<typename T, typename U = StrProType<T>>
//如果能隐式转换为string类型就可以构造

Human(T&& tmpname) :m_name(std::forward<T>(tmpname)) {
std::cout << "Human(T&& tmpname)执行了\n";
}

//拷贝构造函数
Human(const Human& th) :m_name(th.m_name) {
std::cout << "Human(Human const&th)执行了\n";
}

Human(Human&& th) :m_name(std::move(th.m_name)) {
std::cout << "Human(Human && th)执行了\n";
}

};

我们引入的新的一个模板 s t d : : i s _ c o n v e r t i b l e std::is\_convertible std::is_convertible,这是一个用于判断两个类型是否能隐式转换的函数模板,返回一个 b o o l bool bool类型。下面介绍一下简单的使用方法:

void Test6() {
//判断是否能隐式转换
std::cout << "std::string -> double: " << std::is_convertible<std::string, double>::value << "\n";
std::cout << "char -> int: " << std::is_convertible<char, int>::value << "\n";
std::cout << "const char* -> std::string: " << std::is_convertible<const char*, std::string>::value << "\n";
}

运行结果:

在这里插入图片描述


下面我们来看我们对构造函数模板的改造:

using StrProType = std::enable_if_t<std::is_convertible<T, std::string>::value>;

//完美转发构造函数模板
template<typename T, typename U = StrProType<T>>
//如果能隐式转换为string类型就可以构造

Human(T&& tmpname) :m_name(std::forward<T>(tmpname)) {
std::cout << "Human(T&& tmpname)执行了\n";
}

为了防止代码过长,我们使用了类型别名来简化代码。

我们利用了 s t d : : e n a b l e _ i f _ t std::enable\_if\_t std::enable_if_t,然后传入 s t d : : i s _ c o n v e r t i b l e std::is\_convertible std::is_convertible,传入当前的类型和 s t d : : s t r i n g std::string std::string类型,如果当前类型能与 s t d : : s t r i n g std::string std::string类型隐式转换,那么就会返回 t r u e true true,因此 s t d : : e n a b l e _ i f _ t std::enable\_if\_t std::enable_if_t就能被实例化出,因此就会调用构造函数模板。反之,将调用拷贝构造函数模板。


再一次调用代码:

void Test7() {
Human h1("ZhangSan");

Human h2(h1);

const char* str = "LiSi";
Human h3(str);
}

这一次,成功调用了拷贝构造函数:

在这里插入图片描述


原文地址:https://blog.csdn.net/Antonio915/article/details/142412968

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!