Does the use a simple-template-id in a nested-name-specifier unambiguously mean a class template specialization?
Asked Answered
O

1

8
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?

Orola answered 26/2, 2021 at 9:18 Comment(15)
"The template T in A does not satisfy the requirement." Which is the requirements for you. T<int> or T<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.Laudianism
@Laudianism I don't know what you are saying.Orola
Adding typename/template only "helps" gcc Demo.Laudianism
I don't know the answer. I meant that it is not clear what "requirement" means. You seems to understand that it produces valid "interpretation" for code, but C::T<int> is ambiguous, C::T<int>::type is only valid for B, C::T<int>::type d{1, 42}; would be invalid for both. Adding typename should force the "context" to exclude A::T, but doesn't for clang/msvc.Laudianism
From the "note" of class.member.lookup#4, we are in a place where we can have both a type or a function, so both A::T and B::T are "valid". But basic.lookup.qual#1 should indeed discards A::T<int> from my understanding.Laudianism
@Laudianism I think template is not necessary because of timsong-cpp.github.io/cppwp/n4861/temp.names#2.sentence-3Berck
@LanguageLawyer: My point was mostly on typename. template doesn't hurt. (I tried with and without and no difference indeed).Laudianism
@Laudianism Why not do you think that C::T<int>:: is a nested-name-specifier? The name followed by operator :: should obey basic.lookup.qual#1 that is the requirement here for lookupOrola
@Laudianism As per temp.res#3. I think adding typename/template is essentially equavelent to without the typename/templateOrola
I would expect that adding typename (or use using D = C::T<int>::type) would discard non-type A::T (from class.member.lookup#4), but it seems not.Laudianism
Not a language lawyer. As I (mis?)understand basic.lookup.qual#1, in SomeStuff::, SomeStuff should only consider class/namespace, so discard function A::T<int>. (but compilers disagree).Laudianism
@Laudianism If you change the example, that is, change the template function and class in A and B to a common function and class, respectively. Then these compilers will agree basic.lookup.qual#1. It makes no sense that they don't agree with that rule in the case of template specialization.Orola
Worth to note, that gcc accepts typename C::T<int>::type d; but both 'typename` and template should be redundant here according to the standard.Truman
Long ago, Johannes Schaub observed, that gcc rejects function name look-ups in a typename-specifier lookup: bugs.llvm.org/show_bug.cgi?id=8263 That would explain gcc's special behavior here. A very similar (but unfortunately quite mixed up issues) question was stated here: #18311996Truman
An example that is the case fixed by P1787.Orola
K
4

The lookahead required to have the lookup for T depend on the :: even as the interpretation of < depends on the meaning of T is considered undesirable. As such, lookup for a name followed by < is not restricted to types and namespaces regardless of any >:: found later. P1787R6 fixed this, restricting the special lookup to identifiers immediately followed by :: (since other kinds of names can’t refer to types or namespaces anyway).

Kanchenjunga answered 27/2, 2021 at 0:37 Comment(13)
Do you refer to basic.lookup.qual#general-1? "If a name, template-id, or decltype-specifier is followed by a ​::​, it shall designate a namespace, class, enumeration, or dependent type, and the ​::​ is never interpreted as a complete nested-name-specifier." However, it requires us firstly to determine whether T<int> is a template-id or not. Hence temp.names#3.2 applies here to try to lookup whether T is a template. That means there's no other restriction for this lookup,Orola
We would find both A::T and B::T in the scope of C, which is an ambiguous merge. So, Do you mean we should add the keyword template before T to make T<int> be considered as a template-id, after doing it, then [temp.res#general-4.1] will apply to T, Right?Orola
@jackX: template would make /4.1 unnecessary, but being in a type-only context doesn’t affect what name lookup finds for T, so the ambiguity still renders the program ill-formed.Kanchenjunga
Doesn't basic.lookup#general-4 not apply here for the name T? which means T should be a template whose specialization is a type.Orola
@jackX: Type-only lookups are different from type-only contexts, which are a template-specific thing. (That’s why both are italicized when introduced.)Kanchenjunga
So, According to "A qualified-id whose terminal name is dependent and that is in a type-only context is considered to denote a type. ". The name should satisfy two conditions(one is it's a dependent name, the other is it's in a type-only context), then it will affect the lookup rule, Right? In this example, although T is said to be in a type-only context but it is not a dependent name, hence, it does not affect the lookup rule, Right?Orola
@jackX: No, not even that affects lookup—that’s the case where typename need not be used to force interpretation of a qualified name as a type before it can be looked up. You can still find a non-type during instantiation; it’s just an error if you do ([temp.res.general]/3).Kanchenjunga
Is it the reason that sentence [temp.res#general-5] merely says the qualified-id is considered to denote a type when it satisfies these rules. It does not talk about lookup at all. it's the difference with [basic.lookup.qual#general-1], that is Lookup of an identifier followed by a ​::​ scope resolution operator considers only namespaces, types, and templates whose specializations are types. This example indeed proves what's the rule says.Orola
@jackX good example! I think the core question is: Is basic.lookup.qual#general-1 strong enough to prevent the compiler from considering all matching candidates for the current 'local' template instantiation context even if the 'real' usage of the template is ill-formed for all possible cases (merge ambiguity), not just for qualified look-up scenarios. At least, one can interprete basic.lookup.qual#general-1 here as a fallback since 'considering' needn't to be more prioritised than instantiation a priori, solely ensuring the prevention of further wrong parser lines.Truman
@Truman I think P1787 fixed this question by rewording the rule as "Lookup of an identifier followed by a ​::​ scope resolution operator...". That means a template-id is not the case stated here(i.e, there's no restriction to the template-name of a tempalte-id followed by :: when performing the lookup).Orola
@jackX yes, that's it.Truman
@Truman As Divas said in his answers, I conflated the only-type lookup with the only-type context. There's no connection between them.Orola
An example that is the case fixed by P1787.Orola

© 2022 - 2024 — McMap. All rights reserved.