“替换失败不是错误” (Substitution Failure Is Not An Error)
在函数模板的重载决议中会应用此规则:当模板形参在替换成显式指定的类型或推导出的类型失败时,从重载集中丢弃这个特化,而非导致编译失败。
函数模板重载过程
- 在模板实参推导前,使用显示指定的模板实参对形参进行替换
template<class T,int> void fun(T){}
template<class T> void fun(T){}
fun<int,6>(5);
//进行<>中显示部分的替换,T换为int,int换为6
- 在模板实参推导后,使用推导出的实参和从默认项获得的实参对形参进行替换
template<class T,char> void fun(T){}
template<class T,int=6> void fun(T){}
fun(5);
//推导T为int,默认值6替换int
- 选择合适的版本,没有合适的版本编译失败
template<class T = int, int = 6> void fun(int) { std::cout << "int"; }
template<class T = int, int> void fun(T) { std::cout << "T"; }
fun<int,6>(1);//输出int
fun(1);//编译失败
/*template<class T> void fun(...){}兜底模板,只要模板允许什么样的类型都可以调用,
但是优先级低,需要注意的是对无参的调用要显示指定模板类型或者给模板类型加上默认值
*/
对函数模板形参的替换发生于
- 函数体中可能出现的所有类型(形参,返回值等用于标识函数时可能出现的类型)
- 模板声明中出现的所有类型,template那一串
- 部分特化的模板实参列表中使用的所有表达式
C++ 11起 - 函数类型中使用的所有表达式
- 各个模板形参声明中使用的所有表达式
- 部分特化的模板实参列表中使用的所有表达式
C++20 起 - 概念中出现的常量表达式
总结起来很简单就是一句话:替换应当发生在函数体之外的所有地方,函数体内能否匹配应当在运行期间决定,替换失败仅仅意味着重载列表不应该出现这个版本的函数模板并不意味是错误,当然函数重载后不知道调用哪个或是没有可调用的重载的话应当在编译阶段报错。
SFINAE的应用
- 简化特化的实现,待匹配类型(int/char)可以替换为别的常量表达式
template<bool, class T>
struct enable_if {
using type = T;
};
template<class T>
struct enable_if<false, T> {};
template<class T,class R>
constexpr bool same = std::is_same_v<T, R>;
template<class T> void fun(T){ std::cout<<"主模板"; }
template<class T>
void fun(typename enable_if<same<T, int>, int>::type) { std::cout<<"int"; }
template<class T>
void fun(typename enable_if<same<T, char>, char>::type) { std::cout<<"char"; }
fun<int>(6);//调用成功,int版本,优先级高
fun(6);//调用主模板,实参替换时发现自己不认识typename那一坨东西导致直接匹配失败
fun<char>(' ');//调用成功,char版本
- 判断一个类有无指定类型的嵌套属性
#include<iostream>
template<class, class>
struct has_x :std::false_type {};
//判读有无指定类型的成员变量x
template<class T> struct has_x<T, decltype(T::x)> :std::true_type {};
//判读有无指定类型的成员类型x
template<class T> struct has_x<T, typename T::x> :std::true_type {};
struct A { double x; };
struct B { using x = int; };
int main() {
std::cout<<has_x<B,int>::value;
std::cout<<has_x<A, double>::value;
}
整体来说SFINAE描述的就是整个模板推导的规则,通过它我们可以实现高度自适应的代码匹配,且整个过程在编译期间由编译器完成对性能影响小,模板yyds
想要进一步了解的话可以阅读一下技术文档:SFINAE-----cppreference