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