我在 Cppcon14 上观看了 Walter Brown 关于现代模板编程(Part I、Part II)的演讲,其中他展示了他的 void_t
SFINAE 技术。
示例:
给定一个简单的变量模板,如果所有模板参数格式正确,则计算结果为 void
:
template< class ... > using void_t = void;
以及检查是否存在名为 member 的成员变量的以下特征:
template< class , class = void >
struct has_member : std::false_type
{ };
// specialized as has_member< T , void > or discarded (sfinae)
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : std::true_type
{ };
我试图理解这是为什么以及如何工作的。因此,一个小例子:
class A {
public:
int member;
};
class B {
};
static_assert( has_member< A >::value , "A" );
static_assert( has_member< B >::value , "B" );
1. has_member< A >
has_member< A , void_t< decltype( A::member ) > > A::member 存在 decltype( A::member ) 格式正确 void_t<> 有效且计算结果为 void
A::成员存在
decltype( A::member ) 格式正确
void_t<> 有效并计算为 void
has_member< A , void > 因此它选择了专门的模板
has_member< T , void > 并计算为 true_type
2. has_member< B >
has_member< B , void_t< decltype( B::member ) > > B::member 不存在 decltype( B::member ) 格式错误并且静默失败 (sfinae) has_member< B , expression-sfinae > 所以这个模板被丢弃
B::成员不存在
decltype( B::member ) 格式不正确并且静默失败 (sfinae)
has_member< B , expression-sfinae > 所以这个模板被丢弃
编译器发现 has_member< B , class = void > 以 void 作为默认参数
has_member< B > 计算结果为 false_type
问题:
1. 我对此的理解正确吗?
2. Walter Brown 指出,默认参数的类型必须与 void_t
中使用的类型完全相同才能正常工作。这是为什么? (我不明白为什么这些类型需要匹配,不只是任何默认类型都可以完成这项工作吗?)
has_member<A,int>::value
。然后,评估为 has_member<A,void>
的偏特化无法匹配。因此,它需要是 has_member<A,void>::value
,或者,使用语法糖,是 void
类型的默认参数。
void
中默认设置 has_member< T , class = void >
。假设这个特征在任何时候都只能与 1 个模板参数一起使用,那么默认参数可以是任何类型吗?
template <class, class = void>
更改为 template <class, class = void_t<>>
。所以现在我们可以自由地使用 void_t
别名模板实现做任何我们想做的事情:)
1.小学班级模板
当您编写 has_member<A>::value
时,编译器会查找名称 has_member
并找到 primary 类模板,即此声明:
template< class , class = void >
struct has_member;
(在 OP 中,这是作为定义编写的。)
模板参数列表 <A>
与此主模板的模板参数列表进行比较。由于主模板有两个参数,但您只提供了一个,剩余参数默认为默认模板参数:void
。就好像你写了 has_member<A, void>::value
。
2. 专业类模板
现在,将模板参数列表与模板 has_member
的任何特化进行比较。仅当没有专业化匹配时,主模板的定义才用作后备。所以考虑了偏特化:
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
编译器尝试将模板参数 A, void
与部分特化中定义的模式一一匹配:T
和 void_t<..>
。 首先,进行模板参数推导。上面的部分特化仍然是一个带有模板参数的模板,需要用参数“填充”。
第一个模式 T
,允许编译器推导出模板参数 T
。这是一个微不足道的推论,但考虑像 T const&
这样的模式,我们仍然可以推导出 T
。对于模式 T
和模板参数 A
,我们将 T
推导出为 A
。
在第二种模式中 void_t< decltype( T::member ) >
,模板参数 T
出现在无法从任何模板参数推导出的上下文中。
这有两个原因: decltype 内部的表达式被显式排除在模板参数推导之外。我想这是因为它可以任意复杂。即使我们使用像 void_t< T > 这样没有 decltype 的模式,T 的推导也会发生在解析的别名模板上。也就是说,我们解析别名模板,然后尝试从结果模式中推断出类型 T。然而,结果模式是无效的,它不依赖于 T,因此不允许我们找到 T 的特定类型。这类似于试图反转一个常数函数的数学问题(在那些数学意义上条款)。
模板参数推导完成(*),现在替换推导的模板参数。这将创建一个如下所示的专业化:
template<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };
现在可以评估类型 void_t< decltype( A::member ) >
。它在替换后是良构的,因此不会发生替换失败。我们得到:
template<>
struct has_member<A, void> : true_type
{ };
3. 选择
现在,我们可以将此专业化的模板参数列表与提供给原始 has_member<A>::value
的模板参数进行比较。两种类型都完全匹配,因此选择了这种部分特化。
另一方面,当我们将模板定义为:
template< class , class = int > // <-- int here instead of void
struct has_member : false_type
{ };
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
我们最终得到了相同的专业化:
template<>
struct has_member<A, void> : true_type
{ };
但我们的 has_member<A>::value
模板参数列表现在是 <A, int>
。参数与特化的参数不匹配,并且选择主模板作为后备。
(*) 恕我直言,该标准令人困惑,包括替换过程和模板参数推导过程中显式指定模板参数的匹配。例如(N4296 后)[temp.class.spec.match]/2:
如果可以从实际模板参数列表中推导出部分特化的模板参数,则部分特化匹配给定的实际模板参数列表。
但这不仅仅意味着必须推导出偏特化的所有模板参数;这也意味着替换必须成功并且(看起来?)模板参数必须匹配部分特化的(替换的)模板参数。请注意,我并不完全了解标准在何处指定替换参数列表和提供的参数列表之间的比较。
// specialized as has_member< T , void > or discarded (sfinae)
template<class T>
struct has_member<T , void_t<decltype(T::member)>> : true_type
{ };
上述特化仅在其格式正确时才存在,因此当 decltype( T::member )
有效且不含歧义时。 has_member<T , void>
的特化是评论中的状态。
当您编写 has_member<A>
时,它是 has_member<A, void>
,因为默认模板参数。
我们有 has_member<A, void>
的特化(所以从 true_type
继承)但我们没有 has_member<B, void>
的特化(所以我们使用默认定义:从 false_type
继承)
void_t<decltype(T::member)>>
只不过是一种标准化/更清晰的书写方式 decltype(T::member, void())
?
void_t
一起应用。
这个线程和线程 SFINAE: Understanding void_t and detect_if 救了我。我想通过一些例子来展示这种行为:
工具:cppinsights
通过浮点类型和以下类型测试实现:
struct A {
using type = int;
};
struct B{
using type = void;
}
测试者
auto f = has_type_member<float>::value;
auto a = has_type_member<A>::value;
auto b = has_type_member<B>::value;
标准实施
#include <type_traits>
// primary template handles types that have no nested ::type member:
template< class, class = int >
struct has_type_member : std::false_type { };
// specialization recognizes types that do have a nested ::type member:
template< class T >
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };
输出
bool f = false;
bool a = true;
bool b = true;
bool x = has_type_member<A, int>::value; //x = false;
情况1
#include <type_traits>
// primary template handles types that have no nested ::type member:
template< class, class = int >
struct has_type_member : std::false_type { };
template< class T >
struct has_type_member<T, void> : std::true_type { };
// specialization recognizes types that do have a nested ::type member:
template< class T >
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };
输出
/home/insights/insights.cpp:14:8: error: redefinition of 'has_type_member<T, std::void_t<typename T::type>>'
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/insights/insights.cpp:8:8: note: previous definition is here
struct has_type_member<T, void>: std::true_type {};
^
1 error generated.
Error while processing /home/insights/insights.cpp.
因此,has_type_member<T, std::void_t<typename T::type>>
定义了 has_type_member
的特化,并且签名正是 has_type_member<T, void>
。
案例2
#include <type_traits>
template< class, class = void >
struct has_type_member : std::false_type { };
// specialize 2nd type as void
template< class T>
struct has_type_member<T, void> : std::true_type { };
输出:
bool f = true;
bool a = true;
bool b = true;
这个案例表明编译器:
想要找到匹配 has_type_member
案例3
#include <type_traits>
template< class, class = void >
struct has_type_member : std::false_type { };
template<class T>
struct has_type_member<T, typename T::type>: std::true_type {};
输出:
bool f = false;
bool a = false;
bool b = true;
案例 f
has_type_member
案例一
has_type_member 已完成为 has_type_member 然后编译器尝试 has_type_member 并发现它是 has_type_member 编译器确定它不是 has_type_member 然后选择主模板。
案例b
has_type_member 已完成为 has_type_member。然后编译器尝试了 has_type_member 并发现它是 has_type_member。编译器决定它是 has_type_member true_type 的一个特化。
案例4
#include <type_traits>
//int as default 2nd argument
template< class, class = int >
struct has_type_member : std::false_type { };
template<class T>
struct has_type_member<T, std::void<typename T::type>>: std::true_type {};
输出:
bool f = false;
bool a = false;
bool b = false;
对于所有 3 个变量,has_type_member<T>
的类型均为 has_type_member<T, int>
,而 true_type
的签名为 has_type_member<T, void>
(如果有效)。
结论
因此,std::void_t
:
检查 T::type 是否有效。如果仅提供一个模板参数,则提供主模板的特化。
不定期副业成功案例分享