Member selections on dependent expressions
David Vandevoorde
daveed at vandevoorde.com
Fri Jun 25 21:55:17 UTC 2010
We've been working on the new C++0x SFINAE rules and the backward-compatibility consequences for mangling are a bit baffling: I'd like to get input from other implementors (particularly GCC) regarding direction in this area.
Let me start with a seemingly simple example:
namespace N {
struct S { int x; } s;
template<class T> auto f(T p)->decltype(p.S::x);
void g() {
f(s); // i.e. f<S>(s)
}
}
GCC 4.5 encodes N::f<N::S> as "_ZN1N1fINS_1SEEEDtdtfp_srS1_1xET_" -- let me walk through how it gets there.
After the ABI prefix "_Z" comes the <nested-name> N::f<N::S> which is encoded as "N1N1fINS_1SEEE".
Note how the first substitutable entity (code "S_") is the namespace N, and that is used for the second reference to N in the template argument N::S ("NS_1SE"), which itself becomes the second substitutable entity (code "S1_").
The return type follows ("Dtdtfp_srS1_1xE") and breaks down as follows: "Dt ... E" for decltype of the member selection encoded as "dtfp_srS1_1x", "dt" for the dot operator on "fp_" (an encoding that means "first function parameter"), which leaves "srS1_1x" to represent "S::x" in this case.
Now the "sr" ("scope resolution") code in the current ABI spec is specified through the following productions:
<expression> ::= ...
::= sr <type> <unqualified-name> # dependent name
::= sr <type> <unqualified-name> <template-args> # dependent template-id
This seems reasonable here because p in "p.S::x" has a template-dependent type, and so the "x" can be considered a dependent name. "S" is apparently N::S, which, as noted above, is encoded as "S1_" because of the substitution rules.
Now, if I'd written the declaration of f by writing S as N::S:
template<class T> auto f(T p)->decltype(p.N::S::x);
the reasoning above holds and I'd find the same mangled name (and, indeed, GCC 4.5 does that).
However, the language definition throws a wrench in that reasoning: S in "p.S::x" must be looked up both using ordinary lookup and in the scope of whatever class type p ends up having (see 3.4.5/4 in the current working paper for the language), and if the two lookups end up finding different things, it is an error... except that since this is a SFINAE case it just means that that function instance drops out of the overload set. Similarly, N::S in "p.N::S" must be looked up twice, and may end up being invalid. But since S and N::S can yield different results in the class-scope lookup, it means that the two template declarations above are distinct!
Let me demonstrate this by example with a slight modification of the example above (still valid C++0x, but requires support for the new SFINAE rules):
namespace N {
struct S { int x; struct N { enum S {}; }; } s; // Note the added nested class N.
template<class T> auto f(T p)->decltype(p.S::x) { return 1; } // #1
template<class T> auto f(T p)->decltype(p.N::S::x) { return 2; } // #2
void g() {
f(s); // Calls #1
}
}
When substituting N::S for T in template #1, the lookup of S in the scope of p finds the type S itself, which is the same type as what ordinary lookup finds: That substitution is fine.
When substituting N::S for T in template #2, the lookup of N::S in the scope of p finds an enumeration, which conflicts with the class type N::S found through ordinary lookup: The substitution of #2 therefore fails, and the only remaining candidate for the call is #1.
The existing ABI (and, hence, existing practice) is therefore not adequate for the C++0x language rules: We have to break backward compatibility in this area.
(We also looked at changing the C++0x language itself to avoid the problem, but it's a really sell ("please change the language in an obscure/subtle way because it raises some obscure/subtle backward-compatibility issue in our mangling scheme").)
Instead of the current approach, we'll have to encode the source form of the selected member in cases like these. I.e., p.S::x, p.N::S, and even p.::N::S::x must all be encoded differently even though for a valid instantiation they must all resolve to the same thing. I'm thinking that we should continue to use the sr prefix, but what follows would be a <type> only if the qualifier is a template parameter (p.T::x) or a decltype construct (p.decltype(xx)::x); otherwise, it would encode an "unresolved name" (which is not a candidate for substitution; e.g., N::S might be encoded as "N1N1SE" even though there is a substitution code for the type ::N::S.
Thoughts?
Daveed
P.S.: What mitigates the bad news of ABI breakage is that I don't think code like this is widespread yet (and the cases that do occur are probably inlined calls).
More information about the cxx-abi-dev
mailing list