我在 Cppcon14 上观看了 Walter Brown 关于现代模板编程的演讲( 第一部分, 第二部分),他展示了他的 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
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 >
所以这个模板被丢弃
- 编译器发现
has_member< B , class = void >
以 void 作为默认参数 has_member< B >
评估为false_type
问题:
1.我的理解正确吗?
2. Walter Brown 指出,默认参数必须与 void_t
中使用的类型完全相同,才能正常工作。这是为什么? (我不明白为什么这些类型需要匹配,不只是任何默认类型都可以完成这项工作吗?)
原文由 nonsensation 发布,翻译遵循 CC BY-SA 4.0 许可协议
1.小学班级模板
当你写
has_member<A>::value
时,编译器会查找名称has_member
并找到 主 类模板,即这个声明:(在 OP 中,这是作为定义编写的。)
模板参数列表
<A>
与此主模板的模板参数列表进行比较。由于主模板有两个参数,但您只提供了一个,剩余参数默认为默认模板参数:void
。就好像你写了has_member<A, void>::value
。2. 专业类模板
现在,将模板参数列表与模板的任何特化
has_member
进行比较。仅当没有专业化匹配时,主模板的定义才用作后备。所以考虑了偏特化:编译器尝试将模板参数
A, void
与部分特化中定义的模式:T
和void_t<..>
一一匹配。 _首先_,执行模板参数推导。上面的部分特化仍然是一个带有模板参数的模板,需要用参数“填充”。第一个模式
T
,允许编译器推断模板参数T
。这是一个微不足道的推论,但考虑像T const&
这样的模式,我们仍然可以推导出T
。对于模式T
和模板参数A
,我们推导出T
是A
。在第二种模式
void_t< decltype( T::member ) >
中,模板参数T
出现在无法从任何模板参数推导出的上下文中。模板参数推导完成(*) , 现在 替换 推导 的模板参数。这将创建一个如下所示的专业化:
现在可以评估类型
void_t< decltype( A::member ) >
。它在替换后是良构的,因此不会发生 _替换失败_。我们得到:3. 选择
现在,我们可以将此特化的模板参数列表与提供给原始
has_member<A>::value
的模板参数进行比较。两种类型都完全匹配,因此选择了这种部分特化。另一方面,当我们将模板定义为:
我们最终得到了相同的专业化:
但是我们
has_member<A>::value
的模板参数列表现在是<A, int>
。参数与特化的参数不匹配,并且选择主模板作为后备。(*)恕我直言,该标准令人困惑,包括替换过程和 模板参数推导 过程中显式指定模板参数的匹配。例如(N4296 后)[temp.class.spec.match]/2:
但这 不仅仅 意味着必须推导出偏特化的所有模板参数;这也意味着替换必须成功并且(看起来?)模板参数必须匹配部分特化的(替换的)模板参数。请注意,我并不完全了解标准 在何处 指定替换参数列表和提供的参数列表之间的比较。