In the Itanium C++ ABI, why does the mangled name for template functions not resolve dependent typedefs?
Asked Answered
T

1

23

For example:

template <typename T>
struct foo
{
    using bar = int;
};

// _Z3bazi
void baz(foo<int>::bar quux) {
}

template <typename T>
void baz(typename foo<T>::bar quux) {
}

// _Z3bazIiEvN3fooIT_E3barE
template void baz<int>(foo<int>::bar quux);

Why does the mangled form of baz<int> mention foo at all? How come it's not _Z3bazIiEvi?

This is apparently the reason that the C++17 std::default_order<T> proposal is dead in the water.

Teamwork answered 20/10, 2016 at 14:37 Comment(5)
So that when demangled, it looks like the source code?Tamatave
relevant discussion on redditCanaveral
I untagged [gcc] because as an ABI question it also applies to Clang etc. I reproduced the problem with Clang, and it's very surprising… incredible that this could be conforming to the C++ standard. So the first step is to check that the abi does in fact dictate it.Algol
@Algol Indeed, the Itanium ABI is not specific to gcc. But I tagged [gcc] anyway because I felt like gcc experts may have the necessary knowledge to answer this question. Also, I believe lots of the mangling rules were invented by gcc and codified into the Itanium ABI later, no?Teamwork
@Barry Nonconformance comes if you can observe that a distinct symbol exists, for example if the two names of the one specialization yield different addresses. At first I thought Clang was letting me generate one specialization twice, but actually that's not the case.Algol
A
9

The issue comes from the <unresolved-name> construct in the ABI. Why would we ever want to use an unresolved name? It's all about declaration matching and overloads. C++14 §14.5.6.1/3 notes,

Two distinct function templates may have identical function return types and function parameter lists, even if overload resolution alone cannot distinguish them.

You can have another function in a different file,

template <typename T>
void baz(int quux) { std::abort(); }

Although this signature can't peacefully coexist in the same file — it cannot be named because of overload ambiguity — it can exist in a different file so it needs a distinct mangling.

