SFINAE 与 type_traits
SFINAE
替换失败不是错误 (Substitution Failure Is Not An Error),此特性被用于模板元编程。
在函数模板的重载决议中应用此规则,当将模板形参替换为显式指定的类型或推导的类型失败时,从重载集中丢弃这个特化,而非导致编译失败。
type_traits
类型特性 定义一个编译时基于模板的结构,以查询或修改类型的属性,是一种类型萃取技术。
type_traits 在 C++ 中是基于 SFINAE 实现的,通过模板的偏特化来查询或者修改类型的属性,无运行时开销。
其核心是整数常量模板 integral_constant
,MSVC 的实现如下:
template
struct integral_constant { // convenient template for integral constant types
static constexpr _Ty value = _Val;
using value_type = _Ty;
using type = integral_constant;
constexpr operator value_type() const noexcept { // return stored value
return (value);
}
constexpr value_type operator()() const noexcept { // return stored value
return (value);
}
};
其实平平无奇,保存了类模板的类型和值而已,没有什么特殊的地方,现在将这个模板特化为bool类型
// ALIAS TEMPLATE bool_constant
template using bool_constant = integral_constant;
using true_type = bool_constant;
using false_type = bool_constant;
bool_constant 的实参只能是 bool 类型, true_type 的 integral_constant::value
是true,false_type 的 value 是 false,构建 traits 的基础已经完成。
常见的几种 traits 的 SFINAE 应用
像 std::enable_if, is_same, remove_const, remove_reference 等这些是最常用的 traits,STL的代码中这些到处可见。
enable_if
根据编译期布尔常量隐藏一个函数重载或模板特化。
// STRUCT TEMPLATE enable_if
template
struct enable_if { // type is undefined for assumed !_Test
};
template struct enable_if { // type is _Ty for _Test
using type = _Ty;
};
template
using enable_if_t = typename enable_if<_Test, _Ty>::type;
若 B 为 _Test ,则 std::enable_if 拥有等同于 T 的公开成员 typedef type (第二个模板实参),否则,无该成员 typedef。
is_same
检测两个参数类型是否相同,如果相同特供 value 为 true,否则为false。实现简单,相同参数的继承 true_type, 不同参数的继承 false_type
// STRUCT TEMPLATE is_same
template
struct is_same
: false_type { // determine whether _Ty1 and _Ty2 are the same type
};
template
struct is_same<_Ty1, _Ty1>
: true_type { // determine whether _Ty1 and _Ty2 are the same type
};
template
constexpr bool is_same_v = is_same<_Ty, _Uty>::value;
remove_const
控制给定类型限定符是否存在。volatile 同理。
// STRUCT TEMPLATE remove_const
template struct remove_const { // remove top level const qualifier
using type = _Ty;
};
template
struct remove_const { // remove top level const qualifier
using type = _Ty;
};
template using remove_const_t = typename remove_const<_Ty>::type;
remove_reference
从给定类型移除引用,add_*value_reference 实现类似。
// STRUCT TEMPLATE remove_reference
template struct remove_reference { // remove reference
using type = _Ty;
};
template struct remove_reference<_Ty &> { // remove reference
using type = _Ty;
};
template
struct remove_reference<_Ty &&> { // remove rvalue reference
using type = _Ty;
};
template
using remove_reference_t = typename remove_reference<_Ty>::type;
其他需要编译器外挂的 traits
像上面这些都是可以通过库来实现的 traits,这种一般也比较简单的。其他的和类 枚举 虚函数 这些相关一般都要通过内建支持。
最先想到的就是 is_trivial
,这种要通过库来实现就不可能了:
- 没有虚函数或虚基类;
- 由C++编译器提供默认的特殊成员函数(默认的构造函数、拷贝构造函数、移动构造函数、赋值运算符、移动赋值运算符和析构函数),并且最少有一个未弃置;
- 数据成员同样需要满足条件 1 和 2。
// STRUCT TEMPLATE is_trivial
template
struct is_trivial
: bool_constant<__is_trivial(_Ty)> { // determine whether _Ty is trivial
};
__is_trivial
就是编译器的外挂了,traits 的这种实现一直秉持着能够库实现,绝不编译器去做的原则。
参考
- SFINAE, cppreference.com.
- std::is_trivial, cppreference.com.