This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 119a. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.
2025-12-20
[Voted into WP at April 2005 meeting.]
The ambiguity text in 6.5.2 [class.member.lookup]may not say what we intended. It makes thefollowing example ill-formed:
struct A { int x(int); }; struct B: A { using A::x; float x(float); }; int f(B* b) { b->x(3); // ambiguous }This is a name lookup ambiguity because of 6.5.2 [class.member.lookup] paragraph 2:... Each of these declarations that was introduced by a using-declarationis considered to be from each sub-object of C that is of the type containingthe declaration designated by the using-declaration. If the resulting setof declarations are not all from sub-objects of the same type, or the sethas a nonstatic member and includes members from distinct sub-objects,there is an ambiguity and the program is ill-formed.This contradicts the text and example in paragraph 12 of 9.10 [namespace.udecl].
Proposed Resolution (10/00):
Replace the two cited sentences from 6.5.2 [class.member.lookup] paragraph 2with the following:
The resulting set of declarations shall all be from sub-objectsof the same type, or there shall be a set of declarations fromsub-objects of a single type that containsusing-declarationsfor the declarations found in all other sub-object types.Furthermore, for nonstatic members, the resulting set ofdeclarations shall all be from a single sub-object, or there shallbe a set of declarations from a single sub-object that containsusing-declarations for the declarations found in all othersub-objects. Otherwise, there is an ambiguity and the program isill-formed.
Replace the examples in 6.5.2 [class.member.lookup] paragraph 3with the following:
struct A { int x(int); static int y(int); }; struct V { int z(int); }; struct B: A, virtual V { using A::x; float x(float); using A::y; static float y(float); using V::z; float z(float); }; struct C: B, A, virtual V { }; void f(C* c) { c->x(3); // ambiguous -- more than one sub-object A c->y(3); // not ambiguous c->z(3); // not ambiguous }Notes from 04/01 meeting:
The following example should be accepted but is rejected bythe wording above:
struct A { static void f(); }; struct B1: virtual A { using A::f; }; struct B2: virtual A { using A::f; }; struct C: B1, B2 { }; void g() { C::f(); // OK, calls A::f() }Notes from 10/01 meeting (Jason Merrill):
The example in the issues list:
struct A { int x(int); }; struct B: A { using A::x; float x(float); }; int f(B* b) { b->x(3); // ambiguous }Is broken under the existing wording:... Each of these declarations that was introduced by a using-declarationis considered to be from each sub-object of C that is of the type containingthe declaration designated by the using-declaration. If the resulting setof declarations are not all from sub-objects of the same type, or the sethas a nonstatic member and includes members from distinct sub-objects,there is an ambiguity and the program is ill-formed.Since the two x's are considered to be "from" different objects, looking upx produces a set including declarations "from" different objects, and theprogram is ill-formed. Clearly this is wrong. The problem with theexisting wording is that it fails to consider lookup context.
The first proposed solution:
The resulting set of declarations shall all be from sub-objectsof the same type, or there shall be a set of declarations fromsub-objects of a single type that containsusing-declarationsfor the declarations found in all other sub-object types.Furthermore, for nonstatic members, the resulting set ofdeclarations shall all be from a single sub-object, or there shallbe a set of declarations from a single sub-object that containsusing-declarations for the declarations found in all othersub-objects. Otherwise, there is an ambiguity and the program isill-formed.breaks this testcase:
struct A { static void f(); }; struct B1: virtual A { using A::f; }; struct B2: virtual A { using A::f; }; struct C: B1, B2 { }; void g() { C::f(); // OK, calls A::f() }because it considers the lookup context, but not the definition context;under this definition of "from", the two declarations found are theusing-declarations, which are "from" B1 and B2.The solution is to separate the notions of lookup and definition context.I have taken an algorithmic approach to describing the strategy.
Incidentally, the earlier proposal allows one base to have a superset ofthe declarations in another base; that was an extension, and my proposaldoes not do that. One algorithmic benefit of this limitation is tosimplify the case of a virtual base being hidden along one arm and notanother ("domination"); if we allowed supersets, we would need to rememberwhich subobjects had which declarations, while under the followingresolution we need only keep two lists, of subobjects and declarations.
Proposed resolution (October 2002):
Replace 6.5.2 [class.member.lookup] paragraph 2 with:
The following steps define the result of name lookup for a membername f in a class scope C.
Thelookup set for f in C, called S(f,C), consists of twocomponent sets: thedeclaration set, a set of members named f; andthesubobject set, a set of subobjects where declarations of thesemembers (possibly including using-declarations) were found. In thedeclaration set, using-declarations are replaced by the members theydesignate, and type declarations (including injected-class-names) arereplaced by the types they designate. S(f,C) is calculated asfollows.
If C contains a declaration of the name f, the declaration set containsevery declaration of f in C (excluding bases), the subobject set contains Citself, and calculation is complete.
Otherwise, S(f,C) is initially empty. If C has base classes, calculatethe lookup set for f in each direct base class subjobject Bi,and merge each such lookup set S(f,Bi) in turn into S(f,C).
The following steps define the result of merging lookup setS(f,Bi) into the intermediate S(f,C):
- If each of the subobject members of S(f,Bi) is a baseclass subobject of at least one of the subobject members of S(f,C), S(f,C)is unchanged and the merge is complete. Conversely, if each of thesubobject members of S(f,C) is a base class subobject of at least one ofthe subobject members of S(f,Bi), the new S(f,C) is a copy ofS(f,Bi).
- Otherwise, if the declaration sets of S(f,Bi) and S(f,C)differ, the merge is ambiguous: the new S(f,C) is a lookup set with aninvalid declaration set and the union of the subobject sets. In subsequentmerges, an invalid declaration set is considered different from anyother.
- Otherwise, consider each declaration d in the set, where d is amember of class A. If d is a nonstatic member, compare the A base classsubobjects of the subobject members of S(f,Bi) andS(f,C). If they do not match, the merge is ambiguous, as in theprevious step. [Note: It is not necessary to remember which A subobjecteach member comes from, since using-declarations don't disambiguate. ]
- Otherwise, the new S(f,C) is a lookup set with the sharedset of declarations and the union of the subobject sets.
The result of name lookup for f in C is the declaration set ofS(f,C). If it is an invalid set, the program is ill-formed.
[Example:
struct A { int x; }; // S(x,A) = {{ A::x }, { A }} struct B { float x; }; // S(x,B) = {{ B::x }, { B }} struct C: public A, public B { }; // S(x,C) = { invalid, { A in C, B in C }} struct D: public virtual C { }; // S(x,D) = S(x,C) struct E: public virtual C { char x; }; // S(x,E) = {{ E::x }, { E }} struct F: public D, public E { }; // S(x,F) = S(x,E) int main() { F f; f.x = 0; // OK, lookup finds { E::x } }S(x,F) is unambiguous because the A and B base subobjects of D are alsobase subobjects of E, so S(x,D) is discarded in the first merge step. --endexample]
Turn 6.5.2 [class.member.lookup] paragraphs 5 and 6 into notes.
Notes from October 2003 meeting:
Mike Miller raised some new issues in N1543, and we adjusted theproposed resolution as indicated in that paper.
Further information from Mike Miller (January 2004):
Unfortunately, I've become aware of a minor glitch inthe proposed resolution for issue 39 in N1543, so I'd like tosuggest a change that we can discuss in Sydney.
A brief review and background of the problem: the major change weagreed on in Kona was to remove detection of multiple-subobjectambiguity from class lookup (6.5.2 [class.member.lookup]) and insteadhandle it as partof the class member access expression. It was pointed out inKona that 11.8.3 [class.access.base]/5 has this effect:
If a class member access operator, including an implicit "this->," is used to access a nonstatic data member or nonstatic member function, the reference is ill-formed if the left operand (considered as a pointer in the "." operator case) cannot be implicitly converted to a pointer to the naming class of the right operand.
After the meeting, however, I realized that this requirement isnot sufficient to handle all the cases. Consider, for instance,
struct B { int i; }; struct I1: B { }; struct I2: B { }; struct D: I1, I2 { void f() { i = 0; // not ill-formed per 11.2p5 } };Here, both the object expression ("this") and the naming classare "D", so the reference to "i" satisfies the requirement in11.8.3 [class.access.base]/5, even though it involves amultiple-subobject ambiguity.
In order to address this problem, I proposed in N1543 to add aparagraph following 7.6.1.5 [expr.ref]/4:
If E2 is a non-static data member or a non-static member function, the program is ill-formed if the class of E1 cannot be unambiguously converted (10.2) to the class of which E2 is directly a member.
That's not quite right. It does diagnose the case above aswritten; however, it breaks the case where qualification is usedto circumvent the ambiguity:
struct D2: I1, I2 { void f() { I2::i = 0; // ill-formed per proposal } };In my proposed wording, the class of "this" can't be converted to"B" (the qualifier is ignored), so the access is ill-formed.Oops.
I think the following is a correct formulation, so the proposedresolution we discuss in Sydney should contain the followingparagraph instead of the one in N1543:
If E2 is a nonstatic data member or a non-static member function, the program is ill-formed ifthe naming class (11.2) of E2 cannot be unambiguously converted (10.2) to the class of which E2 is directly a member.
This reformulation also has the advantage of pointing readers to11.8.3 [class.access.base], where the the convertibility requirementfrom the class ofE1 to the naming class is located and which might otherwise beoverlooked.
Notes from the March 2004 meeting:
We discussed this further and agreed with these latestrecommendations. Mike Miller has produced a paper N1626 that givesjust the final collected set of changes.
(This resolution also resolvesisssue 306.)