SFINAE 是C++ 11 推出的一种模板匹配机制,SFINAE最主要的作用,是保证编译器在泛型函数、偏特化、及一般重载函数中遴选函数原型的候选列表时不被打断。
简单来说就是SFINAE机制可以在编译器进行函数匹配发生Error时,并不会立即报错,而是继续查找正确匹配的模板,可以利用该机制完成对不同类型的同种操作的分类的操作
1.使用decltype(A,B)的形式来作为模板函数的参数列表,A用于甄别传入类型,而B用于初始化实际的参数列表,因为()表达式的值为最右边的值。
template<typename T>
struct hasSerialize {
template<typename C>
static constexpr decltype(std::declval<C>().serialize(), bool()) test(int) {
return true;
}
template<typename C>
static constexpr bool test(...) {
return false;
}
static constexpr bool value = test<T>(int());
};
代码中涉及到的decay_t:
decay 使变量类型退化,数组退化为指针,函数退化为函数指针,其他退化为原始类型
a. T为数组U或数组U引用,则type为U*.
b. T为函数时,则type为std::add_pointer::type.
c. 其它类型则移除cv限定符(const和volatile),则type为std::remove_cv<std::remove_reference::type>::type.
std::declval 返回对象的右值引用,不管对象是否有构造函数,一般配合decltype使用
因此 std::declval<C>().serialize() 表达式即为调用类型 C 的 serialize 成员函数,如果类型C没有 serialize 成员函数,编译器并不会马上报错,而是认为该模板不能够匹配当前传入的参数,并且会继续查找后续其他的模板函数来匹配传入的参数。利用这种方式可以做到对类型的判断.
2.利用 类模板 的传入类型来进行类型判断
template<typename T, typename U =std::string>
struct hasSerialize : std::false_type {
};
template<typename T>
struct hasSerialize<T, decltype(std::declval<T>().serialize())> : std::true_type {
//继承了true_type 因此就拥有 了true_type的成员 value
};//偏特化,第二个参数为string类型(可以使用serialize进行转化)
两个同名的hasSerialize类模板,分别继承了std::true_type 和 std::false_type ,并且第一个模板的第二个type参数默认为string,而第二个模板的第二个type为 decltype(std::declval<T>().serialize()),因此会过滤掉不不能调用 .serialize() 成员函数 的类型 T。
3、利用函数模板的偏特化来进行遴选
template<class T>
struct hasSerialize {
// 编译时比较
typedef char yes[1];
typedef char no[2];
template<typename U, U u>
struct reallyHas;
// std::string (C::*)() 是函数指针声明
template<typename C>
static yes &test(reallyHas<std::string (C::*)(), &C::serialize> * /*unused*/) {}
template<typename C>
static yes &test(reallyHas<std::string (C::*)() const, &C::serialize> * /*unused*/) {}
template<typename>
static no &test(...) { /* dark matter */ }
//用作测试的返回值的常数。
//由于编译时评估的大小,因此实际上在这里完成了测试。
static constexpr bool value = sizeof(test<T>(0)) == sizeof(yes);
};
这种方式能够严格的检查是否含有 名为 serialize 且返回类型为 string 的函数
但是使用这种方式进行选择类型需要满足一下情况:
第二个参数必须是第一个参数的类型
它仅适用于 整数常量 和 指针 (因此函数指针可以使用) ,例如:reallyHas<std::string (C::*)(), &C::serialize> 替换为 reallyHas<std::string (C::*)(), std::string (C::*)() &C::serialize> 并起作用。
因为
constexpr的作用是指 尽量将计算放在编译器,用法有if constexpr ,constexpr修饰函数,修饰结构体构造函数
1、constexpr修饰变量: 可以使得变量的计算在编译期进行,但是变量初始化的赋值必须是常量表达式
2、在constexpr修饰函数时:
修饰的函数只能包括return 语句。
修饰的函数只能引用全局const常量。
修饰的函数只能调用其他constexpr修饰的函数。
该函数必须有返回值,即函数的返回值类型不能是 void。
该函数只有在所有参数都是常量表达式,且返回的结果被用于常量表达式时,才会触发编译期求值
3、constexpr修饰类的构造函数:
constexpr Rectangle (int h, int w) : _h(h), _w(w) {} 使用constexpr修饰结构体的构造函数
constexpr Rectangle obj(10, 20); 在构造结构体对象时使用constexpr进行修饰可以在编译器构建好结构体对象
但是需要注意的是被constexpr修饰的结构体在初始化时 以及 被constexpr 修饰的函数 传参时都需要传入 全局const常量