Document number: | J16/01-0012 = WG21 N1298 |
Date: | 27 March, 2001 |
Project: | Programming Language C++ |
Reference: | ISO/IEC IS 14882:1998(E) |
Reply to: | William M. Miller |
wmm@fastdial.net |
This document contains the C++ core language issues on which the Committee (J16 + WG21) has not yet acted, that is, issues issues with status "Ready," "Review," "Drafting," and "Open."
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:
The purpose of these documents is to record the disposition of issues which have come before the Core Language Working Group of the ANSI (J16) and ISO (WG21) C++ Standard Committee.
Issues represent potential defects in the ISO/IEC IS 14882:1998(E) document; they are not necessarily formal ISO Defect Reports (DRs). While some issues will eventually be elevated to DR status, others will be disposed of in other ways. (See Issue Status below.)
The most current public version of this document can be found at http://www.dkuug.dk/jtc1/sc22/wg21. Requests for further information about these documents should include the document number, reference ISO/IEC 14882:1998(E), and be submitted to the Information Technology Information Council (ITI), 1250 Eye Street NW, Washington, DC 20005, USA.
Information regarding how to obtain a copy of the C++ Standard, join the Standard Committee, or submit an issue can be found in the C++ FAQ at http://www.research.att.com/~austern/csc/faq.html . Public discussion of the C++ Standard and related issues occurs on newsgroup comp.std.c++.
Issues progress through various statuses as the Core Language Working Group and, ultimately, the full J16 and WG21 committees deliberate and act. For ease of reference, issues are grouped in these documents by their status. Issues have one of the following statuses:
Open: The issue is new or the working group has not yet formed an opinion on the issue. If a Suggested Resolution is given, it reflects the opinion of the issue's submitter, not necessarily that of the working group or the Committee as a whole.
Drafting: Informal consensus has been reached in the working group and is described in rough terms in a Tentative Resolution, although precise wording for the change is not yet available.
Review: Exact wording of a Proposed Resolution is now available for an issue on which the working group previously reached informal consensus.
Ready: The working group has reached consensus that the issue is a defect in the Standard, the Proposed Resolution is correct, and the issue is ready to forward to the full Committee for ratification as a proposed defect report.
DR: The full J16 Committee has approved the item as a proposed defect report. The Proposed Resolution in an issue with this status reflects the best judgment of the Committee at this time regarding the action that will be taken to remedy the defect; however, the current wording of the Standard remains in effect until such time as a Technical Corrigendum or a revision of the Standard is issued by ISO.
Dup: The issue is identical to or a subset of another issue, identified in a Rationale statement.
NAD: The working group has reached consensus that the issue is not a defect in the Standard. A Rationale statement describes the working group's reasoning.
Extension: The working group has reached consensus that the issue is not a defect in the Standard but is a request for an extension to the language. Under ISO rules, extensions cannot be considered for at least five years from the approval of the Standard, at which time the Standard will be open for review. The working group expresses no opinion on the merits of an issue with this status; however, the issue will be maintained on the list for possible future consideration when extension proposals will be in order.
7.5 dcl.link paragraph 6 says the following:
extern "C" { static void f(int) {} static void f(float) {} };Can a function with internal linkage "have C linkage" at all (assuming that phrase means "has extern "C" linkage"), for how can a function be extern "C" if it's not extern? The function type can have extern "C" linkage — but I think that's independent of the linkage of the function name. It should be perfectly reasonable to say, in the example above, that extern "C" applies only to the types of f(int) and f(float), not to the function names, and that the rule in 7.5 dcl.link paragraph 6 doesn't apply.
Suggested resolution: The extern "C" linkage specification applies only to the type of functions with internal linkage, and therefore some of the rules that have to do with name overloading don't apply.
Proposed Resolution:
The intent is to distingush implicit linkage from explicit linkage for both name linkage and language (function type) linkage. (It might be more clear to use the terms name linkage and type linkage to distinguish these concepts. A function can have a name with one kind of linkage and a type with a different kind of linkage. The function itself has no linkage: it has no name, only the declaration has a name. This becomes more obvious when you consider function pointers.)
The tentatively agreed proposal is to apply implicit linkage to names declared in brace-enclosed linkage specifications and to non-top-level names declared in simple linkage specifications; and to apply explicit linkage to top-level names declared in simple linkage specifications.
The language linkage of any function type formed through a function declarator is that of the nearest enclosing linkage-specification. For purposes of determining whether the declaration of a namespace-scope name matches a previous declaration, the language linkage portion of the type of a function declaration (that is, the language linkage of the function itself, not its parameters, return type or exception specification) is ignored.
For a linkage-specification using braces, i.e.
extern string-literal { declaration-seqopt }the linkage of any declaration of a namespace-scope name (including local externs) which is not contained in a nested linkage-specification, is not declared to have no linkage (static), and does not match a previous declaration is given the linkage specified in the string-literal. The language linkage of the type of any function declaration of a namespace-scope name (including local externs) which is not contained in a nested linkage-specification and which is declared with function declarator syntax is the same as that of a matching previous declaration, if any, else is specified by string-literal.
For a linkage-specification without braces, i.e.
extern string-literal declaration
the linkage of the names declared in the top-level declarators of declaration is specified by string-literal; if this conflicts with the linkage of any matching previous declarations, the program is ill-formed. The language linkage of the type of any top-level function declarator is specified by string-literal; if this conflicts with the language linkage of the type of any matching previous function declarations, the program is ill-formed. The effect of the linkage-specification on other (non top-level) names declared in declaration is the same as that of the brace-enclosed form.
Bill Gibbons: In particular, these should be well-formed:
extern "C" void f(void (*fp)()); // parameter type is pointer to // function with C language linkage extern "C++" void g(void (*fp)()); // parameter type is pointer to // function with C++ language linkage extern "C++" { // well-formed: the linkage of "f" void f(void(*fp)()); // and the function type used in the } // parameter still "C" extern "C" { // well-formed: the linkage of "g" void g(void(*fp)()); // and the function type used in the } // parameter still "C++"
but these should not:
extern "C++" void f(void(*fp)()); // error - linkage of "f" does not // match previous declaration // (linkage of function type used in // parameter is still "C" and is not // by itself ill-formed) extern "C" void g(void(*fp)()); // error - linkage of "g" does not // match previous declaration // (linkage of function type used in // parameter is still "C++" and is not // by itself ill-formed)
That is, non-top-level declarators get their linkage from matching declarations, if any, else from the nearest enclosing linkage specification. (As already described, top-level declarators in a brace-enclosed linkage specification get the linkage from matching declarations, if any, else from the linkage specifcation; while top-level declarators in direct linkage specifications get their linkage from that specification.)
Mike Miller: This is a pretty significant change from the current specification, which treats the two forms of language linkage similarly for most purposes. I don't understand why it's desirable to expand the differences.
It seems very unintuitive to me that you could have a top-level declaration in an extern "C" block that would not receive "C" linkage.
In the current standard, the statement in 7.5 dcl.link paragraph 4 that
the specified language linkage applies to the function types of all function declarators, function names, and variable names introduced by the declaration(s)
applies to both forms. I would thus expect that in
extern "C" void f(void(*)()); extern "C++" { void f(void(*)()); } extern "C++" f(void(*)());
both "C++" declarations would be well-formed, declaring an overloaded version of f that takes a pointer to a "C++" function as a parameter. I wouldn't expect that either declaration would be a redeclaration (valid or invalid) of the "C" version of f.
Bill Gibbons: The potential difficulty is the matching process and the handling of deliberate overloading based on language linkage. In the above examples, how are these two declarations matched:
extern "C" void f(void (*fp1)()); extern "C++" { void f(void(*fp2)()); }
given that the linkage that is part of fp1 is "C" while the linkage (prior to the matching process) that is part of fp2 is "C++"?
The proposal is that the linkage which is part of the parameter type is not determined until after the match is attempted. This almost always correct because you can't overload "C" and "C++" functions; so if the function names match, it is likely that the declarations are supposed to be the same.
Mike Miller: This seems like more trouble than it's worth. This comparison of function types ignoring linkage specifications is, as far as I know, not found anywhere in the current standard. Why do we need to invent it?
Bill Gibbons: It is possible to construct pathological cases where this fails, e.g.
extern "C" typedef void (*PFC)(); // pointer to "C" linkage function void f(PFC); // parameter is pointer to "C" function void f(void (*)()); // matching declaration or overload based on // difference in linkage type?
It is reasonable to require explicit typedefs in this case so that in the above example the second function declaration gets its parameter type function linkage from the first function declaration.
(In fact, I think you can't get into this situation without having already used typedefs to declare different language linkage for the top-level and parameter linkages.)
For example, if the intent is to overload based on linkage a typedef is needed:
extern "C" typedef void (*PFC)(); // pointer to "C" linkage function void f(PFC); // parameter is pointer to "C" function typedef void (*PFCPP)(); // pointer to "C++" linkage function void f(PFCPP); // parameter is pointer to "C++" function
In this case the two function declarations refer to different functions.
Mike Miller: This seems pretty strange to me. I think it would be simpler to determine the type of the parameter based on the containing linkage specification (implicitly "C++") and require a typedef if the user wants to override the default behavior. For example:
extern "C" { typedef void (*PFC)(); // pointer to "C" function void f(void(*)()); // takes pointer to "C" function } void f(void(*)()); // new overload of "f", taking // pointer to "C++" function void f(PFC); // redeclare extern "C" version
Notes from 04/00 meeting:
The following changes were tentatively approved, but because they do not completely implement the proposal above the issue is being kept for the moment in "drafting" status.
Notes from 10/00 meeting:
After further discussion, the core language working group determined that the more extensive proposal described above is not needed and that the following changes are sufficient.
Proposed resolution (10/00):
Change the first sentence of 7.5 dcl.link paragraph 1 from
All function types, function names, and variable names have a language linkage.
to
All function types, function names with external linkage, and variable names with external linkage have a language linkage.
In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names, and variable names introduced by the declaration(s).
to
In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names with external linkage, and variable names with external linkage declared within the linkage-specification.
Add at the end of the final example on 7.5 dcl.link paragraph 4:
extern "C" { static void f4(); // the name of the function f4 has // internal linkage (not C language // linkage) and the function's type // has C language linkage } extern "C" void f5() { extern void f4(); // Okay -- name linkage (internal) // and function type linkage (C // language linkage) gotten from // previous declaration. } extern void f4(); // Okay -- name linkage (internal) // and function type linkage (C // language linkage) gotten from // previous declaration. void f6() { extern void f4(); // Okay -- name linkage (internal) // and function type linkage (C // language linkage) gotten from // previous declaration. }
Change 7.5 dcl.link paragraph 7 from
Except for functions with internal linkage, a function first declared in a linkage-specification behaves as a function with external linkage. [Example:
extern "C" double f(); static double f(); // erroris ill-formed (7.1.1 dcl.stc). ] The form of linkage-specification that contains a braced-enclosed declaration-seq does not affect whether the contained declarations are definitions or not (3.1 basic.def); the form of linkage-specification directly containing a single declaration is treated as an extern specifier (7.1.1 dcl.stc) for the purpose of determining whether the contained declaration is a definition. [Example:
extern "C" int i; // declaration extern "C" { int i; // definition }—end example] A linkage-specification directly containing a single declaration shall not specify a storage class. [Example:
extern "C" static void f(); // error—end example]
to
A declaration directly contained in a linkage-specification is treated as if it contains the extern specifier (7.1.1 dcl.stc) for the purpose of determining the linkage of the declared name and whether it is a definition. Such a declaration shall not specify a storage class. [Example:extern "C" double f(); static double f(); // error extern "C" int i; // declaration extern "C" { int i; // definition } extern "C" static void g(); // error—end example]
The description of copy-initialization in 8.5 dcl.init paragraph 14 says:
struct A { A(A&); }; struct B : A { }; struct C { operator B&(); }; C c; const A a = c; // allowed?
The temporary created with the conversion function is an lvalue of type B. If the temporary must have the cv-qualifiers of the destination type (i.e. const) then the copy-constructor for A cannot be called to create the object of type A from the lvalue of type const B. If the temporary has the cv-qualifiers of the result type of the conversion function, then the copy-constructor for A can be called to create the object of type A from the lvalue of type const B. This last outcome seems more appropriate.
Steve Adamczyk:
Because of late changes to this area, the relevant text is now the third sub-bullet of the fourth bullet of 8.5 dcl.init paragraph 14:
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated... The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.
The issue still remains whether the wording should refer to "the cv-unqualified version of the destination type." I think it should.
Notes from 10/00 meeting:
The original example does not illustrate the remaining problem. The following example does:
struct C { }; C c; struct A { A(const A&); A(const C&); }; const volatile A a = c; // Okay
Proposed Resolution (10/00):
In 8.5 dcl.init, paragraph 14, bullet 4, sub-bullet 3, change
if the function is a constructor, the call initializes a temporary of the destination type.
to
if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type.
As above.
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 (10/00):
The resolution for this issue is contained in the resolution for issue 45.
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*.
Proposed Resolution (10/99):
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:...
(See J16/99-0042 = WG21 N1218.)
Notes from 04/00 meeting:
The resolution for issue 207, when it becomes available, should be incorporated into this wording.
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 (10/99): As described for Core issue 9. With that change, it is clear that the example is ill-formed.
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 (10/00):
The resolution for this issue is incorporated in the resolution for issue 45.
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-formedThis 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&) { } //3If 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 (10/00):
The resolution for this issue is incorporated into the resolution for issue 45.
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 (10/00):
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, 10, and 77.
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/00):
The proposed resolution is contained in the proposal for issue 201.
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/00):
[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 upon return from the constructor.
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 (10/00):
In 3.4.2 basic.lookup.koenig 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.
Also, remove the associated footnote 116.
Is the intent of 13.5.3 over.ass paragraph 1 that all assignment operators be non-static member functions (including operator+=, operator*=, etc.) or only simple assignment operators (operator=)?
Notes from 04/00 meeting:
Nearly all references to "assignment operator" in the IS mean operator= and not the compound assignment operators. The ARM was specific that this restriction applied only to operator=. If it did apply to compound assignment operators, it would be impossible to overload these operators for bool operands.
Proposed resolution (04/00):
Change the title of 5.17 expr.ass from "Assignment operators" to "Assignment and compound assignment operators."
Change the first sentence of 5.17 expr.ass paragraph 1 from
There are several assignment operators, all of which group right-to-left. All require a modifiable lvalue as their left operand, and the type of an assignment expression is that of its left operand. The result of the assignment operation is the value stored in the left operand after the assignment has taken place; the result is an lvalue.
to
The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue with the type and value of the left operand after the assignment has taken place.
Additional note (10/00): Paragraphs 2-6 of 5.17 expr.ass should all be understood to apply to simple assignment only and not to compound assignment operators.
Is it permitted to jump from a handler of a function-try-block into the body of the function?
15 except paragraph 2 would appear to disallow such a jump:
A goto, break, return, or continue statement can be used to transfer control out of a try block or handler, but not into one.
However, 15.3 except.handle paragraph 14 mentions only constructors and destructors for the prohibition:
If the handlers of a function-try-block contain a jump into the body of a constructor or destructor, the program is ill-formed.
Is this paragraph simply reemphasizing the more general restriction, or does it assume that such a jump would be permitted for functions other than constructors or destructors? If the former interpretation is correct, it is confusing and should be either eliminated or turned into a note. If the latter interpretation is accurate, 15 except paragraph 2 must be revised.
(See also issue 98.)
Proposed resolution (10/00):
Delete 15.3 except.handle paragraph 14.
Section 14.3.1 temp.arg.type paragraph 2 says
A local type, a type with no linkage, an unnamed type or a type compounded from any of these types shall not be used as a template-argument for a template type-parameter.
It probably wasn't intended that classes with unnamed members should be included in this list, but they are arguably compounded from unnamed types.
Proposed resolution (10/00):
In 14.3.1 temp.arg.type paragraph 2, change
A local type, a type with no linkage, an unnamed type or a type compounded from any of these types shall not be used as a template-argument for a template type-parameter.
to
The following types shall not be used as a template-argument for a template type-parameter:
- a type whose name has no linkage
- an unnamed class or enumeration type that has no name for linkage purposes (7.1.3 dcl.typedef)
- a cv-qualified version of one of the types in this list
- a type created by application of declarator operators to one of the types in this list
- a function type that uses one of the types in this list
Mike Miller: A question about typename came up in the discussion of issue 68 that is somewhat relevant to the idea of omitting typename in contexts where it is clear that a type is required: consider something like
template <class T> class X { friend class T::nested; };Is typename required here? If so, where would it go? (The grammar doesn't seem to allow it anywhere in an elaborated-type-specifier that has a class-key.)
Bill Gibbons: The class applies to the last identifier in the qualified name, since all the previous names must be classes or namespaces. Since the name is specified to be a class it does not need typename. [However,] it looks like 14.6 temp.res paragraph 3 requires typename and the following paragraphs do not exempt this case. This is not what we agreed on.
Proposed resolution (10/00):
In 14.6 temp.res paragraph 5, change
The keyword typename is not permitted in a base-specifier or in a mem-initializer; in these contexts a qualified-name that depends on a template-parameter (14.6.2 temp.dep) is implicitly assumed to be a type name.
to
The keyword typename is not permitted in a base-specifier, a mem-initializer-id, or following the class-key or enum keyword of an elaborated-type-specifier. In these contexts, a qualified-id that depends on a template-parameter (14.6.2 temp.dep) is implicitly assumed to be a type name.
Some compilers reject the following:
struct A { template <int I> void f(); template <> void f<0>(); };on the basis of 14.7.3 temp.expl.spec paragraph 2:
An explicit specialization shall be declared in the namespace of which the template is a member, or, for member templates, in the namespace of which the enclosing class or enclosing class template is a member. An explicit specialization of a member function, member class or static data member of a class template shall be declared in the namespace of which the class template is a member. ...claiming that the specialization above is not "in the namespace of which the enclosing class ... is a member". Elsewhere, declarations are sometimes required to be "at" or "in" "namespace scope", which is not what it says here. Paragraph 17 says:
A member or a member template may be nested within many enclosing class templates. If the declaration of an explicit specialization for such a member appears in namespace scope, the member declaration shall be preceded by a template<> for each enclosing class template that is explicitly specialized.The qualification "if the declaration ... appears in namespace scope", implies that it might appear elsewhere. The only other place I can think of for a member specialization is in class scope.
Was it the intent of the committee to forbid the construction above? (Note that A itself is not a template.) If so, why?
Proposed resolution (10/99): In-class specializations of member templates are not allowed. In 14.7.3 temp.expl.spec paragraph 17, replace
If the declaration of an explicit specialization for such a member appears in namespace scope...with
In an explicit specialization for such a member...
Notes from 04/00 meeting:
This issue was kept in "review" status for two major reasons:
Notes from 10/00 meeting:
The core working group felt that the value of additional clarity here outweighs the potential disadvantages that were noted at the preceding meeting.
Paragraph 4 lists contexts in which template formals are not deduced. Were template formals in an expression in the array bound of an array type specification intentionally left out of this list? Or was the intent that such formals always be explicitly specified? Otherwise I believe the following should be valid:
template <int I> class IntArr {}; template <int I, int J> void concat( int (&d)[I+J], const IntArr<I>& a, const IntArr<J>& b ) {} int testing() { IntArr<2> a; IntArr<3> b; int d[5]; concat( d, a, b ); }Can anybody shed some light on this?
From John Spicer:
Expressions involving nontype template parameters are nondeduced contexts, even though they are omitted from the list in 14.8.2.4 temp.deduct.type paragraph 4. See 14.8.2.4 temp.deduct.type paragraphs 12-14:
...
Proposed resolution (10/99): In 14.8.2.4 temp.deduct.type paragraph 4, add a third bullet:
Paragraph 7 of 15.1 except.throw discusses which exception is thrown by a throw-expression with no operand.
May an expression which has been "finished" (paragraph 7) by an inner catch block be rethrown by an outer catch block?
catch(...) // Catch the original exception { try{ throw; } // rethrow it at an inner level // (in reality this is probably // inside a function) catch (...) { } // Here, an exception (the original object) // is "finished" according to 15.1p7 wording // 15.1p7 says that only an unfinished exception // may be rethrown. throw; // Can we throw it again anyway? It is // certainly still alive (15.1p4). }
I believe this is ok, since the paragraph says that the exception is finished when the "corresponding" catch clause exits. However since we have two clauses, and only one exception, it would seem that the one exception gets "finished" twice.
Proposed resolution (04/00):
In 15.1 except.throw paragraph 4, change
When the last handler being executed for the exception exits by any means other than throw; ...to
When the last remaining active handler for the exception exits by any means other than throw; ...
In 15.1 except.throw paragraph 6, change
A throw-expression with no operand rethrows the exception being handled.to
A throw-expression with no operand rethrows the currently handled exception (15.3 except.handle).
Delete 15.1 except.throw paragraph 7.
Add the following before 15.1 except.throw paragraph 6:
An exception is considered caught when a handler for that exception becomes active (15.3 except.handle). [Note: an exception can have active handlers and still be considered uncaught if it is rethrown.]
Change 15.3 except.handle paragraph 8 from
An exception is considered handled upon entry to a handler. [Note: the stack will have been unwound at that point.]to
A handler is considered active when initialization is complete for the formal parameter (if any) of the catch clause. [Note: the stack will have been unwound at that point.] Also, an implicit handler is considered active when std::terminate() or std::unexpected() is entered due to a throw. A handler is no longer considered active when the catch clause exits or when std::unexpected() exits after being entered due to a throw.
The exception with the most recently activated handler that is still active is called the currently handled exception.
In 15.3 except.handle paragraph 16, change "exception being handled" to "currently handled exception."
In 15.4 except.spec paragraph 2:
An exception-specification shall appear only on a function declarator in a function, pointer, reference or pointer to member declaration or definition.Does that mean in the top-level function declarator, or one at any level? Can one, for example, specify an exception specification on a pointer-to-function parameter of a function?
void f(int (*pf)(float) throw(A))Suggested answer: no. The exception specifications are valid only on the top-level function declarators.
However, if exception specifications are made part of a function's type as has been tentatively agreed, they would have to be allowed on any function declaration.
There is already an example of an exception specification for a parameter in the example in 15.4 except.spec paragraph 1.
Proposed resolution: Change text in 15.4 except.spec paragraph 1 from:
An exception-specification shall appear only on a function declarator in a function, pointer, reference or pointer to member declaration or definition.to:
An exception-specification shall appear only on a function declarator for a function type, pointer to function type, reference to function type, or pointer to member function type that is the top-level type of a declaration or definition, or on such a type appearing as a parameter or return type in a function declarator.
(See also issues 25, 92, and 133.)
The main defect is in the library, where the binder template can easily lead to reference-to-reference situations.
Proposed resolution (10/00):
If a typedef TD names a reference type S&, then cv TD& names the reference type cv S&. [Example:
int i; typedef int& RI; RI r1 = i; RI& r = i; // r has the type int&—end example]
If a template-argument for a template-parameter T names a reference type S&, then cv T& names the reference type cv S&. [Example:
template <class T> class X { f(const T&); /* ... */ }; X<int&> x; // X<int&>::f has the parameter type const int&—end example]
(See also paper J16/00-0022 = WG21 N1245.)
The example in 3.4.1 basic.lookup.unqual paragraph 3 is incorrect:
typedef int f; struct A { friend void f(A &); operator int(); void g(A a) { f(a); } };Regardless of the resolution of other issues concerning the lookup of names in friend declarations, this example is ill-formed (the function and the typedef cannot exist in the same scope).
One possible repair of the example would be to make f a class with a constructor taking either A or int as its parameter.
(See also issues 95, 136, 138, 143, 165, and 166.)
Proposed resolution:
Change the example to use an intermediate namespace, i.e.,
typedef int f; namespace N { struct A { friend void f(A &); operator int(); void g(A a) { f(a); } }; }
(Later note: this resolution is still ill-formed, because the redeclaration of a is in the same scope as the parameter. It should either be moved into a nested scope or the f(a) should be used as part of an expression (after adjusting the return type of f(A&) to int to make the alternate interpretation plausible. The latter approach is preferable as it comports with the description of the example in the text.)
A name having namespace scope has external linkage if it is the name ofThat prohibits for example:
- [...]
- a named enumeration (7.2 dcl.enum), or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes (7.1.3 dcl.typedef)
typedef enum { e1 } *PE; void f(PE) {} // Cannot declare a function (with linkage) using a // type with no linkage.
However, the same prohibition was not made for class scope types. Indeed, 3.5 basic.link paragraph 5 says:
In addition, a member function, static data member, class or enumeration of class scope has external linkage if the name of the class has external linkage.
That allows for:
struct S { typedef enum { e1 } *MPE; void mf(MPE) {} };
My guess is that this is an unintentional consequence of 3.5 basic.link paragraph 5, but I would like confirmation on that.
Proposed resolution:
Change text in 3.5 basic.link paragraph 5 from:
In addition, a member function, static data member, class or enumeration of class scope has external linkage if the name of the class has external linkage.to:
In addition, a member function, a static data member, a named class or enumeration of class scope, or an unnamed class or enumeration defined in a class-scope typedef declaration such that the class or enumeration has the typedef name for linkage purposes (7.1.3 dcl.typedef), has external linkage if the name of the class has external linkage.
5.1 expr.prim paragraph 11 reads,
A template-id shall be used as an unqualified-id only as specified in 14.7.2 temp.explicit , 14.7 temp.spec , and 14.5.4 temp.class.spec .
What uses of template-ids as unqualified-ids is this supposed to prevent? And is the list of referenced sections correct/complete? For instance, what about 14.8.1 temp.arg.explicit, "Explicit template argument specification?" Does its absence from the list in 5.1 expr.prim paragraph 11 mean that "f<int>()" is ill-formed?
This is even more confusing when you recall that unqualified-ids are contained in qualified-ids:
qualified-id: ::opt nested-name-specifier templateopt unqualified-id
Is the wording intending to say "used as an unqualified-id that is not part of a qualified-id?" Or something else?
Proposed resolution (10/00):
Remove the referenced sentence altogether.
The example below is ambiguous.
struct A{ struct B{}; }; A::B C(); namespace B{ A C(); } struct Test { friend A::B ::C(); };Here, it is not clear whether the friend declaration denotes A B::C() or A::B C(), yet the standard does not resolve this ambiguity.
The ambiguity arises since both the simple-type-specifier (7.1.5.2 dcl.type.simple paragra 1) and an init-declararator (8 dcl.decl paragraph 1) contain an optional :: and an optional nested-name-specifier (5.1 expr.prim paragraph 1). Therefore, two different ways to analyse this code are possible:
simple-type-specifier = A::Bor
init-declarator = ::C()
simple-declaration = friend A::B ::C();
simple-type-specifier = ASince it is a friend declaration, the init-declarator may be qualified, and start with a global scope.
init-declarator = ::B::C()
simple-declaration = friend A ::B::C();
Suggested Resolution: In the definition of nested-name-specifier, add a sentence saying that a :: token immediately following a nested-name-specifier is always considered as part of the nested-name-specifier. Under this interpretation, the example is ill-formed, and should be corrected as either
friend A (::B::C)(); //or friend A::B (::C)();
An alternate suggestion — changing 7.1 dcl.spec to say that
The longest sequence of tokens that could possibly be a type name is taken as the decl-specifier-seq of a declaration.
— is undesirable because it would make the example well-formed rather than requiring the user to disambiguate the declaration explicitly.
Proposed resolution (10/00):
Split 5.1 expr.prim paragraph 7 into three paragraphs. The first paragraph would end immediately preceding the grammar for qualified-id. The third paragraph would consist of the text that currently follows the grammar fragment. The second paragraph would consist of the grammar fragement, preceded by the sentence
A qualified-id is an id-expression that contains the scope resolution operator ::.
with the following text added after the grammar:
The longest sequence of tokens that could form a qualified-id constitutes a single qualified-id. [Example:
friend A ::B::C(); // ill-formed, means friend (A::B::C)(); friend A (::B::C)(); // well-formed friend A::B ::C(); // ill-formed, means friend (A::B::C)(); friend A::B (::C)(); // well-formed—end example]
Christophe de Dinechin: In 5.2.2 expr.call , paragraph 2 reads:
If no declaration of the called function is visible from the scope of the call the program is ill-formed.I think nothing there or in the previous paragraph indicates that this does not apply to calls through pointer or virtual calls.
Mike Miller: "The called function" is unfortunate phraseology; it makes it sound as if it's referring to the function actually called, as opposed to the identifier in the postfix expression. It's wrong with respect to Koenig lookup, too (the declaration need not be visible if it can be found in a class or namespace associated with one or more of the arguments).
In fact, this paragraph should be a note. There's a general rule that says you have to find an unambiguous declaration of any name that is used (3.4 basic.lookup paragraph 1); the only reason this paragraph is here is to contrast with C's implicit declaration of called functions.
Proposed resolution:
Change section 5.2.2 expr.call paragraph 2 from:If no declaration of the called function is visible from the scope of the call the program is ill-formed.to:
[Note: if a function or member function name is used, and name lookup (3.4 basic.lookup) does not find a declaration of that name, the program is ill-formed. No function is implicitly declared by such a call. ]
(See also issue 218.)
8.2 dcl.ambig.res paragraph 3 shows an example that includes <cstddef> with no using declarations or directives and refers to size_t without the std:: qualification.
Many references to size_t throughout the document omit the std:: namespace qualification.
This is a typical case. The use of std:: is inconsistent throughout the document.
In addition, the use of exception specifications should be examined for consistency.
Proposed resolution:
In 1.9 intro.execution paragraph 9, replace all two instances of "sig_atomic_t" by "std::sig_atomic_t".
In 3.1 basic.def paragraph 4, replace all three instances of "string" by "std::string" in the example and insert "#include <string>" at the beginning of the example code.
In 3.6.1 basic.start.main paragraph 4, replace
void exit(int);
by
namespace std { void exit(int); }
and also replace "exit" by "std::exit" in the last sentence of that paragraph.
In 3.6.1 basic.start.main first sentence of paragraph 5, replace "exit" by "std::exit".
In 3.6.2 basic.start.init paragraph 4, replace "terminate" by "std::terminate".
In 3.6.3 basic.start.term paragraph 1, replace "exit" by "std::exit" (see also issue 28).
In 3.6.3 basic.start.term paragraph 3, replace all three instances of "atexit" by "std::atexit" and both instances of "exit" by "std::exit" (see also issue 28).
In 3.6.3 basic.start.term paragraph 4, replace
void abort();
by
namespace std { void abort(); }and "atexit" by "std::atexit" (see also issue 28).
In 3.7.3.1 basic.stc.dynamic.allocation paragraph 1 third sentence, replace "size_t" by "std::size_t".
In 3.7.3.1 basic.stc.dynamic.allocation paragraph 3, replace "new_handler" by "std::new_handler". Furthermore, replace "set_new_handler" by "std::set_new_handler" in the note.
In 3.7.3.1 basic.stc.dynamic.allocation paragraph 4, replace "type_info" by "std::type_info" in the note.
In 3.7.3.2 basic.stc.dynamic.deallocation paragraph 3, replace all four instances of "size_t" by "std::size_t".
In 3.8 basic.life paragraph 5, replace "malloc" by "std::malloc" in the example code and insert "#include <cstdlib>" at the beginning of the example code.
In 3.9 basic.types paragraph 2, replace "memcpy" by "std::memcpy" (the only instance in the footnote and both instances in the example) and replace "memmove" by "std::memmove" in the footnote (see also issue 43).
In 3.9 basic.types paragraph 3, replace "memcpy" by "std::memcpy", once in the normative text and once in the example (see also issue 43).
In 3.9.1 basic.fundamental paragraph 8 last sentence, replace "numeric_limits" by "std::numeric_limits".
In 5.2.7 expr.dynamic.cast paragraph 9 second sentence, replace "bad_cast" by "std::bad_cast".
In 5.2.8 expr.typeid paragraph 1 (footnote), replace "extended_type_info" by "std::extended_type_info". ?????????????????????
In 5.2.8 expr.typeid paragraph 2, replace "type_info" by "std::type_info" and "bad_typeid" by "std::bad_typeid".
In 5.2.8 expr.typeid paragraph 3, replace "type_info" by "std::type_info".
In 5.2.8 expr.typeid paragraph 4, replace both instances of "type_info" by "std::type_info".
In 5.3.3 expr.sizeof paragraph 6, replace both instances of "size_t" by "std::size_t".
In 5.3.4 expr.new paragraph 11 last sentence, replace "size_t" by "std::size_t".
In 5.7 expr.add paragraph 6, replace both instances of "ptrdiff_t" by "std::ptrdiff_t".
In 5.7 expr.add paragraph 8, replace "ptrdiff_t" by "std::ptrdiff_t".
In 6.6 stmt.jump paragraph 2, replace "exit" by "std::exit" and "abort" by "std::abort" in the note.
In 8.2 dcl.ambig.res paragraph 3, replace "size_t" by "std::size_t" in the example.
In 8.4 dcl.fct.def paragraph 5, replace "printf" by "std::printf" in the note.
In 9.3.1 class.mfct.nonstatic paragraph 2, replace the footnote
See, for example, <cstring> (21.4 lib.c.strings).
by
See, for example, <cstring> (21.4 lib.c.strings), with suitable using-declarations for std::strlen, std::perror, and std::strcpy added.
In 12.4 class.dtor paragraph 13, replace "size_t" by "std::size_t" in the example.
In 12.5 class.free paragraph 2, replace all four instances of "size_t" by "std::size_t" in the example.
In 12.5 class.free paragraph 6, replace both instances of "size_t" by "std::size_t" in the example.
In 12.5 class.free paragraph 7, replace all four instances of "size_t" by "std::size_t" in the two examples.
In 12.7 class.cdtor paragraph 4, replace "type_info" by "std::type_info".
In 13.6 over.built paragraph 13, replace all five instances of "ptrdiff_t" by "std::ptrdiff_t".
In 13.6 over.built paragraph 14, replace "ptrdiff_t" by "std::ptrdiff_t".
In 13.6 over.built paragraph 21, replace both instances of "ptrdiff_t" by "std::ptrdiff_t".
In 14.2 temp.names paragraph 4, replace both instances of "size_t" by "std::size_t" in the example. (The example is quoted in issue 96.)
In 14.3 temp.arg paragraph 1, replace "complex" by "std::complex", once in the example code and once in the comment.
In 14.7.3 temp.expl.spec paragraph 8, issue 24 has already corrected the example.
In 15.1 except.throw paragraph 6, replace "uncaught_exception" by "std::uncaught_exception".
In 15.1 except.throw paragraph 7, replace "terminate" by "std::terminate" and both instances of "unexpected" by "std::unexpected".
In 15.1 except.throw paragraph 8, replace "terminate" by "std::terminate".
In 15.2 except.ctor paragraph 3, replace "terminate" by "std::terminate".
In 15.3 except.handle paragraph 9, replace "terminate" by "std::terminate".
In 15.4 except.spec paragraph 8, replace "unexpected" by "std::unexpected".
In 15.4 except.spec paragraph 9, replace "unexpected" by "std::unexpected" and "terminate" by "std::terminate".
In 15.5 except.special paragraph 1, replace "terminate" by "std::terminate" and "unexpected" by "std::unexpected".
In the heading of 15.5.1 except.terminate, replace "terminate" by "std::terminate".
In 15.5.1 except.terminate paragraph 1, footnote in the first bullet, replace "terminate" by "std::terminate". In the same paragraph, fifth bullet, replace "atexit" by "std::atexit". In the same paragraph, last bullet, replace "unexpected_handler" by "std::unexpected_handler".
In 15.5.1 except.terminate paragraph 2, replace
void terminate();
by
namespace std { void terminate(); }
and replace all three instances of "terminate" by "std::terminate".
In the heading of 15.5.2 except.unexpected, replace "unexpected" by "std::unexpected".
In 15.5.2 except.unexpected paragraph 1, replace
void unexpected();
by
namespace std { void unexpected(); }.
In 15.5.2 except.unexpected paragraph 2, replace "unexpected" by "std::unexpected" and "terminate" by "std::terminate".
In 15.5.2 except.unexpected paragraph 3, replace "unexpected" by "std::unexpected".
In the heading of 15.5.3 except.uncaught, replace "uncaught_exception" by "std::uncaught_exception".
In 15.5.3 except.uncaught paragraph 1, replace
bool uncaught_exception()
by
namespace std { bool uncaught_exception(); }.
In the last sentence of the same paragraph, replace "uncaught_exception" by "std::uncaught_exception".
8.3.5 dcl.fct paragraph 3 says,
All declarations for a function with a given parameter list shall agree exactly both in the type of the value returned and in the number and type of parameters.It is not clear what this requirement means with respect to a pair of declarations like the following:
int f(const int); int f(int x) { ... }Do they violate this requirement? Is x const in the body of the function declaration?
Tom Plum: I think the FDIS quotation means that the pair of decls are valid. But it doesn't clearly answer whether x is const inside the function definition. As to intent, I know the intent was that if the function definition wants to specify that x is const, the const must appear specifically in the defining decl, not just on some decl elsewhere. But I can't prove that intent from the drafted words.
Mike Miller: I think the intent was something along the following lines:
Two function declarations denote the same entity if the names are the same and the function signatures are the same. (Two function declarations with C language linkage denote the same entity if the names are the same.) All declarations of a given function shall agree exactly both in the type of the value returned and in the number and type of parameters; the presence or absence of the ellipsis is considered part of the signature.(See 3.5 basic.link paragraph 9. That paragraph talks about names in different scopes and says that function references are the same if the "types are identical for purposes of overloading," i.e., the signatures are the same. See also 7.5 dcl.link paragraph 6 regarding C language linkage, where only the name is required to be the same for declarations in different namespaces to denote the same function.)
According to this paragraph, the type of a parameter is determined by considering its decl-specifier-seq and declarator and then applying the array-to-pointer and function-to-pointer adjustments. The cv-qualifier and storage class adjustments are performed for the function type but not for the parameter types.
If my interpretation of the intent of the second sentence of the paragraph is correct, the two declarations in the example violate that restriction — the parameter types are not the same, even though the function types are. Since there's no dispensation mentioned for "no diagnostic required," an implementation presumably must issue a diagnostic in this case. (I think "no diagnostic required" should be stated if the declarations occur in different translation units — unless there's a blanket statement to that effect that I have forgotten?)
(I'd also note in passing that, if my interpretation is correct,
void f(int); void f(register int) { }is also an invalid pair of declarations.)
Proposed resolution (10/00):
In 1.3.10 defns.signature, change "the types of its parameters" to "its parameter-type-list (8.3.5 dcl.fct)".
In the third bullet of 3.5 basic.link paragraph 9 change "the function types are identical for the purposes of overloading" to "the parameter-type-lists of the functions (8.3.5 dcl.fct) are identical."
In the sub-bullets of the third bullet of 5.2.5 expr.ref paragraph 4, change all four occurrences of "function of (parameter type list)" to "function of parameter-type-list."
In 8.3.5 dcl.fct paragraph 3, change
All declarations for a function with a given parameter list shall agree exactly both in the type of the value returned and in the number and type of parameters; the presence or absence of the ellipsis is considered part of the function type.to
All declarations for a function shall agree exactly in both the return type and the parameter-type-list.
In 8.3.5 dcl.fct paragraph 3, change
The resulting list of transformed parameter types is the function's parameter type list.to
The resulting list of transformed parameter types and the presence or absence of the ellipsis is the function's parameter-type-list.
In 8.3.5 dcl.fct paragraph 4, change "the parameter type list" to "the parameter-type-list."
In the second bullet of 13.1 over.load paragraph 2, change all three occurrences of "parameter types" to "parameter-type-list."
In 13.3 over.match paragraph 1, change "the types of the parameters" to "the parameter-type-list."
In the last sub-bullet of the third bullet of 13.3.1.2 over.match.oper paragraph 3, change "parameter type list" to "parameter-type-list."
8.3.6 dcl.fct.default paragraph 4 says,
For non-template functions, default arguments can be added in later declarations of a function in the same scope. Declarations in different scopes have completely distinct sets of default arguments. That is, declarations in inner scopes do not acquire default arguments from declarations in outer scopes, and vice versa.It is unclear how this wording applies to friend function declarations. For example,
void f(int, int, int=0); // #1 class C { friend void f(int, int=0, int); // #2 }; void f(int=0, int, int); // #3Does the declaration at #2 acquire the default argument from #1, and does the one at #3 acquire the default arguments from #2?
There are several related questions involved with this issue:
Mike Miller: 8.3.6 dcl.fct.default paragraph 4 is speaking about the lexical location of the declaration... The friend declaration occurs in a different declarative region from the declaration at #1, so I would read [this paragraph] as saying that it starts out with a clean slate of default arguments.
Bill Gibbons: Yes. It occurs in a different region, although it declares a name in the same region (i.e. a redeclaration). This is the same as with local externs and is intended to work the same way. We decided that local extern declarations cannot add (beyond the enclosing block) new default arguments, and the same should apply to friend declarations.
John Spicer: The question is whether [this paragraph] does (or should) mean declarations that appear in the same lexical scope or declarations that declare names in the same scope. In my opinion, it really needs to be the latter. It seems somewhat paradoxical to say that a friend declaration declares a function in namespace scope yet the declaration in the class still has its own attributes. To make that work I think you'd have to make friends more like block externs that really do introduce a name into the scope in which the declaration is contained.
Bill Gibbons: In the absence of a declaration visible in class scope to which they could be attached, default arguments on friend declarations do not make sense. [They should be] ill-formed, to prevent surprises.
John Spicer: It is important that the following case work correctly:
class X { friend void f(X x, int i = 1){} }; int main() { X x; f(x); }
In other words, a function first declared in a friend declaration must be permitted to have default arguments and those default arguments must be usable when the function is found by argument dependent lookup. The reason that this is important is that it is common practice to define functions in friend declarations in templates, and that definition is the only place where the default arguments can be specified.
John Spicer: We want to avoid instantiation side effects. IMO, the way to do this would be to prohibit a friend declaration from providing default arguments if a declaration of that function is already visible. Once a function has had a default specified in a friend declaration it should not be possible to add defaults in another declaration be it a friend or normal declaration.
Mike Miller: The position that seems most reasonable to me is to allow default arguments in friend declarations to be used in Koenig lookup, but to say that they are completely unrelated to default arguments in declarations in the surrounding scope; and to forbid use of a default argument in a call if more than one declaration in the overload set has such a default, as in the proposed resolution for issue 1.
Notes from 10/99 meeting:
Four possible outcomes were identified:
The core group eliminated the first and fourth options from consideration, but split fairly evenly between the remaining two.
A straw poll of the full committee yielded the following results (given as number favoring/could live with/"over my dead body"):
Additional discussion is recorded in the "Record of Discussion" for the meeting, J16/99-0036 = WG21 N1212. See also paper J16/00-0040 = WG21 N1263.
Proposed resolution (10/00):
In 8.3.6 dcl.fct.default, add following paragraph 4:
If a friend declaration specifies a default argument expression, that declaration must be a definition and shall be the only declaration of the function or function template in the translation unit.
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 }
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).
According to 14.1 temp.param paragraph 3, the following fragment is ill-formed:
template <class T> class X{ friend void T::foo(); };
In the friend declaration, the T:: part is a nested-name-specifier (8 dcl.decl paragraph 4), and T must be a class-name or a namespace-name (5.1 expr.prim paragraph 7). However, according to 14.1 temp.param paragraph 3, it is only a type-name. The fragment should be well-formed, and instantiations of the template allowed as long as the actual template argument is a class which provides a function member foo. As a result of this defect, any usage of template parameters in nested names is ill-formed, e.g., in the example of 14.6 temp.res paragraph 2.
Notes from 04/00 meeting:
The discussion at the meeting revealed a self-contradiction in the current IS in the description of nested-name-specifiers. According to the grammar in 5.1 expr.prim paragraph 7, the components of a nested-name-specifier must be either class-names or namespace-names, i.e., the constraint is syntactic rather than semantic. On the other hand, 3.4.3 basic.lookup.qual paragraph 1 describes a semantic constraint: only object, function, and enumerator names are ignored in the lookup for the component, and the program is ill-formed if the lookup finds anything other than a class-name or namespace-name. It was generally agreed that the syntactic constraint should be eliminated, i.e., that the grammar ought to be changed not to use class-or-namespace-name.
A related point is the explicit prohibition of use of template parameters in elaborated-type-specifiers in 7.1.5.3 dcl.type.elab paragraph 2. This rule was the result of an explicit Committee decision and should not be unintentionally voided by the resolution of this issue.
Proposed resolution (10/00):
In 3.4.3 basic.lookup.qual paragraph 1, change the last sentence from
If the name found is not a class-name (clause 9 class) or namespace-name (7.3.1 namespace.def), the program is ill-formed.
to
If the name found does name a class (clause 9 class), a namespace (7.3.1 namespace.def), or a template type parameter (14.1 temp.param), the program is ill-formed.
Change 5.1 expr.prim paragraph 7 and A.4 gram.expr from
to
In 5.1 expr.prim paragraph 7, add the following text immediately following the grammar:
A nested-name that names a template type parameter is a class.
Notes from 10/00 meeting:
The proposed resolution does not (but should) handle the case where the nested-name-specifier refers to a typedef-name whose type is a template type parameter.
The definition of when a type is dependent, given in 14.6.2.1 temp.dep.type, is essentially syntactic: if the reference is a qualified-id and one of the class-names in the nested-name-specifier is dependent, the type is dependent. This approach leads to surprising results:
template <class T> class X { typedef int I; I a; // non-dependent typename X<T>::I b; // dependent typename X::I c; // dependent (X is equivalent to X<T>) };
Suggested resolution:
The decision on whether a name is dependent or non-dependent should be based on lookup, not on the form of the name: if the name can be looked up in the definition context and cannot be anything else as the result of specialization, the name should be non-dependent.
See papers J16/00-0028 = WG21 N1251 and J16/00-0056 = WG21 N1279.
Proposed resolution (10/00):
Replace section 14.6.2.1 temp.dep.type with the following:
In the definition of a class template, a nested class of a class template, a member of a class template, or a member of a nested class of a class template, a name refers to the current instantiation if it is
- the injected-class-name (clause 9 class) of the class template or nested class,
- in the definition of a primary class template, the name of the class template followed by the template argument list of the primary template (as described below) enclosed in <>,
- in the definition of a nested class of a class template, the name of the nested class referenced as a member of the current instantiation, or
- in the definition of a partial specialization, the name of the class template followed by the template argument list of the partial specialization enclosed in <>.
The template argument list of a primary template is a template argument list in which the nth template argument has the value of the nth template parameter of the class template.
A template argument that is equivalent to a template parameter (i.e., has the same constant value or the same type as the template parameter) can be used in place of that template parameter in a reference to the current instantiation. In the case of a nontype template argument, the argument must have been given the value of the template parameter and not an expression involving the template parameter.
[Example:
template <class T> class A { A* p1; // A is the current instantiation A<T>* p2; // A<T> is the current instantiation A<T*> p3; // A<T*> is not the current instantiation ::A<T>* p4; // ::A<T> is the current instantiation class B { B* p1; // B is the current instantiation A<T>::B* p2; // A<T>::B is the current instantiation typename A<T*>::B* p3; // A<T*>::B is not the // current instantiation }; }; template <class T> class A<T*> { A<T*>* p1; // A<T*> is the current instantiation A<T>* p2; // A<T> is not the current instantiation }; template <class T1, class T2, int I> struct B { B<T1, T2, I>* b1; // refers to the current instantiation B<T2, T1, I>* b2; // not the current instantiation typedef T1 my_T1; static const int my_I = I; static const int my_I2 = I+0; static const int my_I3 = my_I; B<my_T1, T2, my_I>* b3; // refers to the current instantiation B<my_T1, T2, my_I2>* b4; // not the current instantiation B<my_T1, T2, my_I3>* b5; // refers to the current instantiation };—end example]
A name is a member of the current instantiation if it is
- An unqualified name that, when looked up, refers to a member of a class template. [Note: This can only occur when looking up a name in a scope enclosed by the definition of a class template.]
- A qualified-id in which the nested-name-specifier refers to the current instantiation.
[Example:
template <class T> class A { static const int i = 5; int n1[i]; // i refers to a member of the current instantiation int n2[A::i]; // A::i refers to a member of the current instantiation int n3[A<T>::i]; // A<T>::i refers to a member of the current instantiation int f(); }; template <class T> int A<T>::f() { return i; // i refers to a member of the current instantiation }—end example]
A name is a member of an unknown specialization if the name is a qualified-id in which the nested-name-specifier names a dependent type that is not the current instantiation.
A type is dependent if it is
- a template parameter,
- a member of an unknown specialization,
- a nested class that is a member of the current instantiation,
- a cv-qualified type where the cv-unqualified type is dependent,
- a compound type constructed from any dependent type,
- an array type constructed from any dependent type or whose size is specified by a constant expression that is value-dependent, or
- a template-id in which either the template name is a template parameter or any of the template arguments is a dependent type or an expression that is type-dependent or value-dependent.
[Note: Because typedefs to not introduce new types, but instead simply refer to other types, a name that refers to a typedef that is a member of the current instantiation is dependent only if the type referred to is dependent.]
In 14.6.2.2 temp.dep.expr paragraph 3, replace
- a nested-name-specifier that contains a class-name that names a dependent type.
with
- a nested-name-specifier or qualified-id that names a member of an unknown specialization.
In 14.6.2.2 temp.dep.expr, add the following paragraph:
A class member access expression (5.2.5 expr.ref) is type-dependent if the type of the referenced member is dependent. [Note: In an expression of the form x.y or xp->y the type of the expression is usually the type of the member y of the class of x (or the class pointed to by xp). However, if x or xp refers to a dependent type that is not the current instantiation, the type of y is always dependent. If x or xp refers to a non-dependent type or refers to the current instantiation, the type of y is the type of the class member access expression.]
In 14.6 temp.res paragraph 3, replace
A qualified-name that refers to a type and that depends on a template-parameter (14.6.2 temp.dep) shall be prefixed by the keyword typename.
with
A qualified-id that refers to a type and that depends on a template-parameter (14.6.2 temp.dep) but does not refer to a member of the current instantiation shall be prefixed by the keyword typename.
In 14.2 temp.names paragraph 4, replace
When the name of a member template specialization appears after . or -> in a postfix-expression, or after a nested-name-specifier in a qualified-id, and the postfix-expression or qualified-id explicitly depends on a template-parameter (14.6.2 temp.dep), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.
with
When the name of a member template specialization appears after . or -> in a postfix-expression, or after a nested-name-specifier in a qualified-id, and the postfix-expression or qualified-id explicitly depends on a template-parameter (14.6.2 temp.dep) but does not refer to a member of the current instantiation (14.6.2.1 temp.dep.type), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.
In 14.6.1 temp.local paragraph 2, remove the following text, which was added for issue 108. The updated definition of dependent name now addresses this case.
Within the scope of a class template, when the unqualified name of a nested class of the class template is referred to, it is equivalent to the name of the nested class qualified by the name of the enclosing class template. [Example:
template <class T> struct A { class B {}; // B is equivalent to A::B, which is equivalent to A<T>::B, // which is dependent. class C : B { }; };—end example]
Paragraphs 1 and 2 of 3.4.2 basic.lookup.koenig say, in part,
When an unqualified name is used as the postfix-expression in a function call (5.2.2 expr.call )... namespace-scope friend function declarations (11.4 class.friend ) not otherwise visible may be found... the set of declarations found by the lookup of the function name [includes] the set of declarations found in the... classes associated with the argument types.The most straightforward reading of this wording is that if a function of namespace scope (as opposed to a class member function) is declared as a friend in a class, and that class is an associated class in a function call, the friend function will be part of the overload set, even if it is not visible to normal lookup.
Consider the following example:
namespace A { class S; }; namespace B { void f(A::S); }; namespace A { class S { int i; friend void B::f(S); }; } void g() { A::S s; f(s); // should find B::f(A::S) }This example would seem to satisfy the criteria from 3.4.2 basic.lookup.koenig : A::S is an associated class of the argument, and A::S has a friend declaration of the namespace-scope function B::f(A::S), so Koenig lookup should include B::f(A::S) as part of the overload set in the call.
Another interpretation is that, instead of finding the friend declarations in associated classes, one only looks for namespace-scope functions, visible or invisible, in the namespaces of which the the associated classes are members; the only use of the friend declarations in the associated classes is to validate whether an invisible function declaration came from an associated class or not and thus whether it should be included in the overload set or not. By this interpretation, the call f(s) in the example will fail, because B::f(A::S) is not a member of namespace A and thus is not found by the lookup.
Proposed Resolution (10/99): The second interpretation is correct. The wording should be revised to make clear that Koenig lookup works by finding "invisible" declarations in namespace scope and not by finding friend declarations in associated classes.
(See also issues 95, 136, 138, 139, 165, 166, and 218.)
I have some concerns with the description of name lookup for elaborated type specifiers in 3.4.4 basic.lookup.elab:
Paragraph 2 has some parodoxical statements concerning looking up names that are simple identifers:
If the elaborated-type-specifier refers to an enum-name and this lookup does not find a previously declared enum-name, the elaborated-type-specifier is ill-formed. If the elaborated-type-specifier refers to an [sic] class-name and this lookup does not find a previously declared class-name... the elaborated-type-specifier is a declaration that introduces the class-name as described in 3.3.1 basic.scope.pdecl."
It is not clear how an elaborated-type-specifier can refer to an enum-name or class-name given that the lookup does not find such a name and that class-name and enum-name are not part of the syntax of an elaborated-type-specifier.
The second sentence quoted above seems to suggest that the name found will not be used if it is not a class name. typedef-name names are ill-formed due to the sentence preceding the quote. If lookup finds, for instance, an enum-name then a new declaration will be created. This differs from C, and from the enum case, and can have surprising effects:
struct S { enum E { one = 1 }; class E* p; // declares a global class E? };
Was this really the intent? If this is the case then some more work is needed on 3.4.4 basic.lookup.elab. Note that the section does not make finding a type template formal ill-formed, as is done in 7.1.5.3 dcl.type.elab. I don't see anything that makes a type template formal name a class-name. So the example in 7.1.5.3 dcl.type.elab of friend class T; where T is a template type formal would no longer be ill-formed with this interpretation because it would declare a new class T.
(See also issue 254.)
Proposed resolution (10/00):
Change the referenced sentences of 3.4.4 basic.lookup.elab paragraph 2 to read,
If the elaborated-type-specifier is introduced by the enum keyword and this lookup does not find a previously declared enum-name, the elaborated-type-specifier is ill-formed. If the elaborated-type-specifier is introduced by a class-key and this lookup does not find a previously declared class-name, ...
Regarding the second point, the intent is not to provide for overloading class and non-class types in a single scope; however, wording is not yet available to clarify this point.
Paragraph 7 of 3.4.5 basic.lookup.classref says,
If the id-expression is a conversion-function-id, its conversion-type-id shall denote the same type in both the context in which the entire postfix-expression occurs and in the context of the class of the object expression (or the class pointed to by the pointer expression).Does this mean that the following example is ill-formed?
struct A { operator int(); } a; void foo() { typedef int T; a.operator T(); // 1) error T is not found in the context // of the class of the object expression? }The second bullet in paragraph 1 of 3.4.3.1 class.qual says,
a conversion-type-id of an operator-function-id is looked up both in the scope of the class and in the context in which the entire postfix-expression occurs and shall refer to the same type in both contextsHow about:
struct A { typedef int T; operator T(); }; struct B : A { operator T(); } b; void foo() { b.A::operator T(); // 2) error T is not found in the context // of the postfix-expression? }Is this interpretation correct? Or was the intent for this to be an error only if T was found in both scopes and referred to different entities?
If the intent was for these to be errors, how do these rules apply to template arguments?
template <class T1> struct A { operator T1(); } template <class T2> struct B : A<T2> { operator T2(); void foo() { T2 a = A<T2>::operator T2(); // 3) error? when instantiated T2 is not // found in the scope of the class T2 b = ((A<T2>*)this)->operator T2(); // 4) error when instantiated? } }
(Note bullets 2 and 3 in paragraph 1 of 3.4.3.1 class.qual refer to postfix-expression. It would be better to use qualified-id in both cases.)
Erwin Unruh: The intent was that you look in both contexts. If you find it only once, that's the symbol. If you find it in both, both symbols must be "the same" in some respect. (If you don't find it, its an error).
Mike Miller: What's not clear to me in these examples is whether what is being looked up is T or int. Clearly the T has to be looked up somehow, but the "name" of a conversion function clearly involves the base (non-typedefed) type, not typedefs that might be used in a definition or reference (cf 3 basic paragraph 7 and 12.3 class.conv paragraph 5). (This is true even for types that must be written using typedefs because of the limited syntax in conversion-type-ids — e.g., the "name" of the conversion function in the following example
typedef void (*pf)(); struct S { operator pf(); };is S::operator void(*)(), even though you can't write its name directly.)
My guess is that this means that in each scope you look up the type named in the reference and form the canonical operator name; if the name used in the reference isn't found in one or the other scope, the canonical name constructed from the other scope is used. These names must be identical, and the conversion-type-id in the canonical operator name must not denote different types in the two scopes (i.e., the type might not be found in one or the other scope, but if it's found in both, they must be the same type).
I think this is all very vague in the current wording.
Jack Rouse: 3.8 basic.life paragraph 1 includes:
The lifetime of an object is a runtime property of the object. The lifetime of an object of type T begins when:Consider the code:
- storage with the proper alignment and size for type T is obtained, and
- if T is a class type with a non-trivial constructor (12.1 class.ctor ), the constructor call has completed.
struct B { B( int = 0 ); ~B(); }; struct S { B b1; }; int main() { S s = { 1 }; return 0; }In the code above, class S does have a non-trivial constructor, the default constructor generated by the compiler. According the text above, the lifetime of the auto s would never begin because a constructor for S is never called. I think the second case in the text needs to include aggregate initialization.
Mike Miller: I see a couple of ways of fixing the problem. One way would be to change "the constructor call has completed" to "the object's initialization is complete."
Another would be to add following "a class type with a non-trivial constructor" the phrase "that is not initialized with the brace notation (8.5.1 dcl.init.aggr )."
The first formulation treats aggregate initialization like a constructor call; even POD-type members of an aggregate could not be accessed before the aggregate initialization completed. The second is less restrictive; the POD-type members of the aggregate would be usable before the initialization, and the members with non-trivial constructors (the only way an aggregate can acquire a non-trivial constructor) would be protected by recursive application of the lifetime rule.
Proposed resolution (10/00): The first suggestion ("the object's initialization is complete") should be adopted.
Martin O'Riordan: Having gone through all the relevant references in the IS, it is not conclusive that a call via a pointer to a virtual member function is polymorphic at all, and could legitimately be interpreted as being static.
Consider 5.2.2 expr.call paragraph 1:
The function called in a member function call is normally selected according to the static type of the object expression (clause 10 class.derived ), but if that function is virtual and is not specified using a qualified-id then the function actually called will be the final overrider (10.3 class.virtual ) of the selected function in the dynamic type of the object expression.Here it is quite specific that you get the polymorphic call only if you use the unqualified syntax. But, the address of a member function is "always" taken using the qualified syntax, which by inference would indicate that call with a PMF is static and not polymorphic! Not what was intended.
Yet other references such as 5.5 expr.mptr.oper paragraph 4:
If the dynamic type of the object does not contain the member to which the pointer refers, the behavior is undefined.indicate that the opposite may have been intended, by stating that it is the dynamic type and not the static type that matters. Also, 5.5 expr.mptr.oper paragraph 6:
If the result of .* or ->* is a function, then that result can be used only as the operand for the function call operator (). [Example:which also implies that it is the object pointed to that determines both the validity of the expression (the static type of 'ptr_to_obj' may not have a compatible function) and the implicit (polymorphic) meaning. Note too, that this is stated in the non-normative example text.(ptr_to_obj->*ptr_to_mfct)(10);calls the member function denoted by ptr_to_mfct for the object pointed to by ptr_to_obj. ]
Andy Sawyer: Assuming the resolution is what I've assumed it is for the last umpteen years (i.e. it does the polymorphic thing), then the follow on to that is "Should there also be a way of selecting the non-polymorphic behaviour"?
Mike Miller: It might be argued that the current wording of 5.2.2 expr.call paragraph 1 does give polymorphic behavior to simple calls via pointers to members. (There is no qualified-id in obj.*pmf, and the IS says that if the function is not specified using a qualified-id, the final overrider will be called.) However, it clearly says the wrong thing when the pointer-to-member itself is specified using a qualified-id (obj.*X::pmf).
Bill Gibbons: The phrase qualified-id in 5.2.2 expr.call paragraph 1 refers to the id-expression and not to the "pointer-to-member expression" earlier in the paragraph:
For a member function call, the postfix expression shall be an implicit (9.3.1 class.mfct.nonstatic , 9.4 class.static ) or explicit class member access (5.2.5 expr.ref ) whose id-expression is a function member name, or a pointer-to-member expression (5.5 expr.mptr.oper ) selecting a function member.
Mike Miller: To be clear, here's an example:
struct S { virtual void f(); }; void (S::*pmf)(); void g(S* sp) { sp->f(); // 1: polymorphic sp->S::f(); // 2: non-polymorphic (sp->S::f)(); // 3: non-polymorphic (sp->*pmf)(); // 4: polymorphic (sp->*&S::f)(); // 5: polymorphic }
Consider the following:
extern "C" void foo() { extern void bar(); bar(); }Does "bar()" have "C" language linkage?
The ARM is explicit and says
A linkage-specification for a function also applies to functions and objects declared within it.The DIS says
In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names, and variable names introduced by the declaration(s).Is the body of a function definition part of the declaration?
From Mike Miller:
Yes: from 7 dcl.dcl paragraph 1,
From Dag Brück:
Consider the following where extern "C" has been moved to a separate declaration:
extern "C" void foo(); void foo() { extern void bar(); bar(); }I think the ARM wording could possibly be interpreted such that bar() has "C" linkage in my example, but not the DIS wording.
As a side note, I have always wanted to think that placing extern "C" on a function definition or a separate declaration would produce identical programs.
Proposed Resolution:
See the proposed resolution for Core issue 4, which covers this case.
The ODR should also be checked to see whether it addresses name and type linkage.
Steve Clamage: Section 8.3.4 dcl.array paragraph 1 reads in part as follows:
Any type of the form "cv-qualifier-seq array of N T" is adjusted to "array of N cv-qualifier-seq T," and similarly for "array of unknown bound of T." [Example:The Note appears to contradict the sentence that precedes it.typedef int A[5], AA[2][3]; typedef const A CA; // type is "array of 5 const int" typedef const AA CAA; // type is "array of 2 array of 3 const int"—end example] [Note: an "array of N cv-qualifier-seq T" has cv-qualified type; such an array has internal linkage unless explicitly declared extern (7.1.5.1 dcl.type.cv ) and must be initialized as specified in 8.5 dcl.init . ]
Mike Miller: I disagree; all it says is that whether the qualification on the element type is direct ("const int x[5]") or indirect ("const A CA"), the array itself is qualified in the same way the elements are.
Steve Clamage: In addition, section 3.9.3 basic.type.qualifier paragraph 2 says:
A compound type (3.9.2 basic.compound ) is not cv-qualified by the cv-qualifiers (if any) of the types from which it is compounded. Any cv-qualifiers applied to an array type affect the array element type, not the array type (8.3.4 dcl.array )."The Note appears to contradict that section as well.
Mike Miller: Yes, but consider the last two sentences of 3.9.3 basic.type.qualifier paragraph 5:
Cv-qualifiers applied to an array type attach to the underlying element type, so the notation "cv T," where T is an array type, refers to an array whose elements are so-qualified. Such array types can be said to be more (or less) cv-qualified than other types based on the cv-qualification of the underlying element types.I think this says essentially the same thing as 8.3.4 dcl.array paragraph 1 and its note: the qualification of an array is (bidirectionally) equivalent to the qualification of its members.
Mike Ball: I find this a very far reach. The text in 8.3.4 dcl.array is essentially that which is in the C standard (and is a change from early versions of C++). I don't see any justification at all for the bidirectional equivalence. It seems to me that the note is left over from the earlier version of the language.
Steve Clamage: Finally, the Note seems to say that the declaration
volatile char greet[6] = "Hello";gives "greet" internal linkage, which makes no sense.
Have I missed something, or should that Note be entirely removed?
Mike Miller: At least the wording in the note should be repaired not to indicate that volatile-qualification gives an array internal linkage. Also, depending on how the discussion goes, either the wording in 3.9.3 basic.type.qualifier paragraph 2 or in paragraph 5 needs to be amended to be consistent regarding whether an array type is considered qualified by the qualification of its element type.
Steve Adamczyk pointed out that the current state of affairs resulted from the need to handle reference binding consistently. The wording is intended to define the question, "Is an array type cv-qualified?" as being equivalent to the question, "Is the element type of the array cv-qualified?"
Proposed resolution (10/00):
Delete the portion of the note in 8.3.4 dcl.array paragraph 1 beginning with the words, "such an array has internal linkage." The first part of the note is accurate and reflects the approach given in 3.9.3 basic.type.qualifier paragraph 5.
9.8 class.local paragraph 1 says,
Declarations in a local class can use only type names, static variables, extern variables and functions, and enumerators from the enclosing scope.The definition of when an object or function is "used" is found in 3.2 basic.def.odr paragraph 2 and essentially says that the operands of sizeof and non-polymorphic typeid operators are not used. (The resolution for issue 48 will add contexts in which integral constant expressions are required to the list of non-uses.)
This definition of "use" would presumably allow code like
void foo() { int i; struct S { int a[sizeof(i)]; }; };which is required for C compatibility.
However, the restrictions on nested classes in 9.7 class.nest paragraph 1 are very similar to those for local classes, and the example there explicitly states that a reference in a sizeof expression is a forbidden use (abbreviated for exposition):
class enclose { public: int x; class inner { void f(int i) { int a = sizeof(x); // error: refers to enclose::x } }; };
[As a personal note, I have seen real-world code that was exactly like this; it was hard to persuade the author that the required writearound, sizeof(((enclose*) 0)->x), was an improvement over sizeof(x). —wmm]
Similarly, 9.2 class.mem paragraph 9 would appear to prohibit examples like the following:
struct B { char x[10]; }; struct D: B { char y[sizeof(x)]; };
Suggested resolution: Add cross-references to 3.2 basic.def.odr following the word "use" in both 9.7 class.nest and 9.8 class.local , and change the example in 9.7 class.nest to indicate that a reference in a sizeof expression is permitted. In 9.2 class.mem paragraph 9, "referred to" should be changed to "used" with a cross_reference to 3.2 basic.def.odr.
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.
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.
Does dropping a cv-qualifier on a reference binding prevent the binding as far as overload resolution is concerned? Paragraph 4 says "Other restrictions on binding a reference to a particular argument do not affect the formation of a conversion sequence." This was intended to refer to things like access checking, but some readers have taken that to mean that any aspects of reference binding not mentioned in this section do not preclude the binding.
The example in 14.6 temp.res paragraph 9 is incorrect, according to 14.6.4.2 temp.dep.candidate . The example reads,
void f(char); template <class T> void g(T t) { f(1); // f(char); f(T(1)); // dependent f(t); // dependent dd++; // not dependent // error: declaration for dd not found } void f(int); double dd; void h() { g(2); // will cause one call of f(char) followed // by two calls of f(int) g('a'); // will cause three calls of f(char) }Since 14.6.4.2 temp.dep.candidate says that only Koenig lookup is done from the instantiation context, and since 3.4.2 basic.lookup.koenig says that fundamental types have no associated namespaces, either the example is incorrect (and f(int) will never be called) or the specification in 14.6.4.2 temp.dep.candidate is incorrect.
Notes from 04/00 meeting:
The core working group agreed that the example as written is incorrect and should be reformulated to use a class type instead of a fundamental type. It was also decided to open a new issue dealing more generally with Koenig lookup and fundamental types.
(See also issues 213 and 225.)
Does the Standard require that an uninitialized auto variable have a stable (albeit indeterminate) value? That is, does the Standard require that the following function return true?
bool f() { unsigned char i; // not initialized unsigned char j = i; unsigned char k = i; return j == k; // true iff "i" is stable }3.9.1 basic.fundamental paragraph 1 requires that uninitialized unsigned char variables have a valid value, so the initializations of j and k are well-formed and required not to trap. The question here is whether the value of i is allowed to change between those initializations.
Mike Miller: 1.9 intro.execution paragraph 10 says,
An instance of each object with automatic storage duration (3.7.2 basic.stc.auto ) is associated with each entry into its block. Such an object exists and retains its last-stored value during the execution of the block and while the block is suspended...I think that the most reasonable way to read this is that the only thing that is allowed to change the value of an automatic (non-volatile?) value is a "store" operation in the abstract machine. There are no "store" operations to i between the initializations of j and k, so it must retain its original (indeterminate but valid) value, and the result of the program is well-defined.
The quibble, of course, is whether the wording "last-stored value" should be applied to a "never-stored" value. I think so, but others might differ.
Tom Plum: 7.1.5.1 dcl.type.cv paragraph 8 says,
[Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. See 1.9 intro.execution for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. ]From this I would infer that non-volatile means "shall not be changed by means undetectable by an implementation"; that the compiler is entitled to safely cache accesses to non-volatile objects if it can prove that no "detectable" means can modify them; and that therefore i shall maintain the same value during the example above.
Nathan Myers: This also has practical code-generation consequences. If the uninitialized auto variable lives in a register, and its value is really unspecified, then until it is initialized that register can be used as a temporary. Each time it's "looked at" the variable has the value that last washed up in that register. After it's initialized it's "live" and cannot be used as a temporary any more, and your register pressure goes up a notch. Fixing the uninit'd value would make it "live" the first time it is (or might be) looked at, instead.
Mike Ball: I agree with this. I also believe that it was certainly never my intent that an uninitialized variable be stable, and I would have strongly argued against such a provision. Nathan has well stated the case. And I am quite certain that it would be disastrous for optimizers. To ensure it, the frontend would have to generate an initializer, because optimizers track not only the lifetimes of variables, but the lifetimes of values assigned to those variables. This would put C++ at a significant performance disadvantage compared to other languages. Not even Java went this route. Guaranteeing defined behavior for a very special case of a generally undefined operation seems unnecessary.
The nonterminals operator and punctuator in 2.6 lex.token are not defined. There is a definition of the nonterminal operator in 13.5 over.oper paragraph 1, but it is apparent that the two nonterminals are not the same: the latter includes keywords and multi-token operators and does not include the nonoverloadable operators mentioned in paragraph 3.
There is a definition of preprocessing-op-or-punc in 2.12 lex.operators , with the notation that
Each preprocessing-op-or-punc is converted to a single token in translation phase 7 (2.1).However, this list doesn't distinguish between operators and punctuators, it includes digraphs and keywords (can a given token be both a keyword and an operator at the same time?), etc.
Suggested resolution:
3.2 basic.def.odr paragraph 2 says that a deallocation function is "used" by a new-expression or delete-expression appearing in a potentially-evaluated expression. 3.2 basic.def.odr paragraph 3 requires only that "used" functions be defined.
This wording runs afoul of the typical implementation technique for polymorphic delete-expressions in which the deallocation function is invoked from the virtual destructor of the most-derived class. The problem is that the destructor must be defined, because it's virtual, and if it contains an implicit reference to the deallocation function, the deallocation function must also be defined, even if there are no relevant new-expressions or delete-expressions in the program.
For example:
struct B { virtual ~B() { } }; struct D: B { void operator delete(void*); ~D() { } };
Is it required that D::operator delete(void*) be defined, even if no B or D objects are ever created or deleted?
Suggested resolution: Add the words "or if it is found by the lookup at the point of definition of a virtual destructor (12.4 class.dtor)" to the specification in 3.2 basic.def.odr paragraph 2.
The current description of unqualified name lookup in 3.4.1 basic.lookup.unqual paragraph 8 does not correctly handle complex cases of nesting. The Standard currently reads,
A name used in the definition of a function that is a member function (9.3) of a class X shall be declared in one of the following ways:In particular, this formulation does not handle the following example:
- before its use in the block in which it is used or in an enclosing block (6.3), or
- shall be a member of class X or be a member of a base class of X (10.2), or
- if X is a nested class of class Y (9.7), shall be a member of Y, or shall be a member of a base class of Y (this lookup applies in turn to Y's enclosing classes, starting with the innermost enclosing class), or
- if X is a local class (9.8) or is a nested class of a local class, before the definition of class X in a block enclosing the definition of class X, or
- if X is a member of namespace N, or is a nested class of a class that is a member of N, or is a local class or nested class within a local class of a function that is a member of N, before the member function definition, in namespace N or in one of N's enclosing namespaces.
struct outer { static int i; struct inner { void f() { struct local { void g() { i = 5; } }; } }; };Here the reference to i is from a member function of a local class of a member function of a nested class. Nothing in the rules allows outer::i to be found, although intuitively it should be found.
A more comprehensive formulation is needed that allows traversal of any combination of blocks, local classes, and nested classes. Similarly, the final bullet needs to be augmented so that a function need not be a (direct) member of a namespace to allow searching that namespace when the reference is from a member function of a class local to that function. That is, the current rules do not allow the following example:
int j; // global namespace struct S { void f() { struct local2 { void g() { j = 5; } }; } };
The description of name lookup in the parameter-declaration-clause of member functions in 3.4.1 basic.lookup.unqual paragraphs 7-8 is flawed in at least two regards.
First, both paragraphs 7 and 8 apply to the parameter-declaration-clause of a member function definition and give different rules for the lookup. Paragraph 7 applies to names "used in the definition of a class X outside of a member function body...," which includes the parameter-declaration-clause of a member function definition, while paragraph 8 applies to names following the function's declarator-id (see the proposed resolution of issue 41), including the parameter-declaration-clause.
Second, paragraph 8 appears to apply to the type names used in the parameter-declaration-clause of a member function defined inside the class definition. That is, it appears to allow the following code, which was not the intent of the Committee:
struct S { void f(I i) { } typedef int I; };
The wording of 3.4.1 basic.lookup.unqual paragraph 2 is misleading. It says:
The declarations from the namespace nominated by a using-directive become visible in a namespace enclosing the using-directive; see 7.3.4 namespace.udir.
According to 7.3.4 namespace.udir paragraph 1, that namespace is
the nearest enclosing namespace which contains both the using-directive and the nominated namespace.
That would seem to imply the following:
namespace outer { namespace inner { int i; } void f() { using namespace inner; } int j = i; // inner::i is "visible" in namespace outer }
Suggested resolution: Change the first sentence of 3.4.1 basic.lookup.unqual paragraph 2 to read:
The declarations from the namespace nominated by a using-directive become visible in the scope in which the using-directive appears after the using-directive.
The original intent of the Committee when Koenig lookup was added to the language was apparently something like the following:
This approach is not reflected in the current wording of the Standard. Instead, the following appears to be the status quo:
John Spicer: Argument-dependent lookup was created to solve the problem of looking up function names within templates where you don't know which namespace to use because it may depend on the template argument types (and was then expanded to permit use in nontemplates). The original intent only concerned functions. The safest and simplest change is to simply clarify the existing wording to that effect.
Bill Gibbons: I see no reason why non-function declarations should not be found. It would take a special rule to exclude "function objects", as well as pointers to functions, from consideration. There is no such rule in the standard and I see no need for one.
There is also a problem with the wording in 3.4.2 basic.lookup.koenig paragraph 2:
If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered.
This implies that if the ordinary lookup of the name finds the declaration of a data member which is a pointer to function or function object, argument-dependent lookup is still done.
My guess is that this is a mistake based on the incorrect assumption that finding any member other than a member function would be an error. I would just change "class member function" to "class member" in the quoted sentence.
Mike Miller: In light of the issue of "short-circuiting" Koenig lookup when normal lookup finds a non-function, perhaps it should be written as "...finds the declaration of a class member, an object, or a reference, the associated namespaces..."?
Andy Koenig: I think I have to weigh in on the side of extending argument-dependent lookup to include function objects and pointers to functions. I am particularly concerned about [function objects], because I think that programmers should be able to replace functions by function objects without changing the behavior of their programs in fundamental ways.
Bjarne Stroustrup: I don't think we could seriously argue from first principles that [argument-dependent lookup should find only function declarations]. In general, C++ name lookup is designed to be independent of type: First we find the name(s), then, we consider its(their) meaning. 3.4 basic.lookup states "The name lookup rules apply uniformly to all names ..." That is an important principle.
Thus, I consider text that speaks of "function call" instead of plain "call" or "application of ()" in the context of koenig lookup an accident of history. I find it hard to understand how 5.2.2 expr.call doesn't either disallow all occurrences of x(y) where x is a class object (that's clearly not intended) or requires koenig lookup for x independently of its type (by reference from 3.4 basic.lookup). I suspect that a clarification of 5.2.2 expr.call to mention function objects is in order. If the left-hand operand of () is a name, it should be looked up using koenig lookup.
John Spicer: This approach causes otherwise well-formed programs to be ill-formed, and it does so by making names visible that might be completely unknown to the author of the program. Using-directives already do this, but argument-dependent lookup is different. You only get names from using-directives if you actually use using-directives. You get names from argument-dependent lookup whether you want them or not.
This basically breaks an important reason for having namespaces. You are not supposed to need any knowledge of the names used by a namespace.
But this example breaks if argument-dependent lookup finds non-functions and if the translation unit includes the <list> header somewhere.
namespace my_ns { struct A {}; void list(std::ostream&, A&); void f() { my_ns::A a; list(cout, a); } }
This really makes namespaces of questionable value if you still need to avoid using the same name as an entity in another namespace to avoid problems like this.
Erwin Unruh: Before we really decide on this topic, we should have more analysis on the impact on programs. I would also like to see a paper on the possibility to overload functions with function surrogates (no, I won't write one). Since such an extension is bound to wait until the next official update, we should not preclude any outcome of the discussion.
I would like to have a change right now, which leaves open several outcomes later. I would like to say that:
Koenig lookup will find non-functions as well. If it finds a variable, the program is ill-formed. If the primary lookup finds a variable, Koenig lookup is done. If the result contains both functions and variables, the program is ill-formed. [Note: A future standard will assign semantics to such a program.]
I myself are not comfortable with this as a long-time result, but it prepares the ground for any of the following long term solutions:
The note is there to prevent compiler vendors to put their own extensions in here.
(See also issues 113 and 143.)
Notes from 04/00 meeting:
Although many agreed that there were valid concerns motivating a desire for Koenig lookup to find non-function declarations, there was also concern that supporting this capability would be more dangerous than helpful in the absence of overload resolution for mixed function and non-function declarations.
A straw poll of the group revealed 8 in favor of Koenig lookup finding functions and function templates only, while 3 supported the broader result.
In discussing issue 197, the question arose as to whether the handling of fundamental types in argument-dependent lookup is actually what is desired. This question needs further discussion.
The text in 3.4.4 basic.lookup.elab paragraph 2 twice refers to the possibility that an elaborated-type-specifier might have the form
class-key identifier ;
However, the grammar for elaborated-type-specifier does not include a semicolon.
In both 3.4.4 basic.lookup.elab and 7.1.5.3 dcl.type.elab, the text asserts that an elaborated-type-specifier that refers to a typedef-name is ill-formed. However, it is permissible for the form of elaborated-type-specifier that begins with typename to refer to a typedef-name.
This problem is the result of adding the typename form to the elaborated-type-name grammar without changing the verbiage correspondingly. It could be fixed either by updating the verbiage or by moving the typename syntax into its own production and referring to both nonterminals when needed.
3.4.5 basic.lookup.classref paragraph 1 says,
In a class member access expression (5.2.5 expr.ref ), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (14.2 temp.names ) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a class or function template.There do not seem to be any circumstances in which use of a non-member template function would be well-formed as the id-expression of a class member access expression.
According to 3.2 basic.def.odr paragraph 5, it is possible for a static data member of a class template to be defined more than once in a given program provided that each such definition occurs in a different translation unit and the ODR is met.
Now consider the following example:
src1.cpp:
#include <iostream> int initializer() { static int counter; return counter++; } int g_data1 = initializer(); template<class T> struct exp { static int m_data; }; template<class T> int exp<T>::m_data = initializer(); int g_data2 = initializer(); extern int g_data3; int main() { std::cout << exp<char>::m_data << ", " << g_data1 << ", " << g_data2 << ", " << g_data3 << std::endl; return 0; }
src2.cpp:
extern int initializer(); int g_data3 = initializer(); template<class T> struct exp { static int m_data; }; template<class T> int exp<T>::m_data = initializer(); void func() { exp<char>::m_data++; }
The specialization exp<char>::m_data is implicitly instaniated in both translation units, hence (14.7.1 temp.inst paragraph 1) its initialization occurs. And for both definitions of exp<T>::m_data the ODR is met. According to 3.6.2 basic.start.init paragraph 1:
Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.
But for exp<T>::m_data we have two definitions. Does it mean that both g_data1 and g_data3 are guaranteed to be dynamically initialized before exp<char>::m_data?
Suggested Resolution: Insert the following sentence before the last two sentences of 3.2 basic.def.odr paragraph 5:
In the case of D being a static data member of a class template the following shall also hold:
- for a given (not explicit) specialization of D initialized dynamically (3.6.2 basic.start.init), the accumulated set of objects initialized dynamically in namespace scope before the specialization of D shall be the same in every translation unit that contains the definition for this specialization.
(See also issue 270.)
The Standard does not appear to address how the rules for order of initialization apply to static data members of class templates.
Suggested resolution: Add the following verbiage to either 3.6.2 basic.start.init or 9.4.2 class.static.data:
Initialization of static data members of class templates shall be performed during the initialization of static data members for the first translation unit to have static initialization performed for which the template member has been instantiated. This requirement shall apply to both the static and dynamic phases of initialization.
(See also issue 269.)
The C++ standard has inherited the definition of the 'exit' function more or less unchanged from ISO C.
However, when the 'exit' function is called, objects of static extent which have been initialised, will be destructed if their types posses a destructor.
In addition, the C++ standard has inherited the definition of the 'signal' function and its handlers from ISO C, also pretty much unchanged.
The C standard says that the only standard library functions that may be called while a signal handler is executing, are the functions 'abort', 'signal' and 'exit'.
This introduces a bit of a nasty turn, as it is not at all unusual for the destruction of static objects to have fairly complex destruction semantics, often associated with resource release. These quite commonly involve apparently simple actions such as calling 'fclose' for a FILE handle.
Having observed some very strange behaviour in a program recently which in handling a SIGTERM signal, called the 'exit' function as indicated by the C standard.
But unknown to the programmer, a library static object performed some complicated resource deallocation activities, and the program crashed.
The C++ standard says nothing about the interaction between signals, exit and static objects. My observations, was that in effect, because the destructor called a standard library function other than 'abort', 'exit' or 'signal', while transitively in the execution context of the signal handler, it was in fact non-compliant, and the behaviour was undefined anyway.
This is I believe a plausible judgement, but given the prevalence of this common programming technique, it seems to me that we need to say something a lot more positive about this interaction.
Curiously enough, the C standard fails to say anything about the analogous interaction with functions registered with 'atexit' ;-)
Proposed Resolution (10/98):
The current Committee Draft of the next version of the ISO C standard specifies that the only standard library function that may be called while a signal handler is executing is 'abort'. This would solve the above problem.
[This issue should remain open until it has been decided that the next version of the C++ standard will use the next version of the C standard as the basis for the behavior of 'signal'.]
3.8 basic.life and 12.4 class.dtor discuss explicit management of object lifetime. It seems clear that most object lifetime issues apply to sub-objects (array elements, and data members) as well. The standard supports
struct X { T t } x; T* pt = &x.t; pt->~T(); new(pt) T;
and this kind of behavior is useful in allocators.
However the standard does not seem to prohibit the same operations on base sub-objects.
struct D: B{ ... } d; B* pb = &d; pb->~B(); new(pb) B;
However if B and/or D have virtual member functions or virtual bases, it is unlikely that this code will result in a well-formed D object in current implementations (note that the various lines may be in different functions).
Suggested resolution: 12.4 class.dtor should be modified so that explicit destruction of base-class sub-objects be made illegal, or legal only under some restrictive conditions.
The wording in 3.8 basic.life paragraph 6 allows an lvalue designating an out-of-lifetime object to be used as the operand of a static_cast only if the conversion is "ultimately to char& or unsigned char&." This description excludes the possibility of using a cv-qualified version of these types for no apparent reason.
3.9.1 basic.fundamental does not impose a requirement on the floating point types that there be an exact representation of the value zero. This omission is significant in 4.12 conv.bool paragraph 1, in which any non-zero value converts to the bool value true.
Suggested resolution: require that all floating point types have an exact representation of the value zero.
3.9.1 basic.fundamental paragraph 2 says that
There are four signed integer types: "signed char", "short int", "int", and "long int."
This would indicate that const int is not a signed integer type.
3.10 basic.lval paragraph 15 lists the types via which an lvalue can be used to access the stored value of an object; using an lvalue type that is not listed results in undefined behavior. It is permitted to add cv-qualification to the actual type of the object in this access, but only at the top level of the type ("a cv-qualified version of the dynamic type of the object").
However, 4.4 conv.qual paragraph 4 permits a "conversion [to] add cv-qualifiers at levels other than the first in multi-level pointers." The combination of these two rules allows creation of pointers that cannot be dereferenced without causing undefined behavior. For instance:
int* jp; const int * const * p1 = &jp; *p1; // undefined behavior!
The reason that *p1 results in undefined behavior is that the type of the lvalue is const int * const", which is not "a cv-qualified version of" int*.
Since the conversion is permitted, we must give it defined semantics, hence we need to fix the wording in 3.10 basic.lval to include all possible conversions of the type via 4.4 conv.qual.
If the object to which the lvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior.
I think there are at least three related issues around this specification:
Presumably assigning a valid value to an uninitialized object allows it to participate in the lvalue-to-rvalue conversion without undefined behavior (otherwise the number of programs with defined behavior would be vanishingly small :-). However, the wording here just says "uninitialized" and doesn't mention assignment.
There's no exception made for unsigned char types. The wording in 3.9.1 basic.fundamental was carefully crafted to allow use of unsigned char to access uninitialized data so that memcpy and such could be written in C++ without undefined behavior, but this statement undermines that intent.
It's possible to get an uninitialized rvalue without invoking the lvalue-to-rvalue conversion. For instance:
struct A { int i; A() { } // no init of A::i }; int j = A().i; // uninitialized rvalue
There doesn't appear to be anything in the current IS wording that says that this is undefined behavior. My guess is that we thought that in placing the restriction on use of uninitialized objects in the lvalue-to-rvalue conversion we were catching all possible cases, but we missed this one.
In light of the above, I think the discussion of uninitialized objects ought to be removed from 4.1 conv.lval paragraph 1. Instead, something like the following ought to be added to 3.9 basic.types paragraph 4 (which is where the concept of "value" is introduced):
Any use of an indeterminate value (5.3.4 expr.new, 8.5 dcl.init, 12.6.2 class.base.init) of any type other than char or unsigned char results in undefined behavior.
John Max Skaller:
A().i had better be an lvalue; the rules are wrong. Accessing a member of a structure requires it be converted to an lvalue, the above calculation is 'as if':
struct A { int i; A *get() { return this; } }; int j = (*A().get()).i;
and you can see the bracketed expression is an lvalue.
A consequence is:
int &j= A().i; // OK, even if the temporary evaporates
j now refers to a 'destroyed' value. Any use of j is an error. But the binding at the time is valid.
The descriptions of explicit (5.2.9 expr.static.cast paragraph 9) and implicit (4.11 conv.mem paragraph 2) pointer-to-member conversions differ in two significant ways:
(This situation cannot arise in an implicit pointer-to-member conversion where the source value is something like &X::f, since you can only implicitly convert from pointer-to-base-member to pointer-to-derived-member. However, if the source value is the result of an explicit "up-cast," the target type of the conversion might still not contain the member referred to by the source value.)
I believe that the committee has neglected to take into account one of the differences between C and C++ when defining sequence points. As an example, consider
(a += b) += c;
where a, b, and c all have type int. I believe that this expression has undefined behavior, even though it is well-formed. It is not well-formed in C, because += returns an rvalue there. The reason for the undefined behavior is that it modifies the value of `a' twice between sequence points.
Expressions such as this one are sometimes genuinely useful. Of course, we could write this particular example as
a += b; a += c;
but what about
void scale(double* p, int n, double x, double y) { for (int i = 0; i < n; ++i) { (p[i] *= x) += y; } }
All of the potential rewrites involve multiply-evaluating p[i] or unobvious circumlocations like creating references to the array element.
One way to deal with this issue would be to include built-in operators in the rule that puts a sequence point between evaluating a function's arguments and evaluating the function itself. However, that might be overkill: I see no reason to require that in
x[i++] = y;
the contents of `i' must be incremented before the assignment.
A less stringent alternative might be to say that when a built-in operator yields an lvalue, the implementation shall not subsequently change the value of that object as a consequence of that operator.
I find it hard to imagine an implementation that does not do this already. Am I wrong? Is there any implementation out there that does not `do the right thing' already for (a += b) += c?
5.17 expr.ass paragraph 1 says,
The result of the assignment operation is the value stored in the left operand after the assignment has taken place; the result is an lvalue.
What is the normative effect of the words "after the assignment has taken place"? I think that phrase ought to mean that in addition to whatever constraints the rules about sequence points might impose on the implementation, assignment operators on built-in types have the additional constraint that they must store the left-hand side's new value before returning a reference to that object as their result.
One could argue that as the C++ standard currently stands, the effect of x = y = 0; is undefined. The reason is that it both fetches and stores the value of y, and does not fetch the value of y in order to compute its new value.
I'm suggesting that the phrase "after the assignment has taken place" should be read as constraining the implementation to set y to 0 before yielding the value of y as the result of the subexpression y = 0.
Francis Glassborow:
My understanding is that for a single variable:
It is the 3) that is often ignored because in practice the compiler hardly ever codes for the read because it already has that value but in complicated evaluations with a shortage of registers, that is not always the case. Without getting too close to the hardware, I think we both know that a read too close to a write can be problematical on some hardware.
So, in x = y = 0;, the implementation must NOT fetch a value from y, instead it has to "know" what that value will be (easy because it has just computed that in order to know what it must, at some time, store in y). From this I deduce that computing the lvalue (to know where to store) and the rvalue to know what is stored are two entirely independent actions that can occur in any order commensurate with the overall requirements that both operands for an operator be evaluated before the operator is.
Erwin Unruh:
C distinguishes between the resulting value of an assignment and putting the value in store. So in C a compiler might implement the statement x=y=0; either as x=0;y=0; or as y=0;x=0; In C the statement (x += 5) += 7; is not allowed because the first += yields an rvalue which is not allowed as left operand to +=. So in C an assignment is not a sequence of write/read because the result is not really "read".
In C++ we decided to make the result of assignment an lvalue. In this case we do not have the option to specify the "value" of the result. That is just the variable itself (or its address in a different view). So in C++, strictly speaking, the statement x=y=0; must be implemented as y=0;x=y; which makes a big difference if y is declared volatile.
Furthermore, I think undefined behaviour should not be the result of a single mentioning of a variable within an expression. So the statement (x +=5) += 7; should NOT have undefined behaviour.
In my view the semantics could be:
Jerry Schwarz:
My recollection is different from Erwin's. I am confident that the intention when we decided to make assignments lvalues was not to change the semantics of evaluation of assignments. The semantics was supposed to remain the same as C's.
Ervin seems to assume that because assignments are lvalues, an assignment's value must be determined by a read of the location. But that was definitely not our intention. As he notes this has a significant impact on the semantics of assignment to a volatile variable. If Erwin's interpretation were correct we would have no way to write a volatile variable without also reading it.
Lawrence Crowl:
For x=y=0, lvalue semantics implies an lvalue to rvalue conversion on the result of y=0, which in turn implies a read. If y is volatile, lvalue semantics implies both a read and a write on y.
The standard apparently doesn't state whether there is a value dependence of the lvalue result on the completion of the assignment. Such a statement in the standard would solve the non-volatile C compatibility issue, and would be consistent with a user-implemented operator=.
Another possible approach is to state that primitive assignment operators have two results, an lvalue and a corresponding "after-store" rvalue. The rvalue result would be used when an rvalue is required, while the lvalue result would be used when an lvalue is required. However, this semantics is unsupportable for user-defined assignment operators, or at least inconsistent with all implementations that I know of. I would not enjoy trying to write such two-faced semantics.
Erwin Unruh:
The intent was for assignments to behave the same as in C. Unfortunately the change of the result to lvalue did not keep that. An "lvalue of type int" has no "int" value! So there is a difference between intent and the standard's wording.
So we have one of several choices:
I think the last one has the least impact on existing programs, but it is an ugly solution.
It is not clear what constraints are placed on a floating point implementation by the wording of the Standard. For instance, is an implementation permitted to generate a "fused multiply-add" instruction if the result would be different from what would be obtained by performing the operations separately? To what extent does the "as-if" rule allow the kinds of optimizations (e.g., loop unrolling) performed by FORTRAN compilers?
It is currently not permitted to cast directly between a pointer to function type and a pointer to object type. This conversion is not listed in 5.2.9 expr.static.cast and 5.2.10 expr.reinterpret.cast and thus requires a diagnostic to be issued. However, if a sufficiently long integral type exists (as is the case in many implementations), it is permitted to cast between pointer to function types and pointer to object types using that integral type as an intermediary.
In C the cast results in undefined behavior and thus does not require a diagnostic, and Unix C compilers generally do not issue one. This fact is used in the definition of the standard Unix function dlsym, which is declared to return void* but in fact may return either a pointer to a function or a pointer to an object. The fact that C++ compilers are required to issue a diagnostic is viewed as a "competitive disadvantage" for the language.
Suggested resolution: Add wording to 5.2.10 expr.reinterpret.cast allowing conversions between pointer to function and pointer to object types, if the implementation has an integral data type that can be used as an intermediary.
Several points were raised in opposition to this suggestion:
Martin O'Riordan suggested an alternative approach:
The advantage of this approach is that it would permit writing portable, well-defined programs involving such conversions. However, it breaks the current degree of compatibility between old and new casts, and it adds functionality to dynamic_cast which is not obviously related to its current meaning.
Notes from 04/00 meeting:
Andrew Koenig suggested yet another approach: specify that "no diagnostic is required" if the implementation supports the conversion.
5.3.1 expr.unary.op paragraph 2 indicates that the type of an address-of-member expression reflects the class in which the member was declared rather than the class identified in the nested-name-specifier of the qualified-id. This treatment is unintuitive and can lead to strange code and unexpected results. For instance, in
struct B { int i; }; struct D1: B { }; struct D2: B { }; int (D1::* pmD1) = &D2::i; // NOT an errorMore seriously, template argument deduction can give surprising results:
struct A { int i; virtual void f() = 0; }; struct B : A { int j; B() : j(5) {} virtual void f(); }; struct C : B { C() { j = 10; } }; template <class T> int DefaultValue( int (T::*m) ) { return T().*m; } ... DefaultValue( &B::i ) // Error: A is abstract ... DefaultValue( &C::j ) // returns 5, not 10.
Suggested resolution: 5.3.1 expr.unary.op should be changed to read,
If the member is a nonstatic member (perhaps by inheritance) of the class nominated by the nested-name-specifier of the qualified-id having type T, the type of the result is "pointer to member of class nested-name-specifier of type T."and the comment in the example should be changed to read,
// has type int B::*
Notes from 04/00 meeting:
The rationale for the current treatment is to permit the widest possible use to be made of a given address-of-member expression. Since a pointer-to-base-member can be implicitly converted to a pointer-to-derived-member, making the type of the expression a pointer-to-base-member allows the result to initialize or be assigned to either a pointer-to-base-member or a pointer-to-derived-member. Accepting this proposal would allow only the latter use.
Additional notes:
Another problematic example has been mentioned:
class Base { public: int func() const; }; class Derived : public Base { }; template<class T> class Templ { public: template<class S> Templ(S (T::*ptmf)() const); }; void foo() { Templ<Derived> x(&Derived::func); // ill-formed }
In this example, even though the conversion of &Derived::func to int (Derived::*)() const is permitted, the initialization of x cannot be done because template argument deduction for the constructor fails.
If the suggested resolution were adopted, the amount of code broken by the change might be reduced by adding an implicit conversion from pointer-to-derived-member to pointer-to-base-member for appropriate address-of-member expressions (not for arbitrary pointers to members, of course).
(See also issue 247.)
At least a couple of places in the IS state that indirection through a null pointer produces undefined behavior: 1.9 intro.execution paragraph 4 gives "dereferencing the null pointer" as an example of undefined behavior, and 8.3.2 dcl.ref paragraph 4 (in a note) uses this supposedly undefined behavior as justification for the nonexistence of "null references."
However, 5.3.1 expr.unary.op paragraph 1, which describes the unary "*" operator, does not say that the behavior is undefined if the operand is a null pointer, as one might expect. Furthermore, at least one passage gives dereferencing a null pointer well-defined behavior: 5.2.8 expr.typeid paragraph 2 says
If the lvalue expression is obtained by applying the unary * operator to a pointer and the pointer is a null pointer value (4.10 conv.ptr), the typeid expression throws the bad_typeid exception (18.5.3 lib.bad.typeid).
This is inconsistent and should be cleaned up.
Bill Gibbons:
At one point we agreed that dereferencing a null pointer was not undefined; only using the resulting value had undefined behavior.
For example:
char *p = 0; char *q = &*p;
Similarly, dereferencing a pointer to the end of an array should be allowed as long as the value is not used:
char a[10]; char *b = &a[10]; // equivalent to "char *b = &*(a+10);"
Both cases come up often enough in real code that they should be allowed.
Mike Miller:
I can see the value in this, but it doesn't seem to be well reflected in the wording of the Standard. For instance, presumably *p above would have to be an lvalue in order to be the operand of "&", but the definition of "lvalue" in 3.10 basic.lval paragraph 2 says that "an lvalue refers to an object." What's the object in *p? If we were to allow this, we would need to augment the definition to include the result of dereferencing null and one-past-the-end-of-array.
Tom Plum:
Just to add one more recollection of the intent: I was very happy when (I thought) we decided that it was only the attempt to actually fetch a value that creates undefined behavior. The words which (I thought) were intended to clarify that are the first three sentences of the lvalue-to-rvalue conversion, 4.1 conv.lval:
An lvalue (3.10 basic.lval) of a non-function, non-array type T can be converted to an rvalue. If T is an incomplete type, a program that necessitates this conversion is ill-formed. If the object to which the lvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior.
In other words, it is only the act of "fetching", of lvalue-to-rvalue conversion, that triggers the ill-formed or undefined behavior. Simply forming the lvalue expression, and then for example taking its address, does not trigger either of those errors. I described this approach to WG14 and it may have been incorporated into C 1999.
Mike Miller:
If we admit the possibility of null lvalues, as Tom is suggesting here, that significantly undercuts the rationale for prohibiting "null references" -- what is a reference, after all, but a named lvalue? If it's okay to create a null lvalue, as long as I don't invoke the lvalue-to-rvalue conversion on it, why shouldn't I be able to capture that null lvalue as a reference, with the same restrictions on its use?
I am not arguing in favor of null references. I don't want them in the language. What I am saying is that we need to think carefully about adopting the permissive approach of saying that it's all right to create null lvalues, as long as you don't use them in certain ways. If we do that, it will be very natural for people to question why they can't pass such an lvalue to a function, as long as the function doesn't do anything that is not permitted on a null lvalue.
If we want to allow &*(p=0), maybe we should change the definition of "&" to handle dereferenced null specially, just as typeid has special handling, rather than changing the definition of lvalue to include dereferenced nulls, and similarly for the array_end+1 case. It's not as general, but I think it might cause us fewer problems in the long run.
The size requested by an array allocation is computed by multiplying the number of elements requested by the size of each element and adding an implementation-specific amount for overhead. It is possible for this calculation to overflow. Is an implementation required to detect this situation and, for instance, throw std::bad_alloc?
On one hand, the maximum allocation size is one of the implementation limits specifically mentioned in Annex B limits, and, according to 1.4 intro.compliance paragraph 2, an implementation is only required to "accept and correctly execute" programs that do not violate its resource limits.
On the other hand, it is difficult or impossible for user code to detect such overflows in a portable fashion, especially given that the array allocation overhead is not fixed, and it would be a service to the user to handle this situation gracefully.
Requirements for the alignment of pointers returned by new-expressions are given in 5.3.4 expr.new paragraph 10:
For arrays of char and unsigned char, the difference between the result of the new-expression and the address returned by the allocation function shall be an integral multiple of the most stringent alignment requirement (3.9 basic.types) of any object type whose size is no greater than the size of the array being created.
The intent of this wording is that the pointer returned by the new-expression will be suitably aligned for any data type that might be placed into the allocated storage (since the allocation function is constrained to return a pointer to maximally-aligned storage). However, there is an implicit assumption that each alignment requirement is an integral multiple of all smaller alignment requirements. While this is probably a valid assumption for all real architectures, there's no reason that the Standard should require it.
For example, assume that int has an alignment requirement of 3 bytes and double has an alignment requirement of 4 bytes. The current wording only requires that a buffer that is big enough for an int or a double be aligned on a 4-byte boundary (the more stringent requirement), but that would allow the buffer to be allocated on an 8-byte boundary — which might not be an acceptable location for an int.
Suggested resolution: Change "of any object type" to "of every object type."
A similar assumption can be found in 5.2.10 expr.reinterpret.cast paragraph 7:
...converting an rvalue of type "pointer to T1" to the type "pointer to T2" (where ... the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value...
Suggested resolution: Change the wording to
...converting an rvalue of type "pointer to T1" to the type "pointer to T2" (where ... the alignment requirements of T1 are an integer multiple of those of T2) and back to its original type yields the original pointer value...
The same change would also be needed in paragraph 9.
5.3.4 expr.new paragraph 10 says that the result of an array allocation function and the value of the array new-expression from which it was invoked may be different, allowing for space preceding the array to be used for implementation purposes such as saving the number of elements in the array. However, there is no corresponding description of the relationship between the operand of an array delete-expression and the argument passed to its deallocation function.
3.7.3.2 basic.stc.dynamic.deallocation paragraph 3 does state that
the value supplied to operator delete[](void*) in the standard library shall be one of the values returned by a previous invocation of either operator new[](std::size_t) or operator new[](std::size_t, const std::nothrow_t&) in the standard library.
This statement might be read as requiring an implementation, when processing an array delete-expression and calling the deallocation function, to perform the inverse of the calculation applied to the result of the allocation function to produce the value of the new-expression. (5.3.5 expr.delete paragraph 2 requires that the operand of an array delete-expression "be the pointer value which resulted from a previous array new-expression.") However, it is not completely clear whether the "shall" expresses an implementation requirement or a program requirement (or both). Furthermore, there is no direct statement about user-defined deallocation functions.
Suggested resolution: A note should be added to 5.3.5 expr.delete to clarify that any offset added in an array new-expression must be subtracted in the array delete-expression.
Does the Standard require that the deallocation function will be called if the destructor throws an exception? For example,
struct S { ~S() { throw 0; } }; void f() { try { delete new S; } catch(...) { } }
The question is whether the memory for the S object will be freed or not. It doesn't appear that the Standard answers the question, although most people would probably assume that it will be freed.
The meaning of an old-style cast is described in terms of const_cast, static_cast, and reinterpret_cast in 5.4 expr.cast paragraph 5. Ignoring const_cast for the moment, it basically says that if the conversion performed by a given old-style cast is one of those performed by static_cast, the conversion is interpreted as if it were a static_cast; otherwise, it's interpreted as if it were a reinterpret_cast, if possible. The following example is given in illustration:
struct A {}; struct I1 : A {}; struct I2 : A {}; struct D : I1, I2 {}; A *foo( D *p ) { return (A*)( p ); // ill-formed static_cast interpretation }
The obvious intent here is that a derived-to-base pointer conversion is one of the conversions that can be performed using static_cast, so (A*)(p) is equivalent to static_cast<A*>(p), which is ill-formed because of the ambiguity.
Unfortunately, the description of static_cast in 5.2.9 expr.static.cast does NOT support this interpretation. The problem is in the way 5.2.9 expr.static.cast lists the kinds of casts that can be performed using static_cast. Rather than saying something like "All standard conversions can be performed using static_cast," it says
An expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration "T t(e);" is well-formed, for some invented temporary variable t.
Given the declarations above, the hypothetical declaration
A* t(p);
is NOT well-formed, because of the ambiguity. Therefore the old-style cast (A*)(p) is NOT one of the conversions that can be performed using static_cast, and (A*)(p) is equivalent to reinterpret_cast<A*>(p), which is well-formed under 5.2.10 expr.reinterpret.cast paragraph 7.
Other situations besides ambiguity which might raise similar questions include access violations, casting from virtual base to derived, and casting pointers-to-members when virtual inheritance is involved.
Does an explicit temporary of an integral type qualify as an integral constant expression? For instance,
void* p = int(); // well-formed?
It would appear to be, since int() is an explicit type conversion according to 5.2.3 expr.type.conv (at least, it's described in a section entitled "Explicit type conversion") and type conversions to integral types are permitted in integral constant expressions (5.19 expr.const). However, this reasoning is somewhat tenuous, and some at least have argued otherwise.
7 dcl.dcl paragraph 3 reads,
In a simple-declaration, the optional init-declarator-list can be omitted only when... the decl-specifier-seq contains either a class-specifier, an elaborated-type-specifier with a class-key (9.1 class.name ), or an enum-specifier. In these cases and whenever a class-specifier or enum-specifier is present in the decl-specifier-seq, the identifiers in those specifiers are among the names being declared by the declaration... In such cases, and except for the declaration of an unnamed bit-field (9.6 class.bit ), the decl-specifier-seq shall introduce one or more names into the program, or shall redeclare a name introduced by a previous declaration. [Example:In the absence of any explicit restrictions in 7.1.3 dcl.typedef , this paragraph appears to allow declarations like the following:enum { }; // ill-formed typedef class { }; // ill-formed—end example]
typedef struct S { }; // no declarator typedef enum { e1 }; // no declaratorIn fact, the final example in 7 dcl.dcl paragraph 3 would seem to indicate that this is intentional: since it is illustrating the requirement that the decl-specifier-seq must introduce a name in declarations in which the init-declarator-list is omitted, presumably the addition of a class name would have made the example well-formed.
On the other hand, there is no good reason to allow such declarations; the only reasonable scenario in which they might occur is a mistake on the programmer's part, and it would be a service to the programmer to require that such errors be diagnosed.
7.1.5.3 dcl.type.elab paragraph 1 seems to impose an ordering constraint on the elements of friend class declarations. However, the general rule is that declaration specifiers can appear in any order. Should
class C friend;be well-formed?
According to 7.2 dcl.enum paragraph 5, the underlying type of an enum is an unspecified integral type, which could potentially be unsigned int. The promotion rules in 4.5 conv.prom paragraph 2 say that such an enumeration value used in an expression will be promoted to unsigned int. This means that a conforming implementation could give the value false for the following code:
enum { zero }; -1 < zero; // might be falseThis is counterintuitive. Perhaps the description of the underlying type of an enumeration should say that an unsigned underlying type can be used only if the values of the enumerators cannot be represented in the corresponding signed type. This approach would be consistent with the treatment of integral promotion of bitfields (4.5 conv.prom paragraph 3).
On a related note, 7.2 dcl.enum paragraph 5 says,
the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int.This specification does not allow for an enumeration like
enum { a = -1, b = UINT_MAX };Since each enumerator can fit in an int or unsigned int, the underlying type is required to be no larger than int, even though there is no such type that can represent all the enumerators.
See also issue 58.
7.3.1.2 namespace.memdef paragraph 3 says,
If a friend declaration in a non-local class first declares a class or function the friend class or function is a member of the innermost enclosing namespace... When looking for a prior declaration of a class or a function declared as a friend, scopes outside the innermost enclosing namespace scope are not considered.It is not clear from this passage how to determine whether an entity is "first declared" in a friend declaration. One question is whether a using-declaration influences this determination. For instance:
void foo(); namespace A{ using ::foo; class X{ friend void foo(); }; }Is the friend declaration a reference to ::foo or a different foo?
Part of the question involves determining the meaning of the word "synonym" in 7.3.3 namespace.udecl paragraph 1:
A using-declaration introduces a name into the declarative region in which the using-declaration appears. That name is a synonym for the name of some entity declared elsewhere.Is "using ::foo;" the declaration of a function or not?
More generally, the question is how to describe the lookup of the name in a friend declaration.
John Spicer: When a declaration specifies an unqualified name, that name is declared, not looked up. There is a mechanism in which that declaration is linked to a prior declaration, but that mechanism is not, in my opinion, via normal name lookup. So, the friend always declares a member of the nearest namespace scope regardless of how that name may or may not already be declared there.
Mike Miller: 3.4.1 basic.lookup.unqual paragraph 7 says:
A name used in the definition of a class X outside of a member function body or nested class definition shall be declared in one of the following ways:... [Note: when looking for a prior declaration of a class or function introduced by a friend declaration, scopes outside of the innermost enclosing namespace scope are not considered.]The presence of this note certainly implies that this paragraph describes the lookup of names in friend declarations.
John Spicer: It most certainly does not. If that section described the friend lookup it would yield the incorrect results for the friend declarations of f and g below. I don't know why that note is there, but it can't be taken to mean that that is how the friend lookup is done.
void f(){} void g(){} class B { void g(); }; class A : public B { void f(); friend void f(); // ::f not A::f friend void g(); // ::g not B::g };
Mike Miller: If so, the lookups for friend functions and classes behave differently. Consider the example in 3.4.4 basic.lookup.elab paragraph 3:
struct Base { struct Data; // OK: declares nested Data friend class Data; // OK: nested Data is a friend };
If the friend declaration is not a reference to ::foo, there is a related but separate question: does the friend declaration introduce a conflicting (albeit "invisible") declaration into namespace A, or is it simply a reference to an as-yet undeclared (and, in this instance, undeclarable) A::foo? Another part of the example in 3.4.4 basic.lookup.elab paragraph 3 is related:
struct Data { friend struct Glob; // OK: Refers to (as yet) undeclared Glob // at global scope. };
John Spicer: You can't refer to something that has not yet been declared. The friend is a declaration of Glob, it just happens to declare it in a such a way that its name cannot be used until it is redeclared.
(A somewhat similar question has been raised in connection with issue 36. Consider:
namespace N { struct S { }; } using N::S; struct S; // legal?
According to 9.1 class.name paragraph 2,
A declaration consisting solely of class-key identifier ; is either a redeclaration of the name in the current scope or a forward declaration of the identifier as a class name.
Should the elaborated type declaration in this example be considered a redeclaration of N::S or an invalid forward declaration of a different class?)
(See also issues 95, 136, 139, 143, 165, and 166, as well as paper J16/00-0006 = WG21 N1229.)
Section 7.3.3 namespace.udecl paragraph 8 says:
A using-declaration is a declaration and can therefore be used repeatedly where (and only where) multiple declarations are allowed.It contains the following example:
namespace A { int i; } namespace A1 { using A::i; using A::i; // OK: double declaration } void f() { using A::i; using A::i; // error: double declaration }However, if "using A::i;" is really a declaration, and not a definition, it is far from clear that repeating it should be an error in either context. Consider:
namespace A { int i; void g(); } void f() { using A::g; using A::g; }Surely the definition of f should be analogous to
void f() { void g(); void g(); }which is well-formed because "void g();" is a declaration and not a definition.
Indeed, if the double using-declaration for A::i is prohibited in f, why should it be allowed in namespace A1?
Proposed Resolution (04/99): Change the comment "// error: double declaration" to "// OK: double declaration". (This should be reviewed against existing practice.)
Notes from 04/00 meeting:
The core language working group was unable to come to consensus over what kind of declaration a using-declaration should emulate. In a straw poll, 7 members favored allowing using-declarations wherever a non-definition declaration could appear, while 4 preferred to allow multiple using-declarations only in namespace scope (the rationale being that the permission for multiple using-declarations is primarily to support its use in multiple header files, which are seldom included anywhere other than namespace scope). John Spicer pointed out that friend declarations can appear multiple times in class scope and asked if using-declarations would have the same property under the "like a declaration" resolution.
As a result of the lack of agreement, the issue was returned to "open" status.
See also issues 56, 85, and 138..
According to 7.3.3 namespace.udecl paragraph 12,
When a using-declaration brings names from a base class into a derived class scope, member functions in the derived class override and/or hide member functions with the same name and parameter types in a base class (rather than conflicting).
Note that this description says nothing about the cv-qualification of the hiding and hidden member functions. This means, for instance, that a non-const member function in the derived class hides a const member function with the same name and parameter types instead of overloading it in the derived class scope. For example,
struct A { virtual int f() const; virtual int f(); }; struct B: A { B(); int f(); using A::f; }; const B cb; int i = cb.f(); // ill-formed: A::f() const hidden in B
The same terminology is used in 10.3 class.virtual paragraph 2:
If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name and same parameter list as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides Base::vf.
Suggested resolution: The hiding and overriding should be on the basis of the function signature, which includes any cv-qualification on the function.
The interaction of default arguments and ellipsis is not clearly spelled out in the current wording of the Standard. 8.3.6 dcl.fct.default paragraph 4 says,
In a given function declaration, all parameters subsequent to a parameter with a default argument shall have default arguments supplied in this or previous declarations.
Strictly speaking, ellipsis isn't a parameter, but this could be clearer. Also, in 8.3.5 dcl.fct paragraph 2,
If the parameter-declaration-clause terminates with an ellipsis, the number of arguments shall be equal to or greater than the number of parameters specified.
This could be interpreted to refer to the number of arguments after the addition of default arguments to the argument list given in the call expression, but again it could be clearer.
It is not clear whether the following declaration is well-formed:
struct S { int i; } s = { { 1 } };According to 8.5.1 dcl.init.aggr paragraph 2, a brace-enclosed initializer is permitted for a subaggregate of an aggregate; however, i is a scalar, not an aggregate. 8.5 dcl.init paragraph 13 says that a standalone declaration like
int i = { 1 };is permitted, but it is not clear whether this says anything about the form of initializers for scalar members of aggregates.
This is (more) clearly permitted by the C89 Standard.
Is the temporary created during copy-initialization of a class object treated as an lvalue or an rvalue? That is, is the following example well-formed or not?
struct B { }; struct A { A(A&); // not const A(const B&); }; B b; A a = b;
According to 8.5 dcl.init paragraph 14, the initialization of a is performed in two steps. First, a temporary of type A is created using A::A(const B&). Second, the resulting temporary is used to direct-initialize a using A::A(A&).
The second step requires binding a reference to non-const to the temporary resulting from the first step. However, 8.5.3 dcl.init.ref paragraph 5 requires that such a reference be bound only to lvalues.
It is not clear from 3.10 basic.lval whether the temporary created in the process of copy-initialization should be treated as an lvalue or an rvalue. If it is an lvalue, the example is well-formed, otherwise it is ill-formed.
(See also issue 84.)
Paragraph 9 of 8.5 dcl.init says:
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 an 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.
What if a const POD object has no non-static data members? This wording requires an empty initializer for such cases:
struct Z { // no data members operator int() const { return 0; } }; void f() { const Z z1; // ill-formed: no initializer const Z z2 = { }; // well-formed }
Similar comments apply to a non-POD const object, all of whose non-static data members and base class subobjects have default constructors. Why should the class of such an object be required to have a user-declared default constructor?
(See also issue 78.)
There is an inconsistency in the handling of references vs pointers in user defined conversions and overloading. The reason for that is that the combination of 8.5.3 dcl.init.ref and 4.4 conv.qual circumvents the standard way of ranking conversion functions, which was probably not the intention of the designers of the standard.
Let's start with some examples, to show what it is about:
struct Z { Z(){} }; struct A { Z x; operator Z *() { return &x; } operator const Z *() { return &x; } }; struct B { Z x; operator Z &() { return x; } operator const Z &() { return x; } }; int main() { A a; Z *a1=a; const Z *a2=a; // not ambiguous B b; Z &b1=b; const Z &b2=b; // ambiguous }
So while both classes A and B are structurally equivalent, there is a difference in operator overloading. I want to start with the discussion of the pointer case (const Z *a2=a;): 13.3.3 over.match.best is used to select the best viable function. Rule 4 selects A::operator const Z*() as best viable function using 13.3.3.2 over.ics.rank since the implicit conversion sequence const Z* -> const Z* is a better conversion sequence than Z* -> const Z*.
So what is the difference to the reference case? Cv-qualification conversion is only applicable for pointers according to 4.4 conv.qual. According to 8.5.3 dcl.init.ref paragraphs 4-7 references are initialized by binding using the concept of reference-compatibility. The problem with this is, that in this context of binding, there is no conversion, and therefore there is also no comparing of conversion sequences. More exactly all conversions can be considered identity conversions according to 13.3.3.1.4 over.ics.ref paragraph 1, which compare equal and which has the same effect. So binding const Z* to const Z* is as good as binding const Z* to Z* in terms of overloading. Therefore const Z &b2=b; is ambiguous. [13.3.3.1.4 over.ics.ref paragraph 5 and 13.3.3.2 over.ics.rank paragraph 3 rule 3 (S1 and S2 are reference bindings ...) do not seem to apply to this case]
There are other ambiguities, that result in the special treatment of references: Example:
struct A {int a;}; struct B: public A { B() {}; int b;}; struct X { B x; operator A &() { return x; } operator B &() { return x; } }; main() { X x; A &g=x; // ambiguous }
Since both references of class A and B are reference compatible with references of class A and since from the point of ranking of implicit conversion sequences they are both identity conversions, the initialization is ambiguous.
So why should this be a defect?
So overall I think this was not the intention of the authors of the standard.
So how could this be fixed? For comparing conversion sequences (and only for comparing) reference binding should be treated as if it was a normal assignment/initialization and cv-qualification would have to be defined for references. This would affect 8.5.3 dcl.init.ref paragraph 6, 4.4 conv.qual and probably 13.3.3.2 over.ics.rank paragraph 3.
Another fix could be to add a special case in 13.3.3 over.match.best paragraph 1.
With class name injection, when a base class name is used in a derived class, the name found is the injected name in the base class, not the name of the class in the scope containing the base class. Consequently, if the base class name is not accessible (e.g., because is is in a private base class), the base class name cannot be used unless a qualified name is used to name the class in the class or namespace of which it is a member.
Without class name injection the following example is valid. With class name injection, A is inaccessible in class C.
class A { }; class B: private A { }; class C: public B { A* p; // error: A inaccessible };
At the least, the standard should be more explicit that this is, in fact, ill-formed.
(See paper J16/99-0010 = WG21 N1187.)
I think that the definition of a POD class in the current version of the Standard is overly permissive in that it allows for POD classes for which a user-defined operator function operator& may be defined. Given that the idea behind POD classes was to achieve compatibility with C structs and unions, this makes 'Plain old' structs and unions behave not quite as one would expect them to.
In the C language, if x and y are variables of struct or union type S that has a member m, the following expression are allowed: &x, x.m, x = y. While the C++ standard guarantees that if x and y are objects of a POD class type S, the expressions x.m, x = y will have the same effect as they would in C, it is still possible for the expression &x to be interpreted differently, subject to the programmer supplying an appropriate version of a user-defined operator function operator& either as a member function or as a non-member function.
This may result in surprising effects. Consider:
// POD_bomb is a POD-struct. It has no non-static non-public data members, // no virtual functions, no base classes, no constructors, no user-defined // destructor, no user-defined copy assignment operator, no non-static data // members of type pointer to member, reference, non-POD-struct, or // non-POD-union. struct POD_bomb { int m_value1; int m_value2; int operator&() { return m_value1++; } int operator&() const { return m_value1 + m_value2; } };
3.9 basic.types paragraph 2 states:
For any complete POD object type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7 intro.memory) making up the object can be copied into an array of char or unsigned char [footnote: By using, for example, the library functions (17.4.1.2 lib.headers) memcpy or memmove]. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value. [Example:#define N sizeof(T) char buf[N]; T obj; // obj initialized to its original value memcpy(buf, &obj, N); // between these two calls to memcpy, // obj might be modified memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type // holds its original value—end example]
Now, supposing that the complete POD object type T in the example above is POD_bomb, and we cannot any more count on the assertions made in the comments to the example. Given a standard conforming implementation, the code will not even compile. And I see no legal way of copying the contents of an object of a complete object type POD_bomb into an array of char or unsigned char with memcpy or memmove without making use of the unary & operator. Except, of course, by means of an ugly construct like:
struct POD_without_ampersand { POD_bomb a_bomb; } obj; #define N sizeof(POD_bomb) char buf[N]; memcpy(buf, &obj, N); memcpy(&obj, buf, N);
The fact that the definition of a POD class allows for POD classes for which a user-defined operator& is defined, may also present major obstacles to implementers of the offsetof macro from <cstddef>
18.1 lib.support.types paragraph 5 says:
The macro offsetof accepts a restricted set of type arguments in this International Standard. type shall be a POD structure or a POD union (clause 9 class). The result of applying the offsetof macro to a field that is a static data member or a function is undefined."
Consider a well-formed C++ program below:
#include <cstddef> #include <iostream> struct POD_bomb { int m_value1; int m_value2; int operator&() { return m_value1++; } int operator&() const { return m_value1 + m_value2; } }; // POD_struct is a yet another example of a POD-struct. struct POD_struct { POD_bomb m_nonstatic_bomb1; POD_bomb m_nonstatic_bomb2; }; int main() { std::cout << "offset of m_nonstatic_bomb2: " << offsetof(POD_struct, m_nonstatic_bomb2) << '\n'; return 0; }
There doesn't seem to be a prohibition in 9.5 class.union against a declaration like
union { int : 0; } x;Should that be valid? If so, 8.5 dcl.init paragraph 5 third bullet, which deals with default-initialization of unions, should say that no initialization is done if there are no data members.
What about:
union { } x; static union { };If the first example is well-formed, should either or both of these cases be well-formed as well?
(See also the resolution for issue 151.)
Notes from 10/00 meeting: The resolution to issue 178, which was accepted as a DR, addresses the first point above (default initialization). The other questions have not yet been decided, however.
Section 9.6 class.bit paragraph 4 needs to be more specific about the signedness of bit fields of enum type. How much leeway does an implementation have in choosing the signedness of a bit field? In particular, does the phrase "large enough to hold all the values of the enumeration" mean "the implementation decides on the signedness, and then we see whether all the values will fit in the bit field", or does it require the implementation to make the bit field signed or unsigned if that's what it takes to make it "large enough"?
(See also issue 172.)
According to 10.4 class.abstract paragraph 6,
Member functions can be called from a constructor (or destructor) of an abstract class; the effect of making a virtual call (10.3 class.virtual) to a pure virtual function directly or indirectly for the object being created (or destroyed) from such a constructor (or destructor) is undefined.
This prohibition is unnecessarily restrictive. It should not apply to cases in which the pure virtual function has been defined.
Currently the "pure" specifier for a virtual member function has two meanings that need not be related:
The prohibition of virtual calls to pure virtual functions arises from the first meaning and unnecessarily penalizes those who only need the second.
For example, consider a scenario such as the following. A class B is defined containing a (non-pure) virtual function f that provides some initialization and is thus called from the base class constructor. As time passes, a number of classes are derived from B and it is noticed that each needs to override f, so it is decided to make B::f pure to enforce this convention while still leaving the original definition of B::f to perform its needed initialization. However, the act of making B::f pure means that every reference to f that might occur during the execution of one of B's constructors must be tracked down and edited to be a qualified reference to B::f. This process is tedious and error-prone: needed edits might be overlooked, and calls that actually should be virtual when the containing function is called other than during construction/destruction might be incorrectly changed.
Suggested resolution: Allow virtual calls to pure virtual functions if the function has been defined.
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.)
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.
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.
Paragraph 4 of 12.5 class.free speaks of looking up a deallocation function. While it is an error if a placement deallocation function alone is found by this lookup, there seems to be an assumption that a placement deallocation function and a usual deallocation function can both be declared in a given class scope without creating an ambiguity. The normal mechanism by which ambiguity is avoided when functions of the same name are declared in the same scope is overload resolution; however, there is no mention of overload resolution in the description of the lookup. In fact, there appears to be nothing in the current wording that handles this case. That is, the following example appears to be ill-formed, according to the current wording:
struct S { void operator delete(void*); void operator delete(void*, int); }; void f(S* p) { delete p; // ill-formed: ambiguous operator delete }
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.
Must a constructor for an abstract base class provide a mem-initializer for each virtual base class from which it is directly or indirectly derived? Since the initialization of virtual base classes is performed by the most-derived class, and since an abstract base class can never be the most-derived class, there would seem to be no reason to require constructors for abstract base classes to initialize virtual base classes.
It is not clear from the Standard whether there actually is such a requirement or not. The relevant text is found in 12.6.2 class.base.init paragraph 6:
All sub-objects representing virtual base classes are initialized by the constructor of the most derived class (1.8 intro.object). If the constructor of the most derived class does not specify a mem-initializer for a virtual base class V, then V's default constructor is called to initialize the virtual base class subobject. If V does not have an accessible default constructor, the initialization is ill-formed. A mem-initializer naming a virtual base class shall be ignored during execution of the constructor of any class that is not the most derived class.
This paragraph requires only that the most-derived class's constructor have a mem-initializer for virtual base classes. Should the silence be construed as permission for constructors of classes that are not the most-derived to omit such mem-initializers?
Jack Rouse: In 12.8 class.copy paragraph 8, the standard includes the following about the copying of class subobjects in such a constructor:
Mike Miller: I'm more concerned about 12.8 class.copy paragraph 7, which lists the situations in which an implicitly-defined copy constructor can render a program ill-formed. Inaccessible and ambiguous copy constructors are listed, but not a copy constructor with a cv-qualification mismatch. These two paragraphs taken together could be read as requiring the calling of a copy constructor with a non-const reference parameter for a const data member.
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.
Suggested resolution: remove the following highlighted 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. And in this context using &F behaves the same as using the name F by itself.Add the following before the parenthesized sentence at the end of the paragraph:
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.
There is a moderately serious problem with the definition of overload resolution. Consider this example:
struct B; struct A { A(B); }; struct B { operator A(); } b; int main() { (void)A(b); }
This is pretty much the definition of "ambiguous," right? You want to convert a B to an A, and there are two equally good ways of doing that: a constructor of A that takes a B, and a conversion function of B that returns an A.
What we discover when we trace this through the standard, unfortunately, is that the constructor is favored over the conversion function. The definition of direct-initialization (the parenthesized form) of a class considers only constructors of that class. In this case, the constructors are the A(B) constructor and the (implicitly-generated) A(const A&) copy constructor. Here's how they are ranked on the argument match:
A(B) | exact match (need a B, have a B) |
A(const A&) | user-defined conversion (B::operator A used to convert B to A) |
In other words, the conversion function does get considered, but it's operating with, in effect, a handicap of one user defined conversion. To put that a different way, this problem is a problem of weighting, not a problem that certain conversion paths are not considered.
I believe the reason that the standard's approach doesn't yield the "intuitive" result is that programmers expect copy constructor elision to be done whenever reasonable, so the intuitive cost of using the conversion function in the example above is simply the cost of the conversion function, not the cost of the conversion function plus the cost of the copy constructor (which is what the standard counts).
Suggested resolution:
In a direct-initialization overload resolution case, if the candidate function being called is a copy constructor and its argument (after any implicit conversions) is a temporary that is the return value of a conversion function, and the temporary can be optimized away, the cost of the argument match for the copy constructor should be considered to be the cost of the argument match on the conversion function argument.
template <class T> void f(T); template <class T> void g(T); template <class T> void g(T,T); int main() { (&f<int>); (&g<int>); }The question is whether &f<int> identifies a unique function. &g<int> is clearly ambiguous.
13.4 over.over paragraph 1 says that a function template name is considered to name a set of overloaded functions. I believe it should be expanded to say that a function template name with an explicit template argument list is also considered to name a set of overloaded functions.
In the general case, you need to have a destination type in order to identify a unique function. While it is possible to permit this, I don't think it is a good idea because such code depends on there only being one template of that name that is visible.
The EDG front end issues an error on this use of "f". egcs 1.1.1 allows it, but the most current snapshot of egcs that I have also issues an error on it.
It has been pointed out that when dealing with nontemplates, the rules for taking the address of a single function differ from the rules for an overload set, but this asymmetry is needed for C compatibility. This need does not exist for the template case.
My feeling is that a general rule is better than a general rule plus an exception. The general rule is that you need a destination type to be sure that the operation will succeed. The exception is when there is only one template in the set and only then when you provide values for all of the template arguments.
It is true that in some cases you can provide a shorthand, but only if you encourage a fragile coding style (that will cause programs to break when additional templates are added).
I think the standard needs to specify one way or the other how this case should be handled. My recommendation would be that it is ill-formed.
Nico Josuttis: Consider the following example:
template <int VAL> int add (int elem) { return elem + VAL; } std::transform(coll.begin(), coll.end() coll.begin(), add<10>);
If John's recommendation is adopted, this code will become ill-formed. I bet there will be a lot of explanation for users necessary why this fails and that they have to change add<10> to something like (int (*)(int))add<10>.
This example code is probably common practice because this use of the STL is typical and is accepted in many current implementations. I strongly urge that this issue be resolved in favor of keeping this code valid.
Bill Gibbons: I find this rather surprising. Shouldn't a template-id which specifies all of the template arguments be treated like a declaration-only explicit instantiation, producing a set of ordinary function declarations? And when that set happens to contain only one function, shouldn't the example code work?
(See also issue 250.)
According to the Standard (although not implemented this way in most implementations), the following code exhibits non-intuitive behavior:
struct T { operator short() const; operator int() const; }; short s; void f(const T& t) { s = t; // surprisingly calls T::operator int() const }
The reason for this choice is 13.6 over.built paragraph 18:
For every triple (L, VQ, R), where L is an arithmetic type, VQ is either volatile or empty, and R is a promoted arithmetic type, there exist candidate operator functions of the form
VQ L& operator=(VQ L&, R);
Because R is a "promoted arithmetic type," the second argument to the built-in assignment operator is int, causing the unexpected choice of conversion function.
Suggested resolution: Provide built-in assignment operators for the unpromoted arithmetic types.
According to 14 temp paragraph 5,
Except that a function template can be overloaded either by (non-template) functions with the same name or by other function templates with the same name (14.8.3 temp.over ), a template name declared in namespace scope or in class scope shall be unique in that scope.3.3.7 basic.scope.hiding paragraph 2 agrees that only functions, not function templates, can hide a class name declared in the same scope:
A class name (9.1 class.name ) or enumeration name (7.2 dcl.enum ) can be hidden by the name of an object, function, or enumerator declared in the same scope.However, 3.3 basic.scope paragraph 4 treats functions and template functions together in this regard:
Given a set of declarations in a single declarative region, each of which specifies the same unqualified name,
- they shall all refer to the same entity, or all refer to functions and function templates; or
- exactly one declaration shall declare a class name or enumeration name that is not a typedef name and the other declarations shall all refer to the same object or enumerator, or all refer to functions and function templates; in this case the class name or enumeration name is hidden
John Spicer: You should be able to take an existing program and replace an existing function with a function template without breaking unrelated parts of the program. In addition, all of the compilers I tried allow this usage (EDG, Sun, egcs, Watcom, Microsoft, Borland). I would recommend that function templates be handled exactly like functions for purposes of name hiding.
Martin O'Riordan: I don't see any justification for extending the purview of what is decidedly a hack, just for the sake of consistency. In fact, I think we should go further and in the interest of consistency, we should deprecate the hack, scheduling its eventual removal from the C++ language standard.
The hack is there to allow old C programs and especially the 'stat.h' file to compile with minimum effort (also several other Posix and X headers). People changing such older programs have ample opportunity to "do it right". Indeed, if you are adding templates to an existing program, you should probably be placing your templates in a 'namespace', so the issue disappears anyway. The lookup rules should be able to provide the behaviour you need without further hacking.
14 temp paragraph 7 allows class templates to be declared exported, including member classes and member class templates (implicitly by virtue of exporting the containing template class). However, paragraph 8 does not exclude exported class templates from the statement that
An exported template need only be declared (and not necessarily defined) in a translation unit in which it is instantiated.This is an incorrect implication; however, it is also not dispelled in 14.7.1 temp.inst paragraph 6:
If an implicit instantiation of a class template specialization is required and the template is declared but not defined, the program is ill-formed.This wording says nothing about the translation unit in which the definition must be provided. Contrast this with 14.7.2 temp.explicit paragraph 3:
A definition of a class template or a class member template shall be in scope at the point of the explicit instantiation of the class template or class member template.
Suggested resolution:
(See also issue 212.)
Notes from 04/00 meeting:
John Spicer opined that even though 14 temp paragraph 7 speaks of "declaring a class template exported," that does not mean that the class template is "an exported template" in the sense of paragraph 8. He suggested clarifying paragraph 7 to that effect instead of the change to paragraph 8 suggested above, and questioned the need for a change to 14.7.1 temp.inst.
Static data members of template classes and of nested classes of template classes are not themselves templates but receive much the same treatment as template. For instance, 14 temp paragraph 1 says that templates are only "classes or functions" but implies that "a static data member of a class template or of a class nested within a class template" is defined using the template-declaration syntax.
There are many places in the clause, however, where static data members of one sort or another are overlooked. For instance, 14 temp paragraph 6 allows static data members of class templates to be declared with the export keyword. I would expect that static data members of (non-template) classes nested within class templates could also be exported, but they are not mentioned here.
Paragraph 8, however, overlooks static data members altogether and deals only with "templates" in defining the effect of the export keyword; there is no description of the semantics of defining a static data member of a template to be exported.
These are just two instances of a systematic problem. The entire clause needs to be examined to determine which statements about "templates" apply to static data members, and which statements about "static data members of class templates" also apply to static data members of non-template classes nested within class templates.
(The question also applies to member functions of template classes; see issue 217, where the phrase "non-template function" in 8.3.6 dcl.fct.default paragraph 4 is apparently intended not to include non-template member functions of template classes. See also issue 108, which would benefit from understanding nested classes of class templates as templates. Also, see issue 249, in which the usage of the phrase "member function template" is questioned.)
John Spicer: Where can default values for the template parameters of template template parameters be specified and where to they apply?
For normal template parameters, defaults can be specified only in class template declarations and definitions, and they accumulate across multiple declarations in the same way that function default arguments do.
I think that defaults for parameters of template template parameters should be handled differently, though. I see no reason why such a default should extend beyond the template declaration with which it is associated. In other words, such defaults are a property of a specific template declaration and are not part of the interface of the template.
template <class T = float> struct B {}; template <template <class _T = float> class T> struct A { inline void f(); inline void g(); }; template <template <class _T> class T> void A<T>::f() { T<> t; // Okay? (proposed answer - no) } template <template <class _T = char> class T> // Okay? (proposed answer - yes) void A<T>::g() { T<> t; // T<char> or T<float>? (proposed answer - T<char>) } int main() { A<B> ab; ab.f(); }
I don't think this is clear in the standard.
Gabriel Dos Reis: On the other hand I fail to see the reasons why we should introduce yet another special rule to handle that situation differently. I think we should try to keep rules as uniform as possible. For default values, it has been the case that one should look for any declaration specifying default values. Breaking that rules doesn't buy us anything, at least as far as I can see. My feeling is that [allowing different defaults in different declarations] is very confusing.
Mike Miller: I'm with John on this one. Although we don't have the concept of "prototype scope" for template parameter lists, the analogy with function parameters would suggest that the two declarations of T (in the template class definition and the template member function definition) are separate declarations and completely unrelated. While it's true that you accumulate default arguments on top-level declarations in the same scope, it seems to me a far leap to say that we ought also to accumulate default arguments in nested declarations. I would expect those to be treated as being in different scopes and thus not to share default argument information.
When you look up the name T in the definition of A<T>::f(), the declaration you find has no default argument for the parameter of T, so T<> should not be allowed.
The prohibition of default template arguments for function templates is a misbegotten remnant of the time where freestanding functions were treated as second class citizens and required all template arguments to be deduced from the function arguments rather than specified.
The restriction seriously cramps programming style by unnecessarily making freestanding functions different from member functions, thus making it harder to write STL-style code.
Suggested resolution:
Replace
A default template-argument shall not be specified in a function template declaration or a function template definition, nor in the template-parameter-list of the definition of a member of a class template.
by
A default template-argument shall not be specified in the template-parameter-list of the definition of a member of a class template.
The actual rules are as stated for arguments to class templates.
Notes from 10/00 meeting:
The core language working group was amenable to this change. Questions arose, however, over the interaction between default template arguments and template argument deduction: should it be allowed or forbidden to specify a default argument for a deduced parameter? If it is allowed, what is the meaning: should one or the other have priority, or is it an error if the default and deduced arguments are different?
The following is the wording from 14.2 temp.names paragraphs 4 and 5 that discusses the use of the "template" keyword following . or -> and in qualified names.
class X { public: template<std::size_t> X* alloc(); template<std::size_t> static X* adjust(); }; template<class T> void f(T* p) { T* p1 = p->alloc<200>(); // ill-formed: < means less than T* p2 = p->template alloc<200>(); // OK: < starts template argument list T::adjust<100>(); // ill-formed: < means less than T::template adjust<100>(); // OK: < starts explicit qualification }—end example]
If a name prefixed by the keyword template is not the name of a member template, the program is ill-formed. [Note: the keyword template may not be applied to non-template members of class templates. ]
The whole point of this feature is to say that the "template" keyword is needed to indicate that a "<" begins a template parameter list in certain contexts. The constraints in paragraph 5 leave open to debate certain cases.First, I think it should be made more clear that the template name must be followed by a template argument list when the "template" keyword is used in these contexts. If we don't make this clear, we would have to add several semantic clarifications instead. For example, if you say "p->template f()", and "f" is an overload set containing both templates and nontemplates: a) is this valid? b) are the nontemplates in the overload set ignored? If the user is forced to write "p->template f<>()" it is clear that this is valid, and it is equally clear that nontemplates in the overload set are ignored. As this feature was added purely to provide syntactic guidance, I think it is important that it otherwise have no semantic implications.
I propose that paragraph 5 be modified to:
(See also issue 30 and document J16/00-0008 = WG21 N1231.)
Notes from 04/00 meeting:
The discussion of this issue revived interest in issues 11 and 109.
Consider the following example:
template<class T> struct X { virtual void f(); }; template<class T> struct Y { void g(X<T> *p) { p->template X<T>::f(); } };
This is an error because X is not a member template; 14.2 temp.names paragraph 5 says:
If a name prefixed by the keyword template is not the name of a member template, the program is ill-formed.
In a way this makes perfect sense: X is found to be a template using ordinary lookup even though p has a dependent type. However, I think this makes the use of the template prefix even harder to teach.
Was this intentionally outlawed?
Library issue 225 poses the following questions:
For example, a programmer might want to provide a version of std::swap that would be used for any specialization of a particular class template. It is possible to do that for specific types, but not for all specializations of a template.
The problem is due to the fact that programmers are forbidden to add overloads to namespace std, although specializations are permitted. One suggested solution would be to allow partial specialization of function templates, analogous to partial specialization of class templates.
Library issue 225 contains a detailed proposal for adding partial specialization of function templates (not reproduced here in the interest of space and avoiding multiple-copy problems). This Core issue is being opened to provide for discussion of the proposal within the core language working group.
Notes from 10/00 meeting:
A major concern over the idea of partial specialization of function templates is that function templates can be overloaded, unlike class templates. Simply naming the function template in the specialization, as is done for class specialization, is not adequate to identify the template being specialized.
In view of this problem, the library working group is exploring the other alternative, permitting overloads to be added to functions in namespace std, as long as certain restrictions (to be determined) are satisfied.
Issue 1:
14.5.5.2 temp.func.order paragraph 2 says:
Given two overloaded function templates, whether one is more specialized than another can be determined by transforming each template in turn and using argument deduction (14.8.2 temp.deduct ) to compare it to the other.14.8.2 temp.deduct now has 4 subsections describing argument deduction in different situations. I think this paragraph should point to a subsection of 14.8.2 temp.deduct .
Rationale:
This is not a defect; it is not necessary to pinpoint cross-references to this level of detail.
Issue 2:
14.5.5.2 temp.func.order paragraph 4 says:
Using the transformed function parameter list, perform argument deduction against the other function template. The transformed template is at least as specialized as the other if, and only if, the deduction succeeds and the deduced parameter types are an exact match (so the deduction does not rely on implicit conversions).In "the deduced parameter types are an exact match", the terms exact match do not make it clear what happens when a type T is compared to the reference type T&. Is that an exact match?
Issue 3:
14.5.5.2 temp.func.order paragraph 5 says:
A template is more specialized than another if, and only if, it is at least as specialized as the other template and that template is not at least as specialized as the first.What happens in this case:
template<class T> void f(T,int); template<class T> void f(T, T); void f(1,1);For the first function template, there is no type deduction for the second parameter. So the rules in this clause seem to imply that the second function template will be chosen.
Rationale:
This is not a defect; the standard unambiguously makes the above example ill-formed due to ambiguity.
In 14.5.5.2 temp.func.order, partial ordering is explained in terms of template argument deduction. However, the exact procedure for doing so is not specified. A number of details are missing, they are explained as sub-issues below.
template<class T> void g(T); // #1 template<class T> void g(T&); // #2Here, #2 is at least as specialized as #1: With a synthetic type U, #2 becomes g(U&); argument deduction against #1 succeeds with T=U&. However, #1 is not at least as specialized as #2: Deducing g(U) against g(T&) fails. Therefore, the second template is more specialized than the first, and the call g(x) is not ambiguous.
template<class S> void g(S); // #1 template<class T> void g(T const &); // #3Here, #3 is clearly at least as specialized as #1. To determine whether #1 is at least as specialized as #3, a unique type U is synthesized, and deduction of g<U>(U) is performed against #3. Following the rules in 14.8.2.1 temp.deduct.call, deduction succeeds with T=U. Since the template argument is U, and the deduced template parameter is also U, we have an exact match between the template parameters. Even though the conversion from U to U const & is an exact match, it is not clear whether the added qualification should be taken into account, as it is in other places.
Issue 200 covers a related issue, illustrated by the following example:
template <class T> T f(int); template <class T, class U> T f(U); void g() { f<int>(1); }
Even though one template is "obviously" more specialized than the other, deduction fails in both directions because neither function parameter list allows template parameter T to be deduced.
(See also issue 250.)
The standard prohibits a class template from having the same name as one of its template parameters (14.6.1 temp.local paragraph 4). This prohibits
template <class X> class X;for the reason that the template name would hide the parameter, and such hiding is in general prohibited.
Presumably, we should also prohibit
template <template <class T> class T> struct A;for the same reason.
template <class T> class Foo { public: typedef int Bar; Bar f(); }; template <class T> typename Foo<T>::Bar Foo<T>::f() { return 1;} --------------------In the class template definition, the declaration of the member function is interpreted as:
int Foo<T>::f();In the definition of the member function that appears outside of the class template, the return type is not known until the member function is instantiated. Must the return type of the member function be known when this out-of-line definition is seen (in which case the definition above is ill-formed)? Or is it OK to wait until the member function is instantiated to see if the type of the return type matches the return type in the class template definition (in which case the definition above is well-formed)?
Suggested resolution: (John Spicer)
My opinion (which I think matches several posted on the reflector recently) is that the out-of-class definition must match the declaration in the template. In your example they do match, so it is well formed.
I've added some additional cases that illustrate cases that I think either are allowed or should be allowed, and some cases that I don't think are allowed.
template <class T> class A { typedef int X; }; template <class T> class Foo { public: typedef int Bar; typedef typename A<T>::X X; Bar f(); Bar g1(); int g2(); X h(); X i(); int j(); }; // Declarations that are okay template <class T> typename Foo<T>::Bar Foo<T>::f() { return 1;} template <class T> typename Foo<T>::Bar Foo<T>::g1() { return 1;} template <class T> int Foo<T>::g2() { return 1;} template <class T> typename Foo<T>::X Foo<T>::h() { return 1;} // Declarations that are not okay template <class T> int Foo<T>::i() { return 1;} template <class T> typename Foo<T>::X Foo<T>::j() { return 1;}In general, if you can match the declarations up using only information from the template, then the declaration is valid.
Declarations like Foo::i and Foo::j are invalid because for a given instance of A<T>, A<T>::X may not actually be int if the class is specialized.
This is not a problem for Foo::g1 and Foo::g2 because for any instance of Foo<T> that is generated from the template you know that Bar will always be int. If an instance of Foo is specialized, the template member definitions are not used so it doesn't matter whether a specialization defines Bar as int or not.
According to 14.7 temp.spec paragraph 5,
No program shall explicitly instantiate any template more than once, both explicitly instantiate and explicitly specialize a template, or specialize a template more than once for a given set of template-arguments.
This rule has an impact on library issue 120. Library authors would like to have the freedom to specialize (or not) various library functions without having to document their choices, while users need the flexibility to explicitly instantiate library functions in certain translation units.
If this rule could be slightly weakened, it would reduce the need for constraining either the library author or the programmer. For instance, the rule might be recast to say that if a specialization is followed by an explicit instantiation in the same translation unit, the explicit instantiation is ignored. A specialization and an explicit instantiation of the same template in two different translation units would still be an error, no diagnostic required.
A template is implicitly instantiated because of a "pointer conversion" on an argument. This was intended to include related-class conversions, but it also inadvertently includes conversions to void*, null pointer conversions, cv-qualification conversions and the identity conversion.
It is not clear whether a reinterpret_cast of a pointer should cause implicit instantiation.
Proposed resolution (10/99): Replace 14.7.1 temp.inst paragraph 4, up to the example, with the following:
A class template specialization is implicitly instantiated if the class type is used in a context that requires a completely-defined object type or if the completeness of the class type affects the semantics of the program. [Note: in particular, if the semantics of an expression depend on the member or base class lists of a class template specialization, the class template specialization is implicitly generated. For instance, deleting a pointer to class type depends on whether or not the class declares a destructor, and conversion between pointer to class types depends on the inheritance relationship between the two classes involved. ]
(See also issue 212.)
Three points have been raised where the wording in 14.7.1 temp.inst may not be sufficiently clear.
A class template specialization is implicitly instantiated... if the completeness of the class type affects the semantics of the program...
It is not clear what it means for the "completeness... [to affect] the semantics." Consider the following example:
template<class T> struct A; extern A<int> a; void *foo() { return &a; } template<class T> struct A { #ifdef OPTION void *operator &() { return 0; } #endif };
The question here is whether it is necessary for template class A to declare an operator & for the semantics of the program to be affected. If it does not do so, the meaning of &a will be the same whether the class is complete or not and thus arguably the semantics of the program are not affected.
Presumably what was intended is whether the presence or absence of certain member declarations in the template class might be relevant in determining the meaning of the program. A clearer statement may be desirable.
If the overload resolution process can determine the correct function to call without instantiating a class template definition, it is unspecified whether that instantiation actually takes place.
The intent of this wording, as illustrated in the example in that paragraph, is to allow a "smart" implementation not to instantiate class templates if it can determine that such an instantiation will not affect the result of overload resolution, even though the algorithm described in clause 13 over requires that all the viable functions be enumerated, including functions that might be found as members of specializations.
Unfortunately, the looseness of the wording allowing this latitude for implementations makes it unclear what "the overload resolution process" is — is it the algorithm in 13 over or something else? — and what "the correct function" is.
If an implicit instantiation of a class template specialization is required and the template is declared but not defined, the program is ill-formed.
Here, it is not clear what conditions "require" an implicit instantiation. From the context, it would appear that the intent is to refer to the conditions in paragraph 4 that cause a specialization to be instantiated.
This interpretation, however, leads to different treatment of template and non-template incomplete classes. For example, by this interpretation,
class A; template <class T> struct TA; extern A a; extern TA<int> ta; void f(A*); void f(TA<int>*); int main() { f(&a); // well-formed; undefined if A // has operator &() member f(&ta); // ill-formed: cannot instantiate }
A different approach would be to understand "required" in paragraph 6 to mean that a complete type is required in the expression. In this interpretation, if an incomplete type is acceptable in the context and the class template definition is not visible, the instantiation is not attempted and the program is well-formed.
The meaning of "required" in paragraph 6 must be clarified.
In 14.7.2 temp.explicit paragraph 7 we read:
The explicit instantiation of a class template specialization implies the instantiation of all of its members not previously explicitly specialized in the translation unit containing the explicit instantiation.
Is "member" intended to mean "non-inherited member?" If yes, maybe it should be clarified since 10 class.derived paragraph 1 says,
Unless redefined in the derived class, members of a base class are also considered to be members of the derived class.
John Spicer: Certain access checks are suppressed on explicit instantiations. 14.7.2 temp.explicit paragraph 8 says:
The usual access checking rules do not apply to names used to specify explicit instantiations. [Note: In particular, the template arguments and names used in the function declarator (including parameter types, return types and exception specifications) may be private types or objects which would normally not be accessible and the template may be a member template or member function which would not normally be accessible. ]I was surprised that similar wording does not exist (that I could find) for explicit specializations. I believe that the two cases should be handled equivalently in the example below (i.e., that the specialization should be permitted).
template <class T> struct C { void f(); void g(); }; template <class T> void C<T>::f(){} template <class T> void C<T>::g(){} class A { class B {}; void f(); }; template void C<A::B>::f(); // okay template <> void C<A::B>::g(); // error - A::B inaccessible void A::f() { C<B> cb; cb.f(); }
Mike Miller: According to the note in 14.3 temp.arg paragraph 3,
if the name of a template-argument is accessible at the point where it is used as a template-argument, there is no further access restriction in the resulting instantiation where the corresponding template-parameter name is used.(Is this specified anywhere in the normative text? Should it be?) In the absence of text to the contrary, this blanket permission apparently applies to explicitly-specialized templates as well as to implicitly-generated ones (is that right?). If so, I don't see any reason that an explicit instantiation should be treated differently from an explicit specialization, even though the latter involves new program text and the former is just a placement instruction to the implementation.
Consider this example:
namespace N { template <class T> void f(T){} template <class T> void g(T){} template <> void f(int); template <> void f(char); template <> void g(char); } using namespace N; namespace M { template <> void N::f(char){} // prohibited by standard template <class T> void g(T){} template <> void g(char){} // specialization of M::g or ambiguous? template void f(long); // instantiation of N::f? } template <class T> void g(T){} template <> void N::f(char){} // okay template <> void f(int){} // is this a valid specialization of N::f? template void g(int); // instantiation of ::g(int) or ambiguous?
The question here is whether unqualified names made visible by a using-directive can be used as the declarator in an explicit instantiation or explicit specialization.
Note that this question is already answered for qualified names in 8.3 dcl.meaning paragraph 1. In a qualified name such as N::f, f must be a member of class or namespace N, not a name made visible in N by a using-directive (or a using-declaration, for that matter).
The standard does not, as far as a I can tell, specify the behavior of these cases one way or another.
My opinion is that names from using-directives should not be considered when looking up the name in an unqualified declarator in an explicit specialization or explicit instantiation. In such cases, it is reasonable to insist that the programmer know exactly which template is being specialized or instantiated, and that a qualified name must be used if the template is a member of a namespace.
As the example illustrates, allowing names from using-directives to be used would also have the affect of making ambiguous otherwise valid instantiation and specialization directives.
Furthermore, permitting names from using-directives would require an additional rule to prohibit the explicit instantiation of an entity in one namespace from being done in another (non-enclosing) namespace (as in the instantiation of f in namespace M in the example).
Mike Miller: I believe the explicit specialization case is already covered by 7.3.1.2 namespace.memdef paragraph 2, which requires using a qualified name to define a namespace member outside its namespace.
John Spicer: 7.3.1.2 namespace.memdef deals with namespace members. An explicit specialization directive deals with something that is a specialization of a namespace member. I don't think the rules in 7.3.1.2 namespace.memdef could be taken to apply to specializations unless the standard said so explicitly.
Suggested resolution (Jens Maurer):
Add in 14.7.2 temp.explicit paragraph 2 before the example:
If the explicit instantiation appears outside of the namespace of its template, the declaration shall declare a qualified-id.
Change the first sentence of 7.3.1.2 namespace.memdef paragraph 2 from
Members of a named namespace can also be defined...
to
Members (including explicit specializations of templates (14.7.3 temp.expl.spec)) of a named namespace can also be defined...
Change the last sentence of 14.7.3 temp.expl.spec paragraph 2 from
If the declaration is not a definition, the specialization may be defined later in the namespace in which the explicit specialization was declared, or in a namespace that encloses the one in which the explicit specialization was declared.
to
If the declaration is not a definition, the specialization may be defined later (7.3.1.2 namespace.memdef).
The note in paragraph 5 of 14.8.1 temp.arg.explicit makes clear that explicit template arguments cannot be supplied in invocations of constructors and conversion functions because they are called without using a name. However, there is nothing in the current wording of the Standard that makes declaring a constructor or conversion operator that is unusable because of nondeduced parameters (i.e., that would need to be specified explicitly) ill-formed. It would be a service to the programmer to diagnose this useless construct as early as possible.
Nicolai Josuttis sent me an example like the following:
template <typename RET, typename T1, typename T2> const RET& min (const T1& a, const T2& b) { return (a < b ? a : b); } template const int& min<int>(const int&,const int&); // #1 template const int& min(const int&,const int&); // #2
Among the questions was whether explicit instantiation #2 is valid, where deduction is required to determine the type of RET.
The first thing I realized when researching this is that the standard does not really spell out the rules for deduction in declarative contexts (friend declarations, explicit specializations, and explicit instantiations). For explicit instantiations, 14.7.2 temp.explicit paragraph 2 does mention deduction, but it doesn't say which set of deduction rules from 14.8.2 temp.deduct should be applied.
Second, Nicolai pointed out that 14.7.2 temp.explicit paragraph 6 says
A trailing template-argument can be left unspecified in an explicit instantiation provided it can be deduced from the type of a function parameter (14.8.2 temp.deduct).
This prohibits cases like #2, but I believe this was not considered in the wording as there is no reason not to include the return type in the deduction process.
I think there may have been some confusion because the return type is excluded when doing deduction on a function call. But there are contexts where the return type is included in deduction, for example, when taking the address of a function template specialization.
Suggested resolution:
It was tentatively agreed at the Santa Cruz meeting that exception specifications should fully participate in the type system. This change would address gaps in the current static checking of exception specifications such as
void (*p)() throw(int); void (**pp)() throw() = &p; // not currently an error
This is such a major change that it deserves to be a separate issue.
See also issues 25, 87, and 133.
Destructors that throw can easily cause programs to terminate, with no possible defense. Example: Given
struct XY { X x; Y y; };
Assume that X::~X() is the only destructor in the entire program that can throw. Assume further that Y construction is the only other operation in the whole program that can throw. Then XY cannot be used safely, in any context whatsoever, period — even simply declaring an XY object can crash the program:
XY xy; // construction attempt might terminate program: // 1. construct x -- succeeds // 2. construct y -- fails, throws exception // 3. clean up by destroying x -- fails, throws exception, // but an exception is already active, so call // std::terminate() (oops) // there is no defenseSo it is highly dangerous to have even one destructor that could throw.
Suggested Resolution:
Fix the above problem in one of the following two ways. I prefer the first.
Fergus Henderson: I disagree. Code using XY may well be safe, if X::~X() only throws if std::uncaught_exception() is false.
I think the current exception handling scheme in C++ is certainly flawed, but the flaws are IMHO design flaws, not minor technical defects, and I don't think they can be solved by minor tweaks to the existing design. I think that at this point it is probably better to keep the standard stable, and learn to live with the existing flaws, rather than trying to solve them via TC.
Bjarne Stroustrup: I strongly prefer to have the call to std::terminate() be conforming. I see std::terminate() as a proper way to blow away "the current mess" and get to the next level of error handling. I do not want that escape to be non-conforming — that would imply that programs relying on a error handling based on serious errors being handled by terminating a process (which happens to be a C++ program) in std::terminate() becomes non-conforming. In many systems, there are — and/or should be — error-handling and recovery mechanisms beyond what is offered by a single C++ program.
Andy Koenig: If we were to prohibit writing a destructor that can throw, how would I solve the following problem?
I want to write a class that does buffered output. Among the other properties of that class is that destroying an object of that class writes the last buffer on the output device before freeing memory.
What should my class do if writing that last buffer indicates a hardware output error? My user had the option to flush the last buffer explicitly before destroying the object, but didn't do so, and therefore did not anticipate such a problem. Unfortunately, the problem happened anyway. Should I be required to suppress this error indication anyway? In all cases?
In practice, I would rather thrown an exception, even at the risk of crashing the program if we happen to be in the middle of stack unwinding. The reason is that the program would crash only if a hardware error occurred in the middle of cleaning up from some other error that was in the process of being handled. I would rather have such a bizarre coincidence cause a crash, which stands a chance of being diagnosed later, than to be ignored entirely and leave the system in a state where the ignore error could cause other trouble later that is even harder to diagnose.
If I'm not allowed to throw an exception when I detect this problem, what are my options?
Herb Sutter: I understand that some people might feel that "a failed dtor during stack unwinding is preferable in certain cases" (e.g., when recovery can be done beyond the scope of the program), but the problem is "says who?" It is the application program that should be able to decide whether or not such semantics are correct for it, and the problem here is that with the status quo a program cannot defend itself against a std::terminate() — period. The lower-level code makes the decision for everyone. In the original example, the mere existence of an XY object puts at risk every program that uses it, whether std::terminate() makes sense for that program or not, and there is no way for a program to protect itself.
That the "it's okay if the process goes south should a rare combination of things happen" decision should be made by lower-level code (e.g., X dtor) for all apps that use it, and which doesn't even understand the context of any of the hundreds of apps that use it, just cannot be correct.
It is not clear from the Standard what the result of the following example should be:
#define NIL(xxx) xxx #define G_0(arg) NIL(G_1)(arg) #define G_1(arg) NIL(arg) G_0(42)
The relevant text from the Standard is found in 16.3.4 cpp.rescan paragraph 2:
If the name of the macro being replaced is found during this scan of the replacement list (not including the rest of the source file's preprocessing tokens), it is not replaced. Further, if any nested replacements encounter the name of the macro being replaced, it is not replaced. These nonreplaced macro name preprocessing tokens are no longer available for further replacement even if they are later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced.
The sequence of expansion of G0(42) is as follows:
G0(42) NIL(G_1)(42) G_1(42) NIL(42)
The question is whether the use of NIL in the last line of this sequence qualifies for non-replacement under the cited text. If it does, the result will be NIL(42). If it does not, the result will be simply 42.
The original intent of the J11 committee in this text was that the result should be 42, as demonstrated by the original pseudo-code description of the replacement algorithm provided by Dave Prosser, its author. The English description, however, omits some of the subtleties of the pseudo-code and thus arguably gives an incorrect answer for this case.
Suggested resolution (Mike Miller): Replace the cited paragraph with the following:
As long as the scan involves only preprocessing tokens from a given macro's replacement list, or tokens resulting from a replacement of those tokens, an occurrence of the macro's name will not result in further replacement, even if it is later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced.
Once the scan reaches the preprocessing token following a macro's replacement list — including as part of the argument list for that or another macro — the macro's name is once again available for replacement. [Example:
#define NIL(xxx) xxx #define G_0(arg) NIL(G_1)(arg) #define G_1(arg) NIL(arg) G_0(42) // result is 42, not NIL(42)The reason that NIL(42) is replaced is that (42) comes from outside the replacement list of NIL(G_1), hence the occurrence of NIL within the replacement list for NIL(G_1) (via the replacement of G_1(42)) is not marked as nonreplaceable. —end example]
(Note: The resolution of this issue must be coordinated with J11/WG14.)
The grammar in Appendix A does not indicate a grammar sentence symbol and is therefore formally not a grammar.
During the discussion of issues 167 and 174, it became apparent that there was no consensus on the meaning of deprecation. Some thought that deprecating a feature reflected an intent to remove it from the language. Others viewed it more as an encouragement to programmers not to use certain constructs, even though they might be supported in perpetuity.
There is a formal-sounding definition of deprecation in Annex D depr paragraph 2:
deprecated is defined as: Normative for the current edition of the Standard, but not guaranteed to be part of the Standard in future revisions.However, this definition would appear to say that any non-deprecated feature is "guaranteed to be part of the Standard in future revisions." It's not clear that that implication was intended, so this definition may need to be amended.
This issue is intended to provide an avenue for discussing and resolving those questions, after which the original issues may be reopened if that is deemed desirable.
The list of identifier characters specified in the C++ standard annex E extendid and the C99 standard annex D are different. The C99 standard includes more characters.
The C++ standard says that the characters are from "ISO/IEC PDTR 10176" while the C99 standard says "ISO/IEC TR 10176". I'm guessing that the PDTR is an earlier draft of the TR.
Should the list in the C++ standard be updated?
Tom Plum: In my opinion, the "identifier character" issue has not been resolved with certainty within SC22.
One critical difference in C99 was the decision to allow a compiler to accept more characters than are given in the annex. This allows for future expansion.
The broader issue concerns the venue in which the "identifier character" issue will receive ongoing resolution.
Notes from 10/00 meeting:
The core language working group expressed a strong preference (13/0/5 in favor/opposed/abstaining) that the list of identifier characters should be extensible, as is the case in C99. However, the fact that this topic is under active discussion by other bodies was deemed sufficient reason to defer any changes to the C++ specification until the situation is more stable.