Resolved Issues of Name Lookup X3J16/93-0015 WG21/N0223 Prescott K. Turner, Jr. Liant Software Corp. 959 Concord St., Framingham, MA 01701 USA January 22, 1993 This paper presents the issues of name lookup which have been resolved by the core language working group, starting with the June 1991 meeting and ending with the July 1992 meeting. The purpose is to enable the committee to act on adopting these agreements. Lookup in the Context of Nested Classes At the Lund meeting, the core working group reported having reached agreement regarding name lookup in the context of nested classes. Their agreement was presented in terms of an example, and can be expressed using the following three rules. Proposal: Introduce the following rules of hiding and scope of declarations. A declaration in a nested declarative region hides a declaration whose declarative region contains the nested region. A declaration within a member function hides a declaration whose scope extends to or past the end of the member function's class. The scope of a declaration that extends to or past the end of a class declaration also extends to the regions defined by its member definitions, even if defined lexically outside the class (this includes both function member bodies and static data member initializations). The first of these rules uses the term "declarative region," for what has usually been called "scope" until now. The new term is explained toward the end of this paper. Here is the example which was presented by the working group at Lund to demonstrate their agreement. struct X { static int i; // 3 struct Y { int i; // 2 void f(); }; }; int i; // 4 void X::Y::f() { int i; // 1 i = 5; // To which declaration of i does this refer? } In the above example, the hiding rule for nested regions causes 1 to hide 4, 2 to hide 3 and 4, and 3 to hide 4. The hiding rule for declarations within member functions causes 1 to hide 2 and 3. Following is an example involving derivation and nesting. int j; // 2 struct A1 { static int j; // never struct B1 { }; }; struct A2 { static int j; // 1 struct B2 : public A1::B1 { void f(); }; }; void A2::B2::f() { j = 3; // To which declaration of j does this refer? } Above, the scope of A1::j does not include the point where j is used, because it is not a member of B1. Declaration 1 hides 2 by the rule for nested regions. Note that at the Lund meeting the 11.4 rule regarding lookup in friend function definitions was still regarded as controversial. Inline Definitions of Friends According to the Lund minutes there was consensus regarding Mike Miller's fourth issue: inline definition of friend member declaration. A straw vote of the committee of the whole showed lots in favor if removing this feature and only 1 opposed. Proposal: Add to 11.4/5: A friend declaration of a member may not define that member. A definition of a member outside of the definition of its class has special scope rules to allow easy reference to its class's members (9.3/2). For example: class C { int i; int f(); }; int i = 2; int C::f() { return i; // C::i } The proposed limitation helps prevent potential confusion over the meanings of names which occur in the body of such a friend definition. For example, class X { static int i,j; // 1 void f(); class Y { void f(); }; }; class Z { static int i,j; // 2 friend void X::f() { i = 5; } friend void X::Y::f() { j = 5; } }; Under the current rules, both 11.4/5 and 9.3/2 give special rules so that the occurrences of i are in the scope of both the declaration of X::i and Z::i. The proposal would avoid situations in which ambiguous-looking references could occur, making C++ easier to use. At the same time the core language working group would not have to reconcile the two rules. Finishing Up 9.2.1 Scope Rules for Classes 9.2.1 has no examples, although several were discussed in committee and there was a specific request to incorporate one. The following examples illustrate all three rules. Most of these are taken from section 9.9 of the working paper. Proposal: Add the following examples to section 9.2.1. typedef int c; enum { i = 1 }; class X { char v[i]; // error: `i' refers to ::i, // but when reevaluated is X::i int f() { return sizeof(c); } // OK: refers to char X::c char c; enum { i = 2 }; }; typedef char* T; struct Y { T a; // error: `T' refers to ::T, // but when reevaluated is X::T typedef long T; T b; }; struct Z { int f(const R); // undefined: `R' is parameter name, // but swapping the 2 declarations // changes `R' to parameter type typedef int R; }; The second paragraph of section 9.9 has been superseded by section 9.2.1 adopted at the March 1992 meeting. The examples from that paragraph are superseded by the above examples. Proposal: Delete paragraph 9.9/2 and its examples. Matters Which Have Already Been Incorporated Two of the core working group's resolutions of name lookup issues have already been incorporated into the working paper. I mention them here for completeness, as it's unnecessary to provide descriptions. At the July 1992 meeting in Toronto (X3J16/92-0078 WG21/N0155), consistent rules were adopted to govern injection of previously undeclared friend functions. At the March 1992 meeting in London (X3J16/92-0041 WG21/N0118), scope rules for classes were adopted which now constitute section 9.2.1 of the working paper. Coherence The three rules of the first proposal above rely strongly on terms such as "scope" and "name". In formulating the rules, the work Mike Miller and Brian Kennedy did in 1991 was invaluable, both for the way they clarified the terms, and for the consistent framework of nonalgorithmic scope rules which they constructed prior to the Lund meeting. I have adapted their work as a foundation for the proposed rules, and reiterate a version below to provide background. If there's anything good here, please don't attribute its invention to me. I take the blame for flaws. "Scope" has a definition in the working paper 3/1, as a property of declarations. It is sometimes used inconsistently to refer to the block, class, file, etc. in which a declaration appears. The phrase "are declared in the same scope" is typical. For the purposes of this paper "scope" has the 3/1 meaning. For the other meaning of scope a new term is introduced, "region". A region is a portion of a compilation unit that delimits the scopes of declarations. In C++, the following constructs define regions: an entire compilation unit the body of a function, including its arguments a class declaration a template declaration a compound statement the dependent statement(s) of if, while, do, for, and switch One region is nested within another if its defining construct is lexically contained within the defining construct of the other. Regions never partially overlap; either all of a region is nested within another or none of it is. For the purposes of this description, a nested region is not considered to be part of its containing region. A declaration is visible at a particular location in a compilation unit if it can be denoted at that location without qualification, i.e. without use of '::', '.', or '->'. The scope of a declaration is the portion of the compilation unit in which the declaration is potentially visible. The scopes of two or more declarations with the same name may overlap. In such a case, use of the name may be ambiguous, or overloading may disambiguate use of the name, or one declaration may hide the others so as to disambiguate the use. A declaration is visible at a point in a compilation unit if and only if that point is within the scope of that declaration and that declaration is not hidden. The scope of a declaration is determined by the following scoping rules, and which declarations hide other declarations is determined by the following hiding rules. A member of a class is any entity declared in the region of that class, or of a direct or indirect base of that class. Scoping Rules The scope of a declaration in a region extends from the point of declaration (S3.2) to the end of the region in which it is declared. A declaration's scope does not necessarily extend to regions nested within the scope, but a scope does extend to a region, it extends from the beginning to the end of the region, If a region is directly nested in the scope of a declaration, then the scope extends to the nested region, provided the nested region is not a class declaration, or if it is a class declaration and then provided no base class has a member with the same name. The scope of a declaration which belongs to a class region additionally extends to all function bodies, default arguments, and constructor initializers lexically contained in the class. When these constructs occur within a nested class, the same exception for members of base classes applies. The scope of a declaration which belongs to a base class extends to all classes derived from the base class, directly or indirectly. The scope of a declaration that extends to or past the end of a class region also extends to the regions defined by its member definitions, even if defined lexically outside the class (this includes both function member bodies and static data member initializations). Hiding Rules The following "hiding rules" apply only when the declarations in question declare the same name. A declaration in a nested region hides a declaration whose region contains the nested region. A declaration in a member function hides a declaration whose scope extends to or past the end of its class. Use of an unqualified name is invalid if visible declarations of that name belong to more than one region. Discussion These hiding rules do not attempt to resolve ambiguities in the hierarchy of base classes (dominance). Here is an example of the exception to the rule of declarations extending into nested regions. struct B { int i; }; int i; struct D : B { // The declaration of ::i does not extend to here. int foo () { return i; } // refers to B::i }; In the earlier versions of these rules by Miller and Kennedy, the selection of B::i over ::i was based on hiding rules. I found that it must be based on something more powerful than general hiding rules. In particular, a declaration in a derived class does not always hide a declaration in a base class. For example, struct A { int i; }; struct X : A { int i; }; struct C : X { struct D : A { int foo () { return i; } // A::i }; };