(Even this level of coexistence is not guaranteed by the standard for all templates. It's a matter of QOI that the compiler uses the exact form of a function template declaration to perform declaration matching, so that copy-pasting a declaration into a definition will tend to provide an exact match and not a surprising conflict with another function template that resolves to the same signature. See §14.5.6.1/5-6.)

As for raining on default_order's parade, the problem is that template-ids implicitly pull default arguments from templates. So the user could unintentionally have a dependent typename in a signature just by mentioning std::set.

Algol answered 20/10, 2016 at 16:7 Comment(16)
Thanks! Are you sure that template <typenamte T> void baz(int) is actually allowed in a separate translation unit? I always assumed that was one of those "malformed, no diagnostic required" things. In particular, apparently MSVC mangles both the same way. Is that really nonconformant?Teamwork
@TavianBarnes NDR is possible per §14.5.6.1/6 if they are "functionally equivalent but not equivalent." It's a little odd that the rule specifies to compare template declarations according to a process defined only for expressions, so it talks about values when we only have types. I'll edit…Algol
"functionally equivalent" means that "for any given set of template arguments, the evaluation of the expression results in the same value". Even if a similar rule applied to types, it wouldn't apply to default_order, which is meant to be specialized to return something different.Calcic
@Calcic Equivalence is when the expression (or dependent typename) is expressed in a similar way, modulo parameter identifiers. Functional equivalence is when evaluation given all the actual template arguments results in same values (or dependent types?). For the default_order example, functional equivalence only makes sense in the context of use, after any customization. Equivalence is determined in processing declarations; functional equivalence may be determined at overload resolution. Both always apply, though they're relevant at different points in compilation.Algol
You seem to read "for any" as "there exists any". I read it as "for every", so that two expressions are "functionally equivalent" only if there exists no set of arguments for which the two expressions will yield different values. Note that "functionally equivalent" is a property of two function templates, not two function template specializations, so you seem to be saying that template<int I, int J> void f(A<I * J>); template<int I, int J> void f(A<I + J>); is ill-formed NDR, which makes zero sense to me.Calcic
@Calcic You're reading it correctly, but although it's a property of templates (not their specializations), functional equivalence of a function template signature using default_order can still depend on specializations of default_order. Your example is certainly ill-formed NDR. Plug I=0, J=0 and you have a "given set of template arguments, the evaluation of the expression results in the same value." Any resulting confusion is a justification for the Itanium ABI sensibility of QOI.Algol
@Algol No, I + J and I * J are not functionally equivalent under my reading because there exists some sets of arguments for which they do not have the same value (e.g., I=1, J=1). You are reading "for any" as ∃, I'm reading it as ∀. Indeed, under your reading, even the example two paragraphs above in [temp.over.link]/4 would be ill-formed NDR. If that were the intent, I can't imagine why they wouldn't call it out there.Calcic
@Calcic Oh, now I see what you mean. "∀" is probably the correct reading, but seemingly not what MSVC does, so it may be nonconforming. I hedged this answer with "may not be guaranteed." However, that's orthogonal to the issue of default_order specializations. The NDR diagnosis is applied very lazily at link time, if at all, whereas you might be thinking in eager terms at template declaration time.Algol
But they are related; unless for every T, default_order_t<T> and std::less<T> are the same type - and they are not - then template<class T> void f(default_order_t<T>); and template<class T> void f(std::less<T>); are distinct function templates that violate no rule in the standard, and the compiler must ensure that their specializations are not linked together erroneously.Calcic
@Calcic The Itanium ABI seems to go with ∀ which minimizes unintuitive linking-together (but also fails to link together e.g. A+B with B+A). MSVC seems to go with ∃ (or it simply has an ABI bug) and overloading-ambiguous signatures may collide in the linker. The errors in either case come naturally from the linker, not from an attempt to actually prove equivalence. For example, MSVC may normally give same names to specializations of your two f templates, except when default_order is specialized, then the names will be distinct and there is no erroneous linkage.Algol
@Calcic … If default_order is not specialized, then MSVC does link between those two forms (presuming I'm right about it; I cannot verify because I don't have a copy). That's probably what you're really getting at. But under their reading of ∃ it would be ill-formed NDR so linkage, even if erroneous, is actually OK.Algol
"It's a matter of QOI that the compiler uses the exact form of a function template declaration to perform declaration matching, so that copy-pasting a declaration into a definition will tend to provide an exact match and not a surprising conflict with another function template that resolves to the same signature." - since foo can be specialized so that it yields non-int types, I disagree with you. The templates are not functionally equivalent AFAICS.Detached
(note that currently I think the paragraphs you quote only apply to template parameter lists or return types that refer to expressions: "Two function templates are functionally equivalent if they are equivalent except that one or more expressions that involve template parameters in the return types and parameter lists are functionally equivalent using the rules described above to compare expressions involving template parameters.".Detached
AFAIK, this is intended to also cover "type expressions" - in the sense of "and an attempt is made to find template argument values (a type for a type parameter, a..." from [temp.deduct] which is a bit less formal in its use of "values", in the same way that your referenced paragraphs are a bit less formal. But maybe I'm overlooking something.Detached
@JohannesSchaub-litb 1. I've strengthened the "may not" in the preceding sentence. The parenthetical paragraph isn't intended to apply to the example at hand; yes, it involves expressions. 2. Even if the linker only saw fully instantiated templates, it wouldn't have problems with specialized foo. The program is only allowed to have one member-specification per specialization.Algol
@JohannesSchaub-litb The standard mentions "return types and parameter lists that are equivalent using the rules described above to compare expressions involving template parameters" — the equivalence rules are used to account for identifier differences when expressions aren't involved, but I don't see how (mere) functional equivalence can occur without an expression, such in as the given example.Algol

© 2022 - 2024 — McMap. All rights reserved.