- 作者: 雪山肥鱼
- 时间:20220213 12:22
- 目的: enable_if
# 编译器的选择规则
# 替换失败并不是一个错误(SFINAE)
# enable_if
## 基础认识
## enable_if 用于函数模板中
## enable_if 用于类模板中
编译器的选择规则
函数模板 与 普通函数重载
using namespace std;
template <typename T>
void myfunc(const T &t) {
cout << "myfunc 函数模板被执行" << endl;
}
void myfunc(int tmpvalue) {
cout << "myfunc 普通函数 被执行" << endl;
}
int main(int argc, char**argv) {
myfunc(15);
return 0;
}
结果是,优先调用普通函数。
如果将myfunc 形参类型修改成
myfunc(unsigned int tmpvalue) {
}
则优先调用函数模板
从编译器角度来看,15是有符号 int。如果调用普通函数,则需要一次类型转换。所以编译器认为函数模板更为合适。
如果传参是 15U,即 myfunc(15U) 则调用普通函数。
再次修改
myfunc(unsigned short tmpvalue) {
}
同理会调用函数模板
所以编译器对于函数的选则来说,有自己的一套规则,并不总是对普通函数优先选择,函数模板靠后选择。
替换失败并不是一个错误(SFINAE)
Substitution Failure Is Not An Error
c++ 语言的一种特性,一种编译器对模板设计的重要原则。
这个特性是针对 函数模板重载 而言的。
对于函数模板而言,当用一个具体类型(int)替换函数模板的参数时,可能会产生意想不到的问题。
- 比如产生一些毫无意义甚至是看起来语法上有错误的代码。编译器对这些代码并不一定报错。有时候会忽略
忽略的原因: 编译器认为函数模板不匹配针对本次的函数调用。
举例,int 带入 上述 myfunc 函数模板中后,进入myfunc,发现myfunc自己会有错误,则编译器会忽略掉。认为这个函数模板不匹配本次函数调用。可以理解为,编译器自行排错。
就当这个函数模板不存在一样!转而去选择其他更匹配的函数或者函数模板。
以上即 SFINAE概念。
举例:
/*
typename T::size_type, 类型说明,mydouble 要返回的类型
*/
template <typename T>
typename T::size_type mydouble(const T &t) {//本函数对不对取决于T的类型
return t[0] * 2;
}
int main(int argc, char**argv) {
mydouble(15);
return 0;
}
- 用int 替换:
typename int::size_type mydouble(const int &t);
对于int来讲,并不存在 size_type 这个成员。
由于不合适,所以编译器去找其他重载的。所以提供一个合适的即可:
/*
typename T::size_type, 类型说明,mydouble 要返回的类型
*/
template <typename T>
typename T::size_type mydouble(const T &t) {//本函数对不对取决于T的类型
return t[0] * 2;
}
int mydouble(int i) {
return i * 2;
}
int main(int argc, char**argv) {
mydouble(15);
return 0;
}
举例:调用函数模板:
/*
typename T::size_type, 类型说明,mydouble 要返回的类型
*/
template <typename T>
typename T::size_type mydouble(const T &t) {//本函数对不对取决于T的类型
return t[0] * 2;
}
int mydouble(int i) {
return i * 2;
}
int main(int argc, char**argv) {
vector<int> myvec;
myvec.push_back(15);
cout << mydouble(myvec) << endl;
return 0;
}
在vector中是存在 T::size_type 这个类型的。
SFINAE特性:编译器虽然看不出函数模板实例化后的对错(比如一些无效的类型,无效的表达式),但是我能决定是否选择这个函数模板。当编译器觉得这个函数模板不合适的始化,编译器不会报错,但编译器会无视你的存在,从而去找其他函数or函数模板。
enable_if
基础认识
c++11新标准引入的类模板。具有SFINAE特性。定位是一个helper辅助助手模板。
辅助其他模板设计。
作用:编译器的分支逻辑(编译器就可以确定哪条分支)。
enable_if:
//泛化版本
template <bool _Test, class _Ty = void>
struct enable_if {};
//偏特化版本(数量的偏特化,_Test 非类型模板参数)
//只有偏特化版本存在,才存在一个叫做type类型的别名(类型)
//偏特化,有点条件分支语句的意思
template<class _Ty>
struct enable_if<true, _Ty> {
using type = _Ty;
};
注释中的话。
引例:
template <typename T>
struct MEB {
using type = T;
};
int main(int argc, char**argv) {
MEB<int>::type abc = 15;//MEB<int>::type 即int类型
enable_if 用于函数模板中
举例1:
template <bool _Test, class _Ty = void>
struct enable_if {};
template<class _Ty>
struct enable_if<true, _Ty> {
using type = _Ty;
};
template <typename T>
struct MEB {
using type = T;
};
int main(int argc, char**argv) {
//MEB<int>::type abc = 15;//MEB<int>::type 即int类型
std::enable_if<(3 > 2)>::type *mypoint = nullptr; //void*mypoint1 = nullptr;
return 0;
}
编译通过,type是有默认值,这个默认值对偏特化也有效。
3>2 是true,所以存在type类型。type类型就是 _Ty类型 void。
std::enable_if<(3<2)>::type* mypiont1 = nullptr;
就注定会报错。对应的泛化版本,没有type功能。即无中生有。
enable_if 典型应用是作为函数与模板的返回类型。
template <typename T>
typename std::enable_if<(sizeof(T) > 2)>::type funceb() {
//
}
int main(int argc, char **argv) {
funceb<int>();
return 0;
}
编译通过。int > 2
上述模板等价于
void funceb(){}
如果传入的是char就报错,无type类型
funceb<char>();//报错,type就没有,会继续寻找其他重载函数。
char类型 并不满足 char >2, 所以去寻找了 enable_if 的泛化版本,泛化版本中并没有定义 type 类型。根据 SFNASE,编译器丢掉这个函数模板。
c++14写法
template <typename T>
std::enable_if_t<(sizeof(T) > 2)> funceb() {
}
给 _Ty 赋值
template <typename T>
typename std::enable_if<(sizeof(T) > 2), T>::type funceb() {
T myt = {};
return myt;
}
int main(int argc, char **argv) {
funceb<int>();//int funceb() {}; 0;
return 0;
}
如果 不满足 enable_if 的判定条件,编译器则会忽略 这个函数模板。寻找更合适的重载函数。
enable_if 用于类模板中
继续讨论完美转发中遇到的拷贝构造函数问题。
Human myhuman3(myhuman1);
代码解决办法:
针对构造函数模板,如果给进来的参数是一个string类型,则用构造函数模板生效。
否则就让这个构造函数忽略。
结合 enable_if 即可。
同时引入与enable_if 一起工作的 std::is_convertible模板。c++11 引入。即判断能否从某个类型,隐式转换到另外一个类型。返回的是一个bool值。
cout << "string=>float: " << std::is_convertible<string, float>::value << endl; //0
cout << "float=>int: " << std::is_convertible<float, int>::value << endl;//1
究极写法:
class Human {
public:
template <
typename T,
typename = std::enable_if_t<std::is_convertible<T, std::string>::value>
//如果成立,则是 typename = void,则不会被抛弃,否则被抛弃,typename = 实际就是一个辅助
>
Human(T&& tmpname) :m_sname(std::forward<T>(tmpname)) {
cout << "Human(T&& tmpname) 执行" << endl;
}
//拷贝
Human(const Human & th) : m_sname(th.m_sname) {
cout << "Human(const Human &th) copy构造函数执行" << endl;
}
//移动构造函数
Human(Human &&th) :m_sname(std::move(th.m_sname)) {
cout << "Human(Human &&th) 移动构造被执行" << endl;
}
private:
string m_sname;
};
int main(int argc, char **argv) {
cout << "string=>float: " << std::is_convertible<string, float>::value << endl;
cout << "float=>int: " << std::is_convertible<float, int>::value << endl;
string sname = "ZhangSan";
Human myhuman1(sname);
Human myhuman3(myhuman1);//忽略掉构造函数模板
return 0;
}
template <typename T, typename U = >....
如果U 用不到 可以简化写成
typename = ...
别名简化:
template <typename T>
using StrProcType = std::enable_if_t<std::is_convertible<T, std::string>::value>;
class Human {
public:
template <
typename T,
typename = StrProcType<T>
>
Human(T&& tmpname) :m_sname(std::forward<T>(tmpname)) {
cout << "Human(T&& tmpname) 执行" << endl;
}
//拷贝
Human(const Human & th) : m_sname(th.m_sname) {
cout << "Human(const Human &th) copy构造函数执行" << endl;
}
//移动构造函数
Human(Human &&th) :m_sname(std::move(th.m_sname)) {
cout << "Human(Human &&th) 移动构造被执行" << endl;
}
private:
string m_sname;
};
int main(int argc, char **argv) {
cout << "string=>float: " << std::is_convertible<string, float>::value << endl;
cout << "float=>int: " << std::is_convertible<float, int>::value << endl;
string sname = "ZhangSan";
Human myhuman1(sname);
Human myhuman3(myhuman1);
return 0;
}
网友评论