鉴于以下代码,
template <class> using void_t = void;
template <class C, class = void> struct X { enum { v = 0 }; };
template <class C> struct X<C, void_t<typename C::T> > { enum { v = 1 }; };
struct T { };
int main() { return X<T>::v; }
什么应该主要回归? GCC和MSVC说1,Clang说0。
我认为Clang就在这里。 [class.qual]的规则是:
在查找中,函数名称不被忽略,而嵌套名称说明符指定类
C
:
- 如果在
C
中查找的嵌套名称说明符之后指定的名称是C
([class])的注入类名,或者- [......这里无关紧要......]
而是将该名称视为命名类
C
的构造函数。 [注意:例如,构造函数在详细类型说明符中不是可接受的查找结果,因此不会使用构造函数来代替inject-class-name。 - 结束注释]这样的构造函数名称只能用于命名构造函数或using-declaration的声明的declarator-id。 [实施例:struct A { A(); }; struct B: public A { B(); }; A::A() { } B::B() { } B::A ba; // object of type A A::A a; // error, A::A is not a type name struct A::A a2; // object of type A
- 结束例子]
typename C::T
与A::A
是同一类,它的查找函数名称不被忽略(typename
不会导致函数名被忽略)。因此,在typename C::T
中,当C
是T
时,T
这个名称被认为是构造函数的名称。由于它不是类型名称,我们应该获得替换失败并回退到主模板。
提起86818。
为了完成Barry的回答,typename
只对编译器说,以下名称是模板实例化之前执行的分析的类型。实例化后,执行名称查找,就好像typename不存在一样,[temp.res]/4:
即使存在typename,通常使用限定名称查找来查找qualified-id。
所以Clang是对的。为了获得一致的编译器行为,您可以使用elaborated type specifier struct C::T
代替typename C::T
:
template <class> using void_t = void;
template <class C, class = void> struct X { enum { v = 0 }; };
template <class C> struct X<C, void_t<struct C::T> > { enum { v = 1 }; };
struct T { };
int main() { return X<T>::v; }