SFINAE
(std::enable_if
)是C++11
模板中利用类型萃取判断泛型是否满足特定鸭子类型,从而保证模板代码能正确地条件化生成的方法,但是语法上可读性差,不易扩展。
而 C++20
中的Concept
使用不求值表达式表达泛型约束,语法上限制更少,可读性强,结合C++14
的if constexpr
,可以平替满是语法噪音的 SFINAE
。最重要的是,concepts
报错能精确到requires
,而没有与static_assert
的 SFINAE
很难定位写模板时的bug
假设一个模板函数内,要根据类型是否具有特定成员变量(函数)使用if constexpr
条件编译,比如下方代码需要确定类T
是否有public
的int x
, auto f() -> void
struct A{
double x;
void f(){};
};
struct B{
int x;
void g(){};
};
template <typename T> t_func(T t) {
if constexpr (has_x<T> && has_f<T>)
branch1();
else
branch2();
}
C++20
前使用 SFINAE
可以这样写:
template<typename T>
struct has_x {
template<typename U, typename = void>
struct has_x_impl : std::false_type {
};
template<typename U>
struct has_x_impl<U, std::void_t<decltype(std::declval<U>().x)>> {
constexpr inline bool static value = std::is_same_v<int, decltype(std::declval<U>().x)>;
};
constexpr inline bool static value = has_x_impl<T>::value;
};
如果T::x
存在,则has_x<T>
会实例化为没有默认typename = void
的第二个版本的has_x_impl
,has_x<T>::value
当T::x
为int
时为真
如果T::x
不存在,第二个版本的has_x_impl
替换失败,根据SFINAE
实例化为第一个版本,has_x<T>::value==false
为了实现 SFINAE
,使用了:1.一个额外的typename=void
用于触发SFINAE
2.decltype
用于类型提取 3. 在decltype
的不求值表达式中使用std::declval<U>()
,防止U
没有默认构造函数导致编译错误
判断成员函数的代码类似
template<typename T>
struct has_f {
template<typename U, typename = void>
struct has_f_impl : std::false_type {
};
template<typename U>
struct has_f_impl<U, std::void_t<decltype(std::declval<U>().f())>> {
constexpr inline bool static value = std::is_same_v<void, decltype(std::declval<U>().f())>;
};
constexpr inline bool static value = has_f_impl<T>::value;
};
而在c++20
中,使用Concepts
将上述满满语法噪音的代码化简为:
template<typename T>
concept has_x = requires(T a) {
requires std::same_as<decltype(a.x), int> ;
};
template<typename T>
concept has_f = requires(T a) {
{a.f()} -> std::same_as<void>;
};
当然has_x
也可写成
template<typename T>
concept has_x = requires(T a) {
{a.x} -> std::same_as<int&> ;
};
不写成std::same_as<int>
是因为这里和{} ->
检查的是表达式a.x
的类型,而不是变量a.x
本身的类型。编译器转换的代码大致等价于
template<typename T>
concept has_x = requires(T a) {
requires std::same_as<decltype((a.x)), int&> ;
};
requires
表达式中从T
类型对象中拿出成员,嵌套一层requires
检查变量类型/函数签名
使用例
struct A{
double x;
void f(){};
A() = delete;
};
struct B{
int x;
void g(){};
};
int main(){
cout << has_x<A> << endl << has_x<B> << endl;
cout << has_f<A> << endl << has_f<B> << endl;
// 0, 1, 1, 0
}