struct A{
template<typename U>
void T(){}
};
struct B{
template<typename U>
struct T{
using type = U;
};
};
struct C:A,B{
};
int main(){
C::T<int>::type d;
}
This example is accepted by neither GCC nor Clang.
As per basic.lookup.qual#1
The name of a class or namespace member or enumerator can be referred to after the:: scope resolution operator ([expr.prim.id.qual]) applied to a nested-name-specifier that denotes its class, namespace, or enumeration. If a:: scope resolution operator in a nested-name-specifier is not preceded by a decltype-specifier, lookup of the name preceding that :: considers only namespaces, types, and templates whose specializations are types.
That means that when looking up the declarations for the template name T
, the specialization of T
shall denote a type in this context. On the other hand, as per class.member.lookup#4
If C contains a declaration of the name f, the declaration set contains every declaration of f declared in C that satisfies the requirements of the language construct in which the lookup occurs.
Again, when looking up the template T
in the scope of C
, only those templates whose specialization is a type should be considered by this lookup. The scope of C
does not have any declarations for T
, hence the lookup will be performed for S(T,C)
in every one of its base classes. The template T
in A
does not satisfy the requirement. Meanwhile, the template T
declared in the scope of B
does satisfy the requirement. So the lookup is not ambiguous and the B::T
is the unique result. That means C::T<int>::type d
should be well-formed. Why do both GCC and Clang reject this example? Can it be considered a bug of in both? If I missed something, what's the reason that this example should be ill-formed?
T<int>
orT<int>::type
or something else... I would have said the first one, but I'm not even sure it is the requirement in concern here, and it doesn't talk about context. – LaudianismC::T<int>
is ambiguous,C::T<int>::type
is only valid forB
,C::T<int>::type d{1, 42};
would be invalid for both. Addingtypename
should force the "context" to excludeA::T
, but doesn't for clang/msvc. – LaudianismA::T
andB::T
are "valid". But basic.lookup.qual#1 should indeed discardsA::T<int>
from my understanding. – Laudianismtemplate
is not necessary because of timsong-cpp.github.io/cppwp/n4861/temp.names#2.sentence-3 – Bercktypename
.template
doesn't hurt. (I tried with and without and no difference indeed). – LaudianismC::T<int>::
is a nested-name-specifier? The name followed by operator::
should obeybasic.lookup.qual#1
that is the requirement here for lookup – Orolatypename/template
is essentially equavelent to without thetypename/template
– Orolatypename
(or useusing D = C::T<int>::type
) would discard non-typeA::T
(from class.member.lookup#4), but it seems not. – LaudianismSomeStuff::
,SomeStuff
should only consider class/namespace, so discard functionA::T<int>
. (but compilers disagree). – Laudianismtypename C::T<int>::type d;
but both 'typename` andtemplate
should be redundant here according to the standard. – Truman