itanium-cxx-abi / cxx-abi

C++ ABI Summary
505 stars 94 forks source link

mangling for placeholders for deduced class template specializations (CTAD) #109

Open zygoloid opened 4 years ago

zygoloid commented 4 years ago

Testcase:

namespace N { template<typename T> struct A { A(T); }; }
template<typename T> void f(T t, decltype(N::A(t)) b, N::A<int> c);
void g() { f(0, 0, N::A(0)); }

GCC mangles this as: _Z1fIiEvT_DTcvDafL0p_EN1N1AIiEE That is: f(T t, decltype(auto(t)), N::A<int>)

Clang trunk asserts; older Clangs mangle this as: _Z1fIiEvT_DTcv1AfL0p_ENS1_IiEE That is: f(T t, decltype(A(t)), {subst for A}<int>)

ICC mangles this as: _Z1fIiEvT_DTcvT18446744073709551614_fL0p_EN1N1AIiEE That is: f(T t, decltype(<template parameter 18446744073709551615>(t)), N::A<int>)

These are all different from each other and none of them is reasonable.

The natural way to mange such a placeholder type would seem to be as a \, where the final \ in a \ would be the template name. So the mangling for the above example would be: _Z1fIiEvT_DTcvN1N1AEfL0p_ENS1_1AIiEE. (Note that the \ in the first parameter is not a substitution candidate for the \ in the second.)

(I'm not sure if this needs actual ABI updates or if this is just an implementation bug shared by every implementation; there don't seem to be any other natural choices for how to mangle this.)

rjmccall commented 4 years ago

It would be better if it were a substitution candidate, right? And that would only change mangling for this case, where we already have implementation disagreement?

zygoloid commented 4 years ago

Well, the complete type will be a substitution candidate if the same deduced placeholder is used twice, so we will get some substitutions at least. But we won't treat the type N::A as a substitution for the template name in N::A<int> (because one of them is a type and the other is a template name). At least in Clang, this seemed more in line with the "substitutions correspond to symbol table entries" principle.

If we choose to treat it as a substitution candidate, would we introduce two N::A substitutions (one for N::A as a \ and one for N::A as a \) or only one substitution with two meanings? Either way seems a little unpleasant in some ways.

rjmccall commented 4 years ago

I think you're drawing a distinction that doesn't exist; template names are usually substitution candidates when they appear in later types, e.g.

template <class> class A;
template <template <class> class T, class U> void foo() {}
template void foo<A, A<int>>();  // _Z3fooI1AS0_IiEEvv
zygoloid commented 4 years ago

Your example certainly addresses half of my concern: it demonstrates that we'll use \<substitution>s in one production (A is mangled as a \<type>) when mangling a different production (the A in A<int> is mangled as a \<template-prefix>, which doesn't even use the same mangling scheme). However, in your example, both occurrences of A refer to the template A, not to a type with the same spelling, so I don't think it's exactly the same situation.

Here's a situation that I think is a little more directly analogous, where we use the same name as both a type and a template:

template<typename> struct U {};
struct T : U<int> {};
template<typename, template<typename> typename> struct X {};
template<typename T> void g(X<typename T::U, T::template U>) {}
void h() { g<T>(X<U<int>, U>()); }

While Clang and GCC mangle this slightly differently, both agree that typename T::U is not a substitution candidate for T::template U, (presumably) because the latter is a template and the former is a type. (If you were to allow such substitutions, it's not too hard to formulate a testcase that would have a mangling collision.)

Nonetheless, I don't think there are any practical problems with allowing a substitution to be used in this specific case -- I don't think we can ever encounter a mangling collision due to this, even though (as noted above) the template/type duality of template names can lead to mangling collisions in other contexts. But I don't think it falls out from the "substitution represents a particular entity" model, because A as a class template and A as a placeholder type for class template argument deduction aren't the same entity -- they're not even the same kind of entity. So I think if we want this, we should call it out explicitly in the mangling rule (eg, "The entity associated with a substitution for a placeholder type for class template deduction is the class template.").

rjmccall commented 4 years ago

However, in your example, both occurrences of A refer to the template A, not to a type with the same spelling, so I don't think it's exactly the same situation.

I don't think it's ever the rule that substitutions are production-specific. The rule is focused squarely on whether you're talking about the same entity. And the similarity of the two N::As in your example is not about spelling: they're actually concretely the same entity, and it wouldn't matter if you named that entity in wildly different ways (e.g. with a using declaration or directive), it would still be the same entity. That is, we wouldn't normally mangle the spelling here at all, we'd mangle the reference to a concrete (if unspecialized) declaration.

Am I missing something where placeholder types can be more complicated, e.g. where you infer arguments from multiple levels of type at once?

My concrete suggestion is that we should just allow cv to take a <template-name>. If that would be ambiguous — which it might be, since I suppose you need to distinguish T::typename X(foo) from T::template X(foo)? — then maybe we shouldn't use cv for this.

zygoloid commented 3 years ago

My concrete suggestion is that we should just allow cv to take a <template-name>.

I don't think we should special-case cv: such a placeholder type can appear in other places (such as in a tl or nw mangling or in the type of a template parameter), and I'd expect that in the not-too-distant future we'll allow such types as a function parameter or return type; I think we should support it as a general <type> mangling. So concretely I suppose I'd propose this:

<type> ::= <template name>  # placeholder for deduced class type

... with an explicit mention in 5.1.10 that such a placeholder is a substitution candidate for its deduced template and vice versa.

I suppose you need to distinguish T::typename X(foo) from T::template X(foo)?

I don't think we do, because I don't think there's any context in which we can encounter T::X as a placeholder for a deduced class type and also encounter it as a template-name or as an injected-class-name type. But I don't have a proof it's impossible, I've just been unable to find an example :)

rjmccall commented 3 years ago

Ah, sure. That works for me.