SFINAE技术学习笔记
1.函数重载解决流程
- (1) 将所有对应”同名“候选函数做成一个集合;
- (2) 根据函数声明,从集合中移除掉一些不合适的候选函数;
- (3) 在集合剩余函数中,依据参数挑选一个最合适的函数,若挑选不到,或无法决定哪个最合适,就报错;
- (4) 如果在上一步挑选到了最合适函数,还会继续做一些检查,比如是否是被delete的;
第二步中依靠SFINAE,第三步中,依据参数挑选最合适的函数是指实参和形参地匹配是有优先级顺序的:
(1) 完全匹配;
(2) const 转换;
(3) 可变参数fun(...);
2.模板特例的局限性
#include <iostream>
#include <vector>
class CMyClass {
public:
using size_type = int;
};
template <typename T, unsigned N>
std::size_t len(const T (&)[N]) {
return N;
}
template <typename T>
typename T::size_type len(const T &t) {
return t.size();
}
unsigned len(...) {
return 0;
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
std::cout << len(arr) << std::endl; // 5
std::vector<int> vec = {1, 2, 3, 4, 5};
std::cout << len(vec) << std::endl; // 5
std::cout << len(3.14) << std::endl;
std::cout << len(CMyClass()) << std::endl; // 0
return 0;
}
按照设计,我们想要 len(CMyClass())
调用 unsigned len(...)
接口,但是按照参数匹配,会匹配到 typename T::size_type len(const T &t)
接口,而类 CMyClass
没有 .size()
方法,所以会报错。
3.SFINAE
在解决函数重载过程中,编译器会使用实参类型对函数声明做替换,这里和模板展开是不同的,这里只针对函数声明做替换,并不针对整个函数体,如果替换完成后,发现得到的函数声明是没有意义的,就会从候选函数集合中移除,并且不报错,这种替换失败不报错的行为,英文名就是 Substitution Failure Is Not An Error, SFINAE
。
使用 SFINAE
对上述代码进行改进:
#include <iostream>
#include <vector>
class CMyClass {
public:
using size_type = int;
};
template <typename T, unsigned N>
std::size_t len(const T (&)[N]) {
return N;
}
template <typename T>
decltype(T().size(), typename T::size_type()) len(const T &t) {
return t.size();
}
unsigned len(...) {
return 0;
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
std::cout << len(arr) << std::endl; // 5
std::vector<int> vec = {1, 2, 3, 4, 5};
std::cout << len(vec) << std::endl; // 5
std::cout << len(3.14) << std::endl;
std::cout << len(CMyClass()) << std::endl; // 0
return 0;
}
输出结果:
5
5
0
0
这里 decltype(T().size(), typename T::size_type())
是一个逗号运算,最终结果为 decltype(typename T::size_type())
,然而在编译期间对函数重载时,T().size()
会被替换为 CMyClass().size()
,而此时展开的结果是无效的,因此会替换失败,但是不会触发报错,编译器会继续找到更合适的函数 unsigned len(...)
,最终输出0.