| Document number: | J16/08-0015 = WG21 N2505 |
| Date: | 2008-02-03 |
| Project: | Programming Language C++ |
| Reference: | ISO/IEC IS 14882:2003 |
| Reply to: | William M. Miller |
| Edison Design Group, Inc. | |
| wmm@edg.com |
This document contains the C++ core language issues that have been categorized as Defect Reports by the Committee (J16 + WG21), that is, issues with status "DR," "WP," and "TC1," along with their proposed resolutions. ONLY RESOLUTIONS FOR ISSUES WITH TC1 STATUS ARE PART OF THE INTERNATIONAL STANDARD FOR C++. The other issues are provided for informational purposes only, as an indication of the intent of the Committee. They should not be considered definitive until or unless they appear in an approved Technical Corrigendum or revised International Standard for C++.
This document is part of a group of related documents that together describe the issues that have been raised regarding the C++ Standard. The other documents in the group are:
For more information, including a description of the meaning of the issue status codes and instructions on reporting new issues, please see the Active Issues List.
Section references in this document reflect the section numbering of document J16/07-0331 = WG21 N2461.
[Moved to DR at October 2007 meeting.]
C99 and C++ differ in their approach to universal character names (UCNs).
Issue 248 already covers the differences in UCNs allowed for identifiers, but a more fundamental issue is that of UCNs that correspond to codes reserved by ISO 10676 for surrogate pair forms.
Specifically, C99 does not allow UCNs whose short names are in the range 0xD800 to 0xDFFF. I think C++ should have the same constraint. If someone really wants to place such a code in a character or string literal, they should use a hexadecimal escape sequence instead, for example:
wchar_t w1 = L'\xD900'; // Okay.
wchar_t w2 = L'\uD900'; // Error, not a valid character.
(Compare 6.4.3 paragraph 2 in ISO/IEC 9899/1999 with 2.2 [lex.charset] paragraph 2 in the C++ standard.)
Proposed resolution (October, 2007):
This issue is resolved by the adoption of paper J16/07-0030 = WG21 N2170.
[Voted into WP at April, 2007 meeting.]
Section 1.3.11 [defns.signature], definition of "signature" omits the function name as part of the signature. Since the name participates in overload resolution, shouldn't it be included in the definition? I didn't find a definition of signature in the ARM, but I might have missed it.
Fergus Henderson: I think so. In particular, 17.4.3.1.2 [global.names] reserves certain "function signatures" for use by the implementation, which would be wrong unless the signature includes the name.
-2- Each global function signature declared with external linkage in a header is reserved to the implementation to designate that function signature with external linkage.
-5- Each function signature from the Standard C library declared with external linkage is reserved to the implementation for use as a function signature with both extern "C" and extern "C++" linkage, or as a name of namespace scope in the global namespace.
Other uses of the term "function signature" in the description of the standard library also seem to assume that it includes the name.
James Widman:
Names don't participate in overload resolution; name lookup is separate from overload resolution. However, the word “signature” is not used in clause 13 [over]. It is used in linkage and declaration matching (e.g., 14.5.6.1 [temp.over.link]). This suggests that the name and scope of the function should be part of its signature.
Proposed resolution (October, 2006):
Replace 1.3.11 [defns.signature] with the following:
the name and the parameter-type-list (8.3.5 [dcl.fct]) of a function, as well as the class or namespace of which it is a member. If a function or function template is a class member its signature additionally includes the cv-qualifiers (if any) on the function or function template itself. The signature of a function template additionally includes its return type and its template parameter list. The signature of a function template specialization includes the signature of the template of which it is a specialization and its template arguments (whether explicitly specified or deduced). [Note: Signatures are used as a basis for name-mangling and linking. —end note]
Delete paragraph 3 and replace the first sentence of 14.5.6.1 [temp.over.link] as follows:
The signature of a function template specialization consists of the signature of the function template and of the actual template arguments (whether explicitly specified or deduced).The signature of a function template
consists of its function signature, its return type and its template parameter listis defined in 1.3.11 [defns.signature]. The names of the template parameters are significant...
(See also issue 537.)
[Voted into WP at April, 2007 meeting.]
The standard defines “signature” in two places: 1.3.11 [defns.signature] and 14.5.6.1 [temp.over.link] paragraphs 3-4. The former seems to be meant as a formal definition (I think it's the only place covering the nontemplate case), yet it lacks some bits mentioned in the latter (specifically, the notion of a “signature of a function template,” which is part of every signature of the associated function template specializations).
Also, I think the 1.3.11 [defns.signature] words “the information about a function that participates in overload resolution” isn't quite right either. Perhaps, “the information about a function that distinguishes it in a set of overloaded functions?”
Eric Gufford:
In 1.3.11 [defns.signature] the definition states that “Function signatures do not include return type, because that does not participate in overload resolution,” while 14.5.6.1 [temp.over.link] paragraph 4 states “The signature of a function template consists of its function signature, its return type and its template parameter list.” This seems inconsistent and potentially confusing. It also seems to imply that two identical function templates with different return types are distinct signatures, which is in direct violation of 13.3 [over.match]. 14.5.6.1 [temp.over.link] paragraph 4 should be amended to include verbiage relating to overload resolution.
Either return types are included in function signatures, or they're not, across the board. IMHO, they should be included as they are an integral part of the function declaration/definition irrespective of overloads. Then verbiage should be added about overload resolution to distinguish between signatures and overload rules. This would help clarify things, as it is commonly understood that overload resolution is based on function signature.
In short, the term “function signature” should be made consistent, and removed from its (implicit, explicit or otherwise) linkage to overload resolution as it is commonly understood.
James Widman:
The problem is that (a) if you say the return type is part of the signature of a non-template function, then you have overloading but not overload resolution on return types (i.e., what we have now with function templates). I don't think anyone wants to make the language uglier in that way. And (b) if you say that the return type is not part of the signature of a function template, you will break code. Given those alternatives, it's probably best to maintain the status quo (which the implementors appear to have rendered faithfully).
Proposed resolution (September, 2006):
This issue is resolved by the resolution of issue 357.
[Voted into WP at April, 2006 meeting.]
The standard uses “most derived object” in some places (for example, 1.3.4 [defns.dynamic.type], 5.3.5 [expr.delete]) to refer to objects of both class and non-class type. However, 1.8 [intro.object] only formally defines it for objects of class type.
Possible fix: Change the wording in 1.8 [intro.object] paragraph 4 from
an object of a most derived class type is called a most derived object
to
an object of a most derived class type, or of non-class type, is called a most derived object
Proposed resolution (October, 2005):
Add the indicated words to 1.8 [intro.object] paragraph 4:
If a complete object, a data member (9.2 [class.mem]), or an array element is of class type, its type is considered the most derived class, to distinguish it from the class type of any base class subobject; an object of a most derived class type, or of a non-class type, is called a most derived object.
[Voted into WP at the October, 2006 meeting.]
Issue 298, recently approved, affirms that cv-qualified class types can be used as nested-name-specifiers. Should the same be true for base-specifiers?
Rationale (April, 2005):
The resolution of issue 298 added new text to 9.1 [class.name] paragraph 5 making it clear that a typedef that names a cv-qualified class type is a class-name. Because the definition of base-specifier simply refers to class-name, it is already the case that cv-qualified class types are permitted as base-specifiers.
Additional notes (June, 2005):
It's not completely clear what it means to have a cv-qualified type as a base-specifier. The original proposed resolution for issue 298 said that “the cv-qualifiers are ignored,” but that wording is not in the resolution that was ultimately approved.
If the cv-qualifiers are not ignored, does that mean that the base-class subobject should be treated as always similarly cv-qualified, regardless of the cv-qualification of the derived-class lvalue used to access the base-class subobject? For instance:
typedef struct B {
void f();
void f() const;
int i;
} const CB;
struct D: CB { };
void g(D* dp) {
dp->f(); // which B::f?
dp->i = 3; // permitted?
}
Proposed resolution (October, 2005):
Change 9.1 [class.name] paragraph 5 as indicated:
A typedef-name (7.1.3 [dcl.typedef]) that names a class type, or a cv-qualified version thereof, is also aclass-name, butclass-name. If a typedef-name that names a cv-qualified class type is used where a class-name is required, the cv-qualifiers are ignored. A typedef-name shall not be used as the identifier in a class-head.
Delete 7.1.3 [dcl.typedef] paragraph 8:
[Note: if the typedef-name is used where a class-name (or enum-name) is required, the program is ill-formed. For example,
typedef struct { S(); // error: requires a return type because S is // an ordinary member function, not a constructor } S;—end note]
[Voted into WP at April 2005 meeting.]
The ambiguity text in 10.2 [class.member.lookup] may not say what we intended. It makes the following 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 10.2
[class.member.lookup]
paragraph 2:
... Each of these declarations that was introduced by a using-declaration is considered to be from each sub-object of C that is of the type containing the declaration designated by the using-declaration. If the resulting set of declarations are not all from sub-objects of the same type, or the set has 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 7.3.3 [namespace.udecl] .
Proposed Resolution (10/00):
Replace the two cited sentences from 10.2 [class.member.lookup] paragraph 2 with the following:
The resulting set of declarations shall all be from sub-objects of the same type, or there shall be a set of declarations from sub-objects of a single type that contains using-declarations for the declarations found in all other sub-object types. Furthermore, for nonstatic members, the resulting set of declarations shall all be from a single sub-object, or there shall be a set of declarations from a single sub-object that contains using-declarations for the declarations found in all other sub-objects. Otherwise, there is an ambiguity and the program is ill-formed.
Replace the examples in 10.2 [class.member.lookup] paragraph 3 with 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 by the 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-declaration is considered to be from each sub-object of C that is of the type containing the declaration designated by the using-declaration. If the resulting set of declarations are not all from sub-objects of the same type, or the set has 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 up x produces a set including declarations "from" different objects, and the program is ill-formed. Clearly this is wrong. The problem with the existing wording is that it fails to consider lookup context.
The first proposed solution:
The resulting set of declarations shall all be from sub-objects of the same type, or there shall be a set of declarations from sub-objects of a single type that contains using-declarations for the declarations found in all other sub-object types. Furthermore, for nonstatic members, the resulting set of declarations shall all be from a single sub-object, or there shall be a set of declarations from a single sub-object that contains using-declarations for the declarations found in all other sub-objects. Otherwise, there is an ambiguity and the program is ill-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 the
using-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 of the declarations in another base; that was an extension, and my proposal does not do that. One algorithmic benefit of this limitation is to simplify the case of a virtual base being hidden along one arm and not another ("domination"); if we allowed supersets, we would need to remember which subobjects had which declarations, while under the following resolution we need only keep two lists, of subobjects and declarations.
Proposed resolution (October 2002):
Replace 10.2 [class.member.lookup] paragraph 2 with:
The following steps define the result of name lookup for a member name f in a class scope C.
The lookup set for f in C, called S(f,C), consists of two component sets: the declaration set, a set of members named f; and the subobject set, a set of subobjects where declarations of these members (possibly including using-declarations) were found. In the declaration set, using-declarations are replaced by the members they designate, and type declarations (including injected-class-names) are replaced by the types they designate. S(f,C) is calculated as follows.
If C contains a declaration of the name f, the declaration set contains every declaration of f in C (excluding bases), the subobject set contains C itself, and calculation is complete.
Otherwise, S(f,C) is initially empty. If C has base classes, calculate the 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 set S(f,Bi) into the intermediate S(f,C):
- If each of the subobject members of S(f,Bi) is a base class 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 the subobject members of S(f,C) is a base class subobject of at least one of the subobject members of S(f,Bi), the new S(f,C) is a copy of S(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 an invalid declaration set and the union of the subobject sets. In subsequent merges, an invalid declaration set is considered different from any other.
- Otherwise, consider each declaration d in the set, where d is a member of class A. If d is a nonstatic member, compare the A base class subobjects of the subobject members of S(f,Bi) and S(f,C). If they do not match, the merge is ambiguous, as in the previous step. [Note: It is not necessary to remember which A subobject each member comes from, since using-declarations don't disambiguate. ]
- Otherwise, the new S(f,C) is a lookup set with the shared set of declarations and the union of the subobject sets.
The result of name lookup for f in C is the declaration set of S(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 also base subobjects of E, so S(x,D) is discarded in the first merge step. --end example]
Turn 10.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 the proposed resolution as indicated in that paper.
Further information from Mike Miller (January 2004):
Unfortunately, I've become aware of a minor glitch in the proposed resolution for issue 39 in N1543, so I'd like to suggest a change that we can discuss in Sydney.
A brief review and background of the problem: the major change we agreed on in Kona was to remove detection of multiple-subobject ambiguity from class lookup (10.2 [class.member.lookup]) and instead handle it as part of the class member access expression. It was pointed out in Kona that 11.2 [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 is not 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 class are "D", so the reference to "i" satisfies the requirement in 11.2 [class.access.base]/5, even though it involves a multiple-subobject ambiguity.
In order to address this problem, I proposed in N1543 to add a paragraph following 5.2.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 as written; however, it breaks the case where qualification is used to 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 proposed resolution we discuss in Sydney should contain the following paragraph instead of the one in N1543:
If E2 is a nonstatic data member or a non-static member function, the program is ill-formed if the 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 to 11.2 [class.access.base], where the the convertibility requirement from the class of E1 to the naming class is located and which might otherwise be overlooked.
Notes from the March 2004 meeting:
We discussed this further and agreed with these latest recommendations. Mike Miller has produced a paper N1626 that gives just the final collected set of changes.
(This resolution also resolves isssue 306.)
[Voted into WP at April 2005 meeting.]
Is the following well-formed?
struct A {
struct B { };
};
struct C : public A, public A::B {
B *p;
};
The lookup of B finds both the struct B in A
and the injected B from the A::B base class.
Are they the same thing? Does the standard say so?
What if a struct is found along one path and a typedef to that struct is found along another path? That should probably be valid, but does the standard say so?
This is resolved by issue 39
February 2004: Moved back to "Review" status because issue 39 was moved back to "Review".
[Voted into WP at March 2004 meeting.]
In clause 10.4 [class.abstract] paragraph 2, it reads:
A pure virtual function need be defined only if explicitly called with the qualified-id syntax (5.1 [expr.prim]).
This is IMHO incomplete. A dtor is a function (well, a "special member function", but this also makes it a function, right?) but it is called implicitly and thus without a qualified-id syntax. Another alternative is that the pure virtual function is called directly or indirectly from the ctor. Thus the above sentence which specifies when a pure virtual function need be defined ("...only if...") needs to be extended:
A pure virtual function need be defined only if explicitly called with the qualified-id syntax (5.1 [expr.prim]) or if implicitly called (12.4 [class.dtor] or 12.7 [class.cdtor]).
Proposed resolution:
Change 10.4 [class.abstract] paragraph 2 from
A pure virtual function need be defined only if explicitly called with the qualified-id syntax (5.1 [expr.prim]).
to
A pure virtual function need be defined only ifexplicitlycalled with, or as if with (12.4 [class.dtor]), the qualified-id syntax (5.1 [expr.prim]).
Note: 12.4 [class.dtor] paragraph 6 defines the "as if" cited.
[Moved to DR at 4/01 meeting.]
Consider the following example:
class A {
class A1{};
static void func(A1, int);
static void func(float, int);
static const int garbconst = 3;
public:
template < class T, int i, void (*f)(T, int) > class int_temp {};
template<> class int_temp<A1, 5, func> { void func1() };
friend int_temp<A1, 5, func>::func1();
int_temp<A1, 5, func>* func2();
};
A::int_temp<A::A1, A::garbconst + 2, &A::func>* A::func2() {...}
ISSUE 1:
In 11 [class.access] paragraph 5 we have:
A::int_temp
A::A1
A::garbconst (part of an expression)
A::func (after overloading is done)
I suspect that member templates were not really considered when this was
written, and that it might have been written rather differently if they
had been. Note that access to the template arguments is only legal because
the class has been declared a friend, which is probably not what most programmers
would expect.
Rationale:
Not a defect. This behavior is as intended.
ISSUE 2:
Now consider void A::int_temp<A::A1, A::garbconst + 2, &A::func>::func1() {...} By my reading of 11.8 [class.access.nest] , the references to A::A1, A::garbconst and A::func are now illegal, and there is no way to define this function outside of the class. Is there any need to do anything about either of these Issues?
Proposed resolution (04/01):
The resolution for this issue is contained in the resolution for issue 45.
[Voted into WP at the October, 2006 meeting.]
The proposed resolution for issue 45 inserts the following sentence after 11 [class.access] paragraph 1:
A member of a class can also access all names as the class of which it is a member.
I don't think that this is correctly constructed English. I see two possibilities:
This is a typo, and the correct change is:
A member of a class can also access all names of the class of which it is a member.
The intent is something more like:
A member of a nested class can also access all names accessible by any other member of the class of which it is a member.
[Note: this was editorially corrected at the time defect resolutions were being incorporated into the Working Paper to read, “...can also access all the names declared in the class of which it is a member,” which is essentially the same as the preceding option 1.]
I would prefer to use the language proposed for 11.8 [class.access.nest]:
A nested class is a member and as such has the same access rights as any other member.
A second problem is with the text in 11.4 [class.friend] paragraph 2:
[Note: this means that access to private and protected
names is also granted to member functions of the friend class (as
if the functions were each friends) and to the static data member
definitions of the friend class. This also means that private and
protected type names from the class granting friendship can be
used in the base-clause of a nested class of the friend
class. However, the declarations of members of classes nested
within the friend class cannot access the names of private and
protected members from the class granting friendship. Also,
because the base-clause of the friend class is not part of
its member declarations, the base-clause of the friend
class cannot access the names of the private and protected
members from the class granting friendship. For example,
class A {
class B { };
friend class X;
};
class X : A::B { // ill-formed: A::B cannot be accessed
// in the base-clause for X
A::B mx; // OK: A::B used to declare member of X
class Y: A::B { // OK: A::B used to declare member of X
A::B my; // ill-formed: A::B cannot be accessed
// to declare members of nested class of X
};
};
—end note]
This seems to be an oversight. The proposed change to 11.8 [class.access.nest] paragraph 1 would appear to have eliminated the restrictions on nested class access. However, at least one compiler (gcc 3.4.3) doesn't appear to take my view, and continues with the restrictions on access by classes within a friend class, while implementing the rest of the resolution of issue 45.
Note (March, 2005):
Andreas Hommel: I think issue 45 requires an additional change in 9.7 [class.nest] paragraph 4:
Like a member function, a friend function (11.4 [class.friend]) defined within a nested class is in the lexical scope of that class; it obeys the same rules for name binding as a static member function of that class (9.4 [class.static]) and has no special access rights to members of an enclosing class.
I believe the “no special access rights” language should be removed.
Proposed resolution (October, 2005):
This issue is resolved by the resolution of issue 372.
[Moved to DR at 4/01 meeting.]
11.2 [class.access.base] paragraph 4 says:
A base class is said to be accessible if an invented public member of the base class is accessible. If a base class is accessible, one can implicitly convert a pointer to a derived class to a pointer to that base class.Given the above, is the following well-formed?
class D;
class B
{
protected:
int b1;
friend void foo( D* pd );
};
class D : protected B { };
void foo( D* pd )
{
if ( pd->b1 > 0 ); // Is 'b1' accessible?
}
Can you access the protected member b1 of B in foo?
Can you convert a D* to a B* in foo?
1st interpretation:
A public member of B is accessible within foo (since foo is a friend), therefore foo can refer to b1 and convert a D* to a B*.
2nd interpretation:
B is a protected base class of D, and a public member of B is a protected member of D and can only be accessed within members of D and friends of D. Therefore foo cannot refer to b1 and cannot convert a D* to a B*.
(See J16/99-0042 = WG21 N1218.)
Proposed Resolution (04/01):
A base class B of N is accessible at R, if
- an invented public member of B would be a public member of N, or
- R occurs in a member or friend of class N, and an invented public member of B would be a private or protected member of N, or
- R occurs in a member or friend of a class P derived from N, and an invented public member of B would be a private or protected member of P, or
- there exists a class S such that B is a base class of S accessible at R and S is a base class of N accessible at R. [Example:
class B { public: int m; }; class S: private B { friend class N; }; class N: private S { void f() { B* p = this; // OK because class S satisfies the // fourth condition above: B is a base // class of N accessible in f() because // B is an accessible base class of S // and S is an accessible base class of N. } };—end example]
A base class is said to be accessible if an invented public member of the base class is accessible.
A member m is accessible at the point R when named in class N if
- m as a member of N is public, or
- m as a member of N is private, and R occurs in a member or friend of class N, or
- m as a member of N is protected, and R occurs in a member or friend of class N, or in a member or friend of a class P derived from N, where m as a member of P is private or protected, or
- there exists a base class B of N that is accessible at R, and m is accessible at R when named in class B. [Example:...
The resolution for issue 207 modifies this wording slightly.
[Moved to DR at 4/01 meeting.]
The text in 11.2 [class.access.base] paragraph 4 does not seem to handle the following cases:
class D;
class B {
private:
int i;
friend class D;
};
class C : private B { };
class D : private C {
void f() {
B::i; //1: well-formed?
i; //2: well-formed?
}
};
The member i is not a member of D and cannot be
accessed in the
scope of D. What is the naming class of the member
i on line //1
and line //2?
Proposed Resolution (04/01): The resolution for this issue is contained in the resolution for issue 9..
[Moved to DR at 10/01 meeting.]
Consider the following example:
class A {
protected:
static void f() {};
};
class B : A {
public:
using A::f;
void g() {
A::f();
}
};
The standard says in 11.2 [class.access.base] paragraph 4 that the call to A::f is ill-formed:
A member m is accessible when named in class N if
- m as a member of N is public, or
- m as a member of N is private, and the reference occurs in a member or friend of class N, or
- m as a member of N is protected, and the reference occurs in a member or friend of class N, or in a member or friend of a class P derived from N, where m as a member of P is private or protected, or
- there exists a base class B of N that is accessible at the point of reference, and m is accessible when named in class B.
Here, m is A::f and N is A.
It seems clear to me that the third bullet should say "public, private or protected".
Steve Adamczyk:The words were written before using-declarations existed, and therefore didn't anticipate this case.
Proposed resolution (04/01):
Modify the third bullet of the third change ("A member m is accessible...") in the resolution of issue 9 to read "public, private, or protected" instead of "private or protected."
[Moved to DR at 4/02 meeting.]
The definition of "friend" in 11.4 [class.friend] says:
A friend of a class is a function or class that is not a member of the class but is permitted to use the private and protected member names from the class. ...A nested class, i.e. INNER in the example below, is a member of class OUTER. The sentence above states that it cannot be a friend. I think this is a mistake.
class OUTER {
class INNER;
friend class INNER;
class INNER {};
};
Proposed resolution (04/01):
Change the first sentence of 11.4 [class.friend] as follows:
A friend of a class is a function or class that isnot a member of the class but is allowedgiven permission to use the private and protected member names from the class.The name of a friend is not in the scope of the class, and the friend is not called with the member access operators (5.2.5 [expr.ref]) unless it is a member of another class.A class specifies its friends, if any, by way of friend declarations. Such declarations give special access rights to the friends, but they do not make the nominated friends members of the befriending class.
[Voted into WP at the October, 2006 meeting.]
I don't know the reason for this distinction, but it seems to be surprising that Base::A is legal and D is illegal in this example:
class D;
class Base
{
class A;
class B;
friend class D;
};
class Base::B
{
};
class Base::A : public Base::B // OK because of issue 45
{
};
class D : public Base::B // illegal because of 11.4p4
{
};
Shouldn't this be consistent (either way)?
Notes from the April, 2005 meeting:
In discussing issue 372, the CWG decided that access in the base-specifiers of a class should be the same as for its members, and that resolution will apply to friend declarations, as well.
Proposed resolution (October, 2005):
This issue is resolved by the resolution of issue 372.
[Voted into WP at October 2004 meeting.]
We consider it not unreasonable to do the following
class A {
protected:
void g();
};
class B : public A {
public:
using A::g; // B::g is a public synonym for A::g
};
class C: public A {
void foo();
};
void C::foo() {
B b;
b.g();
}
However the EDG front-end does not like and gives the error
#410-D: protected function "A::g" is not accessible through a "B" pointer or object
b.g();
^
Steve Adamczyk: The error in this case is due to 11.5 [class.protected] of the standard, which is an additional check on top of the other access checking. When that section says "a protected nonstatic member function ... of a base class" it doesn't indicate whether the fact that there is a using-declaration is relevant. I'd say the current wording taken at face value would suggest that the error is correct -- the function is protected, even if the using-declaration for it makes it accessible as a public function. But I'm quite sure the wording in 11.5 [class.protected] was written before using-declarations were invented and has not been reviewed since for consistency with that addition.
Notes from April 2003 meeting:
We agreed that the example should be allowed.
Proposed resolution (April 2003, revised October 2003):
Change 11.5 [class.protected] paragraph 1 from
When a friend or a member function of a derived class references a protected nonstatic member function or protected nonstatic data member of a base class, an access check applies in addition to those described earlier in clause 11 [class.access]. [Footnote: This additional check does not apply to other members, e.g. static data members or enumerator member constants.] Except when forming a pointer to member (5.3.1 [expr.unary.op]), the access must be through a pointer to, reference to, or object of the derived class itself (or any class derived from that class (5.2.5 [expr.ref]). If the access is to form a pointer to member, the nested-name-specifier shall name the derived class (or any class derived from that class).
to
An additional access check beyond those described earlier in clause 11 [class.access] is applied when a nonstatic data member or nonstatic member function is a protected member of its naming class (11.2 [class.access.base]). [Footnote: This additional check does not apply to other members, e.g., static data members or enumerator member constants.] As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C. If the access is to form a pointer to member (5.3.1 [expr.unary.op]), the nested-name-specifier shall name C or a class derived from C. All other accesses involve a (possibly implicit) object expression (5.2.5 [expr.ref]). In this case, the class of the object expression shall be C or a class derived from C.
Additional discussion (September, 2004):
Steve Adamczyk: I wonder if this wording is incorrect. Consider:
class A {
public:
int p;
};
class B : protected A {
// p is a protected member of B
};
class C : public B {
friend void fr();
};
void fr() {
B *pb = new B;
pb->p = 1; // Access okay? Naming class is B, p is a protected member of B,
// the "C" of the issue 385 wording is C, but access is not via
// an object of type C or a derived class thereof.
}
I think the formulation that the member is a protected member of its naming class is not what we want. I think we intended that the member is protected in the declaration that is found, where the declaration found might be a using-declaration.
Mike Miller: I think the proposed wording makes the access pb->p ill-formed, and I think that's the right thing to do.
First, protected inheritance of A by B means that B intends the public and protected members of A to be part of B's implementation, available to B's descendants only. (That's why there's a restriction on converting from B* to A*, to enforce B's intention on the use of members of A.) Consequently, I see no difference in access policy between your example and
class B {
protected:
int p;
};
Second, the reason we have this rule is that C's use of inherited protected members might be different from their use in a sibling class, say D. Thus members and friends of C can only use B::p in a manner consistent with C's usage, i.e., in C or derived-from-C objects. If we rewrote your example slightly,
class D: public B { };
void fr(B* pb) {
pb->p = 1;
}
void g() {
fr(new D);
}
it's clear that the intent of this rule is broken — fr would be accessing B::p assuming C's policies when the object in question actually required D's policies.
(See also issues 471 and 472.)
[Moved to DR at 4/01 meeting.]
Paragraph 1 says: "The members of a nested class have no special access to members of an enclosing class..."
This prevents a member of a nested class from being defined outside of its class definition. i.e. Should the following be well-formed?
class D {
class E {
static E* m;
};
};
D::E* D::E::m = 1; // ill-formed
This is because the nested class does not have access to the member E
in D. 11
[class.access]
paragraph 5 says that access to D::E is checked with
member access to class E, but unfortunately that doesn't give
access to D::E. 11
[class.access]
paragraph 6 covers the access for D::E::m,
but it doesn't affect the D::E access. Are there any implementations
that are standard compliant that support this?
Here is another example:
class C {
class B
{
C::B *t; //2 error, C::B is inaccessible
};
};
This causes trouble for member functions declared outside of the class
member list. For example:
class C {
class B
{
B& operator= (const B&);
};
};
C::B& C::B::operator= (const B&) { } //3
If the return type (i.e. C::B) is access checked in the scope
of class B (as implied by
11
[class.access]
paragraph 5)
as a qualified name, then
the return type is an error just like referring to C::B in the
member list of class B above (i.e. //2) is ill-formed.
Proposed resolution (04/01):
The resolution for this issue is incorporated into the resolution for issue 45.
[Moved to DR at 4/01 meeting.]
Example:
#include <iostream.h>
class C { // entire body is private
struct Parent {
Parent() { cout << "C::Parent::Parent()\n"; }
};
struct Derived : Parent {
Derived() { cout << "C::Derived::Derived()\n"; }
};
Derived d;
};
int main() {
C c; // Prints message from both nested classes
return 0;
}
How legal/illegal is this? Paragraphs that seem to apply here are:
11 [class.access] paragraph 1:
A member of a class can beand 11.8 [class.access.nest] paragraph 1:
- private; that is, its name can be used only by members and friends of the class in which it is declared. [...]
The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (clause 11 [class.access] ) shall be obeyed. [...]This makes me think that the ': Parent' part is OK by itself, but that the implicit call of 'Parent::Parent()' by 'Derived::Derived()' is not.
From Mike Miller:
I think it is completely legal, by the reasoning given in the (non-normative) 11.8 [class.access.nest] paragraph 2. The use of a private nested class as a base of another nested class is explicitly declared to be acceptable there. I think the rationale in the comments in the example ("// OK because of injection of name A in A") presupposes that public members of the base class will be public members in a (publicly-derived) derived class, regardless of the access of the base class, so the constructor invocation should be okay as well.
I can't find anything normative that explicitly says that, though.
(See also papers J16/99-0009 = WG21 N1186, J16/00-0031 = WG21 N1254, and J16/00-0045 = WG21 N1268.)
Proposed Resolution (04/01):
Insert the following as a new paragraph following 11 [class.access] paragraph 1:
A member of a class can also access all names as the class of which it is a member. A local class of a member function may access the same names that the member function itself may access. [Footnote: Access permissions are thus transitive and cumulative to nested and local classes.]
Delete 11 [class.access] paragraph 6.
In 11.8 [class.access.nest] paragraph 1, change
The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (clause 11 [class.access]) shall be obeyed.
to
A nested class is a member and as such has the same access rights as any other member.
Change
B b; // error: E::B is private
to
B b; // Okay, E::I can access E::B
Change
p->x = i; // error: E::x is private
to
p->x = i; // Okay, E::I can access E::x
Delete 11.8 [class.access.nest] paragraph 2.
(This resolution also resolves issues 8 and 10.
[Voted into WP at April 2003 meeting.]
According to 12.1 [class.ctor] paragraph 1, a declaration of a constructor has a special limited syntax, in which only function-specifiers are allowed. A friend specifier is not a function-specifier, so one interpretation is that a constructor cannot be declared in a friend declaration.
(It should also be noted, however, that neither friend nor function-specifier is part of the declarator syntax, so it's not clear that anything conclusive can be derived from the wording of 12.1 [class.ctor].)
Notes from 04/01 meeting:
The consensus of the core language working group was that it should be permitted to declare constructors as friends.
Proposed Resolution (revised October 2002):
Change paragraph 1a in 3.4.3.1 [class.qual] (added by the resolution of issue 147) as follows:
If the nested-name-specifier nominates a class C, and the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (clause 9 [class]), the name is instead considered to name the constructor of class C. Such a constructor name shall be used only in the declarator-id of aconstructor definitiondeclaration thatappears outside of the class definitionnames a constructor....
Note: the above does not allow qualified names to be used for in-class declarations; see 8.3 [dcl.meaning] paragraph 1. Also note that issue 318 updates the same paragraph.
Change the example in 11.4 [class.friend], paragraph 4 as follows:
class Y {
friend char* X::foo(int);
friend X::X(char); // constructors can be friends
friend X::~X(); // destructors can be friends
//...
};
[Voted into WP at October 2003 meeting.]
In 12.1 [class.ctor] paragraph 5, the standard says "A constructor is trivial if [...]", and goes on to define a trivial default constructor. Taken literally, this would mean that a copy constructor can't be trivial (contrary to 12.8 [class.copy] paragraph 6). I suggest changing this to "A default constructor is trivial if [...]". (I think the change is purely editorial.)
Proposed Resolution (revised October 2002):
Change 12.1 [class.ctor] paragraph 5-6 as follows:
A default constructor for a class X is a constructor of class X that can be called without an argument. If there is no
user-declareduser-declared constructor for class X, a default constructor is implicitly declared. Animplicitly-declaredimplicitly-declared default constructor is an inline public member of its class. A default constructor is trivial if it isanimplicitly-declareddefault constructorand if:
- its class has no virtual functions (10.3 [class.virtual]) and no virtual base classes (10.1 [class.mi]), and
- all the direct base classes of its class have trivial default constructors, and
- for all the nonstatic data members of its class that are of class type (or array thereof), each such class has a trivial default constructor.
Otherwise, the default constructor is non-trivial.
Change 12.4 [class.dtor] paragraphs 3-4 as follows (the main changes are removing italics):
If a class has no
user-declareduser-declared destructor, a destructor is declared implicitly. Animplicitly-declaredimplicitly-declared destructor is an inline public member of its class. A destructor is trivial if it isanimplicitly-declareddestructorand if:
- all of the direct base classes of its class have trivial destructors and
- for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.
Otherwise, the destructor is
non-trivialnon-trivial.
In 9.5 [class.union] paragraph 1, change "trivial constructor" to "trivial default constructor".
In 12.2 [class.temporary] paragraph 3, add to the reference to 12.1 [class.ctor] a second reference, to 12.8 [class.copy].
[Voted into WP at October 2003 meeting.]
12.1 [class.ctor] paragraph 10 states
A copy constructor for a class X is a constructor with a first parameter of type X & or of type const X &. [Note: see 12.8 [class.copy] for more information on copy constructors.]
No mention is made of constructors with first parameters of types volatile X & or const volatile X &. This statement seems to be in contradiction with 12.8 [class.copy] paragraph 2 which states
A non-template constructor for class X is a copy constructor if its first parameter is of type X &, const X &, volatile X & or const volatile X &, ...
12.8 [class.copy] paragraph 5 also mentions the volatile versions of the copy constructor, and the comparable paragraphs for copy assignment (12.8 [class.copy] paragraphs 9 and 10) all allow volatile versions, so it seems that 12.1 [class.ctor] is at fault.
Proposed resolution (October 2002):
Change 12.1 [class.ctor] paragraph 10 from
A copy constructor for a class X is a constructor with a first parameter of type X& or of type const X&. [Note: see 12.8 [class.copy] for more information on copy constructors. ]to (note that the dropping of italics is intentional):
A copy constructor (12.8 [class.copy]) is used to copy objects of class type.
[Voted into WP at April, 2006 meeting.]
In 12.2 [class.temporary] paragraph 5, should binding a reference to the result of a "?" operation, each of whose branches is a temporary, extend both temporaries?
Here's an example:
const SFileName &C = noDir ? SFileName("abc") : SFileName("bcd");
Do the temporaries created by the SFileName conversions survive the end of the full expression?
Notes from 10/00 meeting:
Other problematic examples include cases where the temporary from one branch is a base class of the temporary from the other (i.e., where the implementation must remember which type of temporary must be destroyed), or where one branch is a temporary and the other is not. Similar questions also apply to the comma operator. The sense of the core language working group was that implementations should be required to support these kinds of code.
Notes from the March 2004 meeting:
We decided that the cleanest model is one in which any "?" operation that returns a class rvalue always copies one of its operands to a temporary and returns the temporary as the result of the operation. (Note that this may involve slicing.) An implementation would be free to optimize this using the rules in 12.8 [class.copy] paragraph 15, and in fact we would expect that in many cases compilers would do such optimizations. For example, the compiler could construct both rvalues in the above example into a single temporary, and thus avoid a copy.
See also issue 446.
Proposed resolution (October, 2004):
This issue is resolved by the resolutions of issue 446.
Note (October, 2005):
This issue was overlooked when issue 446 was moved to “ready” status and was thus inadvertently omitted from the list of issues accepted as Defect Reports at the October, 2005 meeting.
[Moved to DR at 4/01 meeting.]
Jack Rouse: 12.2 [class.temporary] states that temporary objects will normally be destroyed at the end of the full expression in which they are created. This can create some unique code generation requirements when initializing a class array with a default constructor that uses a default argument. Consider the code:
struct T {
int i;
T( int );
~T();
};
struct S {
S( int = T(0).i );
~S();
};
S* f( int n )
{
return new S[n];
}
The full expression allocating the array in f(int) includes the
default constructor for S. Therefore according to
1.9
[intro.execution] paragraph 14, it
includes the default argument expression for S(int).
So evaluation of
the full expression should include evaluating the default argument "n"
times and creating "n" temporaries of type T. But the destruction of
the temporaries must be delayed until the end of the full expression
so this requires allocating space at runtime for "n" distinct
temporaries. It is unclear how these temporaries are supposed to be
allocated and deallocated. They cannot readily be autos because a
variable allocation is required.
I believe that many existing implementations will destroy the temporaries needed by the default constructor after each array element is initialized. But I can't find anything in the standard that allows the temporaries to be destroyed early in this case.
I think the standard should allow the early destruction of temporaries used in the default initialization of class array elements. I believe early destruction is the status quo, and I don't think the users of existing C++ compilers have been adversely impacted by it.
Proposed resolution (04/01):
The proposed resolution is contained in the proposal for issue 201.
[Voted into the WP at the April, 2007 meeting as part of paper J16/07-0099 = WG21 N2239.]
12.2 [class.temporary] paragraph 3 simply states the requirement that temporaries created during the evaluation of an expression
are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created.There is nothing said about the relative order in which these temporaries are destroyed.
Paragraph 5, dealing with temporaries bound to references, says
the temporaries created during the evaluation of the expression initializing the reference, except the temporary to which the reference is bound, are destroyed at the end of the full-expression in which they are created and in the reverse order of the completion of their construction.Is this difference intentional? May temporaries in expressions other than those initializing references be deleted in non-LIFO order?
Notes from 04/00 meeting:
Steve Adamczyk expressed concern about constraining implementations that are capable of fine-grained parallelism -- they may be unable to determine the order of construction without adding undesirable overhead.
[Moved to DR at 4/01 meeting.]
According to 12.2 [class.temporary] paragraph 4, an expression appearing as the initializer in an object definition constitutes a context "in which temporaries are destroyed at a different point than the end of the full-expression." It goes on to say that the temporary containing the value of the expression persists until after the initialization is complete (see also issue 117). This seems to presume that the end of the full-expression is a point earlier than the completion of the initialization.
However, according to 1.9 [intro.execution] paragraphs 12-13, the full-expression in such cases is, in fact, the entire initialization. If this is the case, the behavior described for temporaries in an initializer expression is simply the normal behavior of temporaries in any expression, and treating it as an exception to the general rule is both incorrect and confusing.
Proposed resolution (04/01):
[Note: this proposal also addresses issue 124.]
Add to the end of 1.9 [intro.execution] paragraph 12:
If the initializer for an object or sub-object is a full-expression, the initialization of the object or sub-object (e.g., by calling a constructor or copying an expression value) is considered to be part of the full-expression.
Replace 12.2 [class.temporary] paragraph 4 with:
There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array. If the constructor has one or more default arguments, any temporaries created in the default argument expressions are destroyed immediately after return from the constructor.
[Voted into WP at April 2005 meeting.]
Section 12.2 [class.temporary] paragraph 2, abridged:
X f(X); void g() { X a; a = f(a); }a=f(a) requires a temporary for either the argument a or the result of f(a) to avoid undesired aliasing of a.
The note seems to imply that an implementation is allowed to omit copying "a" to f's formal argument, or to omit using a temporary for the return value of f. I don't find that license in normative text.
Function f returns an X by value, and in the expression the value is assigned (not copy-constructed) to "a". I don't see how that temporary can be omitted. (See also 12.8 [class.copy] p 15)
Since "a" is an lvalue and not a temporary, I don't see how copying "a" to f's formal parameter can be avoided.
Am I missing something, or is 12.2 [class.temporary] p 2 misleading?
Proposed resolution (October, 2004):
In 12.2 [class.temporary] paragraph 2, change the last sentence as indicated:
On the other hand, the expression a=f(a) requires a temporary foreither the argument a or the result of f(a) to avoid undesired aliasing of athe result of f(a), which is then assigned to a.
[Voted into WP at March 2004 meeting.]
class C {
public:
C();
~C();
int& get() { return p; } // reference return
private:
int p;
};
int main ()
{
if ( C().get() ) // OK?
}
Section 12.2 [class.temporary] paragraph 3 says a temp is destroyed as the last step in evaluating the full expression. But the expression C().get() has a reference type. Does 12.2 [class.temporary] paragraph 3 require that the dereference to get a boolean result occur before the destructor runs, making the code valid? Or does the code have undefined behavior?
Bill Gibbons: It has undefined behavior, though clearly this wasn't intended. The lvalue-to-rvalue conversion that occurs in the "if" statement is not currently part of the full-expression.
From section 12.2 [class.temporary] paragraph 3:
Temporary objects are destroyed as the last step in evaluating the full-expression (1.9 [intro.execution]) that (lexically) contains the point where they were created.
From section 1.9 [intro.execution] paragraph 12:
A full-expression is an expression that is not a subexpression of another expression. If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition.
The note in section 1.9 [intro.execution] paragraph 12 goes on to explain that this covers expressions used as initializers, but it does not discuss lvalues within temporaries.
It is a small point but it is probably worth correcting 1.9 [intro.execution] paragraph 12. Instead of the "implicit call of a function" wording, it might be better to just say that a full-expression includes any implicit use of the expression value in the enclosing language construct, and include a note giving implicit calls and lvalue-to-rvalue conversions as examples.
Offhand the places where this matters include: initialization (including member initializers), selection statements, iteration statements, return, throw
Proposed resolution (April 2003):
Change 1.9 [intro.execution] paragraph 12-13 to read:
A full-expression is an expression that is not a subexpression of another expression. If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition. Conversions applied to the result of an expression in order to satisfy the requirements of the language construct in which the expression appears are also considered to be part of the full-expression.
[Note: certain contexts in C++ cause the evaluation of a full-expression that results from a syntactic construct other than expression (5.18 [expr.comma]). For example, in 8.5 [dcl.init] one syntax for initializer is
( expression-list )
but the resulting construct is a function call upon a constructor function with expression-list as an argument list; such a function call is a full-expression. For example, in 8.5 [dcl.init], another syntax for initializer is
= initializer-clause
but again the resulting construct might be a function call upon a constructor function with one assignment-expression as an argument; again, the function call is a full-expression. ][Example:struct S { S(int i): I(i) { } int& v() { return I; } private: int I; }; S s1(1); // full-expression is call of S::S(int) S s2 = 2; // full-expression is call of S::S(int) void f() { if (S(3).v()) // full-expression includes lvalue-to-rvalue and // int to bool conversions, performed before // temporary is deleted at end of full-expression { } }--- end example]
[Voted into WP at April 2005 meeting.]
There seems to be a typo in 12.2 [class.temporary]/5, which says "The temporary to which the reference is bound or the temporary that is the complete object TO a subobject OF which the TEMPORARY is bound persists for the lifetime of the reference except as specified below."
I think this should be "The temporary to which the reference is bound or the temporary that is the complete object OF a subobject TO which the REFERENCE is bound persists for the lifetime of the reference except as specified below."
I used upper-case letters for the parts I think need to be changed.
Proposed resolution (October, 2004):
Change 12.2 [class.temporary] paragraph 5 as indicated:
The temporary to which the reference is bound or the temporary that is the complete objecttoof a subobjectofto which thetemporaryreference is bound persists for the lifetime of the reference except as specified below.
[Voted into WP at April, 2006 meeting.]
Section 12.2 [class.temporary] paragraph 5 ends with this "rule":
For the temporary to be destroyed after obj2 is destroyed, when obj2 has static storage, I would say that the reference to the temporary should also have static storage, but that is IMHO not clear from the paragraph.
Example:
void f ()
{
const T1& ref = T1();
static T2 obj2;
...
}
Here the temporary would be destoyed before obj2, contrary to the rule above.
Steve Adamczyk: I agree there's a minor issue here. I think the clause quoted above meant for obj1 and obj2 to have the same storage duration. Replacing "obj2 is an object with static or automatic storage duration" by "obj2 is an object with the same storage duration as obj1" would, I believe, fix the problem.
Notes from October 2004 meeting:
We agreed with Steve Adamczyk's suggestion.
Proposed resolution (October, 2005):
Change 12.2 [class.temporary] paragraph 5 as follows:
... In addition, the destruction of temporaries bound to references shall take into account the ordering of destruction of objects with static or automatic storage duration (3.7.1 [basic.stc.static], 3.7.2 [basic.stc.auto]); that is, if obj1 is an objectwith static or automatic storage durationcreated before the temporary is created with the same storage duration as the temporary, the temporary shall be destroyed before obj1 is destroyed; if obj2 is an objectwith static or automatic storage durationcreated after the temporary is created with the same storage duration as the temporary, the temporary shall be destroyed after obj2 is destroyed...
[Moved to DR at October 2002 meeting.]
May user-defined conversion functions be static? That is, should this compile?
class Widget {
public:
static operator bool() { return true; }
};
All my compilers hate it. I hate it, too. However, I don't see anything in 12.3.2 [class.conv.fct] that makes it illegal. Is this a prohibition that arises from the grammar, i.e., the grammar doesn't allow "static" to be followed by a conversion-function-id in a member function declaration? Or am I just overlooking something obvious that forbids static conversion functions?
Proposed Resolution (4/02):
Add to 12.3.2 [class.conv.fct] as a new paragraph 7:
Conversion functions cannot be declared static.
[Moved to DR at October 2002 meeting.]
12.4 [class.dtor] contains this example:
struct B {
virtual ~B() { }
};
struct D : B {
~D() { }
};
D D_object;
typedef B B_alias;
B* B_ptr = &D_object;
void f() {
D_object.B::~B(); // calls B's destructor
B_ptr->~B(); // calls D's destructor
B_ptr->~B_alias(); // calls D's destructor
B_ptr->B_alias::~B(); // calls B's destructor
B_ptr->B_alias::~B_alias(); // error, no B_alias in class B
}
On the other hand, 3.4.3 [basic.lookup.qual] contains this example:
struct C {
typedef int I;
};
typedef int I1, I2;
extern int* p;
extern int* q;
p->C::I::~I(); // I is looked up in the scope of C
q->I1::~I2(); // I2 is looked up in the scope of
// the postfix-expression
struct A {
~A();
};
typedef A AB;
int main()
{
AB *p;
p->AB::~AB(); // explicitly calls the destructor for A
}
Note that
B_ptr->B_alias::~B_alias();
is claimed to be an error, while the equivalent
p->AB::~AB();
is claimed to be well-formed.
I believe that clause 3 is correct and that clause 12 is in error. We worked hard to get the destructor lookup rules in clause 3 to be right, and I think we failed to notice that a change was also needed in clause 12.
Mike Miller:
Unfortunately, I don't believe 3.4.3 [basic.lookup.qual] covers the case of p->AB::~AB(). It's clearly intended to do so, as evidenced by 3.4.3.1 [class.qual] paragraph 1 ("a destructor name is looked up as specified in 3.4.3 [basic.lookup.qual]"), but I don't think the language there does so.
The relevant paragraph is 3.4.3 [basic.lookup.qual] paragraph 5. (None of the other paragraphs in that section deal with this topic at all.) It has two parts. The first is
If a pseudo-destructor-name (5.2.4 [expr.pseudo]) contains a nested-name-specifier, the type-names are looked up as types in the scope designated by the nested-name-specifier.
This sentence doesn't apply, because ~AB isn't a pseudo-destructor-name. 5.2.4 [expr.pseudo] makes clear that this syntactic production (5.2 [expr.post] paragraph 1) only applies to cases where the type-name is not a class-name. p->AB::~AB is covered by the production using id-expression.
The second part of 3.4.3 [basic.lookup.qual] paragraph 5 says
In a qualified-id of the form:
::opt nested-name-specifier ~ class-name
where the nested-name-specifier designates a namespace name, and in a qualified-id of the form:
::opt nested-name-specifier class-name :: ~ class-name
the class-names are looked up as types in the scope designated by the nested-name-specifier.
This wording doesn't apply, either. The first one doesn't because the nested-name-specifier is a class-name, not a namespace name. The second doesn't because there's only one layer of qualification.
As far as I can tell, there's no normative text that specifies how the ~AB is looked up in p->AB::~AB(). 3.4.3.1 [class.qual], where all the other class member qualified lookups are handled, defers to 3.4.3 [basic.lookup.qual], and 3.4.3 [basic.lookup.qual] doesn't cover the case.
See also issue 305.
Jason Merrill: My thoughts on the subject were that the name we use in a destructor call is really meaningless; as soon as we see the ~ we know what the user means, all we're doing from that point is testing their ability to name the destructor in a conformant way. I think that everyone will agree that
anything::B::~B()should be well-formed, regardless of the origins of the name "B". I believe that the rule about looking up the second "B" in the same context as the first was intended to provide this behavior, but to me this seems much more heavyweight than necessary. We don't need a whole new type of lookup to be able to use the same name before and after the ~; we can just say that if the two names match, the call is well-formed. This is significantly simpler to express, both in the standard and in an implementation.
Anyone writing two different names here is either deliberately writing obfuscated code, trying to call the destructor of a nested class, or fighting an ornery compiler (i.e. one that still wants to see B_alias::~B()). I think we can ignore the first case. The third would be handled by reverting to the old rule (look up the name after ~ in the normal way) with the lexical matching exception described above -- or we could decide to break such code, do no lookup at all, and only accept a matching name. In a good implementation, the second should probably get an error message telling them to write Outer::Inner::~Inner instead.
We discussed this at the meetings, but I don't remember if we came to any sort of consensus on a direction. I see three options:
My order of preference is 2, 3, 1.
Incidentally, it seems to me oddly inconsistent to allow Namespace::~Class, but not Outer::~Inner. Prohibiting the latter makes sense from the standpoint of avoiding ambiguity, but what was the rationale for allowing the former?
John Spicer: I agree that allowing Namespace::~Class is odd. I'm not sure where this came from. If we eliminated that special case, then I believe the #1 rule would just be that in A::B1::~B2 you look up B1 and B2 in the same place in all cases.
I don't like #2. I don't think the "old" rules represent a deliberate design choice, just an error in the way the lookup was described. The usage that rule permits p->X::~Y (where Y is a typedef to X defined in X), but I doubt people really do that. In other words, I think that #1 a more useful special case than #2 does, not that I think either special case is very important.
One problem with the name matching rule is handling cases like:
A<int> *aip; aip->A<int>::~A<int>(); // should work aip->A<int>::~A<char>(); // should notI would favor #1, while eliminating the special case of Namespace::~Class.
Proposed resolution (10/01):
Replace the normative text of 3.4.3 [basic.lookup.qual] paragraph 5 after the first sentence with:
Similarly, in a qualified-id of the form:
::opt nested-name-specifieropt class-name :: ~ class-namethe second class-name is looked up in the same scope as the first.
In 12.4 [class.dtor] paragraph 12, change the example to
D D_object;
typedef B B_alias;
B* B_ptr = &D_object;
void f() {
D_object.B::~B(); // calls B's destructor
B_ptr->~B(); // calls D's destructor
B_ptr->~B_alias(); // calls D's destructor
B_ptr->B_alias::~B(); // calls B's destructor
B_ptr->B_alias::~B_alias(); // calls B's destructor
}
April 2003: See issue 399.
[Moved to DR at 10/01 meeting.]
There is a mismatch between 12.4 [class.dtor] paragraph 11 and 12.5 [class.free] paragraph 4 regarding the lookup of deallocation functions in virtual destructors. 12.4 [class.dtor] says,
At the point of definition of a virtual destructor (including an implicit definition (12.8 [class.copy])), non-placement operator delete shall be looked up in the scope of the destructor's class (3.4.1 [basic.lookup.unqual]) and if found shall be accessible and unambiguous. [Note: this assures that an operator delete corresponding to the dynamic type of an object is available for the delete-expression (12.5 [class.free]). ]
The salient features to note from this description are:
On the other hand, 12.5 [class.free] says,
If a delete-expression begins with a unary :: operator, the deallocation function's name is looked up in global scope. Otherwise, if the delete-expression is used to deallocate a class object whose static type has a virtual destructor, the deallocation function is the one found by the lookup in the definition of the dynamic type's virtual destructor (12.4 [class.dtor]). Otherwise, if the delete-expression is used to deallocate an object of class T or array thereof, the static and dynamic types of the object shall be identical and the deallocation function's name is looked up in the scope of T. If this lookup fails to find the name, the name is looked up in the global scope. If the result of the lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function, the program is ill-formed.
Points of interest in this description include:
Suggested resolution: Change the description of the lookup in 12.4 [class.dtor] paragraph 11 to match the one in 12.5 [class.free] paragraph 4.
Proposed resolution (10/00):
Replace 12.4 [class.dtor] paragraph 11 with the following:
At the point of definition of a virtual destructor (including an implicit definition), the non-array deallocation function is looked up in the scope of the destructor's class (10.2 [class.member.lookup]), and, if no declaration is found, the function is looked up in the global scope. If the result of this lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function, the program is ill-formed. [Note: this assures that a deallocation function corresponding to the dynamic type of an object is available for the delete-expression (12.5 [class.free]).]
In 12.5 [class.free] paragraph 4, change
...the deallocation function is the one found by the lookup in the definition of the dynamic type's virtual destructor (12.4 [class.dtor]).
to
...the deallocation function is the one selected at the point of definition of the dynamic type's virtual destructor (12.4 [class.dtor]).
[Moved to DR at 10/01 meeting.]
12.4 [class.dtor] paragraph 12 contains the following note:an explicit destructor call must always be written using a member access operator (5.2.5 [expr.ref]); in particular, the unary-expression ~X() in a member function is not an explicit destructor call (5.3.1 [expr.unary.op]).
This note is incorrect, as an explicit destructor call can be written as a qualified-id, e.g., X::~X(), which does not use a member access operator.
Proposed resolution (04/01):
Change 12.4 [class.dtor] paragraph 12 as follows:
[Note: an explicit destructor call must always be written using a member access operator (5.2.5 [expr.ref]) or a qualified-id (5.1 [expr.prim]); in particular, the unary-expression ~X() in a member function is not an explicit destructor call (5.3.1 [expr.unary.op]).]
[Voted into WP at April, 2006 meeting.]
8.5 [dcl.init] paragraph 10 makes it clear that non-static POD class objects with no initializer are left uninitialized and have an indeterminate initial value:
If no initializer is specified for an object, and the object is of (possibly cv-qualified) non-POD class type (or array thereof), the object shall be default-initialized; if the object is of const-qualified type, the underlying class type shall have a user-declared default constructor. Otherwise, if no initializer is specified for a non-static object, the object and its subobjects, if any, have an indeterminate initial value; if the object or any of its subobjects are of const-qualified type, the program is ill-formed.
12.6 [class.init] paragraph 1, however, implies that all class objects without initializers, whether POD or not, are default-initialized:
When no initializer is specified for an object of (possibly cv-qualified) class type (or array thereof), or the initializer has the form (), the object is initialized as specified in 8.5 [dcl.init]. The object is default-initialized if there is no initializer, or value-initialized if the initializer is ().
Proposed resolution (October, 2005):
Remove the indicated words from 12.6 [class.init] paragraph 1:
When no initializer is specified for an object of (possibly cv-qualified) class type (or array thereof), or the initializer has the form (), the object is initialized as specified in 8.5 [dcl.init].The object is default-initialized if there is no initializer, or value-initialized if the initializer is ().
[Moved to DR at October 2002 meeting.]
13.3.1.1 [over.match.call] paragraph 3 says that when a call of the form
(&C::f)()is written, the set of overloaded functions named by C::f must not contain any nonstatic member functions. A footnote gives the rationale: if a member of C::f is a nonstatic member function, &C::f is a pointer to member constant, and therefore the call is invalid.
This is clear, it's implementable, and it doesn't directly contradict anything else in the standard. However, I'm not sure it's consistent with some similar cases.
In 13.4 [over.over] paragraph 5, second example, it is made amply clear that when &C::f is used as the address of a function, e.g.,
int (*pf)(int) = &C::f;the overload set can contain both static and nonstatic member functions. The function with the matching signature is selected, and if it is nonstatic &C::f is a pointer to member function, and otherwise &C::f is a normal pointer to function.
Similarly, 13.3.1.1.1 [over.call.func] paragraph 3 makes it clear that
C::f();is a valid call even if the overload set contains both static and nonstatic member functions. Overload resolution is done, and if a nonstatic member function is selected, an implicit this-> is added, if that is possible.
Those paragraphs seem to suggest the general rule that you do overload resolution first and then you interpret the construct you have according to the function selected. The fact that there are static and nonstatic functions in the overload set is irrelevant; it's only necessary that the chosen function be static or nonstatic to match the context.
Given that, I think it would be more consistent if the (&C::f)() case would also do overload resolution first. If a nonstatic member is chosen, the program would be ill-formed.
Proposed resolution (04/01):
Change the indicated text in 13.3.1.1 [over.match.call] paragraph 3:
The fourth case arises from a postfix-expression of the form &F, where F names a set of overloaded functions. In the context of a function call,the set of functions named by F shall contain only non-member functions and static member functions. [Footnote: If F names a non-static member function, &F is a pointer-to-member, which cannot be used with the function call syntax.] And in this context using &F behaves the same as using&F is treated the same as the name F by itself. Thus, (&F)(expression-listopt) is simply (F)(expression-listopt), which is discussed in 13.3.1.1.1 [over.call.func]. If the function selected by overload resolution according to 13.3.1.1.1 [over.call.func] is a nonstatic member function, the program is ill-formed. [Footnote: When F is a nonstatic member function, a reference of the form &A::F is a pointer-to-member, which cannot be used with the function-call syntax, and a reference of the form &F is an invalid use of the "&" operator on a nonstatic member function.] (The resolution of &F in other contexts is described in 13.4 [over.over].)
[Moved to DR at 4/01 meeting.]
In describing non-member functions in an overload set, footnote 116 (13.3.1.1.1 [over.call.func]) says,Because of the usual name hiding rules, these will be introduced by declarations or by using-directives all found in the same block or all found at namespace scope.
At least in terms of the current state of the Standard, this is not correct: a block extern declaration does not prevent Koenig lookup from occurring. For example,
enum E { zero };
void f(E);
void g() {
void f(int);
f(zero);
}
In this example, the overload set will include declarations from both namespace and block scope.
(See also issue 12.)
Proposed resolution (04/01):
In 3.4.2 [basic.lookup.argdep] paragraph 2, change
If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered.
to
If the ordinary unqualified lookup of the name finds the declaration of a class member function, or a block-scope function declaration that is not a using-declaration, the associated namespaces and classes are not considered.
and change the example to:
namespace NS {
class T { };
void f(T);
void g(T, int);
}
NS::T parm;
void g(NS::T, float);
int main() {
f(parm); // OK: calls NS::f
extern void g(NS::T, float);
g(parm, 1); // OK: calls g(NS::T, float)
}
In 13.3.1.1.1 [over.call.func] paragraph 3 from:
If the name resolves to a non-member function declaration, that function and its overloaded declarations constitute the set of candidate functions.
to
If the name resolves to a set of non-member function declarations, that set of functions constitutes the set of candidate functions.
Note that this text is also edited by issue 364. Also, remove the associated footnote 116.
[Voted into WP at October 2003 meeting.]
Consider this program:
struct S {
static void f (int);
void f (char);
};
void g () {
S::f ('a');
}
G++ 3.1 rejects it, saying:
test.C:7: cannot call member function `void S::f(char)' without object
Mark Mitchell: It looks to me like G++ is correct, given 13.3.1.1.1 [over.call.func]. This case is the "unqualified function call" case described in paragraph 3 of that section. ("Unqualified" here means that there is no "x->" or "x." in front of the call, not that the name is unqualified.)
That paragraph says that you first do name lookup. It then asks you to look at what declaration is returned. (That's a bit confusing; you presumably get a set of declarations. Or maybe not; the name lookup section says that if name lookup finds a non-static member in a context like this the program is in error. But surely this program is not erroneous. Hmm.)
Anyhow, you have -- at least -- "S::f(char)" as the result of the lookup.
The keyword "this" is not in scope, so "all overloaded declarations of the function name in T become candidate functions and a contrived object of type T becomes the implied object argument." That means we get both versions of "f" at this point. Then, "the call is ill-formed, however, if overload resolution selects one of the non-static members of T in this case." Since, in this case, "S::f(char)" is the winner, the program is ill-formed.
Steve Adamczyk: This result is surprising, because we've selected a function that we cannot call, when there is another function that can be called. This should either be ambiguous, or it should select the static member function. See also 13.3.1 [over.match.funcs] paragraph 2: "Similarly, when appropriate, the context can construct an argument list that contains an implied object argument..."
Notes from October 2002 meeting:
We agreed that g++ has it right, but the standard needs to be clearer.
Proposed resolution (October 2002, revised April 2003):
Change 13.3.1.1.1 [over.call.func] paragraphs 2 and 3 as follows:
In qualified function calls, the name to be resolved is an id-expression and is preceded by an -> or . operator. Since the construct A->B is generally equivalent to (*A).B, the rest of clause 13 [over] assumes, without loss of generality, that all member function calls have been normalized to the form that uses an object and the . operator. Furthermore, clause 13 [over] assumes that the postfix-expression that is the left operand of the . operator has type ``cv T'' where T denotes a class. [Footnote: Note that cv-qualifiers on the type of objects are significant in overload resolution for both lvalue and class rvalue objects. --- end footnote] Under this assumption, the id-expression in the call is looked up as a member function of T following the rules for looking up names in classes (10.2 [class.member.lookup]).
If a member function is found, that function and its overloaded declarationsThe function declarations found by that lookup constitute the set of candidate functions. The argument list is the expression-list in the call augmented by the addition of the left operand of the . operator in the normalized member function call as the implied object argument (13.3.1 [over.match.funcs]).In unqualified function calls, the name is not qualified by an -> or . operator and has the more general form of a primary-expression. The name is looked up in the context of the function call following the normal rules for name lookup in function calls (
3.4.2 [basic.lookup.argdep]3.4 [basic.lookup]).If the name resolves to a non-member function declaration, that function and its overloaded declarationsThe function declarations found by that lookup constitute the set of candidate functions.[Footnote: Because of the usual name hiding rules, these will be introduced by declarations or by using-directives all found in the same block or all found at namespace scope. --- end footnote]Because of the rules for name lookup, the set of candidate functions consists (1) entirely of non-member functions or (2) entirely of member functions of some class T. In case (1), tThe argument list is the same as the expression-list in the call.If the name resolves to a nonstatic member function, then the function call is actually a member function call.In case (2), the argument list is the expression-list in the call augmented by the addition of an implied object argument as in a qualified function call. If the keyword this (9.3.2 [class.this]) is in scope and refers totheclass Tof that member function, or a derived classthereofof T, then thefunction call is transformed into a normalized qualified function call usingimplied object argument is(*this)as the postfix-expression to the left of the . operator.The candidate functions and argument list are as described for qualified function calls above.If the keyword this is not in scope or refers to another class, thenname resolution found a static member of some classT. In this case,all overloaded declarations of the function name in T become candidate functions anda contrived object of type T becomes the implied object argument. [Footnote: An implied object argument must be contrived to correspond to the implicit object parameter attributed to member functions during overload resolution. It is not used in the call to the selected function. Since the member functions all have the same implicit object parameter, the contrived object will not be the cause to select or reject a function. --- end footnote] If the argument list is augmented by a contrived object andThe call is ill-formed, however, ifoverload resolution selects one of the non-static member functions of T, the call is ill-formedin this case.
Note that issue 239 also edits paragraph 3.
[Voted into WP at October 2003 meeting.]
According to 13.3.1.1.2 [over.call.object] paragraph 2, when the primary-expression E in the function call syntax evaluates to a class object of type "cv T", a surrogate call function corresponding to an appropriate conversion function declared in a direct or indirect base class B of T is included or not included in the set of candidate functions based on class B being accessible.
For instance in the following code sample, as per the paragraph in question, the expression c(3) calls f2, instead of the construct being ill-formed due to the conversion function A::operator fp1 being inaccessible and its corresponding surrogate call function providing a better match than the surrogate call function corresponding to C::operator fp2:
void f1(int) { }
void f2(float) { }
typedef void (* fp1)(int);
typedef void (* fp2)(float);
struct A {
operator fp1()
{ return f1; }
};
struct B : private A { };
struct C : B {
operator fp2()
{ return f2; }
};
int main()
{
C c;
c(3); // f2 is called, instead of the construct being ill-formed.
return 0;
}
The fact that the accessibility of a base class influences the overload resolution process contradicts the fundamental language rule (3.4 [basic.lookup] paragraph 1, and 13.3 [over.match] paragraph 2) that access checks are applied only once name lookup and function overload resolution (if applicable) have succeeded.
Notes from 4/02 meeting:
There was some concern about whether 10.2 [class.member.lookup] (or anything else, for that matter) actually defines "ambiguous base class". See issue 39. See also issue 156.
Notes from October 2002 meeting:
It was suggested that the ambiguity check is done as part of the call of the conversion function.
Proposed resolution (revised October 2002):
In 13.3.1.1.2 [over.call.object] paragraph 2, replace the last sentence
Similarly, surrogate call functions are added to the set of candidate functions for each conversion function declared in an accessible base class provided the function is not hidden within T by another intervening declaration.
with
Similarly, surrogate call functions are added to the set of candidate functions for each conversion function declared in a base class of T provided the function is not hidden within T by another intervening declaration.
Replace 13.3.1.1.2 [over.call.object] paragraph 3
If such a surrogate call function is selected by overload resolution, its body, as defined above, will be executed to convert E to the appropriate function and then to invoke that function with the arguments of the call.by
If such a surrogate call function is selected by overload resolution, the corresponding conversion function will be called to convert E to the appropriate function pointer or reference, and the function will then be invoked with the arguments of the call. If the conversion function cannot be called (e.g., because of an ambiguity), the program is ill-formed.
[Voted into WP at October 2004 meeting.]
Normally reference semantics allow incomplete types in certain contexts, but isn't this:
class A;
A& operator<<(A& a, const char* msg);
void foo(A& a)
{
a << "Hello";
}
required to be diagnosed because of the op<<? The reason being that the class may actually have an op<<(const char *) in it.
What is it? un- or ill-something? Diagnosable? No problem at all?
Steve Adamczyk: I don't know of any requirement in the standard that the class be complete. There is a rule that will instantiate a class template in order to be able to see whether it has any operators. But I wouldn't think one wants to outlaw the above example merely because the user might have an operator<< in the class; if he doesn't, he would not be pleased that the above is considered invalid.
Mike Miller: Hmm, interesting question. My initial reaction is that it just uses ::operator<<; any A::operator<< simply won't be considered in overload resolution. I can't find anything in the Standard that would say any different.
The closest analogy to this situation, I'd guess, would be deleting a pointer to an incomplete class; 5.3.5 [expr.delete] paragraph 5 says that that's undefined behavior if the complete type has a non-trivial destructor or an operator delete. However, I tend to think that that's because it deals with storage and resource management, not just because it might have called a different function. Generally, overload resolution that goes one way when it might have gone another with more declarations in scope is considered to be not an error, cf 7.3.3 [namespace.udecl] paragraph 9, 14.6.3 [temp.nondep] paragraph 1, etc.
So my bottom line take on it would be that it's okay, it's up to the programmer to ensure that all necessary declarations are in scope for overload resolution. Worst case, it would be like the operator delete in an incomplete class -- undefined behavior, and thus not required to be diagnosed.
13.3.1.2 [over.match.oper] paragraph 3, bullet 1, says, "If T1 is a class type, the set of member candidates is the result of the qualified lookup of T1::operator@ (13.3.1.1.1 [over.call.func])." Obviously, that lookup is not possible if T1 is incomplete. Should 13.3.1.2 [over.match.oper] paragraph 3, bullet 1, say "complete class type"? Or does the inability to perform the lookup mean that the program is ill-formed? 3.2 [basic.def.odr] paragraph 4 doesn't apply, I don't think, because you don't know whether you'll be applying a class member access operator until you know whether the operator involved is a member or not.
Notes from October 2003 meeting:
We noticed that the title of this issue did not match the body. We checked the original source and then corrected the title (so it no longer mentions templates).
We decided that this is similar to other cases like deleting a pointer to an incomplete class