c++11 std::enable_if在模板偏特化的妙用
1.模板自动推导功能。
先看个例子:
在调用TestTemplate函数时, 我们可以在函数后面加上<类型>无歧义地指定调用的版本。
结果如下:
由于模板参数在函数参数中的位置是固定的,编译器其实可以推导出参数的类型, 这样程序员们就可以不指定模板的类型来调用,代码更加简洁清晰通用,不会出现写错模板类型的错误,如下:
模板自动推导是如此的美好,我们要好好地利用它。 在应用过程中, 也引入了一些问题, 有些情况, 编译器发现某些代码满足多个同名函数模板,无法决定特化成哪一个,便会报错; 或者编译器不报错了,但是我们自己都不确定编译器特化成哪一个, 存在巨大的隐患。
因此,十分有必要搞清楚特化的规则。
2.模板函数推导的优先级。
a.不带模板的函数最优先。
例如增加专门针对int类型的普通函数实现:
结果如下:
当参数是int时, 调用了普通函数的版本, 虽然模板也满足,但是普通函数优先。
如果确实需要调用模板的版本,可以通过指定模板参数类型来实现。
如下:
这样就能调用到模板的版本。
b.模板参数个数越少的越优先(不建议参考)。
我在VS2017测试如下:
TestTemplate接受2个类型T1, T2。以上输出就是调用模板实现。
此处没有歧义,谁赞同,谁反对?
这时增加一个模板函数, 对于T1和T2是同类型时,作出处理。
此时,编译器会调用哪个?是否明确?
我在VS2017上测试,发现调用了模板参数少的,结果如下:
在标准上不知道有没有规定这种情况应该实例化哪个函数, 为了避免引起误会, 建议不能依赖它。 写代码时要避开有歧义的坑, 确保读者写者都对含义的理解完全一致。 后面std::enable_if完全可以清晰地表达我们想要编译器实例化成哪个函数。
3.模板类型推导冲突。
当一个模板函数推导,没有特化满足,或者多于一个特化满足时,编译器都会报错,罢工。
假设我要实现一个转换函数,让不同的类型相互转换,例如short转成int, int 转成string等。
其中从std::string转换成基本类型,或者基本类型转换成std::string, 我希望引入第三方的串行化库来实现,这时,分别对这两种情况进行了模板特化。
以上代码运行良好,输出如下:
当我们增加一行从std::string转换到std::string的模板调用时, 编译器就不干了。
因为这个调用, 以上3个模板定义都满足, 而且无法通过指定模板类型的方法来解决。Transform<std::string>指定了模板参数类型后,依然是冲突的。
解决方案一:
增加一个std::string转std::string的普通函数重载,根据普通函数优先的规则, 编译器锁定了调用的版本,不再有歧义。缺点就是代码对于std::string转std::string的逻辑表达上是冗余的,同一份逻辑,在两个地方重复定义了,而重复是万恶之源。 对于在c++11以前, 也没有更好的表达办法了。
解决方案二:
在语法上明确地告知编译器,我们想要它怎样去选择。在c++11以后,我们有了这样的表达工具,那就是std::enable_if。具体用法在后面介绍。
4.std::enable_if用法
enable_if 的主要作用就是:当某个条件成立时,enable_if表示指定的类型;当条件不成立时, enable_if表示未定义,但不会报错,编译器在实例化时就会忽略它,最终定位在唯一符合实例化的实现。声明如下:
template<bool Cond, class T = void> struct enable_if;
a.SFINAE特性
SFINAE 全称是 Substitution Failure Is Not An Error。 当模板定义中出现了一些符合c++语法,但是不存在的函数,或者变量等。只要没有真正实例化, 也不会报错。
例如:
T::testError函数,在没有实例化之前, 编译器都不会去考虑它是否存在,是否合法。只要不违反c++语法就可以了。
违反C++语法的,即使没有实例化,也会报错的。例如输入了一些中文:
b.有了SFINAE特性, std::enable_if才能放心地大展拳脚。
现在我们来解决前面提到的std::string转换成std::string的特化问题。
先写出了Transform函数的通用表达如下:
之前的所有Transform调用都是通过的。
typename std::enable_if<true, "类型">::type 等价于 "类型"
typename std::enable_if<false, "类型">::type 等价于 未定义
<==>表示等价于, 那么:
typename std::enable_if<true, bool>::tpye <==> bool
typename std::enable_if<true, int>::tpye <==> int
typename std::enable_if<true, T1>::tpye <==> T1
typename std::enable_if<false, bool>::tpye <==> 未定义
typename std::enable_if<false, int>::tpye <==> 未定义
typename std::enable_if<false, T1>::tpye <==> 未定义
未定义表示, 该类型标识所在的函数等价于没有定义, 编译器在实例化时会忽略它。
Transform函数修改如下:
由于std::enable_if中的condition固定为true,
typename std::enable_if<true, bool>::tpye <==> bool
整个模板函数,跟修改前其实时一样的,因此编译也通过。
一旦把std::enable_if里的条件改成false, 编译器便报错。
std::is_same用于判断两个类型是否相同。
std::is_same(T1, T2)::value的结果就是bool类型。
如果T1,T2类型相同,std::is_same(T1, T2)::value = true;
如果T1,T2类型不同,std::is_same(T1, T2)::value = false;
std::is_same等函数与std::enable_if组合起来就可以实现根据类型选择不同的模板特化的精准控制。
例如在以上基础上增加std::is_same(T1, T2)判断:
编译器提示T1与T2类型不相同的模板实例化失败。 而T1和T2类型相同情况,就精准地调用了我们对类型相同判断后的特化。
接下来我们分别对
目标是字符串,但源不是。
以及目标不是字符串, 但源是的情况做特化。
结果准确地如我们所愿:
并且当我们把int转成unsigned short时, 它能准确地报错,告知我们该转换需要另外实现。
std::enable_if用法规则如上, 除了可以用在函数返回值上, 还能
a.用在函数末尾增加一个冗余的参数。
例如:
用在函数末尾,表示函数的最后一个参数的类型根据T1, T2的判断来决定, 判断为true, 则正确定义了一个冗余的类型指针,成功模板实例化, 并且由于指定了默认参数,调用者与原来的调用一致。 类型判断结果为false时, 类型未定义,实例化时自动忽略跳过。 比std::enable_if用在函数返回值更清晰一些。
b.用在模板的参数上。
模板参数支持bool, int, 指针等类型,把std::enable_if用在模板的参数上, 函数的形式与原来完全一致, 我更加偏向于使用这种方式。
最后提醒一点, 前面所有例子对于类型的判别都没有做去除修饰词操作。
例如模板中的T1, T2, 有时候并非推导成某个类型type, 可能是type&, 可能是const type, 也可能是const type&, 为了准确地使用std::is_same等函数判断类型, 通常还需要对T1, T2做一个移除修饰词的操作。 std::decay就是专门做这个事情。
无论T1推导成int, 或者int&,或者const int, 或者const int&, std::decay<T1>::type总是表示int。
因此最终实际使用时, 所有模板标签判断语句都应该加上std::decay。
最终变成:
std::is_same<typename std::decay<T1>::type, typename std::decay<T2>::type>::value,
或者std::is_same<std::decay_t<T1>, std::decay_t<T2>>::value。
c++11 type_traits 增加了很多对于类型判断的功能,例如std::is_integral std::is_class, std::is_enum等...
详情参考标准https://en.cppreference.com/w/cpp/header/type_traits
c++11 std::enable_if在模板偏特化的妙用相关推荐
- C++ 模板偏特化-来自STL的思考
之前学习STL时接触过一段时间的模板,模板是C++泛型编程编程的基础 STL从头到尾都是模板泛型编程,我觉得用的最巧妙的就是在traits萃取技巧时用到的模板偏特化 先简要回顾一下模板吧,模板主要分为 ...
- C++_模板特化(specialization),模板偏特化(局部特化)(partial specialization)
C++_模板特化(specialization),模板偏特化(局部特化)(partial specialization) 1.模板特化 函数模板也可以特化,特化要符合模板参数类型 2.模板偏特化(局部 ...
- (C++模板编程):std::enable_if的使用(下)
目录 std::enable_if的使用 std::enable_if std::enable_if源码 偏特化完全可以理解成一种(在编译期)条件分支语句. std::enable_if基础认识 en ...
- C++模板之特化与偏特化详解
2019独角兽企业重金招聘Python工程师标准>>> C++函数模板与类模板实例解析_C 语言_脚本之家 http://www.jb51.net/article/53746.htm ...
- C++ 模板特化与偏特化
文章目录 1.模板特化 1.1 概述 1.2 函数模板特化 1.3 类模板特化 2.模板偏特化 2.1 概述 2.2 函数模板偏特化 2.3 类模板偏特化 3.模板类调用优先级 参考文献 1.模板特化 ...
- 操作符重载and模板(泛化, 全特化, 偏特化)
模板 Header(头文件)中的防卫式声明.布局 // complex.h// guard 防卫式声明 #ifndef __COMPLEX__ #define __COMPLEX__// 0.forw ...
- C++ 模板 全特化与偏特化
C++ 模板 全特化与偏特化 模板 模板定义:模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数, 从而实现了真正的代码可重用性.模版可以分为两类,一个是函数模版,另外一个是 ...
- C++模板特化和偏特化(二)
一.函数模板 (1)函数的匹配优先级: 普通函数: 重载函数: 普通函数模板: 全特化函数模板. 函数模板不允许使用偏特化,如有需求,改成模板函数的重载. (2)函数模板特化 函数模板特化主要的用途都 ...
- C++模板的全特化和偏特化
C++模板的全特化与偏特化 全特化 偏特化 例子 总结 全特化 全特化一般用于处理有特殊要求的类或者函数,此时依靠泛型模板无法处理这种情况.,因此全特化可以运用在类模板和函数模板当中.其模板参数列表为 ...
最新文章
- ImportError: No localization support for language ‘eng’ in python
- ASP.NET Core [1]:Hosting(笔记)
- win10 自待wmi应用 查询wmi
- Please install 'webpack-cli' in addition to webpack itself to use the CLI
- scala 基础十一 scala 中的trait特质
- 停车场系统管理数据库设计说明书
- 语料库的获取与词频分析
- js设置一个打点计时器
- 电容的字母型规格型号标号材料容差总结
- 2012年国内薪资最高的IT公司排行
- WPS文字表格外计算功能配合书签使用公式轻松实现
- 武汉市申请国家现代农业产业科技创新中心发展奖励标准及申请要求
- C#/WPF/.NET 第三方ddl强签名解决(xxx, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null)
- linq和lambda_最小起订量:应用于模拟对象的Linq,Lambda和谓词
- [教程] 教你简单解决邮件乱码(Mac/iPhone/iPad通用)
- a-H3X R4900 G2服务器安装redhat6.8
- 一文读懂贝叶斯原理(Bayes‘ theorem)
- 文件上传-01基础及过滤方式
- R语言-将数据按照月份、季度、年份划分及求某个代码(地名、产业名等)对应的累积值
- 魔乐科技安卓开发教程----李兴华----01文件存储