| Document number: | PL22.16/10-0016 = WG21 N3026 |
| Date: | 2010-02-16 |
| Project: | Programming Language C++ |
| Reference: | ISO/IEC IS 14882:2003 |
| Reply to: | William M. Miller |
| Edison Design Group, Inc. | |
| wmm@edg.com |
This document contains the C++ core language issues on which the Committee (J16 + WG21) has not yet acted, that is, issues with status "Ready," "Tentatively 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:
Section references in this document reflect the section numbering of document PL22.16/09-0190 = WG21 N3000.
The purpose of these documents is to record the disposition of issues that have come before the Core Language Working Group of the ANSI (INCITS PL22.16) and ISO (WG21) C++ Standard Committee.
Some issues represent potential defects in the ISO/IEC IS 14882:2003 document and corrected defects in the earlier ISO/IEC 14882:1998 document; others refer to text in the working draft for the next revision of the C++ language, informally known as C++0x, and not to any Standard text. Issues 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.open-std.org/jtc1/sc22/wg21. Requests for further information about these documents should include the document number, reference ISO/IEC 14882:2003, and be submitted to the InterNational Committee for Information Technology Standards (INCITS), 1250 Eye Street NW, Suite 200, 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.comeaucomputing.com/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 PL22.16 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.
Tentatively Ready: Like "ready" except that the resolution was produced and approved by a subset of the working group membership between meetings. Persons not participating in these betwee-meeting activities are encouraged to review such resolutions carefully and to alert the working group with any problems that may be found.
DR: The full 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.
TC1: A DR issue included in Technical Corrigendum 1. TC1 is a revision of the Standard issued in 2003.
CD1: A DR issue not resolved in TC1 but included in Committee Draft 1. CD1 was advanced for balloting at the September, 2008 WG21 meeting.
WP: A DR issue whose resolution is reflected in the current Working Paper. The Working Paper is a draft for a future version of the Standard.
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. 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 as an extension proposal.
Concepts: The issue relates to the “Concepts” proposal that was removed from the working paper at the Frankfurt (July, 2009) meeting and hence is no longer under consideration.
1.10 [intro.multithread] paragraph 12 says,
A visible side effect A on an object M with respect to a value computation B of M satisfies the conditions:
A happens before B, and
there is no other side effect X to M such that A happens before X and X happens before B.
The value of a non-atomic scalar object M, as determined by evaluation B, shall be the value stored by the visible side effect A. [Note: If there is ambiguity about which side effect to a non-atomic object is visible, then there is a data race, and the behavior is undefined. —end note]
The note here suggests that, except in the case of a data race, visible side effects to value computation can always be determined. But unsequenced and indeterminately sequenced side effects on the same object create ambiguities with respect to a later value computation as well. So the wording needs to be revisited, see the following examples.
int main(){
int i = 0;
i = // unsequenced side effect A
i++; // unsequenced side effect B
return i; // value computation C
}
According to the definition in the draft, both A and B are visible side effects to C. However, there is no data race, because (paragraph 14) a race involves at least two threads. So the note in paragraph 12 is logically false.
The model introduces the special case of indeterminately sequenced side effects, that leave open what execution order is taken in a concrete situation. If the execution paths access the same data, unpredictable results are possible, just as it is the case with data races. Whereas data races constitute undefined behavior, indeterminatedly sequenced side effects on the same object do not. As a consequence of this disparity, indeterminately sequenced execution occasionally needs exceptional treatment.
int i = 0;
int f(){
return
i = 1; // side effect A
}
int g(){
return
i = 2; // side effect B
}
int h(int, int){
return i; // value computation C
}
int main(){
return h(f(),g()); // function call D returns 1 or 2?
}
Here, either A or B is the visible side effect on the value computation C, but you cannot tell which (cf. 1.9 [intro.execution] paragraph 16). Although an ambiguity is present, it is neither because of a data race, nor is the behavior undefined, in total contradiction to the note.
Proposed resolution (October, 2009):
Change 1.10 [intro.multithread] paragraph 12 as follows:
...The value of a non-atomic scalar object or bit-field M, as determined by evaluation B, shall be the value stored by the visible side effect A. [Note: If there is ambiguity about which side effect to a non-atomic object or bit-field is visible, then there is a data race, and the behavior is either unspecified or undefined. —end note]...
The specification of raw string literals interacts poorly with the specification of preprocessing tokens. The grammar in 2.5 [lex.pptoken] has a production reading
This is echoed in the max-munch rule in paragraph 3:
If the input stream has been parsed into preprocessing tokens up to a given character, the next preprocessing token is the longest sequence of characters that could constitute a preprocessing token, even if that would cause further lexical analysis to fail.
This raises questions about the handling of raw string literals. Consider, for instance,
#define R "x"
const char* s = R"y";
The character sequence R"y" does not satisfy the syntactic requirements for a raw string. Should it be diagnosed as an ill-formed attempt at a raw string, or should it be well-formed, interpreting R as a preprocessor token that is a macro name and thus initializing s with a pointer to the string "xy"?
For another example, consider:
#define R "]"
const char* x = R"foo[";
Presumably this means that the entire rest of the file must be scanned for the characters ]foo" and, if they are not found, macro-expand R and initialize x with a pointer to the string "]foo[". Is this the intended result?
Finally, does the requirement in 2.14.5 [lex.string] that
A d-char-sequence shall consist of at most 16 characters.
mean that
#define R "x"
const char* y = R"12345678901234567[y]12345678901234567";
is ill-formed, or a valid initialization of y with a pointer to the string "x12345678901234567[y]12345678901234567"?
Additional note, June, 2009:
The translation of characters that are not in the basic source character set into universal-character-names in translation phase 1 raises an additional problem: each such character will occupy at least six of the 16 r-chars that are permitted. Thus, for example, R"@@@[]@@@" is ill-formed because @@@ becomes \u0040\u0040\u0040, which is 18 characters.
One possibility for addressing this might be to disallow the \ character completely as an d-char, which would have the effect of restricting r-chars to the basic source character set.
Proposed resolution (October, 2009):
Change the grammar in 2.14.5 [lex.string] as follows:
Change 2.14.5 [lex.string] paragraph 2 as follows:
A string literal that has an R in the prefix is a raw string literal. The d-char-sequence serves as a delimiter. The terminating d-char-sequence of a raw-string is the same sequence of characters as the initial d-char-sequence. A d-char-sequence shall consist of at most 16 characters. If the input stream contains a sequence of characters that could be the prefix and initial double quote of a raw string literal, such as R", those characters are considered to begin a raw string literal even if that literal is not well-formed. [Example:
#define R "x" const char* s = R"y"; // ill-formed raw string, not "x" "y"—end example]
Since members of the basic source character set can be written inside a string using a universal character name, it is not clear whether a UCN that represents ']' or one of the characters in the terminating d-char-sequence should be interpreted as that character or as an attempt to “escape” that character and prevent its interpretation as part of the terminating sequence of a raw character string.
Notes from the July, 2009 meeting:
The CWG supported a resolution in which the d-char-sequence of a raw string literal is considered to be outside the literal and thus, by 2.3 [lex.charset] paragraph 2, could not contain a UCN designating a member of the basic source character set.
Proposed resolution (October, 2009):
Change 2.3 [lex.charset] paragraph 2 as follows:
Additionally, if the hexadecimal value for a universal-character-name outside the c-char-sequence, s-char-sequence, or r-char-sequence of a character or string literal corresponds to a control character (in either of the ranges 0x00-0x1F or 0x7F-0x9F, both inclusive) or to a character in the basic source character set, the program is ill-formed.
2.14.8 [lex.ext] paragraph 5 says,
If L is a user-defined-string-literal, let str be the literal without its ud-suffix and let len be the number of characters (or code points) in str (i.e., its length excluding the terminating null character).
The length of a null-terminated string is defined in 17.5.2.1.4.1 [byte.strings] as the number of bytes preceding the terminator, but a single code point in a UTF-8 string can require more than one byte, so this sentence is inconsistent and needs to be revised to make clear which definition is in view.
Proposed resolution (October, 2009):
Change 2.14.8 [lex.ext] paragraph 5 as follows:
If L is a user-defined-string-literal, let str be the literal without its ud-suffix and let len be the number of characters (or code points) code units in str (i.e., its length excluding the terminating null character)...
At least in the new wording for 5.1.2 [expr.prim.lambda] paragraph 10 as found in paper N2927, this is explicitly assumed to be an entity. It should be investigated whether this should be added to the list of entities found in 3 [basic] paragraph 3.
Proposed resolution (October, 2009):
Change 3 [basic] paragraph 3 as follows:
An entity is a value, object, variable, reference, function, enumerator, type, class member, template, template specialization, namespace, or parameter pack, or this.
Change 3.2 [basic.def.odr] paragraph 2 as follows:
...is immediately applied. this is used if it appears as a potentially-evaluated expression (including as the result of the implicit transformation in the body of a non-static member function (9.3.1 [class.mfct.non-static])). A virtual member function...
Delete 5.1.2 [expr.prim.lambda] paragraph 7:
For the purpose of describing the behavior of lambda-expressions below, this is considered to be “used” if replacing this by an invented variable v with automatic storage duration and the same type as this would result in v being used (3.2 [basic.def.odr]).
The Standard uses the terms “block scope” and “local scope” interchangeably, but the former is never formally defined. Would it be better to use only one term consistently? “Block scope” seems to be more frequently used.
Notes from the October, 2007 meeting:
The CWG expressed a preference for the term “local scope.”
Notes from the September, 2008 meeting:
Reevaluating the relative prevalence of the two terms (including the fact that new uses of “block scope” are being introduced, e.g., in both the lambda and thread-local wording) led to CWG reversing its previous preference for “local scope.” The resolution will need to add a definition of “block scope” and should change the title of 3.3.3 [basic.scope.local].
Proposed resolution (October, 2009):
Change 3.3.2 [basic.scope.pdecl] paragraph 2 as follows:
[Note: a nonlocal name from an outer scope remains visible up to the point of declaration of the local name that hides it. [Example:
const int i = 2; { int i[i]; }declares a local block-scope array of two integers. —end example] —end note]
Change the section heading of 3.3.3 [basic.scope.local] from “Local scope” to “Block scope.”
Change 3.3.3 [basic.scope.local] paragraph 1 as follows:
A name declared in a block (6.3 [stmt.block]) is local to that block; it has block scope. Its potential scope begins at its point of declaration (3.3.2 [basic.scope.pdecl]) and ends at the end of its declarative region block. A variable declared at block scope is a local variable.
Change 3.3.3 [basic.scope.local] paragraph 3 as follows:
The name in a catch exception-declaration declared in an exception-declaration is local to the handler handler and shall not be redeclared in the outermost block of the handler handler.
Change 3.3.11 [basic.scope.hiding] paragraph 3 as follows:
In a member function definition, the declaration of a local name at block scope hides the declaration of a member of the class with the same name...
Change 3.5 [basic.link] paragraph 8 as follows:
...Moreover, except as noted, a name declared in a local at block scope (3.3.3 [basic.scope.local]) has no linkage...
Change 3.6.3 [basic.start.term] paragraph 1 as follows:
...For an object of array or class type, all subobjects of that object are destroyed before any local block-scope object with static storage duration initialized during the construction of the subobjects is destroyed.
Change 3.6.3 [basic.start.term] paragraph 2 as follows:
If a function contains a local block-scope object of static or thread storage duration that has been destroyed and the function is called during the destruction of an object with static or thread storage duration, the program has undefined behavior if the flow of control passes through the definition of the previously destroyed local block-scope object. Likewise, the behavior is undefined if the function-local block-scope object is used indirectly (i.e., through a pointer) after its destruction.
Change 3.6.3 [basic.start.term] paragraph 3 as follows:
If the completion of the initialization of a non-local non-block-scope object with static storage duration is sequenced before a call to std::atexit (see <cstdlib>, 18.5 [support.start.term]), the call to the function passed to std::atexit is sequenced before the call to the destructor for the object. If a call to std::atexit is sequenced before the completion of the initialization of a non-local non-block-scope object with static storage duration, the call to the destructor...
[Editorial note: the occurrences of “non-local” in this change are removed by the proposed resolution for issue 946.]
Change 6.3 [stmt.block] paragraph 1 as follows:
...A compound statement defines a local block scope (3.3 [basic.scope])...
Change 6.4 [stmt.select] paragraph 1 as follows:
...The substatement in a selection-statement (each substatement, in the else form of the if statement) implicitly defines a local block scope (3.3 [basic.scope])...
Change 6.4 [stmt.select] paragraph 5 as follows:
If a condition can be syntactically resolved as either an expression or the declaration of a local block-scope name, it is interpreted as a declaration.
Change 6.5 [stmt.iter] paragraph 2 as follows:
The substatement in an iteration-statement implicitly defines a local block scope (3.3 [basic.scope]) which is entered and exited each time through the loop.
Change 6.7 [stmt.dcl] paragraph 3 as follows:
...A program that jumps84 from a point where a local variable with automatic storage duration...
Change 6.7 [stmt.dcl] paragraph 4 as follows:
The zero-initialization (8.5 [dcl.init]) of all local block-scope objects with static storage duration (3.7.1 [basic.stc.static]) or thread storage duration (3.7.2 [basic.stc.thread]) is performed before any other initialization takes place. Constant initialization (3.6.2 [basic.start.init]) of a local block-scope entity with static storage duration, if applicable, is performed before its block is first entered. An implementation is permitted to perform early initialization of other local block-scope objects...
Change 6.7 [stmt.dcl] paragraph 5 as follows:
The destructor for a local block-scope object with static or thread storage duration will be executed if and only if the variable was constructed. [Note: 3.6.3 [basic.start.term] describes the order in which local block-scope objects with static and thread storage duration are destroyed. —end note]
Change 8.4 [dcl.fct.def] paragraph 7 as follows:
In the function-body, a function-local predefined variable denotes a local block-scope object of static storage duration that is implicitly defined (see 3.3.3 [basic.scope.local]).
Change the example in 9.1 [class.name] paragraph 2 as follows:
...
void g() {
struct s; // hide global struct s
// with a local block-scope declaration
...
Change the example in 9.1 [class.name] paragraph 3 as follows:
...
void g(int s) {
struct s* p = new struct s; // global s
p->a = s; // local parameter s
}
18.5 [support.start.term] paragraph 7 says that the order of destruction of objects with static storage duration and calls to functions registered by calling std::atexit is given in 3.6.3 [basic.start.term]. Paragraph 1 of 3.6.3 [basic.start.term] says,
If the completion of the constructor or dynamic initialization of an object with static storage duration is sequenced before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first.
This wording covers both local and namespace-scope objects, so it fixes the relative ordering of local object destructors with respect to those of namespace scope. Paragraph 3 says,
If the completion of the initialization of a non-local object with static storage duration is sequenced before a call to std::atexit (see <cstdlib>, 18.5 [support.start.term]), the call to the function passed to std::atexit is sequenced before the call to the destructor for the object. If a call to std::atexit is sequenced before the completion of the initialization of a non-local object with static storage duration, the call to the destructor for the object is sequenced before the call to the function passed to std::atexit.
This fixes the relative ordering of destructors for namespace scope objects with respect to calls of atexit functions. However, the relative ordering of local destructors and atexit functions is left unspecified.
In the 2003 Standard, this was clear: 18.3 paragraph 8 said,
A local static object obj3 is destroyed at the same time it would be if a function calling the obj3 destructor were registered with atexit at the completion of the obj3 constructor.
Proposed resolution (October, 2009):
Change 3.6.3 [basic.start.term] paragraph 3 as follows:
If the completion of the initialization of a non-local an object with static storage duration is sequenced before a call to std::atexit (see <cstdlib>, 18.5 [support.start.term]), the call to the function passed to std::atexit is sequenced before the call to the destructor for the object. If a call to std::atexit is sequenced before the completion of the initialization of a non-local an object with static storage duration, the call to the destructor for the object is sequenced before the call to the function passed to std::atexit...
According to 20.8.12.6 [util.dynamic.safety] paragraph 16, when std::get_pointer_safety() returns std::pointer_safety::relaxed,
pointers that are not safely derived will be treated the same as pointers that are safely derived for the duration of the program.
However, 3.7.4.3 [basic.stc.dynamic.safety] paragraph 4 says unconditionally that
If a pointer value that is not a safely-derived pointer value is dereferenced or deallocated, and the referenced complete object is of dynamic storage duration and has not previously been declared reachable (20.8.12.6 [util.dynamic.safety]), the behavior is undefined.
This is a contradiction: the library clause attempts to constrain undefined behavior, which by definition is unconstrained.
Proposed resolution (July, 2009):
Change 3.7.4.3 [basic.stc.dynamic.safety] paragraph 4 as follows to define the terms “strict pointer safety” and “relaxed pointer safety,” which could then be used by the library clauses to achieve the desired effect:
An implementation may have relaxed pointer safety, in which case the validity of a pointer value does not depend on whether it is a safely-derived pointer value or not. Alternatively, an implementation may have strict pointer safety, in which case if If a pointer value that is not a safely-derived pointer value is dereferenced or deallocated, and the referenced complete object is of dynamic storage duration and has not previously been declared reachable (20.8.12.6 [util.dynamic.safety]), the behavior is undefined. [Note: this is true even if the unsafely-derived pointer value might compare equal to some safely-derived pointer value. —end note] It is implementation-defined whether an implementation has relaxed or strict pointer safety.
Read literally, 3.8 [basic.life] paragraphs 1 and 5 would make any access to non-static members of a class from the class's destructor undefined behavior. This is clearly not the intent.
Proposed resolution (October, 2009):
Change 3.8 [basic.life] paragraphs 5-6 as follows:
...any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways. Such For an object under construction or destruction, see 12.7 [class.cdtor]. Otherwise, such a pointer refers to allocated storage...
...any lvalue which refers to the original object may be used but only in limited ways. Such For an object under construction or destruction, see 12.7 [class.cdtor]. Otherwise, such an lvalue refers to allocated storage...
3.10 [basic.lval] paragraph 7 says,
Whenever an lvalue appears in a context where an rvalue is expected, the lvalue is converted to an rvalue
That is not correct in the context of an attempt to bind an rvalue reference to an lvalue (8.5.3 [dcl.init.ref]).
Proposed resolution (October, 2009):
Change 3.10 [basic.lval] paragraph 7 as follows:
Whenever an lvalue appears in a context where an rvalue is expected and an lvalue is not explicitly prohibited (as, for example, in 8.5.3 [dcl.init.ref]), the lvalue it is converted to an rvalue; see 4.1 [conv.lval], 4.2 [conv.array], and 4.3 [conv.func].
The following is not allowed by the current syntax of lambda-capture but would be useful:
template <typename ...Args> void f(Args... args) {
auto l = [&, args...] { return g(args...); };
}
Proposed resolution (October, 2009):
Change the grammar in 5.1.2 [expr.prim.lambda] paragraph 1 as follows:
Add a new paragraph at the end of 5.1.2 [expr.prim.lambda]:
A capture followed by an ellipsis is a pack expansion (14.6.3 [temp.variadic]). [Example:
template<typename ...Args> void f(Args... args) { auto l = [&, args...] { return g(args...); }; l(); }—end example]
Add a new bullet to the list in 14.6.3 [temp.variadic] paragraph 4:
[Editorial note: the editor may wish to consider sorting the bullets in this list in order of section reference.]
The specification of the members of a closure type does not rule out the possibility that its operator() might be virtual. It would be better to make it clear that it cannot.
Proposed resolution (October, 2009):
Change 5.1.2 [expr.prim.lambda] paragraph 5 as follows:
... followed by mutable. It is not neither virtual nor declared volatile. Default arguments...
The note in 5.2.10 [expr.reinterpret.cast] paragraph 2 says,
Subject to the restrictions in this section, an expression may be cast to its own type using a reinterpret_cast operator.
However, there is nothing in the normative text that permits this conversion, and paragraph 1 forbids any conversion not explicitly permitted.
(See also issue 944.)
Proposed resolution (October, 2009):
Change 5.2.10 [expr.reinterpret.cast] paragraph 2 as follows:
The reinterpret_cast operator shall not cast away constness (5.2.11 [expr.const.cast]). [Note: Subject to the restrictions in this section, an expression may be cast to its own type using a reinterpret_cast operator. —end note] An expression of integral, enumeration, pointer, or pointer-to-member type can be explictly converted to its own type; such a cast yields the value of its operand.
Change 5.2.10 [expr.reinterpret.cast] paragraph 10 as follows:
An rvalue of type “pointer to member of X of type T1” can be explicitly converted to an rvalue of a different type “pointer to member of Y of type T2” if T1 and T2 are both function types or both object types...
The resolution of issue 39 changed the diagnosis of ambiguity because of multiple subobjects from being a lookup error to being diagnosed where the result of the lookup is used. The formation of a pointer to member is one such context but was overlooked in the changes. 5.3.1 [expr.unary.op] paragraph 3 should have language similar to 5.2.5 [expr.ref] paragraph 5 and should be mentioned in the note in 10.2 [class.member.lookup] paragraph 13.
Proposed resolution (October, 2009):
Change 5.3.1 [expr.unary.op] paragraph 3 as follows:
...For a qualified-id, if the member is a static member of type “T”, the type of the result is plain “pointer to T.” If the member is a non-static member of class C of type T, the type of the result is “pointer to member of class C of type T.,” and the program is ill-formed if C is an ambiguous base (10.2 [class.member.lookup]) of the class designated by the nested-name-specifier of the qualified-id....
Change 10.2 [class.member.lookup] paragraph 13 as follows:
[Note: Even if the result of name lookup is unambiguous, use of a name found in multiple subobjects might still be ambiguous (4.11 [conv.mem], 5.2.5 [expr.ref], 5.3.1 [expr.unary.op], 11.2 [class.access.base]). —end note] [Example:...
The current wording of the draft does not indicate what is supposed to happen when an rvalue of type std::nullptr_t is compared with an integral null pointer constant. (This could occur, for example, in template code like
template<typename T> void f(T t) {
if (t == 0) // ...
}
in a call like f(nullptr) -- presumably the body of the template was written before nullptr became available and thus used an integral null pointer constant.) Because an integral null pointer constant can be converted to std::nullptr_t (4.10 [conv.ptr] paragraph 1), one might expect that 0 would be converted to std::nullptr_t and the two operands would compare equal, but 5.9 [expr.rel] paragraph 2 does not handle this case at all, leaving it as undefined behavior.
The current situation is more well-defined (but perhaps not better) with respect to the conditional operator. 5.16 [expr.cond] paragraphs 3-6 make it ill-formed to have std::nullptr_t and 0 as the second and third operands. Again, it's not too hard to imagine a legacy function template like
template<typename T> void f(T t, bool b) {
T t = b ? t : 0;
}
which would be ill-formed under the current wording of 5.16 [expr.cond].
Either 5.9 [expr.rel] and 5.10 [expr.eq] should be changed to make this combination of operands ill-formed, or those two sections should be changed to give the comparison defined semantics and 5.16 [expr.cond] should be changed to make those operands well-formed.
Proposed resolution (October, 2009):
Change 5.9 [expr.rel] paragraph 2 as follows:
The usual arithmetic conversions are performed on operands of arithmetic or enumeration type. Pointer conversions (4.10 [conv.ptr]) and qualification conversions (4.4 [conv.qual]) are performed on pointer operands (or on a pointer operand and a null pointer constant, or on two null pointer constants, at least one of which is non-integral) to bring them to their composite pointer type. If one operand is a null pointer constant, the composite pointer type is std::nullptr_t if the other operand is also a null pointer constant or, if the other operand is a pointer, the type of the other operand. Otherwise...
Change 5.16 [expr.cond] paragraph 6 bullet 3 as follows:
According to 7.1.1 [dcl.stc] paragraph 4,
The thread_local specifier shall be applied only to the names of objects or references of namespace scope and to the names of objects or references of block scope that also specify static.
Why require two keywords, where one on its own becomes ill-formed? thread_local should imply static in this case, and the combination of keywords should be banned rather than required. This would also eliminate the one of two exceptions documented in paragraph 1.
Notes from the July, 2009 meeting:
The consensus of the CWG was that thread_local should imply static, as suggested, but that the combination should still be allowed (it is needed, for example, for thread-local static data members).
Proposed resolution (October, 2009):
Change 7.1.1 [dcl.stc] paragraph 4 as follows:
The thread_local specifier indicates that the named entity has thread storage duration (3.7.2 [basic.stc.thread]). It shall be applied only to the names of objects or references of namespace scope, to the names of objects or references of or block scope that also specify extern or static, and to the names of static data members. It specifies that the named object or reference has thread storage duration (3.7.2 [basic.stc.thread]). When thread_local is applied to a variable of block scope the storage-class-specifier static is implied if it does not appear explicitly.
The normative text in 7.1.6.1 [dcl.type.cv] paragraph 2 reads,
An object declared in namespace scope with a const-qualified type has internal linkage unless it is explicitly declared extern or unless it was previously declared to have external linkage. A variable of non-volatile const-qualified integral or enumeration type initialized by an integral constant expression can be used in integral constant expressions (5.19 [expr.const]).
These two sentences parallel the specifications of 7.1.1 [dcl.stc] paragraph 7 and 5.19 [expr.const]. However, the passages are not identical, leading to questions about whether the meanings are the same.
Proposed resolution (October, 2009):
Change 7.1.6.1 [dcl.type.cv] paragraph 2 as follows:
An object declared in namespace scope with a const-qualified type has internal linkage unless it is explicitly declared extern or unless it was previously declared to have external linkage. A variable of non-volatile const-qualified integral or enumeration type initialized by an integral constant expression can be used in integral constant expressions (5.19 [expr.const]). [Note: Declaring a variable const can affect its linkage (7.1.1 [dcl.stc]) and its usability in constant expressions (5.19 [expr.const]). As as described in 8.5 [dcl.init], the definition of an object or subobject of const-qualified type must specify an initializer or be subject to default-initialization. —end note]
References to references are ill-formed, but special provision is made in cases where this occurs via typedefs or template type parameters. A similar provision is probably needed for types resulting from decltype:
int x, *p = &x;
decltype(*p) &y = *p; // reference to reference is ill-formed
Proposed resolution (October, 2009):
Delete 7.1.3 [dcl.typedef] paragraph 9:
If a typedef TD names a type that is a reference to a type T, an attempt to create the type “lvalue reference to cv TD” creates the type “lvalue reference to T,” while an attempt to create the type “rvalue reference to cv TD” creates the type TD. [Example: ... —end example]
Delete 14.4.1 [temp.arg.type] paragraph 4:
If a template-argument for a template-parameter T names a type that is a reference to a type A, an attempt to create the type “lvalue reference to cv T” creates the type “lvalue reference to A,” while an attempt to create the type “rvalue reference to cv T” creates the type T [Example: ... —end example]
Add the following as a new paragraph at the end of 8.3.2 [dcl.ref]:
If a typedef (7.1.3 [dcl.typedef]), a type template-parameter (14.4.1 [temp.arg.type]), or a decltype-specifier (7.1.6.2 [dcl.type.simple]) denotes a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T,” while an attempt to create the type “rvalue reference to cv TR” creates the type TR. [Example:
int i; typedef int& LRI; typedef int&& RRI; LRI& r1 = i; // r1 has the type int& const LRI& r2 = i; // r2 has the type int& const LRI&& r3 = i; // r3 has the type int& RRI& r4 = i; // r4 has the type int& RRI&& r5 = i; // r5 has the type int&& decltype(r2)& r6 = i; // r6 has the type int& decltype(r2)&& r7 = i; // r7 has the type int&—end example]
There is a lack of symmetry in the specification of attributes that apply to class and enum types. For example:
class X [[attr]]; // #1
typedef class Y [[attr]] YT; // #2
According to 7.1.6.3 [dcl.type.elab] paragraph 1, #1 associates the attr attribute with class X for all subsequent references. On the other hand, 8.3 [dcl.meaning] paragraph 5 says that #2 associates the attr attribute with the type but not with class Y.
Existing implementations (Microsoft, GNU, Sun) with attributes place an attribute that is intended to be associated with a class type between the class-key and the class name, and it would be preferable to adopt such an approach instead of the contextual approach in the current formulation.
Proposed resolution (October, 2009):
Change 3.3.2 [basic.scope.pdecl] paragraph 6 bullet 1 as follows:
for a declaration of the form
the identifier is declared...
Change 3.4.4 [basic.lookup.elab] paragraph 2 as follows:
...unless the elaborated-type-specifier appears in a declaration with the following form:
class-key attribute-specifieropt identifier attribute-specifieropt ;
the identifier is looked up... if the elaborated-type-specifier appears in a declaration with the form:
class-key attribute-specifieropt identifier attribute-specifieropt ;
the elaborated-type-specifier is a declaration...
In 7.1.6.3 [dcl.type.elab], change the grammar and paragraph 1 as follows:
elaborated-type-specifier:
class-key attribute-specifieropt ::opr nested-name-specifieropt identifier
...An attribute-specifier shall not appear in an elaborated-type-specifier unless the latter is the sole constituent of a declaration. If an elaborated-type-specifier is the sole constituent of a declaration, the declaration is ill-formed unless it is an explicit specialization (14.8.3 [temp.expl.spec]), an explicit instantiation (14.8.2 [temp.explicit]) or it has one of the following forms:
class-key attribute-specifieropt identifier attribute-specifieropt ;
...
Change the grammar in 7.2 [dcl.enum] paragraph 1 as follows:
Change the grammar in 9 [class] paragraph 1 as follows:
The auto specifier can be used only in certain contexts, as specified in 7.1.6.4 [dcl.spec.auto] paragraphs 2-3:
Otherwise (auto appearing with no type specifiers other than cv-qualifiers), the auto type-specifier signifies that the type of an object being declared shall be deduced from its initializer. The name of the object being declared shall not appear in the initializer expression.
This use of auto is allowed when declaring objects in a block (6.3 [stmt.block]), in namespace scope (3.3.6 [basic.scope.namespace]), and in a for-init-statement (6.5.3 [stmt.for]). The decl-specifier-seq shall be followed by one or more init-declarators, each of which shall have a non-empty initializer of either of the following forms:
= assignment-expression
( assignment-expression )
It was intended that auto could be used only at the top level of a declaration, but it is not clear whether this wording is sufficient to forbid usage like the following:
template <class T> struct A {};
template <class T> void f(A<T> x) {}
void g()
{
f(A<short>());
A<auto> x = A<short>();
}
Notes from the February, 2008 meeting:
It was agreed that the example should be ill-formed.
Proposed resolution (October, 2009):
Change 7.1.6.4 [dcl.spec.auto] paragraph 3 as follows:
...The auto shall appear as one of the decl-specifiers in the decl-specifier-seq and the decl-specifier-seq shall be followed by one or more init-declarators, each of which shall have a non-empty initializer.
7.1.6.4 [dcl.spec.auto] paragraph 6 says,
Once the type of a declarator-id has been determined according to 8.3 [dcl.meaning], the type of the declared variable using the declarator-id is determined from the type of its initializer using the rules for template argument deduction. Let T be the type that has been determined for a variable identifier d. Obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initializer is a braced-init-list (8.5.4 [dcl.init.list]), with std::initializer_list<U>. The type deduced for the variable d is then the deduced type determined using the rules of template argument deduction from a function call (14.9.2.1 [temp.deduct.call]), where P is a function template parameter type and the initializer for d is the corresponding argument.
The reference to “the deduced type” is unclear; it could be taken as referring either to the template parameter or to the function parameter type. 14.9.2.1 [temp.deduct.call] uses the term “deduced A,” and that usage should be repeated here.
Proposed resolution (October, 2009):
Change 7.1.6.4 [dcl.spec.auto] paragraph 6 as follows:
...The type deduced for the variable d is then the deduced type A determined using the rules of template argument deduction...
According to 7.3.4 [namespace.udir] paragraph 4,
The using-directive is transitive: if a scope contains a using-directive that nominates a second namespace that itself contains using-directives, the effect is as if the using-directives from the second namespace also appeared in the first.
This is true only for unqualified lookup; the algorithm in 3.4.3.2 [namespace.qual] paragraph 2 gives different results (the transitive closure terminates when a declaration of the name being looked up is found).
Proposed resolution (October, 2009):
Change 7.3.4 [namespace.udir] paragraph 4 as follows:
The For unqualified lookup (3.4.1 [basic.lookup.unqual]), the using-directive is transitive: if a scope contains a using-directive that nominates a second namespace that itself contains using-directives, the effect is as if the using-directives from the second namespace also appeared in the first. [Note: For qualified lookup, see 3.4.3.2 [namespace.qual]. —end note] [Example:...The terms “appertain” and “apply” are used in different ways in different subsections of 7.6 [dcl.attr]. A thorough editorial sweep of the entire section is needed to regularize their usage.
Proposed resolution (October, 2009):
Change 7.6.1 [dcl.attr.grammar] paragraph 4 as follows:
...If an attribute-specifier that appertains to some entity or statement contains an attribute that does not is not allowed to apply to that entity or statement, the program is ill-formed...
Change 7.6.2 [dcl.align] paragraph 1 as follows:
...The attribute can may be applied to a variable...
Change 7.6.3 [dcl.attr.noreturn] paragraph 1 as follows:
...The attribute applies may be applied to the declarator-id in a function declaration...
Change 7.6.4 [dcl.attr.final] paragraph 1 as follows:
...The attribute applies may be applied to class definitions...
Change 7.6.5 [dcl.attr.override] paragraph 1 as follows:
...The attribute applies may be applied to virtual member functions...
Change 7.6.5 [dcl.attr.override] paragraph 3 as follows:
...The attribute applies may be applied to class members...
Change 7.6.5 [dcl.attr.override] paragraph 5 as follows:
...The attribute applies may be applied to a class definition.
Change 7.6.6 [dcl.attr.depend] paragraph 1 as follows:
...The attribute applies may be applied to the declarator-id of a parameter-declaration... The attribute may also applies be applied to the declarator-id of a function declaration...
7.6.1 [dcl.attr.grammar] paragraph 3 specifies that keywords can be used as attribute-tokens. However, the alternative tokens in 2.6 [lex.digraph], such as bitor and compl, are not keywords. The text should be changed to make the alternative tokens acceptable as attribute-tokens as well.
Proposed resolution, October, 2009:
Change 7.6.1 [dcl.attr.grammar] paragraph 3 as follows:
...A If a keyword (2.12 [lex.key]) or an alternative token (2.6 [lex.digraph]) that satisfies the syntactic requirements of an identifier (2.11 [lex.name]) is contained in an attribute-token, it is considered an identifier...
According to 7.6.4 [dcl.attr.final] paragraph 1, the [[final]] attribute applied to a class is just a shorthand notation for marking each of the class's virtual functions as [[final]]. This is different from the similar usage in other languages, where it means that the class so marked cannot be used as a base class. This discrepancy is confusing, and the definition used by the other languages is more useful.
Notes from the March, 2009 meeting:
The intent of the [[final]] attribute is as an aid in optimization, to avoid virtual function calls when the final overrider is known. It is possible to use the [[final]] attribute to prevent derivation by marking the destructor as [[final]]; in fact, as most polymorphic classes will, as a matter of good programming practice, have a virtual destructor, marking the class as [[final]] will have the effect of preventing derivation.
Nonetheless, the general consensus of the CWG was to change the meaning of class [[final]] to parallel the usage in other languages.
Proposed resolution (October, 2009):
Change 7.6.4 [dcl.attr.final] paragraph 1 and add a new paragraph, as follows:
The attribute-token final specifies derivation semantics for a class and overriding semantics for a virtual function. It shall appear at most once in each attribute-list and no attribute-argument-clause shall be present. The attribute applies to class definitions and to virtual member functions being declared in a class definition. If the attribute is specified for a class definition, it is equivalent to being specified for each virtual member function of that class, including inherited member functions.
If some class B is marked final and a class D is derived from B the program is ill-formed.
Change the example in 7.6.4 [dcl.attr.final] paragraph 3 as follows:
struct B1 { virtual void f [[ final ]] (); }; struct D1 : B1 { void f(); // ill-formed }; struct [[ final ]] B2 { }; struct D2 : B2 { // ill-formed };
The current wording for the carries_dependency attribute does not limit it to value-returning functions (when applied to the declarator-id, indicating that the return value is affected), nor does it prohibit use in the declaration of a typedef or function pointer. Arguably these meaningless declarations should be prohibited.
Proposed resolution (October, 2009):
Change 7.6.6 [dcl.attr.depend] paragraph 1 as follows:
...The attribute applies to the declarator-id of a parameter-declaration in a function declaration or lambda, in which case it specifies that the initialization of the parameter carries a dependency to (1.10 [intro.multithread]) each lvalue-to-rvalue conversion (4.1 [conv.lval]) of that object. The attribute also applies to the declarator-id of a function declaration, in which case it specifies that the return value, if any, carries a dependency to the evaluation of the function call expression.
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.
It is not clear whether this applies to parameter packs or not. For example, is the following well-formed?
template <typename... T> void f(int i = 0, T ...args) { }
Note for comparison the corresponding wording in 14.2 [temp.param] paragraph 11 regarding template parameter packs:
If a template-parameter of a class template has a default template-argument, each subsequent template-parameter shall either have a default template-argument supplied or be a template parameter pack.
Proposed resolution (October, 2009):
Change 8.3.6 [dcl.fct.default] paragraph 4:
...In a given function declaration, all each parameters subsequent to a parameter with a default argument shall have a default arguments supplied in this or a previous declarations or shall be a function parameter pack. A default argument...
According to 8.4 [dcl.fct.def] paragraph 10,
A deleted definition of a function shall be the first declaration of the function.
The Standard is not currently clear about what the “first declaration” of an explicit specialization of a function template is. For example,
template<typename T> void f() { }
template<> void f<int>() = delete; // First declaration?
Proposed resolution (October, 2009):
A deleted definition of a function shall be the first declaration of the function or, for an explicit specialization of a function template, the first declaration of that specialization.
(This resolution also resolves issue 915.)
Notes from the October, 2009 meeting:
It was observed that this specification is complicated by the fact that the “first declaration” of a function might be in a block-extern declaration.
The only restriction placed on the use of “=default” in 8.4 [dcl.fct.def] paragraph 9 is that a defaulted function must be a special member function. However, there are many variations of declarations of special member functions, and it's not clear which of those should be able to be defaulted. Among the possibilities:
default arguments
by-value parameter for a copy assignment operator
exception specifications
arbitrary return values for copy assignment operators
a const reference parameter when the implicit function would have a non-const
Presumably, you should only be able to default a function if it is declared compatibly with the implicit declaration that would have been generated.
Proposed resolution (October, 2009):
Change 8.4 [dcl.fct.def] paragraph 9 as follows:
A function definition of the form:
decl-specifier-seqopt attribute-specifieropt declarator = default ;
is called an explicitly-defaulted definition. Only special member functions may be explicitly defaulted, and the implementation shall define them as if they had implicit definitions (12.1 [class.ctor], 12.4 [class.dtor], 12.8 [class.copy]). A function that is explicitly defaulted shall
be a special member function,
have the same declared function type (except for possibly-differing ref-qualifiers and except that in the case of a copy constructor or copy assignment operator, the parameter type may be “reference to non-const T,” where T is the name of the member function's class) as if it had been implicitly declared,
not have default arguments, and
not have an exception-specification.
[Note: This implies that parameter types, return type, and cv-qualifiers must match the hypothetical implicit declaration. —end note] An explicitly-defaulted function may be declared constexpr only if it would have been implicitly declared as constexpr. If it is explicitly defaulted on its first declaration,
it shall be public,
it shall not be explicit,
it shall not be virtual,
it is implicitly considered to have the same exception-specification as if it had been implicitly declared (15.4 [except.spec]), and
in the case of a copy constructor or copy assignment operator, it shall have the same parameter type as if it had been implicitly declared.
[Note: Such a special member function may be trivial, and thus its accessibility and explicitness should match the hypothetical implicit definition; see below. —end note] [Example:
struct S { S(int a = 0) = default; // ill-formed: default argument void operator=(const S&) = default; // ill-formed: non-matching return type ~S() throw() = default; // ill-formed: exception-specification private: S(S&); // OK: private copy constructor }; S::S(S&) = default; // OK: defines copy constructor—end example] Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them (12.1 [class.ctor], 12.4 [class.dtor], 12.8 [class.copy]), which might mean defining them as deleted.A special member function that would be implicitly defined as deleted may be explicitly defaulted only on its first declaration, in which case it is defined as deleted. A special member function is user-provided if it is user-declared and not explicitly defaulted on its first declaration. A user-provided explicitly-defaulted function is defined at the point where it is explicitly defaulted. [Note:...
[Editorial note: this change incorporates the overlapping portion of the resolution of issue 667.]
Change 12.1 [class.ctor] paragraph 6 as follows:
This resolution also resolves issue 905. See also issue 667.
The final set of declarations in the example following 8.5.4 [dcl.init.list] paragraph 3 bullet 3 is:
struct S2 {
int m1;
double m2,m3;
};
S2 s21 = { 1, 2, 3.0 }; // OK
S2 s22 { 1.0, 2, 3 }; // error: narrowing
S2 s23 {}; // OK: default to 0,0,0
However, S2 is an aggregate. Aggregates are handled in bullet 1, while bullet 3 deals with classes with constructors. This part of the example should be moved to the first bullet.
Proposed resolution (October, 2009):
Move the S2 example from bullet 3 to bullet 1 in 8.5.4 [dcl.init.list] paragraph 3:
If T is an aggregate, aggregate initialization is performed (8.5.1 [dcl.init.aggr]).
[Example:
double ad[] = { 1, 2.0 }; // OK
int ai[] = { 1, 2.0 }; // error: narrowing
struct S2 {
int m1;
double m2,m3;
};
S2 s21 = { 1, 2, 3.0 }; // OK
S2 s22 { 1.0, 2, 3 }; // error: narrowing
S2 s23 {}; // OK: default to 0,0,0
—end example]
Otherwise, if T is a specialization...
Otherwise, if T is a class type...
[Example:
...
S s3 { }; // OK: invoke #2
struct S2 {
int m1;
double m2,m3;
};
S2 s21 = { 1, 2, 3.0 }; // OK
S2 s22 { 1.0, 2, 3 }; // error: narrowing
S2 s23 {}; // OK: default to 0,0,0
—end example]
...
It is presumably possible to declare a defaulted copy constructor to be explicit. Should that render a class not trivially copyable, even though the copy constructor is trivial? That is, does being “trivally copyable” mean that copy initialization, and not just direct initialization, is possible?
A related question is whether the specification of triviality should require that the copy constructor and copy assignment operator must be public. (With the advent of “=default” it is possible to make them non-public, which was not the case when these definitions were crafted.)
Proposed resolution (October, 2009):
This issues is resolved by the resolution of issue 906.
(From message 14555.)
The reasons for which an implicitly-declared default constructor is defined as deleted, given in 12.1 [class.ctor] paragraph 4, all deal with cases in which a member cannot be default-initialized. Presumably a brace-or-equal-initializer for such a member would eliminate the need to define the constructor as deleted, but this case is not addressed by the current wording.
Proposed resolution (October, 2009):
Change 12.1 [class.ctor] paragraph 5, the second list, as follows:
An implicitly-declared default constructor for class X is defined as deleted if:
X is a union-like class that has a variant member with a non-trivial default constructor,
any non-static data member with no brace-or-equal-initializer is of reference type,
any non-static data member of const-qualified type (or array thereof) with no brace-or-equal-initializer does not have a user-provided default constructor, or
any direct or virtual base class, or non-static data member with no brace-or-qual-initializer, or direct or virtual base class has class type M (or array thereof) and either M has no default constructor, or if constructor or overload resolution (13.3 [over.match]) as applied to M's default constructor, results in an ambiguity or in a function that is deleted or inaccessible from the implicitly-declared default constructor.
Consider the following example:
struct A {
A() {
std::thread(&A::Func, this).detach();
}
virtual void Func() {
printf("In A");
}
};
struct B : public A {
virtual void Func() {
printf("In B");
}
};
struct C : public B {
virtual void Func() {
printf("In C");
}
};
C c;
What is the program allowed to print? Should it be undefined behavior or merely unspecified which of the Func()s is called?
There is a related question about which variables C::Func() can depend on having been constructed. Unless we want to require the equivalent of at least memory_order_consume on the presumed virtual function table pointer, I think the answer is just the members of A.
If I instead just have
A a;
I think the only reasonable behavior is to print In A.
Finally, given
struct F {
F() {
std::thread(&F::Func, this).detach();
}
virtual void Func() {
print("In F");
}
};
struct G : public F {
};
G g;
I can see the behavior being undefined, but I think a lot of people would be confused if it did anything other than print In F.
Suggested resolution:
I think the intent here is that an object should not be used in another thread until any non-trivial constructor has been called. One possible way of saying that would be to add a new paragraph at the end of 12.7 [class.cdtor]:
A constructor for a class with virtual functions or virtual base classes modifies a memory location in the object that is accessed by any access to a virtual function or virtual base class or by a dynamic_cast. [Note: This implies that access to an object by another thread while it is being constructed often introduces a data race (see 1.10 [intro.multithread]). —end note]
Proposed resolution (October, 2009):
Add the following as a new paragraph at the end of 3.8 [basic.life]:
In this section, “before” and “after” refer to the “happens before” relation (1.10 [intro.multithread]). [Note: Therefore, undefined behavior results if an object that is being constructed in one thread is referenced from a different thread without adequate synchronization. —end note]
Should the following class have a trivial copy assignment operator?
struct A {
int& m;
A();
A(const A&);
};
12.8 [class.copy] paragraph 11 does not mention whether the presence of reference members (or cv-qualifiers, etc.) should affect triviality. Should it?
One reason why this matters is that implementations have to make the builtin type trait operator __has_trivial_default_ctor(T) work so that they can support the type trait template std::has_trivial_default_constructor.
Assuming the answer is “yes,” it looks like we probably need similar wording for trivial default and trivial copy ctors.
Notes from the February, 2008 meeting:
Deleted special member functions are also not trivial. Resolution of this issue should be coordinated with the concepts proposal.
Notes from the June, 2008 meeting:
It appears that this issue will be resolved by the concepts proposal directly. The issue is in “review” status to check if that is indeed the case in the final version of the proposal.
Additional notes (May, 2009):
Consider the following example:
struct Base {
private:
~Base() = default;
};
struct Derived: Base {
};
The implicitly-declared destructor of Derived is defined as deleted because Base::~Base() is inaccessible, but it fulfills the requirements for being trivial. Presumably the Base destructor should be non-trivial, either by directly specifying that it is non-trivial or by specifying that it is user-provided. An alternative would be to make it ill-formed to attempt to declare a defaulted non-public special member function.
Any changes to the definition of triviality should be checked against 9 [class] paragraph 6 for any changes needed there to accommodate the new definitions.
Notes from the July, 2009 meeting:
The July, 2009 resolution of issue 906 addresses the example above (with an inaccessible defaulted destructor): a defaulted special member function can only have non-public access if the defaulted definition is outside the class, making it non-trivial. The example as written above would be ill-formed.
Proposed resolution (October, 2009):
Change 8.4 [dcl.fct.def] paragraph 9 as follows:
...Only special member functions may be explicitly defaulted. Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall define them as if they had provide implicit definitions for them (12.1 [class.ctor], 12.4 [class.dtor], 12.8 [class.copy]), which might mean defining them as deleted. A special member function that would be implicitly defined as deleted may be explicitly defaulted only on its first declaration, in which case it is defined as deleted. A special member function is user-provided if it is user-declared and not explicitly defaulted on its first declaration. A user-provided explicitly-defaulted function is defined at the point where it is explicitly defaulted. [Note:...
Change 12.1 [class.ctor] paragraphs 5-6 as follows:
A default constructor for a class X is a constructor of class X that can be called without an argument. If there is no user-declared constructor for class X, a constructor having no parameters is implicitly declared as defaulted (8.4 [dcl.fct.def]). An implicitly-declared default constructor is an inline public member of its class. A default constructor is trivial if it is not user-provided (8.4 [dcl.fct.def]) and if:
its class has no virtual functions (10.3 [class.virtual]) and no virtual base classes (10.1 [class.mi]), and
no non-static data member of its class has a brace-or-equal-initializer, and
all the direct base classes of its class have trivial default constructors, and
for all the non-static data members of its class that are of class type (or array thereof), each such class has a trivial default constructor.
An implicitly-declared defaulted default constructor for class X is defined as deleted if:
X is a union-like class that has a variant member with a non-trivial default constructor,
any non-static data member is of reference type,
any non-static data member of const-qualified type (or array thereof) does not have a user-provided default constructor, or
any non-static data member or direct or virtual base class has class type M (or array thereof) and M has no default constructor, or if overload resolution (13.3 [over.match]) as applied to M's default constructor, results in an ambiguity or a function that is deleted or inaccessible from the implicitly-declared default constructor.
A default constructor is trivial if it is neither user-provided nor deleted and if:
its class has no virtual functions (10.3 [class.virtual]) and no virtual base classes (10.1 [class.mi]), and
no non-static data member of its class has a brace-or-equal-initializer, and
all the direct base classes of its class have trivial default constructors, and
for all the non-static data members of its class that are of class type (or array thereof), each such class has a trivial default constructor.
Otherwise, the default constructor is non-trivial.
A non-user-provided default constructor for a class that is defaulted and not deleted is implicitly defined when it is used (3.2 [basic.def.odr]) to create an object of its class type (1.8 [intro.object]), or when it is explicitly defaulted after its first declaration. The implicitly-defined or explicitly-defaulted default constructor performs the set of initializations of the class that would be performed by a user-written default constructor for that class with no ctor-initializer (12.6.2 [class.base.init]) and an empty compound-statement. If that user-written default constructor would be ill-formed, the program is ill-formed. If that user-written default constructor would satisfy the requirements of a constexpr constructor (7.1.5 [dcl.constexpr]), the implicitly-defined default constructor is constexpr. Before the non-user-provided defaulted default constructor for a class is implicitly defined, all the non-user-provided default constructors for its base classes and its non-static data members shall have been implicitly defined. [Note: an implicitly-declared default constructor has an exception-specification (15.4 [except.spec]). An explicitly-defaulted definition has no implicit exception-specification. —end note]
Change 12.4 [class.dtor] paragraphs 3-4 as follows:
If a class has no user-declared destructor, a destructor is declared implicitly declared as defaulted (8.4 [dcl.fct.def]). An implicitly-declared destructor is an inline public member of its class. If the class is a union-like class that has a variant member with a non-trivial destructor, an implicitly-declared destructor is defined as deleted (8.4 [dcl.fct.def]). A destructor is trivial if it is not user-provided and if:
the destructor is not virtual,
all of the direct base classes of its class have trivial destructors, and
for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.
An implicitly-declared defaulted destructor for a class X is defined as deleted if:
X is a union-like class that has a variant member with a non-trivial destructor,
any of the non-static data members has class type M (or array thereof) and M has an a deleted destructor or a destructor that is inaccessible from the implicitly-declared destructor, or
any direct or virtual base class has a deleted destructor or a destructor that is inaccessible from the implicitly-declared destructor.
A destructor is trivial if it is neither user-provided nor deleted and if:
the destructor is not virtual,
all of the direct base classes of its class have trivial destructors, and
for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.
Otherwise, the destructor is non-trivial.
A non-user-provided destructor that is defaulted and not defined as deleted is implicitly defined when it is used to destroy an object of its class type (3.7 [basic.stc]), or when it is explicitly defaulted after its first declaration. A program is ill-formed if the class for which a destructor is implicitly defined or explicitly defaulted has:
a non-static data member of class type (or array thereof) with an inaccessible destructor, or
a base class with an inaccessible destructor.
Before the non-user-provided defaulted destructor for a class is implicitly defined, all the non-user-defined non-user-provided destructors for its base classes and its non-static data members shall have been implicitly defined. [Note: an implicitly-declared destructor has an exception-specification (15.4 [except.spec]). An explictly defaulted definition has no implicit exception-specification. —end note]
Change 12.8 [class.copy] paragraphs 4-9 as follows:
If the class definition does not explicitly declare a copy constructor, one is declared implicitly implicitly declared as defaulted (8.4 [dcl.fct.def]). Thus...
...An implicitly-declared copy constructor is an inline public member of its class. An implicitly-declared defaulted copy constructor for a class X is defined as deleted if X has: ...
A copy constructor for class X is trivial trivial if it is not neither user-provided nor deleted (8.4 [dcl.fct.def]) and if...
A non-user-provided copy constructor that is defaulted and not defined as deleted is implicitly defined if it is used to initialize an object of its class type from a copy of an object of its class type or of a class type derived from its class type116, or when it is explicitly defaulted after its first declaration. [Note: the copy constructor is implicitly defined even if the implementation elided its use (12.2 [class.temporary]). —end note]
Before the non-user-provided defaulted copy constructor for a class is implicitly defined, all non-user-provided copy constructors...
The implicitly-defined or explicitly-defaulted copy constructor for a non-union class X performs...
The implicitly-defined or explicitly-defaulted copy constructor for a union X copies the object representation (3.9 [basic.types]) of X.
Change 12.8 [class.copy] paragraphs 11-15 as follows:
If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly implicitly declared as defaulted (8.4 [dcl.fct.def])...
...An implicitly-declared defaulted copy assignment operator for class X is defined as deleted if X has:...
A copy assignment operator for class X is trivial if it is not neither user-provided nor deleted and if...
A non-user-provided copy assignment operator that is defaulted and not defined as deleted is implicitly defined when an object of its class type is assigned a value of its class type or a value of a class type derived from its class type, or when it is explicitly defaulted after its first declaration.
Before the non-user-provided defaulted copy assignment operator for a class is implicitly defined...
The implicitly-defined or explicitly-defaulted copy assignment operator for a non-union class X performs...
It is unspecified whether subobjects representing virtual base classes are assigned more than once by the implicitly-defined or explicitly-defaulted copy assignment operator. [Example:...
The implicitly-defined or explicitly-defaulted copy assignment operator for a union X copies the object representation (3.9 [basic.types]) of X.
12.8 [class.copy] paragraph 16 details the conditions under which a thrown object can be moved instead of copied. However, the optimization as currently described is unsafe. Consider the following example:
void f() {
X x;
try {
throw x;
} catch (...) {
}
// x may have been moved from but can still be accessed here
}
When the operation is a throw, as opposed to a return, there must be a restriction that the object potentially being moved be defined within the innermost enclosing try block.
Notes from the July, 2009 meeting:
It is not clear how important this optimization is in the context of throw: how often is a large object with substantial copying overhead thrown? Also, throwing an exception is already a heavyweight operation, so presumably moving instead of copying an object would not make much difference.
Proposed resolution (October, 2009):
Change 12.8 [class.copy] paragraph 17 second bullet as follows:
Consider the following example:
struct C { };
struct A {
explicit operator int() const;
explicit operator C() const;
};
struct B {
int i;
B(const A& a): i(a) { }
};
int main() {
A a;
int i = a;
int j(a);
C c = a;
C c2(a);
}
It's clear that the B constructor and the declaration of j are well-formed and the declarations of i and c are ill-formed. But what about the declaration of c2? This is supposed to work, but it doesn't under the current wording.
C c2(a) is direct-initialization of a class, so constructors are considered. The only possible candidate is the default copy constructor. So we look for a conversion from A to const C&. There is a conversion operator to C, but it is explicit and we are now performing copy-initialization of a reference temporary, so it is not a candidate, and the declaration of c2 is ill-formed.
Proposed resolution (October, 2009):
Change 13.3.1.4 [over.match.copy] paragraph 1 second bullet as follows:
13.3.3.1 [over.best.ics] paragraph 4 says,
However, when considering the argument of a user-defined conversion function that is a candidate by 13.3.1.3 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, by 13.3.1.7 [over.match.list] when passing the initializer list as a single argument or when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X, or by 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are allowed.
This is not quite right, as this applies to constructor arguments, not just arguments of user-defined conversion functions. Furthermore, the word “allowed” might be better replaced by something like,
considered (in particular, for the purposes of determining whether the candidate function (that is either a constructor or a conversion function) is viable)
Proposed resolution (October, 2009):
Change 13.3.3.1 [over.best.ics] paragraph 4 as follows:
However, when considering the argument of a constructor or user-defined conversion function that is a candidate by 13.3.1.3 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, by 13.3.1.7 [over.match.list] when passing the initializer list as a single argument or when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X, or by 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are allowed considered.
Conversion of a pointer or pointer to member to bool is given special treatment as a tiebreaker in overload resolution in 13.3.3.2 [over.ics.rank] paragraph 4, bullet 1:
It would be reasonable to expect a similar provision to apply to conversions of std::nullptr_t to bool.
Proposed resolution (October, 2009):
Change 13.3.3.2 [over.ics.rank] paragraph 4 bullet 1 as follows:
13.6 [over.built] paragraphs 24-25 describe the imaginary built-in conditional operator functions. However, neither paragraph 24 (promoted arithmetic types) nor 25 (pointer and pointer-to-member types) covers scoped enumerations, whose values should be usable in conditional expressions.
(See also issue 835.)
Proposed resolution (October, 2009):
Change 13.6 [over.built] paragraph 25 as follows:
For every type T, where T is a pointer, or pointer-to-member, or scoped enumeration type, there exist candidate operator functions of the form
T operator?(bool, T , T );
5.19 [expr.const] permits literal types with a constexpr conversion function to an integral type to be used in an integral constant expression. However, such conversions are not listed in 14.4.2 [temp.arg.nontype] paragraph 5 bullet 1 among the conversions applied to template-arguments for a non-type template-parameter of integral or enumeration type.
Notes from the March, 2009 meeting:
The original national body comment suggested allowing any literal type as a non-type template argument. The CWG was not in favor of this change, but in the course of discussing the suggestion discovered the problem with template-parameters of integral and enumeration type.
Proposed resolution (October, 2009):
Change 14.4.2 [temp.arg.nontype] paragraph 1 bullet 1 as follows:
Is this code well-formed?
template <typename T> struct A {
struct B;
};
class C {
template <typename T> friend struct A<T>::B;
static int bar;
};
template <> struct A<char> {
struct B {
int f() {
return C::bar; // Is A<char>::B a friend of C?
}
};
};
According to 14.6.4 [temp.friend] paragraph 5,
A member of a class template may be declared to be a friend of a non-template class. In this case, the corresponding member of every specialization of the class template is a friend of the class granting friendship.
This would tend to indicate that the example is well-formed. However, technically A<char>::B does not “correspond to” the same-named member of the class template: 14.8.3 [temp.expl.spec] paragraph 4 says,
The definition of an explicitly specialized class is unrelated to the definition of a generated specialization. That is, its members need not have the same names, types, etc. as the members of a generated specialization.
In other words, there are no “corresponding members” in an explicit specialization.
Is this the outcome we want for examples like the preceding? There is diversity among implementations on this question, with some accepting the example and others rejecting it as an access violation.
Notes from the July, 2009 meeting:
The consensus of the CWG was to allow the correspondence of similar members in explicit specializations.
Proposed resolution (October, 2009):
Change 14.6.4 [temp.friend] paragraph 5 as follows:
A member of a class template may be declared to be a friend of a non-template class. In this case, the corresponding member of every specialization of the class template is a friend of the class granting friendship. For explicit specializations the corresponding member is the member (if any) that has the same name, kind (type, function, class template or function template), template parameters, and signature as the member of the class template instantiation that would otherwise have been generated. [Example:
template<class T> struct A { struct B { }; void f(); struct D { void g(); }; }; template<> struct A<int> { struct B { }; int f(); struct D { void g(); }; }; class C { template<class T> friend struct A<T>::B; // grants friendship to A<int>::B even though // it is not a specialization of A<T>::B template<class T> friend void A<T>::f(); // does not grant friendship to A<int>::f() // because its return type does not match template<class T> friend void A<T>::D::g(); // does not grant friendship to A<int>::D::g() // because A<int>::D is not a specialization of A<T>::D };
14.7.2.2 [temp.dep.expr] paragraph 3 says,
An id-expression is type-dependent if it contains:
- an identifier that was declared with a dependent type...
This treatment seems inadequate with regard to id-expressions in function calls:
According to 14.7.2.1 [temp.dep.type] paragraph 6,
A type is dependent if it is
- ...
- a compound type constructed from any dependent type...
This would apply to the type of a member function of a class template if any of its parameters are dependent, even if the return type is not dependent. However, there is no need for a call to such a function to be a type-dependent expression because the type of the expression is known at definition time.
This wording does not handle the case of overloaded functions, some of which might have dependent types (however defined) and others not.
Notes from the October, 2009 meeting:
The consensus of the CWG was that the first point of the issue is not sufficiently problematic as to require a change.
Proposed resolution (October, 2009):
Change 14.7.2.2 [temp.dep.expr] paragraph 3 as follows:
An id-expression is type-dependent if it contains:
an identifier that was associated by name lookup with one or more declarations declared with a dependent type,
a template-id that is dependent,
a conversion-function-id that specifies a dependent type, or
a nested-name-specifier or a qualified-id that names a member of an unknown specialization.
According to 14.7.4.2 [temp.dep.candidate],
For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, the candidate functions are found using the usual lookup rules (3.4.1 [basic.lookup.unqual], 3.4.2 [basic.lookup.argdep]) except that:
For the part of the lookup using unqualified name lookup (3.4.1 [basic.lookup.unqual]), only function declarations with external linkage from the template definition context are found.
For the part of the lookup using associated namespaces (3.4.2 [basic.lookup.argdep]), only function declarations with external linkage found in either the template definition context or the template instantiation context are found.
It is not at all clear why a call using a template-id would be treated differently from one not using a template-id. Furthermore, is it really necessary to exclude internal linkage functions from the lookup? Doesn't the ODR give implementations sufficient latitude to handle this case without another wrinkle on name lookup?
(See also issue 524.)
Notes from the April, 2006 meeting:
The consensus of the group was that template-ids should not be treated differently from unqualified-ids (although it's not clear how argument-dependent lookup works for template-ids), and that internal-linkage functions should be found by the lookup (although they may result in errors if selected by overload resolution).
Note (June, 2006):
Although the notes from the Berlin meeting indicate that argument-dependent lookup for template-ids is under-specified in the Standard, further examination indicates that that is not the case: the note in 14.9.1 [temp.arg.explicit] paragraph 8 clearly indicates that argument-dependent lookup is to be performed for template-ids, and 3.4.2 [basic.lookup.argdep] paragraph 4 describes the lookup performed:
When considering an associated namespace, the lookup is the same as the lookup performed when the associated namespace is used as a qualifier (3.4.3.2 [namespace.qual]) except that:
Any using-directives in the associated namespace are ignored.
Any namespace-scope friend functions declared in associated classes are visible within their respective namespaces even if they are not visible during an ordinary lookup (11.4 [class.friend]).
Proposed resolution (October, 2009):
Change 14.7.2 [temp.dep] paragraph 1 as follows:
In an expression of the form:
postfix-expression ( expression-listopt )
where the postfix-expression is an unqualified-id but not a template-id, the unqualified-id denotes a dependent name if and only if any of the expressions in the expression-list is a type-dependent expression (14.7.2.2 [temp.dep.expr])...
Change 14.7.4.2 [temp.dep.candidate] paragraph 1 as follows:
For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, or if the function is called using operator notation, the candidate functions are found using the usual lookup rules (3.4.1 [basic.lookup.unqual], 3.4.2 [basic.lookup.argdep]) except that:
For the part of the lookup using unqualified name lookup (3.4.1 [basic.lookup.unqual]), only function declarations with external linkage from the template definition context are found.
For the part of the lookup using associated namespaces (3.4.2 [basic.lookup.argdep]), only function declarations with external linkage found in either the template definition context or the template instantiation context are found.
Consider this example:
template <class T> struct A {
virtual void f() {}
};
extern template struct A<int>;
int main() {
A<int> a;
a.f();
}
The intent is that the explicit instantiation declaration will suppress any compiler-generated machinery such as a virtual function table or typeinfo data in this translation unit, and that because of 14.8.2 [temp.explicit] paragraph 10,
An entity that is the subject of an explicit instantiation declaration and that is also used in the translation unit shall be the subject of an explicit instantiation definition somewhere in the program; otherwise the program is ill-formed, no diagnostic required.
the use of A<int> in declaring a requires an explicit instantiation definition in another translation unit that will provide the requisite compiler-generated data.
The existing wording of 14.8.2 [temp.explicit] does not express this intent clearly enough, however.
Suggested resolution:
Change 14.8.2 [temp.explicit] paragraph 7 as follows:
An explicit instantiation that names a class template specialization is also an explicit instantion of the same kind (declaration or definition) of each of its members (not including members inherited from base classes) that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, except as described below.
Change 14.8.2 [temp.explicit] paragraph 9 as follows:
An explicit instantiation declaration that names a class template specialization has no effect on the class template specialization itself (except for perhaps resulting in its implicit instantiation). Except for inline functions and class template specializations, other explicit instantiation declarations have the effect of suppressing the implicit instantiation of the entity to which they refer...
Proposed resolution (October, 2009):
Change 14.8.2 [temp.explicit] paragraphs 7-9 as follows:
An explicit instantiation that names a class template specialization is also an explicit instantion of the same kind (declaration or definition) of each of its members (not including members inherited from base classes) that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, except as described below. [Note: In addition, it will typically be an explicit instantiation of certain implementation-dependent data about the class. —end note]
An explicit instantiation definition that names a class template specialization explicitly instantiates the class template specialization and is only an explicit instantiation definition of only those members whose definition is visible at the point of instantiation.
An explicit instantiation declaration that names a class template specialization has no effect on the class template specialization itself (except for perhaps resulting in its implicit instantiation). Except for inline functions and class template specializations, other explicit instantiation declarations have the effect of suppressing the implicit instantiation of the entity to which they refer...
The adoption of paper N2844 made it ill-formed to attempt to bind an rvalue reference to an lvalue. However, the example in 14.9.2.1 [temp.deduct.call] paragraph 3 still reflects the previous specification:
template <typename T> int f(T&&);
int i;
int j = f(i); // calls f<int&>(i)
template <typename T> int g(const T&&);
int k;
int n = g(k); // calls g<int>(k)
The last line of that example is now ill-formed, attempting to bind the const int&& parameter of g to the lvalue k.
Proposed resolution (July, 2009):
Replace the example in 14.9.2.1 [temp.deduct.call] paragraph 3 with:
template<typename T> int f(T&&);
template<typename T> int g(const T&&);
int i;
int n1 = f(i); // calls f<int&>(int&)
int n2 = f(0); // calls f<int>(int&&)
int n3 = g(i); // error: would call g<int>(const int&&), which would
// bind an rvalue reference to an lvalue
(See also issue 858.)
15.1 [except.throw] paragraph 4 says,
When the last remaining active handler for the exception exits by any means other than throw; the temporary object is destroyed and the implementation may deallocate the memory for the temporary object...
With std::current_exception() (18.8.5 [propagation] paragraph 7), it might be possible to refer to the exception object after its last handler exits (if the exception object is not copied). The text needs to be updated to allow for that possibility.
Proposed resolution (September, 2009):
Change 15.1 [except.throw] paragraph 4 as follows:
The memory for the temporary copy of the exception being thrown exception object is allocated in an unspecified way, except as noted in 3.7.4.1 [basic.stc.dynamic.allocation]. The temporary persists as long as there is a handler being executed for that exception. In particular, if If a handler exits by executing a throw; statement, that passes control rethrowing, control is passed to another handler for the same exception, so the temporary remains. The exception object is destroyed after either When the last remaining active handler for the exception exits by any means other than throw; rethrowing, or the last object of type std::exception_ptr (18.8.5 [propagation]) that refers to the exception object is destroyed, whichever is later. In the former case, the destruction occurs when the handler exits, immediately after the destruction of the object declared in the exception-declaration in the handler, if any. In the latter case, the destruction occurs before the destructor of std::exception_ptr returns. the temporary object is destroyed and the The implementation may then deallocate the memory for the temporary exception object; any such deallocation is done in an unspecified way. The destruction occurs immediately after the destruction of the object declared in the exception-declaration in the handler.
According to 2.3 [lex.charset] paragraph 3,
The values of the members of the execution character sets are implementation-defined, and any additional members are locale-specific.
This makes it sound as if the locale determines only whether an extended character (one not in the basic execution character set) exists, not its value (which is just implementation-defined, not locale-specific). The description should be clarified to indicate that the value of a given character can vary between locales, as well.
Proposed resolution (February, 2010):
Change 2.3 [lex.charset] paragraph 3 as follows:
...The execution character set and the execution wide-character set are implementation-defined supersets of the basic execution character set and the basic execution wide-character set, respectively. The values of the members of the execution character sets and the sets of additional members are implementation-defined, and any additional members are locale-specific.
There are a number of specifications in the Standard that should also apply to references. For example:
3 [basic] paragraphs 3-4 indicate that a reference cannot have a name because it is not an entity. (See also issue 485.)
3.4.1 [basic.lookup.unqual] paragraph 13 covers unqualified lookup in the initializer of a variable member of a namespace but not that of a reference member of a namespace. It would be very strange if the lookup in these two cases were different.
3.5 [basic.link] paragraph 8 prohibits use of a type without linkage as the type of a variable with linkage, but not as the type of a reference with linkage. (References with linkage are explicitly mentioned earlier in the section.)
3.7.1 [basic.stc.static] paragraph 3 permits local static variables but not local static references.
A number of other examples could be cited. A thorough review is needed to make sure that references are completely specified.
Notes from the September, 2008 meeting:
The CWG expressed interest in an approach that would define “variable” to include both objects and references and to use that for both this issue and issue 570.
Proposed resolution (October, 2009):
See paper PL22.16/09-0183 = WG21 N2993. This resolution also resolves issue 570.
3.2 [basic.def.odr] paragraph 1 says,
No translation unit shall contain more than one definition of any variable, function, class type, enumeration type or template.
This says nothing about references. Is it permitted to define a reference more than once in a single translation unit? (The list in paragraph 5 of things that can have definitions in multiple translation units does not include references.)
Notes from the September, 2008 meeting:
The CWG expressed interest in an approach that would define “variable” to include both objects and references and to use that for both this issue and issue 633.
Proposed resolution (October, 2009):
This issue is resolved by the resolution of issue 633.
When 3.4.1 [basic.lookup.unqual] paragraph 10 says,
In a friend declaration naming a member function, a name used in the function declarator and not part of a template-argument in a template-id is first looked up in the scope of the member function's class. If it is not found, or if the name is part of a template-argument in a template-id, the look up is as described for unqualified names in the definition of the class granting friendship.
what does “in the scope of the member function's class” mean? Does it mean that only members of the class and its base classes are considered? Or does it mean that the same lookup is to be performed as if the name appeared in the member function's class? Implementations vary in this regard. For example:
struct s1;
namespace ns {
struct s1;
}
struct s2 {
void f(s1 &);
};
namespace ns {
struct s3 {
friend void s2::f(s1 &);
};
}
Microsoft Visual C++ and Comeau C++ resolve s1 in the friend declaration to ns::s1 and issue an error, while g++ resolves it to ::s1 and accepts the code.
Notes from the April, 2005 meeting:
The phrase “looked up in the scope of [a] class” occurs frequently throughout the Standard and always refers to the member name lookup described in 10.2 [class.member.lookup]. This is the first interpretation mentioned above (“only members of the class and its base classes”), resolving s1 to ns::s1. A cross-reference to 10.2 [class.member.lookup] will be added to 3.4.1 [basic.lookup.unqual] paragraph 10 to make this clearer.
In discussing this question, the CWG noticed another problem: the text quoted above applies to all template-arguments appearing in the function declarator. The intention of this rule, however, is that only template-arguments in the declarator-id should ignore the member function's class scope; template-arguments used elsewhere in the function declarator should be treated like other names. For example:
template<typename T> struct S;
struct A {
typedef int T;
void foo(S<T>);
};
template <typename T> struct B {
friend void A::foo(S<T>); // i.e., S<A::T>
};
Proposed resolution (February, 2010):
Change 3.4.1 [basic.lookup.unqual] paragraph 10 as follows:
In a friend declaration naming a member function, a name used in the function declarator and not part of a template-argument in a template-id the declarator-id is first looked up in the scope of the member function's class (10.2 [class.member.lookup]). If it is not found, or if the name is part of a template-argument in a template-id the declarator-id, the look up is as described for unqualified names in the definition of the class granting friendship. [Example:
struct A { typedef int AT; void f1(AT); void f2(float); template<typename T> void f3(); }; struct B { typedef char AT; typedef float BT; friend void A::f1(AT); // parameter type is A::AT friend void A::f2(BT); // parameter type is B::BT friend void A::f3<AT>(); // template argument is B::AT };—end example]
The recent addition to support inherited constructors changed 3.4.3.1 [class.qual] paragraph 2 to say that
if the name specified after the nested-name-specifier is the same as the identifier or the simple-template-id's template-name in the last component of the nested-name-specifier,
the qualified-id is considered to name a constructor. This causes problems for a common naming scheme used in some class libraries:
struct A {
typedef int type;
};
struct B {
typedef A type;
};
B::type::type t;
This change causes this to name the A constructor instead of the A::type typedef.
Proposed resolution (February, 2010):
Change 3.4.3.1 [class.qual] paragraph 2 as follows:
In a lookup in which the constructor is an acceptable lookup result and the nested-name-specifier nominates a class C:
if the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (Clause 9 [class]), or
in a using-declaration (7.3.3 [namespace.udecl]) that is a member-declaration, if the name specified after the nested-name-specifier is the same as the identifier or the simple-template-id's template-name in the last component of the nested-name-specifier,
the name is instead considered to name the constructor of class C...
The algorithm for namespace-qualified lookup is given in 3.4.3.2 [namespace.qual] paragraph 2:
Given X::m (where X is a user-declared namespace), or given ::m (where X is the global namespace), let S be the set of all declarations of m in X and in the transitive closure of all namespaces nominated by using-directives in X and its used namespaces, except that using-directives that nominate non-inline namespaces (7.3.1 [namespace.def]) are ignored in any namespace, including X, directly containing one or more declarations of m.
Consider the following example:
namespace A {
inline namespace B {
namespace C {
int i;
}
using namespace C;
}
int i;
}
int j = A::i; // ambiguous
The transitive closure includes B because it is inline, and it includes C because there is no declaration of i in B. As a result, A::i finds both the i declared in A and the one declared in C, and the lookup is ambiguous.
This result is apparently unintended.
Proposed resolution (November, 2009):
Change 7.3.1 [namespace.def] paragraph 9 as follows:
These properties are transitive: if a namespace N contains an inline namespace M, which in turn contains an inline namespace O, then the members of O can be used as though they were members of M or N. The transitive closure of all inline namespaces in N is the inline namespace set of N. The set of namespaces consisting of the innermost non-inline namespace enclosing an inline namespace O, together with any intervening inline namespaces, is the enclosing namespace set of O.
Insert a new paragraph before 3.4.3.2 [namespace.qual] paragraph 2 and change the existing paragraph 2 as follows:
For a namespace X and name m, the namespace-qualified lookup set S(X,m) is defined as follows: Let S'(X,m) be the set of all declarations of m in X and the inline namespace set of X (7.3.1 [namespace.def]). If S'(X,m) is not empty, S(X,m) is S'(X,m); otherwise, S(X,m) is the union of S(Ni,m) for all non-inline namespaces Ni nominated by using-directives in X and its inline namespace set.
Given X::m (where X is a user-declared namespace), or given ::m (where X is the global namespace), let S be the set of all declarations of m in X and in the transitive closure of all namespaces nominated by using-directives in X and its used namespaces, except that using-directives that nominate non-inline namespaces (7.3.1 [namespace.def]) are ignored in any namespace, including X, directly containing one or more declarations of m. No namespace is searched more than once in the lookup of a name. If if S(X,m) is the empty set, the program is ill-formed. Otherwise, if S(X,m) has exactly one member, or if the context of the reference is a using-declaration (7.3.3 [namespace.udecl]), S(X,m) is the required set of declarations of m. Otherwise if the use of m is not one that allows a unique declaration to be chosen from S(X,m), the program is ill-formed. [Example:...
The recent changes to allow use of unnamed types as template arguments require some rethinking of how unnamed types are treated in general. At least, a class-scope unnamed type should have the same linkage as its containing class. For example:
// File "hdr.h"
struct S {
static enum { No, Yes } locked;
};
template<class T> void f(T);
// File "impl1.c"
#include "hdr.h"
template void f(decltype(S::locked));
// File "impl2.c"
#include "hdr.h"
template void f(decltype(S::locked));
The two explicit instantiation directives should refer to the same specialization.
Proposed resolution (February, 2010):
Change 3.5 [basic.link] paragraph 8 as follows:
Names not covered by these rules have no linkage. Moreover, except as noted, a name declared in a local scope (3.3.3 [basic.scope.local]) has no linkage. A type is said to have linkage if and only if:
it is a class or enumeration type that is named (or has a name for linkage purposes (7.1.3 [dcl.typedef])) and the name has linkage; or
it is an unnamed class or enumeration member of a class with linkage; or
...
this is a keyword and thus not subject to ordinary name lookup. That makes the interpretation of examples like the following somewhat unclear:
struct outer {
void f() {
struct inner {
int a[sizeof(*this)]; // #1
};
}
};
According to 5.1.1 [expr.prim.general] paragraph 3,
The keyword this shall be used only inside a non-static class member function body (9.3 [class.mfct]) or in a brace-or-equal-initializer for a non-static data member.
Should the use of this at #1 be interepreted as a well-formed reference to outer::f()'s this or as an ill-formed attempt to refer to a this for outer::inner?
One possible interpretation is that the intent is as if this were an ordinary identifier appearing as a parameter in each non-static member function. (This view applies to the initializers of non-static data members as well if they are considered to be rewritten as mem-initializers in the constructor body.) Under this interpretation, the prohibition against using this in other contexts simply falls out of the fact that name lookup would fail to find this anywhere else, so the reference in the example is well-formed. (Implementations vary in their treatment of this example, so clearer wording is needed, whichever way the interpretation goes.)
Proposed resolution (February, 2010):
Change 5.1.1 [expr.prim.general] paragraph 2 as follows:
...The keyword this shall be used only inside the body of a non-static class member function body (9.3 [class.mfct]) of the nearest enclosing class or in a brace-or-equal-initializer for a non-static data member (9.2 [class.mem]). The type of the expression is a pointer to the class of the function or non-static data member, possibly with cv-qualifiers on the class type. The expression is an rvalue. [Example:
class Outer { int a[sizeof(*this)]; // error: not inside a member function unsigned int sz = sizeof(*this); // OK, in brace-or-equal-initializer void f() { int b[sizeof(*this)]; // OK struct Inner { int c[sizeof(*this)]; // error: not inside a member function of Inner }; } };—end example]
The current wording of 5.2.2 [expr.call] paragraph 7 is:
After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or effective class type, the program is ill-formed.
It's not clear whether this is intended to exclude anything other than void, but the effect is to disallow passing nullptr to ellipsis. That seems unnecessary.
Notes from the October, 2009 meeting:
The CWG agreed that this should be supported and the effect should be like passing (void*)nullptr.
Proposed resolution (February, 2010):
Change 5.2.2 [expr.call] paragraph 7 as follows:
When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (18.10 [support.runtime]). [Note: This paragraph does not apply to arguments passed to a function parameter pack. Function parameter packs are expanded during template instantiation (14.6.3 [temp.variadic]), thus each such argument has a corresponding parameter when a function template specialization is actually called. —end note] The lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are performed on the argument expression. An argument that has (possibly cv-qualified) type std::nullptr_t is converted to type void* (4.10 [conv.ptr]). After these conversions...
Consider the following example:
static const char test1 = 'x';
static const char test2 = 'x';
bool f() {
return &test1 != &test2;
}
Is f() allowed to return false? Can a smart optimizer alias these two variables, taking advantage of the fact that they are const, initialized to the same value, and thus can never be different in a well-defined program?
The C++ Standard doesn't explicitly specify address allocation of objects except as members of arrays and classes, so the answer would appear to be that such an implementation would be conforming.
This situation appears to have been the inadvertent result of the resolution of issue 73. Prior to that change, 5.10 [expr.eq] said,
Two pointers of the same type compare equal if and only if they... both point to the same object...
That resolution introduced the current wording,
Two pointers of the same type compare equal if and only if... both represent the same address.
Notes from the March, 2009 meeting:
The CWG agreed that this aliasing should not be permitted in a conforming implementation.
Proposed resolution (November, 2009):
Add the following as a new paragraph after 1.8 [intro.object] paragraph 5:
Unless an object is a bit-field or a base class subobject of zero size, the address of that object is the address of the first byte it occupies. Two distinct objects that are neither bit-fields nor base class subobjects of zero size shall have distinct addresses [Footnote: Under the “as-if” rule an implementation is allowed to store two objects at the same machine address or not store an object at all if the program cannot observe the difference (1.9 [intro.execution]). —end footnote]. [Example:
static const char test1 = 'x'; static const char test2 = 'x'; const bool b = &test1 != &test2; // always true—end example]
Change 5.3.1 [expr.unary.op] paragraph 3 as follows:
The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id. In the first case, if the type of the expression is “T,” the type of the result is “pointer to T.” In particular, the address of an object of type “cv T” is “pointer to cv T,” with the same cv-qualifiers. For a qualified-id, if the member is a static member of type “T”, the type of the result is plain “pointer to T.” If the member is a non-static member of class C of type T, the type of the result is “pointer to member of class C of type T.” If the operand is a qualified-id naming a non-static member m of some class C with type T, the result has type “pointer to member of class C of type T” and is an rvalue designating C::m. Otherwise, if the type of the expression is T, the result has type “pointer to T” and is an rvalue that is the address of the designated object (1.7 [intro.memory]) or a pointer to the designated function. [Note: In particular, the address of an object of type “cv T” is “pointer to cv T,” with the same cv-qualification. —end note] [Example:...
5.2.11 [expr.const.cast] paragraph 4 says,
...Similarly, for two effective object types T1 and T2, an expression of type T1 can be explicitly converted to an rvalue of type T2 using the cast const_cast<T2&&> if a pointer to T1 can be explicitly converted to the type “pointer to T2” using a const_cast. The result of a reference const_cast refers to the original object.
However, in some rvalue-reference const_casts there is no “original object,” e.g.,
const_cast<int&&>(2)
Notes from the July, 2009 meeting:
The coresponding cast to an lvalue reference to const is ill-formed: in such cases, the operand must be an lvalue. The consensus of the CWG was that a cast to an rvalue reference should only accept an rvalue that is an rvalue reference (i.e., an object).
Proposed resolution (February, 2010):
Change 5.2.11 [expr.const.cast] paragraph 4 as follows:
An lvalue of type T1 can be explicitly converted to an lvalue of type T2 using the cast const_cast<T2&> (where T1 and T2 are object types) if a pointer to T1 can be explicitly converted to the type “pointer to T2” using a const_cast. Similarly, for two object types T1 and T2, an expression lvalue of type T1 or, if T1 is a class type, an expression of type T1, can be explicitly converted to an rvalue of type T2 using the cast const_cast<T2&&> if a pointer to T1 can be explicitly converted to the type “pointer to T2” using a const_cast. The result of a reference const_cast refers to the original object.
According to 7.1 [dcl.spec] paragraph 2,
The longest sequence of decl-specifiers that could possibly be a type name is taken as the decl-specifier-seq of a declaration.
However, there are many decl-specifiers that cannot appear in a type name that are, nonetheless, part of a declaration's decl-specifier-seq, such as typedef, friend, static, etc.
Proposed resolution (November, 2009):
Change 7.1 [dcl.spec] paragraph 2 as follows:
The longest sequence of decl-specifiers that could possibly be a type name is taken as the decl-specifier-seq of a declaration If a type-name is encountered while parsing a decl-specifier-seq, it is interpreted as part of the decl-specifier-seq if and only if there is no previous type-specifier other than a cv-qualifier in the decl-specifier-seq.. The sequence shall be self-consistent as described below. [Example:...
7.1.2 [dcl.fct.spec] paragraph 4 specifies that local static variables and string literals appearing in the body of an inline function with external linkage must be the same entities in every translation unit in the program. Nothing is said, however, about whether local types are likewise required to be the same.
Although a conforming program could always have determined this by use of typeid, recent changes to C++ (allowing local types as template type arguments, lambda expression closure classes) make this question more pressing.
Notes from the July, 2009 meeting:
The types are intended to be the same.
Proposed resolution (November, 2009):
Change 7.1.2 [dcl.fct.spec] paragraph 4 as follows:
...A static local variable in an extern inline function always refers to the same object. A string literal in the body of an extern inline function is the same object in different translation units. [Note: A string literal appearing in a default argument expression is not in the body of an inline function merely because the expression is used in a function call from that inline function. —end note] A type defined within the body of an extern inline function is the same type in every translation unit.
It is not clear from the specification in 7.3.1 [namespace.def] paragraph 8 how a declaration in an inline namespace should be handled if the name is the same as one in the containing namespace or in an parallel inline namespace. For example:
namespace Q {
inline namespace V1 {
int i;
int j;
}
inline namespace V2 {
int j;
}
int i;
}
int Q::i = 1; // Q::i or Q::V1::i?
int Q::j = 2; // Q::V1::j or Q::V2::j?
Proposed resolution (July, 2009):
This issue is resolved by the resolution of issue 861.
According to 7.3.1 [namespace.def] paragraph 8,
Members of an inline namespace can be used in most respects as though they were members of the enclosing namespace... Furthermore, each member of the inline namespace can subsequently be explicitly instantiated (14.8.2 [temp.explicit]) or explicitly specialized (14.8.3 [temp.expl.spec]) as though it were a member of the enclosing namespace.
However, that assertion is contradicted for class template specializations by 9 [class] paragraph 11:
If a class-head contains a nested-name-specifier, the class-specifier shall refer to a class that was previously declared directly in the class or namespace to which the nested-name-specifier refers...
It is also contradicted for function template specializations by 3.4.3.2 [namespace.qual] paragraph 6:
In a declaration for a namespace member in which the declarator-id is a qualified-id, given that the qualified-id for the namespace member has the formnested-name-specifier unqualified-id
the unqualified-id shall name a member of the namespace designated by the nested-name-specifier.
Proposed resolution (November, 2009):
Change 9 [class] paragraph 11 as follows:
If a class-head contains a nested-name-specifier, the class-specifier shall refer to a class that was previously declared directly in the class or namespace to which the nested-name-specifier refers, or in an element of the inline namespace set (7.3.1 [namespace.def]) of that namespace (i.e., neither not merely inherited nor or introduced by a using-declaration), and the class-specifier shall appear in a namespace enclosing the previous declaration.
Change 3.4.3.2 [namespace.qual] paragraph 6 as follows:
In a declaration for a namespace member in which the declarator-id is a qualified-id, given that the qualified-id for the namespace member has the form
nested-name-specifier unqualified-id
the unqualified-id shall name a member of the namespace designated by the nested-name-specifier, or of an element of the inline namespace (7.3.1 [namespace.def]) of that namespace. [Example:...
(Note: this resolution depends on the resolution for issue 861.)
According to 7.6.2 [dcl.align] paragraph 1, an alignment attribute can be specified only for a variable or a class data member. The corresponding Microsoft and GNU attributes can be also specified for a class type, and this usage seems to be widespread. It should be permitted with the standard attribute and there seems no good reason not to do so for enumeration types, as well.
Notes from the October, 2009 meeting:
Although there was initial concern for how to integrate the suggested change into the type system, it was observed that current practice is to have the attribute affect only the layout, not the type.
Proposed resolution (February, 2010):
Change 7.6.2 [dcl.align] paragraphs 1-2 as follows:
...The attribute can be applied to a variable that is neither a function parameter nor declared with the register storage class specifier and to a class data member that is not a bit-field. The attribute can also be applied to the declaration of a class or enumeration type.
When the alignment attribute is of the form align(assignment-expression):
...
if the constant expression evaluates to a fundamental alignment, the alignment requirement of the declared object entity shall be the specified fundamental alignment
if the constant expression evaluates to an extended alignment and the implementation supports that alignment in the context of the declaration, the alignment of the declared object entity shall be that alignment
...
Change 7.6.2 [dcl.align] paragraphs 4-6 as follows:
When multiple alignment attributes are specified for an object entity, the alignment requirement shall be set to the strictest specified alignment.
The combined effect of all alignment attributes in a declaration shall not specify an alignment that is less strict than the alignment that would otherwise be required for the object entity being declared.
If the defining declaration of an object entity has an alignment attribute, any non-defining declaration of that object entity shall either specify equivalent alignment or have no alignment attribute. Conversely, if any declaration of an entity has an alignment attribute, every defining declaration of that entity shall specify an equivalent alignment. No diagnostic is required if declarations of an object entity have different alignment attributes in different translation units.
Insert the following as a new paragraph after 7.6.2 [dcl.align] paragraph 6:
[Example:
// Translation unit #1: struct S { int x; } s, p = &s; // Translation unit #2: struct [[align(16)]] S; // error: definition of S lacks alignment; no extern S* p; // diagnostic required—end example]
Delete 7.6.2 [dcl.align] paragraph 8:
[Note: the alignment of a union type can be strengthened by applying the alignment attribute to any non-static data member of the union. —end note]
According to 8.3 [dcl.meaning] paragraph 1,
When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers (or of an inline namespace within that scope (7.3.1 [namespace.def])), and the member shall not have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier of the declarator-id.
This would appear to make the following example ill-formed, even though it would be well-formed if the using-declaration were omitted:
namespace A {
inline namespace B {
template <class T> void foo() { }
}
using B::foo;
}
template void A::foo<int>();
This seems strange.
Proposed resolution (July, 2009):
Change 8.3 [dcl.meaning] paragraph 1 as follows:
...When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers (or, in the case of a namespace, of an element of the inline namespace within that scope set of that namespace (7.3.1 [namespace.def])), and; the member shall not merely have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier of the declarator-id. [Note:...
(Note: this resolution depends on the resolution of issue 861.)
Paragraph 7 of 8.3.4 [dcl.array] says,
If E is an n-dimensional array of rank i × j × ... × k, then E appearing in an expression is converted to a pointer to an (n - 1)-dimensional array with rank j × ... × k.
This formulation does not allow for the existence of expressions in which the array-to-pointer conversion does not occur (as specified in clause 5 [expr] paragraph 9). This paragraph should be no more than a note, if it appears at all, and the wording should be corrected.
Proposed resolution (November, 2009):
Change paragraphs 6-8 of 8.3.4 [dcl.array] into a note and make the indicated changes:
[Note: Except where it has been declared for a class (13.5.5 [over.sub]), the subscript operator [] is interpreted in such a way that E1[E2] is identical to *((E1)+(E2)). Because of the conversion rules that apply to +, if E1 is an array and E2 an integer, then E1[E2] refers to the E2-th member of E1. Therefore, despite its asymmetric appearance, subscripting is a commutative operation.
A consistent rule is followed for multidimensional arrays. If E is an n-dimensional array of rank i × j × . . . × k, then E appearing in an expression that is subject to the array-to-pointer conversion (4.2 [conv.array]) is converted to a pointer to an (n-1)-dimensional array with rank j × . . . × k. If the * operator, either explicitly or implicitly as a result of subscripting, is applied to this pointer, the result is the pointed-to (n-1)-dimensional array, which itself is immediately converted into a pointer.
[Example: consider
int x[3][5];
Here x is a 3 × 5 array of integers. When x appears in an expression, it is converted to a pointer to (the first of three) five-membered arrays of integers. In the expression x[i] which is equivalent to *(x+i), x is first converted to a pointer as described; then x+i is converted to the type of x, which involves multiplying i by the length of the object to which the pointer points, namely five integer objects. The results are added and indirection applied to yield an array (of five integers), which in turn is converted to a pointer to the first of the integers. If there is another subscript the same argument applies again; this time the result is an integer. —end example] —end note]
According to 3.3.4 [basic.scope.proto] paragraph 1,
In a function declaration, or in any function declarator except the declarator of a function definition (8.4 [dcl.fct.def]), names of parameters (if supplied) have function prototype scope, which terminates at the end of the nearest enclosing function declarator.
Happily, this permits the use of parameter names with decltype in a late-specified return type, because the return type is part of the function's declarator. However, the note in 8.3.5 [dcl.fct] paragraph 11 is now inaccurate and should be updated:
[Note: ...If a parameter name is present in a function declaration that is not a definition, it cannot be used outside of the parameter-declaration-clause since it goes out of scope at the end of the function declarator (3.3 [basic.scope]). —end note]
Proposed resolution (February, 2010):
Change the note in 3.3.5 [basic.funscope] paragraph 10 as follows:
...[Note: in particular, parameter names are also optional in function definitions and names used for a parameter in different declarations and the definition of a function need not be the same. If a parameter name is present in a function declaration that is not a definition, it cannot be used outside of the parameter-declaration-clause since it goes out of scope at the end of the function declarator (3.3 [basic.scope]) its function declarator because that is the extent of its potential scope (3.3.4 [basic.scope.proto]). —end note]
8.5 [dcl.init] paragraph 11 says,
If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, a non-static object has indeterminate value.
This is inaccurate, because objects with thread storage duration are zero-initialized (3.6.2 [basic.start.init] paragraph 2).
Proposed resolution (November, 2009):
Change 8.5 [dcl.init] paragraph 11 as follows:
If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, a non-static an object with automatic or dynamic storage duration has indeterminate value. [Note: objects with static or thread storage duration are zero-initialized, see 3.6.2 [basic.start.init]. —end note].
The current wording of 8.5.1 [dcl.init.aggr] paragraph 1 does not consider brace-or-equal-initializers on members as affecting whether a class type is an aggregate or not. Because in-class member initializers are essentially syntactic sugar for mem-initializers, and the presence of a user-provided constructor disqualifies a class from being an aggregate, presumably the same should hold true of member initializers.
Proposed resolution (November, 2009):
Change 8.5.1 [dcl.init.aggr] paragraph 1 as follows:
An aggregate is an array or a class (Clause 9 [class]) with no user-provided constructors (12.1 [class.ctor]), no brace-or-equal-initializers for non-static data members (9.2 [class.mem]), no private or protected non-static data members (Clause 11 [class.access]), no base classes (Clause 10 [class.derived]), and no virtual functions (10.3 [class.virtual]).
10.3 [class.virtual] paragraph 5 requires that covariant return types be either both pointers or both references, but it does not specify that references must be both lvalue references or both rvalue references. Presumably this is an oversight.
Proposed resolution (February, 2010):
Change 10.3 [class.virtual] paragraph 5 bullet 1 as follows:
...If a function D::f overrides a function B::f, the return types of the functions are covariant if they satisfy the following criteria:
both are pointers to classes, both are lvalue references to classes, or both are rvalue references to classes106
...
According to 12.1 [class.ctor] paragraph 5,
An implicitly-declared default constructor for class X is defined as deleted if: ... any non-static data member of const-qualified type (or array thereof) does not have a user-provided default constructor, or...
It is not clear if this adequately covers the case in which some variant members are const-qualified but others are not. The intent of the restriction is to prevent creation of an object with uninitialized members that would require a const_cast to set their value later, but const-qualified members of an anonymous union in which other members are not const do not seem to present that problem.
Proposed resolution (October, 2009):
Change 12.1 [class.ctor] paragraph 5 bullet 3 of the second list and add a fourth bullet as follows:
...
any non-variant non-static data member of const-qualified type (or array thereof) does not have a user-provided default constructor, or
all variant members are of const-qualified type (or array thereof), or
...
Proposed resolution (November, 2009):
Change 12.1 [class.ctor] paragraph 5 bullet 3 of the second list and add two bullets as follows:
...
any non-variant non-static data member of const-qualified type (or array thereof) does not have a user-provided default constructor, or
X is a union and all its variant members are of const-qualified type (or array thereof),
X is a non-union class and all members of any anonymous union member are of const-qualified type (or array thereof), or
...
The terminology used to refer to the parameter for this and its corresponding argument is inconsistent, sometimes using “implied” and sometimes “implicit.” It would be easier to search the text of the Standard if this usage were made regular.
Proposed resolution (February, 2010):
Change the index to refer to “implicit object parameter” and “implied object argument” instead of the current permutations of these terms.
Change 13.3 [over.match] paragraph 1 as follows:
...how well (for non-static member functions) the object matches the implied implicit object parameter...
Change 13.3.1 [over.match.funcs] paragraph 4 as follows:
...For conversion functions, the function is considered to be a member of the class of the implicit implied object argument for the purpose of defining the type of the implicit object parameter...
Change the footnote in 13.3.3 [over.match.best] paragraph 1 bullet 1 as follows:
According to 13.3.3.1.4 [over.ics.ref] paragraphs 3-4,
A standard conversion sequence cannot be formed if it requires binding an lvalue reference to non-const to an rvalue (except when binding an implicit object parameter; see the special rules for that case in 13.3.1 [over.match.funcs]). [Note: this means, for example, that a candidate function cannot be a viable function if it has a non-const lvalue reference parameter (other than the implicit object parameter) and the corresponding argument is a temporary or would require one to be created to initialize the lvalue reference (see 8.5.3 [dcl.init.ref]). —end note]
Other restrictions on binding a reference to a particular argument that are not based on the types of the reference and the argument do not affect the formation of a standard conversion sequence, however.
Because this section does not mention attempting to bind an rvalue reference to an lvalue, such a “conversion sequence” might be selected as best and result in an ill-formed program. It should, instead, be treated like trying to bind an lvalue reference to non-const to an rvalue, making the function non-viable.
Proposed resolution (November, 2009):
Change 13.3.3.1.4 [over.ics.ref] paragraph 3 as follows:
A Except for an implicit object parameter, for which see 13.3.1 [over.match.funcs], a standard conversion sequence cannot be formed if it requires binding an lvalue reference to non-const to an rvalue (except when binding an implicit object parameter; see the special rules for that case in 13.3.1 [over.match.funcs]) or binding an rvalue reference to an lvalue. [Note: this means, for example, that a candidate function cannot be a viable function if it has a non-const lvalue reference parameter (other than the implicit object parameter) and the corresponding argument is a temporary or would require one to be created to initialize the lvalue reference (see 8.5.3 [dcl.init.ref]). —end note]
The list of overloads for user-defined literal operators given in 13.5.8 [over.literal] paragraph 3 should include signatures for char, wchar_t, char16_t, and char32_t.
Proposed resolution (November, 2009):
Change 13.5.8 [over.literal] paragraph 3 as follows:
The declaration of a literal operator shall have a parameter-declaration-clause equivalent to one of the following:
const char* unsigned long long int long double char wchar_t char16_t char32_t const char*, std::size_t const wchar_t*, std::size_t const char16_t*, std::size_t const char32_t*, std::size_t
According to
A template-argument matches a template template-parameter (call it P) when each of the template parameters in the template-parameter-list of the template-argument's corresponding class template or template alias (call it A) matches the corresponding template parameter in the template-parameter-list of P. When P's template-parameter-list contains a template parameter pack (14.6.3 [temp.variadic]), the template parameter pack will match zero or more template parameters or template parameter packs in the template-parameter-list of A with the same type and form as the template parameter pack in P (ignoring whether those template parameters are template parameter packs).
The immediately-preceding example, however, assumes that a parameter pack in the parameter will match only a parameter pack in the argument:
template<class T> class A { /* ... */ };
template<class T, class U = T> class B { /* ... */ };
template<class ... Types> class C { /* ... */ };
template<template<class ...> class Q> class Y { /* ... */ };
Y<A> ya; // ill-formed: a template parameter pack does not match a template parameter
Y<B> yb; // ill-formed: a template parameter pack does not match a template parameter
Y<C> yc; // OK
Proposed resolution (February, 2010):
Change the final three lines of the second example in 14.4.3 [temp.arg.template] paragraph 2 as follows:
Y<A> ya; // ill-formed: a template parameter pack does not match a template parameter OK
Y<B> yb; // ill-formed: a template parameter pack does not match a template parameter OK
Y<C> yc; // OK
Is this allowed?
template<typename T>
struct X
{
static int s[];
int c;
};
template<typename T>
int X<T>::s[sizeof(X<T>)];
int* p = X<char>::s;
I have a compiler claiming that, for the purpose of sizeof(), X<T> is an incomplete type, when it tries to instantiate X<T>::s. It seems to me that X<char> should be considered complete enough for sizeof even though the size of s isn't known yet.
John Spicer: This is a problematic construct that is currently allowed but which I think should be disallowed.
I tried this with a number of compilers. None of which did the right thing. The EDG front end accepts it, but gives X<...>::s the wrong size.
It appears that most compilers evaluate the "declaration" part of the static data member definition only once when the definition is processed. The initializer (if any) is evaluated for each instantiation.
This problem is solvable, and if it were the only issue with incomplete arrays as template static data members, then it would make sense to solve it, but there are other problems.
The first problem is that the size of the static data member is only known if a template definition of the static data member is present. This is weird to start with, but it also means that sizes would not be available in general for exported templates.
The second problem concerns the rules for specialization. An explicit specialization for a template instance can be provided up until the point that a use is made that would cause an implicit instantiation. A reference like "sizeof(X<char>::s)" is not currently a reference that would cause an implicit instantiation of X<char>::s. This means you could use such a sizeof and later specialize the static data member with a different size, meaning the earlier sizeof gave the wrong result. We could, of course, change the "use" rules, but I'd rather see us require that static data members that are arrays have a size specified in the class or have a size based on their initializer.
Notes from the October 2003 meeting:
The example provided is valid according to the current standard. A static data member must be instantiated (including the processing of its initializer, if any) if there is any reference to it. The compiler need not, however, put out a definition in that translation unit. The standard doesn't really have a concept of a "partial instantiation" for a static data member, and although we considered adding that, we decided that to get all the size information that seems to be available one needs a full instantiation in any case, so there's no need for the concept of a partial instantiation.
Note (June, 2006):
Mark Mitchell suggested the following example:
template <int> void g();
template <typename T>
struct S {
static int i[];
void f();
};
template <typename T>
int S<T>::i[] = { 1 };
template <typename T>
void S<T>::f() {
g<sizeof (i) / sizeof (int)>();
}
template <typename T>
int S<int>::i[] = { 1, 2 };
Which g is called from S<int>::f()?
If the program is valid, then surely one would expect g<2> to be called.
If the program is valid, does S<T>::i have a non-dependent type in S<T>::f? If so, is it incomplete, or is it int[1]? (Here, int[1] would be surprising, since S<int>::i actually has type int[2].)
If the program is invalid, why?
For a simpler example, consider:
template <typename T>
struct S {
static int i[];
const int N = sizeof (i);
};
This is only valid if the type of i is dependent, meaning that the sizeof expression isn't evaluated until the class is instantiated.
Proposed resolution (February, 2010):
Add the following as a new paragraph following 14.6.1.3 [temp.static] paragraph 1:
An explicit specialization of a static data member declared as an array of unknown bound can have a different bound from its definition, if any. [Example:
template<class T> struct A { static int i[]; }; template<class T> int A<T>::i[4]; // 4 elements template<> int A<int>::i[] = { 1 }; // 1 element, OK—end example]
Change 14.7.2.2 [temp.dep.expr] paragraph 3 as follows:
An id-expression is type-dependent if it contains:
an identifier that was declared with a dependent type,
a template-id that is dependent,
a conversion-function-id that specifies a dependent type, or
a nested-name-specifier or a qualified-id that names a member of an unknown specialization.;
or if it names a static data member of the current instantiation that has type “array of unknown bound of T” for some T (14.6.1.3 [temp.static]). Expressions of the following forms are type-dependent only if...
14.8.2 [temp.explicit] paragraph 1 says,
An explicit instantiation of a function template shall not use the inline or constexpr specifiers.
This wording should be revised to apply to member functions of class templates as well.
Proposed resolution (February, 2010):
Change 14.8.2 [temp.explicit] paragraph 1 as follows:
...An explicit instantiation of a function template or member function of a class template shall not use the inline or constexpr specifiers.
14.8.2 [temp.explicit] paragraph 5 has an example that reads, in significant part,
namespace N {
template<class T> class Y {
void mf() { }
};
}
using N::Y;
template class Y<int>; // OK: explicit instantiation in namespace N
In fact, paragraph 2 requires that an explicit instantiation with an unqualified name must appear in the same namespace in which the template was declared, so the example is ill-formed.
Proposed resolution (February, 2010):
Change the example in 14.8.2 [temp.explicit] paragraph 5 as follows:
namespace N {
template<class T> class Y { void mf() { } };
}
template class Y<int>; // error: class template Y not visible
// in the global namespace
using N::Y;
template class Y<int>; // OK: explicit instantiation in namespace N
template class Y<int>; // error: explicit instantiation outside of the
// namespace of the template
template class N::Y<char*>; // OK: explicit instantiation in namespace N
template void N::Y<double>::mf(); // OK: explicit instantiation
// in namespace N
According to 14.8.3 [temp.expl.spec] paragraph 14,
An explicit specialization of a function template is inline only if it is explicitly declared to be...
This could be read to require that the inline keyword must appear in the declaration. However, 8.4 [dcl.fct.def] paragraph 10 says that a deleted function is implicitly inline, so it should be made clear that defining an explicit specialization as deleted makes it inline.
Proposed resolution (November, 2009):
Change 14.8.3 [temp.expl.spec] paragraph 14 as follows:
An explicit specialization of a function template is inline only if it is explicitly declared to be with the inline specifier or defined as deleted, and independently of whether its function template is inline. [Example:...
An expression used in an if statement is implicitly converted to type bool (6.4 [stmt.select]). According to the rules of template argument deduction for conversion functions given in 14.9.2.3 [temp.deduct.conv], the following example is ill-formed:
struct X {
template<class T> operator const T&() const;
};
int main()
{
if( X() ) {}
}
Following the logic in 14.9.2.3 [temp.deduct.conv], A is bool and P is const T (because cv-qualification is dropped from P before the reference is removed), and deduction fails.
It's not clear whether this is the intended outcome or not.
Notes from the April, 2005 meeting:
The CWG observed that there is nothing special about either bool or the context in the example above; instead, it will be a problem wherever a copy occurs, because cv-qualification is always dropped in a copy operation. This appears to be a case where the conversion deduction rules are not properly symmetrical with the rules for arguments. The example should be accepted.
Proposed resolution (February, 2010):
This issue is resolved by the resolution of issue 976.
The rules for deducing function template arguments from a conversion function template include provisions in 14.9.2.3 [temp.deduct.conv] paragraph 2 for array and function return types, even though such types are prohibited and cannot occur in the conversion-type-id of a conversion function template. They should be removed.
Proposed resolution (February, 2010):
This issue is resolved by the resolution of issue 976. In particular, under that resolution, if a conversion function returns a reference to an array or function type, the reference will be dropped prior to the adjustments mentioned in this issue, so they are, in fact, needed.
Consider this program:
struct F {
template<class T>
operator const T&() { static T t; return t; }
};
int main() {
F f;
int i = f; // ill-formed
}
It's ill-formed, because according to 14.9.2.3 [temp.deduct.conv], we try to match const T with int.
(The reference got removed from P because of paragraph 3, but the const isn't removed, because paragraph 2 bullet 3 comes before paragraph 3 and thus isn't applied any more.)
Changing the declaration of the conversion operator to
operator T&() { ... }
makes the program compile, which is counter-intuitive to me: I'm in an rvalue (read-only) context, and I can use a conversion to T&, but I can't use a conversion to const T&?
Proposed resolution (February, 2010):
Change 14.9.2.3 [temp.deduct.conv] paragraphs 1-3 as follows, inserting a new paragraph between the current paragraphs 1 and 2:
Template argument deduction is done by comparing the return type of the conversion function template (call it P; see 8.5 [dcl.init], 13.3.1.5 [over.match.conv], and 13.3.1.6 [over.match.ref] for the determination of that type) with the type that is required as the result of the conversion (call it A) as described in 14.9.2.5 [temp.deduct.type].
If P is a reference type, the type referred to by P is used in place of P for type deduction and for any further references to or transformations of P in the remainder of this section.
If A is not a reference type:
If P is an array type, the pointer type produced by the array-to-pointer standard conversion (4.2 [conv.array]) is used in place of P for type deduction; otherwise,
If P is a function type, the pointer type produced by the function-to-pointer standard conversion (4.3 [conv.func]) is used in place of P for type deduction; otherwise,
If P is a cv-qualified type, the top level cv-qualifiers of P's type are ignored for type deduction.
If A is a cv-qualified type, the top level cv-qualifiers of A's type are ignored for type deduction. If A is a reference type, the type referred to by A is used for type deduction. If P is a reference type, the type referred to by P is used for type deduction.
(This resolution also resolves issues 493 and 913.)
[Drafting note: This change intentionally reverses the resolution of issue 322 (and applies it in a different form).]
There is no prohibition against specifying a function type in an
exception-specification, and the normal conversion of a
function type to a pointer-to-function type occurs in both
throw-expressions (
Proposed resolution (February, 2010):
Change 15.4 [except.spec] paragraphs 2-3 as follows:
A type denoted in an exception-specification shall not denote an incomplete type. A type denoted in an exception-specification shall not denote a pointer or reference to an incomplete type, other than void*, const void*, volatile void*, or const volatile void*. A type cv T, “array of T,” or “function returning T” denoted in an exception-specification is adjusted to type T, “pointer to T,” or “pointer to function returning T,” respectively.
If any declaration of a function has an exception-specification, all declarations, including the definition and an any explicit specialization, of that function shall have an exception-specification with the same set of type-ids adjusted types. If any declaration of a pointer to function, reference to function, or pointer to member function has an exception-specification, all occurrences of that declaration shall have an exception-specification with the same set of type-ids adjusted types. In an explicit instantiation an exception-specification may be specified, but is not required. If an exception-specification is specified in an explicit instantiation directive, it shall have the same set of type-ids adjusted types as other declarations of that function. A diagnostic is required only if the sets of type-ids adjusted types are different within a single translation unit.
According to 1.3 [intro.defs], “dynamic type,”
The dynamic type of an rvalue expression is its static type.
This is not true of an rvalue reference, which can be bound to an object of a class type derived from the reference's static type.
Proposed resolution (June, 2008):
Change 1.3 [intro.defs], “dynamic type,” as follows:
the type of the most derived object (1.8 [intro.object]) to which the lvalue denoted by an lvalue or an rvalue-reference (clause 5 [expr]) expression refers. [Example: if a pointer (8.3.1 [dcl.ptr]) p whose static type is “pointer to class B” is pointing to an object of class D, derived from B (clause 10 [class.derived]), the dynamic type of the expression *p is “D.” References (8.3.2 [dcl.ref]) are treated similarly. —end example] The dynamic type of an rvalue expression that is not an rvalue reference is its static type.
Notes from the June, 2008 meeting:
Because expressions have an rvalue reference type only fleetingly, immediately becoming either lvalues or rvalues and no longer references, the CWG expressed a desire for a different approach that would somehow describe an rvalue that resulted from an rvalue reference instead of using the concept of an expression that is an rvalue reference, as above. This approach could also be used in the resolution of issue 664.
Additional note (August, 2008):
This issue, along with issue 664, indicates that rvalue references have more in common with lvalues than with other rvalues: they denote particular objects, thus allowing object identity and polymorphic behavior. That suggests that these issues may be just the tip of the iceberg: restrictions on out-of-lifetime access to objects, the aliasing rules, and many other specifications are written to apply only to lvalues, on the assumption that only lvalues refer to specific objects. That assumption is no longer valid with rvalue references.
This suggests that it might be better to classify all rvalue references, not just named rvalue references, as lvalues instead of rvalues, and then just change the reference binding, overload resolution, and template argument deduction rules to cater to the specific kind of lvalues that are associated with rvalue references.
Additional note, May, 2009:
Another place in the Standard where the assumption is made that only lvalues can have dynamic types that differ from their static types is 5.2.8 [expr.typeid] paragraph 2.
(See also issues 846 and 863.)
Additional note, September, 2009:
Yet another complication is the statement in 3.10 [basic.lval] paragraph 9 stating that “non-class rvalues always have cv-unqualified types.” If an rvalue reference is an rvalue, then the following example is well-formed:
void f(int&&); // reference to non-const
void g() {
const int i = 0;
f(static_cast<const int&&>(i));
}
The static_cast binds an rvalue reference to the const object i, but the fact that it's an rvalue means that the cv-qualification is lost, effectively allowing the parameter of f, a reference to non-const, to bind directly to the const object.
Proposed resolution (February, 2010):
See paper N3030.
There are several instances of undefined behavior in lexical processing:
2.2 [lex.phases] paragraph 1, phase 2: a universal-character-name resulting from a line splice.
2.2 [lex.phases] paragraph 1, phase 2: a file ending without a new-line character or with a new-line character that is spliced away.
2.2 [lex.phases] paragraph 1, phase 4: a universal-character-name resulting from macro token concatenation.
2.9 [lex.header] paragraph 2: ', \, /*, //, or " appearing in a header-name.
These would be more appropriately handled as conditionally-supported behavior, requiring implementations either to document their handling of these constructs or to issue a diagnostic.
Additional note, March, 2009:
The undefined behavior referred to above regarding universal-character-names is the result of the considerations described in the C99 Rationale, section 5.2.1, in the part entitled “UCN models.” Three different models for support of UCNs are described, each involving different conversions between UCNs and wide characters and/or at different times during program translation. Implementations, as well as the specification in a language standard, can employ any of the three, but it must be impossible for a well-defined program to determine which model was actually employed by implementation. The implication of this “equivalence principle” is that any construct that would give different results under the different models must be classified as undefined behavior. For example, an apparent UCN resulting from a line-splice would be recognized as a UCN by an implementation in which all wide characters were translated immediately into UCNs, as described in C++ phase 1, but would not be recognized as a UCN by another implementation in which all UCNs were translated immediately into wide characters (a possibility mentioned parenthetically in C++ phase 1).
There are additional implications for this “equivalence principle” beyond the ones identified in the UK CD comments. See also issue 578; presumably a string like the one in that issue should also be described as having undefined behavior. Also, because C++'s model introduces backslash characters as part of UCNs for any character outside the basic source character set, any header-name that contains such a character (e.g., #include "@.h") will have undefined behavior in C++. This is also the reason that UCNs are translated into wide characters inside raw strings: two of the three models articulated in the C99 Rationale translate to or from UCNs in phase 1, before raw strings are recognized as tokens in phase 3, so raw strings cannot treat UCNs differently from the way they are treated in other contexts. See also issue 789 for similar points regarding trigraphs.
Notes from the October, 2009 meeting:
The CWG decided that the non-UCN aspects of this issue should be resolved, while the overall questions regarding trigraphs, UCNs, and raw strings will be investigated separately.
Proposed resolution (February, 2010):
Change 2.2 [lex.phases] paragraph 1 phase 2 as follows:
...If a A source file that is not empty and that does not end in a new-line character, or that ends in a new-line character immediately preceded by a backslash character before any such splicing takes place, the behavior is undefined shall be processed as if an additional new-line character were appended to the file.
Change 2.9 [lex.header] paragraph 2 as follows:
If The appearance of either of the characters ' or \, or of either of the character sequences /* or // appears in a q-char-sequence or a an h-char-sequence is conditionally-supported with implementation-defined semantics, or as is the appearance of the character " appears in a an h-char-sequence, the behavior is undefined. [Footnote: Thus, a sequences of characters that resembles an escape sequences cause undefined behavior might result in an error, be interpreted as the character corresponding to the escape sequence, or have a completely different meaning, depending on the implementation. —end footnote]
Trigraphs are a complicated solution to an old problem, that cause more problems than they solve in the modern environment. Unexpected trigraphs in string literals and occasionally in comments can be very confusing for the non-expert. They should be deprecated.
Notes from the March, 2009 meeting:
IBM, at least, uses trigraphs in its header files in conditional compilation directives to select character-set dependent content in a character-set independent fashion and would thus be negatively affected by the removal of trigraphs. One possibility that was discussed was to avoid expanding trigraphs inside character string literals, which is the context that causes most surprise and confusion, but still to support them in the rest of the program text. Specifying that approach, however, would be challenging because trigraphs are replaced in phase 1, before character strings are recognized in phase 3. See also the similar discussion of universal-character-names in issue 787.
The consensus of the CWG was that trigraphs should be deprecated.
Proposed resolution (September, 2009):
See paper PL22.16/09-0168 = WG21 N2978.
Notes from the October, 2009 meeting:
The CWG is interested in exploring other alternatives that address the particular problem of trigraphs in raw strings but that do not require the grammar changes of the approach in N2978. One possibility might be to recognize raw strings in some way in translation phase 1.
3.1 [basic.def] makes statements about declarations that do not appear to apply to static_assert-declarations. For example, paragraph 1 says,
A declaration (clause 7 [dcl.dcl]) introduces names into a translation unit or redeclares names introduced by previous declarations. A declaration specifies the interpretation and attributes of these names.
What name is being declared or described by a static_assert-declaration?
Also, paragraph 2 lists the kinds of declarations that are not definitions, and a static_assert-declaration is not among them. Is it intentional that static_assert-declarations are definitions?
Proposed resolution (February, 2010):
Change 3.1 [basic.def] paragraphs 1-2 as follows:
A declaration (Clause 7 [dcl.dcl]) introduces names into a translation unit or redeclares names introduced by previous declarations. A declaration specifies the interpretation and attributes of these names. A declaration may also have effects including
a static assertion (Clause 7 [dcl.dcl]),
immediate template instantiation (14.8.2 [temp.explicit]),
deferral of template instantiation (14.8.2 [temp.explicit]),
the establishment of attributes (Clause 7 [dcl.dcl]), or
nothing (in the case of an empty-declaration).
A declaration is a definition unless it declares a function without specifying the function's body (8.4 [dcl.fct.def]), it contains the extern specifier (7.1.1 [dcl.stc]) or a linkage-specification24 (7.5 [dcl.link]) and neither an initializer nor a function-body, it declares a static data member in a class definition (9.4 [class.static]), it is a class name declaration (9.1 [class.name]), it is an opaque-enum-declaration (7.2 [dcl.enum]), or it is a typedef declaration (7.1.3 [dcl.typedef]), a using-declaration (7.3.3 [namespace.udecl]), a static_assert-declaration (Clause 7 [dcl.dcl]), an attribute-declaration (Clause 7 [dcl.dcl]), an empty-declaration (Clause 7 [dcl.dcl]), or a using-directive (7.3.4 [namespace.udir]).
I thought this case would result in undefined behavior according to 3.2 [basic.def.odr]:
// t.h:
struct A { void (*p)(); };
// t1.cpp:
#include "t.h" // A::p is a pointer to C++ func
// t2.cpp:
extern "C" {
#include "t.h" // A::p is a pointer to C func
}
...but I don't see how any of the bullets in the list in paragraph 5 apply.
Proposed resolution (February, 2010):
Add a new bullet following 3.2 [basic.def.odr] paragraph 5, second bullet:
...Given such an entity named D defined in more than one translation unit, then
each definition of D shall consist of the same sequence of tokens; and
in each definition of D, corresponding names, looked up according to 3.4 [basic.lookup], shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution (13.3 [over.match]) and after matching of partial template specialization (14.9.3 [temp.over]), except that a name can refer to a const object with internal or no linkage if the object has the same literal type in all definitions of D, and the object is initialized with a constant expression (5.19 [expr.const]), and the value (but not the address) of the object is used, and the object has the same value in all definitions of D; and
in each definition of D, corresponding entities that have a given language linkage shall have the same language linkage; and
...
In describing static data members initialized inside the class definition, 9.4.2 [class.static.data] paragraph 3 says,
The member shall still be defined in a namespace scope if it is used in the program...
The definition of “used” is in 3.2 [basic.def.odr] paragraph 1:
An object or non-overloaded function whose name appears as a potentially-evaluated expression is used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19 [expr.const]) and the lvalue-to-rvalue conversion (4.1 [conv.lval]) is immediately applied.
Now consider the following example:
struct S {
static const int a = 1;
static const int b = 2;
};
int f(bool x) {
return x ? S::a : S::b;
}
According to the current wording of the Standard, this example requires that S::a and S::b be defined in a namespace scope. The reason for this is that, according to 5.16 [expr.cond] paragraph 4, the result of this conditional-expression is an lvalue and the lvalue-to-rvalue conversion is applied to that, not directly to the object, so this fails the “immediately applied” requirement. This is surprising and unfortunate, since only the values and not the addresses of the static data members are used. (This problem also applies to the proposed resolution of issue 696.)
Proposed resolution (November, 2009):
Change 3.2 [basic.def.odr] paragraph 1 as follows:
...An object or non-overloaded function whose name appears as a potentially-evaluated expression x is used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19 [expr.const]) and the lvalue-to-rvalue conversion (4.1 [conv.lval]) is immediately applied eventually applied to all lvalue expressions e that could possibly denote that object, where e is a subexpression of the full-expression containing x...
Additional notes (November, 2009):
The proposed wording (like the existing wording) requires that S::a be defined in the following example:
struct S {
static const int a = 1;
};
void g() {
S::a; // no lvalue-to-rvalue conversion
}
Although this particular example is obviously unimportant, there could be similar cases where a use is buried in a nested conditional and the result eventually discarded, perhaps as the result of a macro expansion. An alternative approach that addresses this point might be something along the lines of
There is no lvalue expression e of which x is a subexpression to which a reference is bound or to which the unary & operator is applied that could possibly denote that object.
One objection to this latter approach is that it would require defining S::a if the expression were something like *&S::a, which would not be the case with the wording in the proposed resolution.
The various uses of the term “declarative region” throughout the Standard indicate that the term is intended to refer to the entire block, class, or namespace that contains a given declaration. For example, 3.3 [basic.scope] paragraph 2 says, in part:
[Example: in
int j = 24; int main() { int i = j, j; j = 42; }The declarative region of the first j includes the entire example... The declarative region of the second declaration of j (the j immediately before the semicolon) includes all the text between { and }...
However, the actual definition given for “declarative region” in 3.3 [basic.scope] paragraph 1 does not match this usage:
Every name is introduced in some portion of program text called a declarative region, which is the largest part of the program in which that name is valid, that is, in which that name may be used as an unqualified name to refer to the same entity.
Because (except in class scope) a name cannot be used before it is declared, this definition contradicts the statement in the example and many other uses of the term throughout the Standard. As it stands, this definition is identical to that of the scope of a name.
The term “scope” is also misused. The scope of a declaration is defined in 3.3 [basic.scope] paragraph 1 as the region in which the name being declared is valid. However, there is frequent use of the phrase “the scope of a class,” not referring to the region in which the class's name is valid but to the declarative region of the class body, and similarly for namespaces, functions, exception handlers, etc. There is even a mention of looking up a name “in the scope of the complete postfix-expression” (3.4.5 [basic.lookup.classref] paragraph 3), which is the exact inverse of the scope of a declaration.
This terminology needs a thorough review to make it logically consistent. (Perhaps a discussion of the scope of template parameters could also be added to section 3.3 [basic.scope] at the same time, as all other kinds of scopes are described there.)
Proposed resolution (November, 2006):
Change 3.3 [basic.scope] paragraph 1 as follows:
Every name is introduced in some portion of program text called a declarative region, which is the largest part of the program in which that name is valid, that is, in which that name may be used as an unqualified name to refer to the same entity a statement, block, function declarator, function-definition, class, handler, template-declaration, template-parameter-list of a template template-parameter, or namespace. In general, each particular name is valid may be used as an unqualified name to refer to the entity of its declaration or to the label only within some possibly discontiguous portion of program text called its scope. To determine the scope of a declaration...
Change 3.3 [basic.scope] paragraph 3 as follows:
The names declared by a declaration are introduced into the scope in which the declaration occurs declarative region that directly encloses the declaration, except that declaration-statements, function parameter names in the declarator of a function-definition, exception-declarations (3.3.3 [basic.scope.local]), the presence of a friend specifier (11.4 [class.friend]), certain uses of the elaborated-type-specifier (7.1.6.3 [dcl.type.elab]), and using-directives (7.3.4 [namespace.udir]) alter this general behavior.
Change 3.3.3 [basic.scope.local] paragraphs 1-3 and add a new paragraph 4 before the existing paragraph 4 as follows:
A name declared in a block (6.3 [stmt.block]) is local to that block. Its potential scope begins at its point of declaration (3.3.2 [basic.scope.pdecl]) and ends at the end of its declarative region. The declarative region of a name declared in a declaration-statement is the directly enclosing block (6.3 [stmt.block]). Such a name is local to the block.
The potential scope declarative region of a function parameter name (including one appearing in the declarator of a function-definition or in a lambda-parameter-declaration-clause) or of a function-local predefined variable in a function definition (8.4 [dcl.fct.def]) begins at its point of declaration. If the function has a function-try-block the potential scope of a parameter or of a function-local predefined variable ends at the end of the last associated handler, otherwise it ends at the end of the outermost block of the function definition. A parameter name is the entire function definition or lambda-expression. Such a name is local to the function definition and shall not be redeclared in the any outermost block of the function definition nor in the outermost block of any handler associated with a function-try-block function-body (including handlers of a function-try-block) or lambda-expression.
The name in a catch exception-declaration The declarative region of a name declared in an exception-declaration is its entire handler. Such a name is local to the handler and shall not be redeclared in the outermost block of the handler.
The potential scope of any local name begins at its point of declaration (3.3.2 [basic.scope.pdecl]) and ends at the end of its declarative region.
Change 3.3.5 [basic.funscope] as indicated:
Labels (6.1 [stmt.label]) have function scope and may be used anywhere in the function in which they are declared except in members of local classes (9.8 [class.local]) of that function. Only labels have function scope.
Change 6.7 [stmt.dcl] paragraph 1 as follows:
A declaration statement introduces one or more new identifiers names into a block; it has the form
declaration-statement:
block-declaration
[Note: If an identifier a name introduced by a declaration was previously declared in an outer block, the outer declaration is hidden for the remainder of the block, after which it resumes its force (3.3.11 [basic.scope.hiding]). —end note]
[Drafting notes: This resolution deals almost exclusively with the unclear definition of “declarative region.” I've left the ambiguous use of “scope” alone for now. However sections 3.3.x all have headings reading “xxx scope,” but they don't mean the scope of a declaration but the different kinds of declarative regions and their effects on the scope of declarations contained therein. To me, it looks like most of 3.4 should refer to “declarative region” and not to “scope.”
The change to 6.7 fixes an “identifier” misuse (e.g., extern T operator+(T,T); at block scope introduces a name but not an identifier) and removes normative redundancy.]
The Standard does not completely specify how to look up the type-name(s) in a pseudo-destructor-name (5.2 [expr.post] paragraph 1, 5.2.4 [expr.pseudo]), and what information it does have is incorrect and/or in the wrong place. Consider, for instance, 3.4.5 [basic.lookup.classref] paragraphs 2-3:
If the id-expression in a class member access (5.2.5 [expr.ref]) is an unqualified-id, and the type of the object expression is of a class type C (or of pointer to a class type C), the unqualified-id is looked up in the scope of class C. If the type of the object expression is of pointer to scalar type, the unqualified-id is looked up in the context of the complete postfix-expression.
If the unqualified-id is ~type-name, and the type of the object expression is of a class type C (or of pointer to a class type C), the type-name is looked up in the context of the entire postfix-expression and in the scope of class C. The type-name shall refer to a class-name. If type-name is found in both contexts, the name shall refer to the same class type. If the type of the object expression is of scalar type, the type-name is looked up in the scope of the complete postfix-expression.
There are at least three things wrong with this passage with respect to pseudo-destructors:
A pseudo-destructor call (5.2.4 [expr.pseudo]) is not a “class member access”, so the statements about scalar types in the object expressions are vacuous: the object expression in a class member access is required to be a class type or pointer to class type (5.2.5 [expr.ref] paragraph 2).
On a related note, the lookup for the type-name(s) in a pseudo-destructor name should not be described in a section entitled “Class member access.”
Although the class member access object expressions are carefully allowed to be either a class type or a pointer to a class type, paragraph 2 mentions only a “pointer to scalar type” (disallowing references) and paragraph 3 deals only with a “scalar type,” presumably disallowing pointers (although it could possibly be a very subtle way of referring to both non-class pointers and references to scalar types at once).
The other point at which lookup of pseudo-destructors is mentioned is 3.4.3 [basic.lookup.qual] paragraph 5:
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.
Again, this specification is in the wrong location (a pseudo-destructor-name is not a qualified-id and thus should not be treated in the “Qualified name lookup” section).
Finally, there is no place in the Standard that describes the lookup for pseudo-destructor calls of the form p->T::~T() and r.T::~T(), where p and r are a pointer and reference to scalar, respectively. To the extent that it gives any guidance at all, 3.4.5 [basic.lookup.classref] deals only with the case where the ~ immediately follows the . or ->, and 3.4.3 [basic.lookup.qual] deals only with the case where the pseudo-destructor-name contains a nested-name-specifier that designates a scope in which names can be looked up.
See document J16/06-0008 = WG21 N1938 for further discussion of this and related issues, including 244, 305, 399, and 414.
Proposed resolution (June, 2008):
Add a new paragraph following 5.2 [expr.post] paragraph 2 as follows:
When a postfix-expression is followed by a dot . or arrow -> operator, the interpretation depends on the type T of the expression preceding the operator. If the operator is ., T shall be a scalar type or a complete class type; otherwise, T shall be a pointer to a scalar type or a pointer to a complete class type. When T is a (pointer to) a scalar type, the postfix-expression to which the operator belongs shall be a pseudo-destructor call (5.2.4 [expr.pseudo]); otherwise, it shall be a class member access (5.2.5 [expr.ref]).
Change 5.2.4 [expr.pseudo] paragraph 2 as follows:
The left-hand side of the dot operator shall be of scalar type. The left-hand side of the arrow operator shall be of pointer to scalar type. This scalar type The type of the expression preceding the dot operator, or the type to which the expression preceding the arrow operator points, is the object type...
Change 5.2.5 [expr.ref] paragraph 2 as follows:
For the first option (dot) the type of the first expression (the object expression) shall be “class object” (of a complete type) is a class type. For the second option (arrow) the type of the first expression (the pointer expression) shall be “pointer to class object” (of a complete type) is a pointer to a class type. In these cases, the id-expression shall name a member of the class or of one of its base classes.
Add a new paragraph following 3.4 [basic.lookup] paragraph 2 as follows:
In a pseudo-destructor-name that does not include a nested-name-specifier, the type-names are looked up as types in the context of the complete expression.
Delete the last sentence of 3.4.5 [basic.lookup.classref] paragraph 2:
If the id-expression in a class member access (5.2.5 [expr.ref]) is an unqualified-id, and the type of the object expression is of a class type C, the unqualified-id is looked up in the scope of class C. If the type of the object expression is of pointer to scalar type, the unqualified-id is looked up in the context of the complete postfix-expression.
Is this case valid? G++ compiles it.
namespace X {
namespace Y {
struct X {
void f()
{
using namespace X::Y;
namespace Z = X::Y;
}
};
}
}
The relevant citation from the standard is 3.4.6 [basic.lookup.udir]: "When looking up a namespace-name in a using-directive or namespace-alias-definition, only namespace names are considered." This statement could reasonably be interpreted to apply only to the last element of a qualified name, and that's the way EDG and Microsoft seem to interpret it.
However, since a class can't contain a namespace, it seems to me that this interpretation is, shall we say, sub optimal. If the X qualifiers in the above example are interpreted as referring to the struct X, an error of some sort is inevitable, since there can be no namespace for the qualified name to refer to. G++ apparently interprets 3.4.6 [basic.lookup.udir] as applying to nested-name-specifiers in those contexts as well, which makes a valid interpretation of the test possible.
I'm thinking it might be worth tweaking the words in 3.4.6 [basic.lookup.udir] to basically mandate the more useful interpretation. Of course a person could argue that the difference would matter only to a perverse program. On the other hand, namespaces were invented specifically to enable the building of programs that would otherwise be considered perverse. Where name clashes are concerned, one man's perverse is another man's real world.
Proposed Resolution (November, 2006):
Change 3.4.6 [basic.lookup.udir] paragraph 1 as follows:
When looking up a namespace-name in a using-directive or namespace-alias-definition, In a using-directive or namespace-alias-definition, during the lookup for a namespace-name or for a name in a nested-name-specifier, only namespace names are considered.
An lvalue referring to an out-of-lifetime non-POD class objects can be used in limited ways, subject to the restrictions in 3.8 [basic.life] paragraph 6:
if the original object will be or was of a non-POD class type, the program has undefined behavior if:
the lvalue is used to access a non-static data member or call a non-static member function of the object, or
the lvalue is implicitly converted (4.10 [conv.ptr]) to a reference to a base class type, or
the lvalue is used as the operand of a static_cast (5.2.9 [expr.static.cast]) except when the conversion is ultimately to cv char& or cv unsigned char& ), or
the lvalue is used as the operand of a dynamic_cast (5.2.7 [expr.dynamic.cast]) or as the operand of typeid.
There are at least a couple of questionable things in this list. First, there is no “implicit conversion to a reference to a base class,” as assumed by the second bullet. Presumably this is intended to say that the lvalue is bound to a reference to a base class, and the cross-reference should be to 8.5.3 [dcl.init.ref], not to 4.10 [conv.ptr] (which deals with pointer conversions). However, even given that adjustment, it is not clear why it is forbidden to bind a reference to a non-virtual base class of an out-of-lifetime object, as that is just an address offset calculation. (Binding to a virtual base, of course, would require access to the value of the object and thus cannot be done outside the object's lifetime.)
The third bullet also appears questionable. It's not clear why static_cast is discussed at all here, as the only permissible static_cast conversions involving reference types and non-POD classes are to references to base or derived classes and to the same type, modulo cv-qualification; if implicit “conversion” to a base class reference is forbidden in the second bullet, why would an explicit conversion be permitted in the third? Was this intended to refer to reinterpret_cast? Also, is there a reason to allow char types but disallow array-of-char types (which are more likely to be useful than a single char)?
Proposed resolution (March, 2008):
Change 3.8 [basic.life] paragraph 5 as follows:
...If the object will be or was of a non-trivial class type, the program has undefined behavior if:
the pointer is used to access a non-static data member or call a non-static member function of the object, or
the pointer is implicitly converted (
4.10 [conv.ptr] ) to a pointer to a virtual base class type, orthe pointer is used as the operand of a static_cast (5.2.9 [expr.static.cast]) (except when the conversion is to void*, or to void* and subsequently to char*, or unsigned char*). pointer to void, or to pointer to void and subsequently to pointer to cv char or pointer to cv unsigned char, or
the pointer is used as the operand of a dynamic_cast (5.2.7 [expr.dynamic.cast])...
Change 3.8 [basic.life] paragraph 6 as follows:
...if the original object will be or was of a non-trivial class type, the program has undefined behavior if:
the lvalue is used to access a non-static data member or call a non-static member function of the object, or
the lvalue is implicitly converted (4.10 [conv.ptr]) bound to a reference to a virtual base class type (8.5.3 [dcl.init.ref]), or
the lvalue is used as the operand of a static_cast (5.2.9 [expr.static.cast]) except when the conversion is ultimately to cv char& or cv unsigned char&, or
the lvalue is used as the operand of a dynamic_cast (5.2.7 [expr.dynamic.cast]) or as the operand of typeid.
[Drafting notes: Paragraph 5 was changed to track the changes to paragraph 6. See also the resolution for issue 658.]
Sent in by David Abrahams:
Yes, and to add to this tangent, 3.9.1 [basic.fundamental] paragraph 1 states "Plain char, signed char, and unsigned char are three distinct types." Strangely, 3.9 [basic.types] paragraph 2 talks about how "... the underlying bytes making up the object can be copied into an array of char or unsigned char. 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." I guess there's no requirement that this copying work properly with signed chars!
Notes from October 2002 meeting:
We should do whatever C99 does. 6.5p6 of the C99 standard says "array of character type", and "character type" includes signed char (6.2.5p15), and 6.5p7 says "character type". But see also 6.2.6.1p4, which mentions (only) an array of unsigned char.
Proposed resolution (April 2003):
Change 3.8 [basic.life] paragraph 5 bullet 3 from
to
Change 3.8 [basic.life] paragraph 6 bullet 3 from
to
Change the beginning of 3.9 [basic.types] paragraph 2 from
For any object (other than a base-class subobject) of POD 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.
to
For any object (other than a base-class subobject) of POD 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 byte-character type.
Add the indicated text to 3.9.1 [basic.fundamental] paragraph 1:
Objects declared as characters (char) shall be large enough to store any member of the implementation's basic character set. If a character from this set is stored in a character object, the integral value of that character object is equal to the value of the single character literal form of that character. It is implementation-defined whether a char object can hold negative values. Characters can be explicitly declared unsigned or signed. Plain char, signed char, and unsigned char are three distinct types, called the byte-character types. A char, a signed char, and an unsigned char occupy the same amount of storage and have the same alignment requirements (3.9 [basic.types]); that is, they have the same object representation. For byte-character types, all bits of the object representation participate in the value representation. For unsigned byte-character types, all possible bit patterns of the value representation represent numbers. These requirements do not hold for other types. In any particular implementation, a plain char object can take on either the same values as a signed char or an unsigned char; which one is implementation-defined.
Change 3.10 [basic.lval] paragraph 15 last bullet from
to
Notes from October 2003 meeting:
It appears that in C99 signed char may have padding bits but no trap representation, whereas in C++ signed char has no padding bits but may have -0. A memcpy in C++ would have to copy the array preserving the actual representation and not just the value.
March 2004: The liaisons to the C committee have been asked to tell us whether this change would introduce any unnecessary incompatibilities with C.
Notes from October 2004 meeting:
The C99 Standard appears to be inconsistent in its requirements. For example, 6.2.6.1 paragraph 4 says:
The value may be copied into an object of type unsigned char [n] (e.g., by memcpy); the resulting set of bytes is called the object representation of the value.
On the other hand, 6.2 paragraph 6 says,
If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one.
Mike Miller will investigate further.
Proposed resolution (February, 2010):
Change 3.8 [basic.life] paragraph 5 bullet 4 as follows:
...The program has undefined behavior if:
...
the pointer is used as the operand of a static_cast (5.2.9 [expr.static.cast]) (except when the conversion is to cv void*, or to cv void* and subsequently to char*, or unsigned char* a pointer to a cv-qualified or cv-unqualified byte-character type (3.9.1 [basic.fundamental])), or
...
Change 3.8 [basic.life] paragraph 6 bullet 4 as follows:
...The program has undefined behavior if:
...
the lvalue is used as the operand of a static_cast (5.2.9 [expr.static.cast]) except when the conversion is ultimately to cv char& or cv unsigned char& a reference to a cv-qualified or cv-unqualified byte-character type (3.9.1 [basic.fundamental]) or an array thereof, or
...
Change 3.9 [basic.types] paragraph 2 as follows:
For any object (other than a base-class subobject) of trivially copyable 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 a byte-character type (3.9.1 [basic.fundamental]).39 If the content of the that array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value. [Example:...
Change 3.9.1 [basic.fundamental] paragraph 1 as follows:
...Characters can be explicitly declared unsigned or signed. Plain char, signed char, and unsigned char are three distinct types, called the byte-character types. A char, a signed char, and an unsigned char occupy the same amount of storage and have the same alignment requirements (3.11 [basic.align]); that is, they have the same object representation. For byte-character types, all bits of the object representation participate in the value representation. For unsigned character types unsigned char, all possible bit patterns of the value representation represent numbers...
Change 3.10 [basic.lval] paragraph 15 final bullet as follows:
If a program attempts to access the stored value of an object through an lvalue of other than one of the following types the behavior is undefined 52
...
a char or unsigned char byte-character type (3.9.1 [basic.fundamental]).
Change 3.11 [basic.align] paragraph 6 as follows:
The alignment requirement of a complete type can be queried using an alignof expression (5.3.6 [expr.alignof]). Furthermore, the byte-character types (3.9.1 [basic.fundamental]) char, signed char, and unsigned char shall have the weakest alignment requirement. [Note: this enables the byte-character types to be used as the underlying type for an aligned memory area (7.6.2 [dcl.align]). —end note]
Change 5.3.4 [expr.new] paragraph 10 as follows:
...For arrays of char and unsigned char a byte-character type (3.9.1 [basic.fundamental]), the difference between the result of the new-expression and the address returned by the allocation function shall be an integral multiple of the strictest fundamental alignment requirement (3.11 [basic.align]) of any object type whose size is no greater than the size of the array being created. [Note: Because allocation functions are assumed to return pointers to storage that is appropriately aligned for objects of any type with fundamental alignment, this constraint on array allocation overhead permits the common idiom of allocating byte-character arrays into which objects of other types will later be placed. —end note]
Is the following example well-formed?
struct S {
static char a[5];
};
char S::a[]; // Unspecified bound in definition
3.5 [basic.link] paragraph 10 certainly makes allowance for declarations to differ in the presence or absence of a major array bound. However, 3.1 [basic.def] paragraph 5 says that
A program is ill-formed if the definition of any object gives the object an incomplete type (3.9 [basic.types]).
3.9 [basic.types] paragraph 7 says,
The declared type of an array object might be an array of unknown size and therefore be incomplete at one point in a translation unit and complete later on; the array types at those two points (“array of unknown bound of T” and “array of N T”) are different types.
This wording appears to make no allowance for the C concept of “composite type;” instead, each declaration is said to have its own type. By this interpretation, the example is ill-formed, because the type declared by the definition of S::a is incomplete.
If the example is intended to be well-formed, the Standard needs explicit wording stating that an omitted array bound in a declaration is implicitly taken from that of a visible declaration of that object, if any.
Notes from the April, 2007 meeting:
The CWG agreed that this usage should be permitted.
Proposed resolution (June, 2008):
Change 8.3.4 [dcl.array] paragraph 1 as follows:
...If Except as noted below, if the constant expression is omitted, the type of the identifier of D is “derived-declarator-type-list array of unknown bound of T,” an incomplete object type...
Change 8.3.4 [dcl.array] paragraph 3 as follows:
When several “array of” specifications are adjacent, a multidimensional array is created; only the first of the constant expressions that specify the bounds of the arrays can may be omitted only for the first member of the sequence. [Note: this elision is useful for function parameters of array types, and when the array is external and the definition, which allocates storage, is given elsewhere. —end note] In addition to declarations in which an incomplete object type is allowed, an array bound may be omitted in the declaration of a function parameter (8.3.5 [dcl.fct]). The first constant-expression can An array bound may also be omitted when the declarator is followed by an initializer (8.5 [dcl.init]). In this case the bound is calculated from the number of initial elements (say, N) supplied (8.5.1 [dcl.init.aggr]), and the type of the identifier of D is “array of N T.” Furthermore, if there is a visible declaration of the name declared by the declarator-id (if any) in which the bound was specified, an omitted array bound is taken to be the same as in that earlier declaration.
Notes from the September, 2008 meeting:
The proposed resolution does not capture the result favored by the CWG: array bound information should be accumulated across declarations within the same scope, but a block extern declaration in a nested scope should not inherit array bound information from the outer declaration. (This is consistent with the treatment of default arguments in function declarations.) For example:
int a[5];
void f() {
extern int a[];
sizeof(a);
}
Although there was some confusion about the C99 wording dealing with this case, it is probably well-formed in C99. However, it should be ill-formed in C++, because we want to avoid the concept of “compatible types” as it exists in C.
Proposed resolution (February, 2010):
Change 8.3.4 [dcl.array] paragraph 1 as follows:
...If Except as noted below, if the constant expression is omitted, the type of the identifier of D is “derived-declarator-type-list array of unknown bound of T,” an incomplete object type...
Change 8.3.4 [dcl.array] paragraphs 3-4 as follows:
When several “array of” specifications are adjacent, a multidimensional array is created; only the first of the constant expressions that specify the bounds of the arrays can may be omitted only for the first member of the sequence. [Note: this elision is useful for function parameters of array types, and when the array is external and the definition, which allocates storage, is given elsewhere. —end note] In addition to declarations in which an incomplete object type is allowed, an array bound may be omitted in the declaration of a function parameter (8.3.5 [dcl.fct]). The first constant-expression can An array bound may also be omitted when the declarator is followed by an initializer (8.5 [dcl.init]). In this case the bound is calculated from the number of initial elements (say, N) supplied (8.5.1 [dcl.init.aggr]), and the type of the identifier of D is “array of N T.” Furthermore, if there is a preceding declaration of the entity in the same scope in which the bound was specified, an omitted array bound is taken to be the same as in that earlier declaration, and similarly for the definition of a static data member of a class.
[Example:...
...can reasonably appear in an expression. Finally,
extern int x[10]; struct S { static int y[10]; }; int x[]; //OK: bound is 10 int S::y[]; //OK: bound is 10 void f() { extern int x[]; int i = sizeof(x); //error: incomplete object type }—end example]
The status of rvalue references to functions is not clear in the current wording. For example, 3.10 [basic.lval] paragraph 2 says,
An lvalue refers to an object or function. Some rvalue expressions—those of (possibly cv-qualified) class or array type—also refer to objects. [Footnote: Expressions such as invocations of constructors and of functions that return a class type refer to objects, and the implementation can invoke a member function upon such objects, but the expressions are not lvalues. —end footnote]
This would tend to indicate that there are no rvalues of function type. However, 5 [expr] paragraph 6 says,
If an expression initially has the type “rvalue reference to T” (8.3.2 [dcl.ref], 8.5.3 [dcl.init.ref]), the type is adjusted to “T” prior to any further analysis, and the expression designates the object or function denoted by the rvalue reference. If the expression is the result of calling a function, whether implicitly or explicitly, it is an rvalue; otherwise, it is an lvalue.
This explicitly indicates that rvalue references to functions are possible and that, in some cases, they yield function-typed rvalues. Furthermore, _N2914_.20.2.4 [concept.operator] paragraph 20 describes the concept Callable as:
auto concept Callable<typename F, typename... Args> {
typename result_type;
result_type operator()(F&, Args...);
result_type operator()(F&&, Args...);
}
It would be strange if Callable were satisfied for a function object type but not for a function type.
However, assuming that rvalue references to functions are
intended to be supported, it is not clear how an rvalue of function
type is supposed to behave. For instance,
For an ordinary function call, the postfix expression shall be either an lvalue that refers to a function (in which case the function-to-pointer standard conversion (4.3 [conv.func]) is suppressed on the postfix expression), or it shall have pointer to function type.
From this, it appears that an rvalue of function type cannot be used in a function call. It can't be converted to a pointer to function, either, as 4.3 [conv.func] paragraph 1 says,
An lvalue of function type T can be converted to an rvalue of type “pointer to T.” The result is a pointer to the function.
(See also issues 664 and especially 690. The approach described in the latter issue, viewing rvalue references as essentially lvalues rather than as essentially rvalues, could resolve the specification problems described above by eliminating the concept of an rvalue of function type.)
Proposed resolution (February, 2010):
See paper N3030.
4 [conv] paragraph 1 says,
Standard conversions are implicit conversions defined for built-in types.
However, enumeration types (which take part in the integral promotions) and class types (which take part in the lvalue-to-rvalue conversion) are not “built-in” types, so the definition of “standard conversions” is wrong.
Proposed resolution (October, 2006):
Change 4 [conv] paragraph 1 as follows:
Standard conversions are implicit conversions defined for built-in types with built-in meaning...
4.1 [conv.lval] paragraph 1 says,
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.
Proposed Resolution (November, 2006):
Add the indicated words to 3.9 [basic.types] paragraph 4:
... For trivial types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values. Any use of an indeterminate value (5.3.4 [expr.new], 8.5 [dcl.init], 12.6.2 [class.base.init]) of a type other than unsigned char results in undefined behavior.
Change 4.1 [conv.lval] paragraph 1 as follows:
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.
Additional note (May, 2008):
The C committee is dealing with a similar issue in their DR336. According to this analysis, they plan to take almost the opposite approach to the one described above by augmenting the description of their version of the lvalue-to-rvalue conversion. The CWG did not consider that access to an unsigned char might still trap if it is allocated in a register and needs to reevaluate the proposed resolution in that light. See also issue 129.
The adoption of paper N2844 made it ill-formed to attempt to bind an rvalue reference to an lvalue, but the example in 5 [expr] paragraph 6 was overlooked in making this change:
struct A { };
A&& operator+(A, A);
A&& f();
A a;
A&& ar = a;
The last line should be changed to use something like static_cast<A&&>(a).
(See also issue 847.)
Proposed resolution (July, 2009):
Change the example in 5 [expr] paragraph 6 as follows:
[Example:
struct A { }; A&& operator+(A, A); A&& f(); A a; A&& ar = static_cast<A&&>(a);The expressions f() and a + a are rvalues of type A. The expression ar is an lvalue of type A. —end example]
The grammar for nested-name-specifier in 5.1.1 [expr.prim.general] paragraph 7 does not allow decltype to be used in a qualified-id. This could be useful for cases like:
auto vec = get_vec(); decltype(vec)::value_type v = vec.first();(See also issue 950.)
Proposed resolution (September, 2009):
See paper PL22.16/09-0181 = WG21 N2991.
Proposed resolution (February, 2010):
See paper PL22.16/10-0021 = WG21 N3031.
A cast to an rvalue reference type produces an rvalue, and rvalues must have complete types (3.10 [basic.lval] paragraph 9). However, none of the sections dealing with cast operators in 5.2 [expr.post] require that the referred-to type must be complete in an rvalue reference cast.
(Note that the approach described for issue 690, in which an rvalue reference type would be essentially an lvalue instead of an rvalue, would address this issue as well, since lvalues can have incomplete types.)Proposed resolution (February, 2010):
See paper N3030.
The resolution to issue 195 makes “converting a pointer to a function into a pointer to an object type or vice versa” conditionally-supported behavior. In doing so, however, it overlooked the fact that void is not an “object type” (3.9 [basic.types] paragraph 9). The wording should be amended to allow conversion to and from void* types.
Proposed resolution (June, 2008):
Change 3.9.2 [basic.compound] paragraph 4 as follows:
Objects of cv-qualified (3.9.3 [basic.type.qualifier]) or cv-unqualified type void* (pointer to void) A pointer to cv-qualified or cv-unqualified void can be used to point to objects of unknown type. A void* shall be able to hold any object pointer and is thus considered to be an object pointer type, although it is not a pointer to object type (because void is not an object type). A cv-qualified or cv-unqualified (3.9.3 [basic.type.qualifier]) An object of type cv void* shall have the same representation and alignment requirements as a cv-qualified or cv-unqualified cv char*.
Change 4.10 [conv.ptr] paragraph 1 as follows:
...A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of pointer to object or pointer to function object pointer or function pointer type...
Change 5.2.10 [expr.reinterpret.cast] paragraph 7 as follows:
A pointer to an object An object pointer can be explicitly converted to a pointer to an object an object pointer of different type. Except that converting an rvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types or void and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified.
Change 5.2.10 [expr.reinterpret.cast] paragraph 8 as follows:
Converting a pointer to a function into a pointer to an object type a function pointer to an object pointer or vice versa is conditionally-supported...
[Drafting note: 14.2 [temp.param] paragraph 4 was not changed and thus continues to allow only pointers to objects, not object pointers, as non-type template parameters.]
Proposed resolution (February, 2010):
Change 3.7.4.3 [basic.stc.dynamic.safety] paragraphs 1-2 as follows:
A traceable pointer object is
an object of pointer-to-object object pointer type (3.9.2 [basic.compound]), or
an object of an integral type that is at least as large as std::intptr_t, or
a sequence of elements in an array of character type, where the size and alignment of the sequence match that of some pointer-to-object object pointer type.
A pointer value is a safely-derived pointer to a dynamic object only if it has pointer-to-object object pointer type and...
Change 3.9.2 [basic.compound] paragraphs 3-4 as follows:
The type of a pointer that can hold the address of an object is called an object pointer type. The type of a pointer that can designate a function is called a function pointer type. A pointer to objects of type T is referred to as a “pointer to T.” Example:...
Objects of cv-qualified (3.9.3 [basic.type.qualifier]) or cv-unqualified type void* (pointer to void), A pointer to cv-qualified or cv-unqualified void can be used to point to objects of unknown type. A void* Such a pointer shall be able to hold any object pointer and is thus considered to be an object pointer and to have an object pointer type (but not a pointer to object type, because void is not an object type). A cv-qualified or cv-unqualified (3.9.3 [basic.type.qualifier]) An object of type cv void* shall have the same representation and alignment requirements as a cv-qualified or cv-unqualified cv char*.
Change 4.10 [conv.ptr] paragraph 1 as follows:
...A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of pointer to object or pointer to function object pointer or function pointer type...
Change 5.2.10 [expr.reinterpret.cast] paragraphs 6-8 as follows:
A pointer to a function function pointer can be explicitly converted to a pointer to a function of a different function pointer type...
A pointer to an object An object pointer can be explicitly converted to a pointer to a different object pointer type.69...
Converting a pointer to a function function pointer into a pointer to an object to an object pointer type or vice versa is conditionally-supported...
In the “Index of Implementation Defined Behavior,” change the following item as indicated:
converting pointer to function into pointer to object function pointer to object pointer and vice versa
Split off from issue 315.
Incidentally, another thing that ought to be cleaned up is the inconsistent use of "indirection" and "dereference". We should pick one.
Proposed resolution (December, 2006):
Change 5.3.1 [expr.unary.op] paragraph 1 as follows:
The unary * operator performs indirection dereferences a pointer value: the expression to which it is applied shall be a pointer...
Change 8.3.4 [dcl.array] paragraph 8 as follows:
The results are added and indirection applied values are added and the result is dereferenced to yield an array (of five integers), which in turn is converted to a pointer to the first of the integers.
Change 8.3.5 [dcl.fct] paragraph 9 as follows:
The binding of *fpi(int) is *(fpi(int)), so the declaration suggests, and the same construction in an expression requires, the calling of a function fpi, and then using indirection through dereferencing the (pointer) result to yield an integer. In the declarator (*pif)(const char*, const char*), the extra parentheses are necessary to indicate that indirection through dereferencing a pointer to a function yields a function, which is then called.
Change the index for * and “dereferencing” no longer to refer to “indirection.”
[Drafting note: 26.6.9 [template.indirect.array] requires no change. Many more places in the current wording use “dereferencing” than “indirection.”]
According to the C++ Standard section 5.3.4 [expr.new] paragraph 21 it is unspecified whether the allocation function is called before evaluating the constructor arguments or after evaluating the constructor arguments but before entering the constructor.
On top of that paragraph 17 of the same section insists that
If any part of the object initialization described above [Footnote: This may include evaluating a new-initializer and/or calling a constructor.] terminates by throwing an exception and a suitable deallocation function is found, the deallocation function is called to free the memory in which the object was being constructed... If no unambiguous matching deallocation function can be found, propagating the exception does not cause the object's memory to be freed...
Now suppose we have:
struct copy_throw {
copy_throw(const copy_throw&)
{ throw std::logic_error("Cannot copy!"); }
copy_throw(long, copy_throw)
{ }
copy_throw()
{ }
};
int main()
try {
copy_throw an_object, /* undefined behaviour */
* a_pointer = ::new copy_throw(0, an_object);
return 0;
}
catch(const std::logic_error&)
{ }
Here the new-expression '::new copy_throw(0, an_object)' throws an exception when evaluating the constructor's arguments and before the allocation function is called. However, 5.3.4 [expr.new] paragraph 17 prescribes that in such a case the implementation shall call the deallocation function to free the memory in which the object was being constructed, given that a matching deallocation function can be found.
So a call to the Standard library deallocation function '::operator delete(void*)' shall be issued, but what argument is an implementation supposed to supply to the deallocation function? As per 5.3.4 [expr.new] paragraph 17 - the argument is the address of the memory in which the object was being constructed. Given that no memory has yet been allocated for the object, this will qualify as using an invalid pointer value, which is undefined behaviour by virtue of 3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 4.
Suggested resolution:
Change the first sentence of 5.3.4 [expr.new] paragraph 17 to read:
If the memory for the object being created has already been successfully allocated and any part of the object initialization described above...
Proposed resolution (March, 2008):
Change 5.3.4 [expr.new] paragraph 18 as follows:
If any part of the object initialization described above [Footnote: ...] terminates by throwing an exception, storage has been obtained for the object, and a suitable deallocation function can be found, the deallocation function is called...
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.
Note (March, 2008):
This issue should be closed as NAD as a result of the rewrite of 5.19 [expr.const] in conjunction with the constexpr proposal.
The grammar for condition in 6.4 [stmt.select] paragraph 1 does not allow for the constexpr specifier. This was not intended by the original proposal.
Proposed resolution (February, 2010):
Change the definition of condition in 6.4 [stmt.select] paragraph 1 as follows:
Insert the following text as a new paragraph at the end of 6.4 [stmt.select]:
In the decl-specifier-seq of a condition, each decl-specifier shall be either a type-specifier or constexpr.
The intent is that the range-based for statement should be able to be used with a braced-init-list as the range over which to iterate. However, this does not work grammatically: a braced-init-list is not an expression, as required by the syntax in 6.5.4 [stmt.ranged] paragraph 1:
Even if this were resolved, the “equivalent to” code is not correct. It contains the declaration,
This has a similar problem, in that 7.1.6.4 [dcl.spec.auto] paragraph 3 requires that the initializer have one of the forms
which does not allow for a braced-initializer-list. In addition, although not allowed by the grammar, 7.1.6.4 [dcl.spec.auto] paragraph 6 treats the braced-init-list specially, in order for the type deduction to work correctly:
Obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initializer is a braced-init-list (8.5.4 [dcl.init.list]), with std::initializer_list<U>.
The problem here is that a parenthesized initializer, as in the code expansion of the range-based for statement, is not a braced-init-list.
Proposed resolution (February, 2010):
Change 6.5 [stmt.iter] paragraph 1 as follows:
Iteration statements specify looping.
iteration-statement:
while ( condition ) statement
do statement while ( expression ) ;
for ( for-init-statement conditionopt ; expressionopt ) statement
for ( for-range-declaration ; expression for-range-initializer ) statement
for-init-statement:
expression-statement
simple-declaration
for-range-declaration:
type-specifier-seq attribute-specifieropt declarator
for-range-initializer:
expression
braced-init-list
[Note: a for-init-statement ends with a semicolon. —end note]
Change 6.5.4 [stmt.ranged] paragraph 1 as follows:
The For a range-based for statement of the form
for ( for-range-declaration : expression ) statement
let range-init be equivalent to the expression surrounded by parentheses like so:
( expression )
[Footnote: this ensures that a top-level comma operator cannot be reinterpreted as a delimiter between init-declarators in the declaration of __range. —end footnote] and for a range-based for statement of the form
for ( for-range-declaration : braced-init-list ) statement
let range-init be equivalent to the braced-init-list. In each case, a range-based for statement is equivalent to
{ auto && __range = ( expression ) range-init; for ( auto __begin = begin-expr, ...
7.1.5 [dcl.constexpr] paragraph 5 applies only to “the instantiated template specialization of a constexpr function template;” it should presumably apply to non-template member functions of a class template, as well.
Notes from the September, 2008 meeting:
This question is more involved than it might appear. For example, a constexpr member function is implicitly const; if the constexpr specifier is ignored, does that make the member function non-const? Also, should this provision apply only to dependent expressions in the function? Should it be an error if no constexpr function can be instantiated from the template, along the lines of the permission given in 14.7 [temp.res] paragraph 8 for an implementation to diagnose a template definition from which no valid specialization can be instantiated?
Notes from the July, 2009 meeting:
The consensus of the CWG was that an “ignored” constexpr specifier in this case simply means that the specialization is not constexpr, not that it is not const. The CWG also decided not to address the question of non-dependent expressions that render a function template specialization non-constexpr, leaving it to quality of implementation whether a (warning) diagnostic is issued in such cases.
Proposed resolution (February, 2010):
Change 7.1.5 [dcl.constexpr] paragraph 5 as follows:
If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, the constexpr specifier is ignored that specialization is not a constexpr function or constexpr constructor. [Note: if the function is a member function it will still be const as described below. Implementations are encouraged to issue a warning if a function was redered not constexpr by a non-dependent construct. —end note]
The body of a constexpr function is required by 7.1.5 [dcl.constexpr] paragraph 3 to be of the form
However, there does not seem to be any good reason for prohibiting the alternate return syntax involving a braced-init-list. The restriction should be removed.
Proposed resolution (February, 2010):
Change 6.6.3 [stmt.return] paragraph 2 as follows:
A return statement without an expression with neither an expression nor a braced-init-list can be used only in functions that do not return a value...
Change 7.1.5 [dcl.constexpr] paragraph 3 bullets 4 and 5 as follows:
its function-body shall be a compound-statement of the form
where expression is a potential constant expression (5.19), or
where every assignment-expression that is an initializer-clause appearing directly or indirectly within the braced-init-list is a potential constant expression
every constructor call and implicit conversion used in converting expression to the function return type initializing the object to be returned (6.6.3 [stmt.return], 8.5 [dcl.init]) shall be one of those allowed in a constant expression (5.19 [expr.const]).
7.1.5 [dcl.constexpr] paragraph 6 says,
A constexpr specifier for a non-static member function that is not a constructor declares that member function to be const (9.3.1 [class.mfct.non-static]).
Is a const qualifier on such a member function redundant or ill-formed?
Notes from the July, 2009 meeting:
The CWG agreed that a const qualifier on a constexpr member function is simply redundant and not an error.
Proposed resolution (February, 2010):
Change 7.1.5 [dcl.constexpr] paragraph 6 as follows:
A constexpr specifier for a non-static member function that is not a constructor declares that member function to be const (9.3.1 [class.mfct.non-static]). [Note: the constexpr specifier has no other effect on the function type. —end note] The keyword const is ignored if it appears in the cv-qualifier-seq of the function declarator of the declaration of such a member function. The class of which that function is a member shall be a literal type (3.9 [basic.types]). [Example:...
The rules for constexpr constructors are missing some necessary requirements. In particular, there is no requirement that a brace-or-equal-initializer for a non-static data member be a constant expression, and the requirement for constexpr constructors for initializing non-static data members applies only to members named in a mem-initializer, allowing a non-constexpr default constructor to be invoked.
Proposed resolution (February, 2010):
Change 7.1.5 [dcl.constexpr] paragraph 4 as follows:
The definition of a constexpr constructor shall satisfy the following constraints:
each of its parameter types shall be a literal type
its function-body shall not be a function-try-block
the compound-statement of its function-body shall be empty
every non-static data member and base class sub-object shall be initialized (12.6.2 [class.base.init])
every constructor involved in initializing non-static data members and base class sub-objects invoked by a mem-initializer shall be a constexpr constructor.
every constructor argument and full-expression in a mem-initializer shall be a potential constant expression
every assignment-expression that is an initializer-clause appearing directly or indirectly within a brace-or-equal-initializer for a non-static data member that is not named by a mem-initializer-id shall be a constant expression
every implicit conversion used in converting a constructor argument to the corresponding parameter type and converting a full-expression to the corresponding member type shall be one of those allowed in a constant expression.
The constraints on type-specifiers given in 7.1.6 [dcl.type] paragraphs 2 and 3 (at most one type-specifier except as specified, at least one type-specifier, no redundant cv-qualifiers) are couched in terms of decl-specifier-seqs and declarations. However, they should also apply to constructs that are not syntactically declarations and that are defined to use type-specifier-seqs, including 5.3.4 [expr.new], 6.6 [stmt.jump], 8.1 [dcl.name], and 12.3.2 [class.conv.fct].
Proposed resolution (March, 2008):
Change 7.1.6 [dcl.type] paragraph 3 as follows:
At In a complete type-specifier-seq or in a complete decl-specifier-seq of a declaration, at least one type-specifier that is not a cv-qualifier is required in a declaration shall appear unless it the declaration declares a constructor, destructor or conversion function.
(Note: paper N2546, voted into the Working Draft in February, 2008, addresses part of this issue.)
In the current specification, a decltype resulting in a class type is not a class-name, meaning that it cannot be used as a base-specifier. There doesn't seem to be any reason not to allow that, and it would be consistent with the proposed outcome of issue 743.
Proposed resolution (February, 2010):
See paper PL22.16/10-0021 = WG21 N3031.
Here's an interesting case:
int f;
namespace N {
extern "C" void f () {}
}
As far as I can tell, this is not precluded by the ODR section
(3.2 [basic.def.odr])
or the extern "C" section (7.5 [dcl.link]).
However, I believe many compilers
do not do name mangling on variables and (more-or-less by definition)
on extern "C" functions. That means the variable and the function
in the above end up having the same name at link time. EDG's front
end, g++, and the Sun compiler all get essentially the same error,
which is a compile-time assembler-level error because of the
duplicate symbols (in other words, they fail to check for this, and the
assembler complains). MSVC++ 7 links the program without error,
though I'm not sure how it is interpreted.
Do we intend for this case to be valid? If not, is it a compile time error (required), or some sort of ODR violation (no diagnostic required)? If we do intend for it to be valid, are we forcing many implementations to break binary compatibility by requiring them to mangle variable names?
Personally, I favor a compile-time error, and an ODR prohibition on such things in separate translation units.
Notes from the 4/02 meeting:
The working group agreed with the proposal. We feel a diagnostic should be required for declarations within one translation unit. We also noted that if the variable in global scope in the above example were declared static we would still expect an error.
Relevant sections in the standard are 7.5 [dcl.link] paragraph 6 and 3.5 [basic.link] paragraph 9. We feel that the definition should be written such that the entities in conflict are not "the same entity" but merely not allowed together.
Additional note (September, 2004)
This problem need not involve a conflict between a function and a variable; it can also arise with two variable declarations:
int x;
namespace N {
extern "C" int x;
}
Proposed resolution (March, 2008):
Change 7.5 [dcl.link] paragraph 6 as follows:
At most one function with a particular name can have C language linkage. Two declarations for a function with C language linkage with the same function name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same function. Two declarations for an object with C language linkage with the same name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same object. A function or object with C linkage shall not be declared with the same name (clause 3 [basic]) as an object or reference declared in global scope, unless both declarations denote the same object; no diagnostic is required if the declarations appear in different translation units. [Note: because of the one definition rule (3.2 [basic.def.odr]), only Only one definition for a function or object with C linkage may appear in the program (see 3.2 [basic.def.odr]); that is, implies that such a function or object must not be defined in more than one namespace scope. For example,
int x; namespace A { extern "C" int f(); extern "C" int g() { return 1; } extern "C" int h(); extern "C" int x(); // ill-formed: same name as global-scope object x } namespace B { extern "C" int f(); // A::f and B::f refer // to the same function extern "C" int g() { return 1; } // ill-formed, the function g // with C language linkage // has two definitions } int A::f() { return 98; } // definition for the function f // with C language linkage extern "C" int h() { return 97; } // definition for the function h // with C language linkage // A::h and ::h refer to the same function—end note]
Notes from the September, 2008 meeting:
It should also be possible to declare references with C name linkage (although the meaning the first sentence of 7.5 [dcl.link] paragraph 1 with respect to the meaning of such a declaration is not clear), which would mean that the changed wording should refer to declaring “the same entity” instead of “the same object.” The formulation here would probably benefit from the approach currently envisioned for issues 570 and 633, in which “variable” is defined as being either an object or a reference.
Proposed resolution (February, 2010):
Change 7.5 [dcl.link] paragraph 6 as follows:
At most one function with a particular name can have C language linkage. Two declarations for a function with C language linkage with the same function name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same function. Two declarations for an object or reference with C language linkage with the same name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same object or reference. An entity with C language linkage shall not be declared with the same name as an entity in global scope, unless both declarations denote the same object or reference; no diagnostic is required if the declarations appear in different translation units. [Note: because of the one definition rule (3.2 [basic.def.odr]), only Only one definition for a function or object an entity with C linkage may appear in the program (see 3.2 [basic.def.odr]); that is, implies that such a function or object an entity must not be defined in more than one namespace scope. —end note] For example, [Example:
int x; namespace A { extern "C" int f(); extern "C" int g() { return 1; } extern "C" int h(); extern "C" int x(); // ill-formed: same name as global-scope object x } namespace B { extern "C" int f(); // A::f and B::f refer // to the same function extern "C" int g() { return 1; } // ill-formed, the function g // with C language linkage // has two definitions } int A::f() { return 98; } // definition for the function f // with C language linkage extern "C" int h() { return 97; } // definition for the function h // with C language linkage // A::h and ::h refer to the same function—end note example]
Additional note (February, 2010):
The proposed wording above does not cover the case where two different entities with C linkage are declared in different namespaces, only the case where one of the entities is in global scope.
A function with an exception-specification of throw() must be given a catch(...) clause to enforce its contract, i.e., to call std::unexpected() if it exits with an exception. It would be useful to have an attribute indicating that the function really does throw nothing and thus that the catch(...) clause need not be generated.
(See also issue 830.)
Proposed resolution (September, 2009):
See paper PL22.16/09-0162 = WG21 N2972.
There are a number of problems with the treatment of attributes in the current draft. One issue is the failure to permit attributes to appear at various points in the grammar at which one might plausibly expect them:
In a new-type-id (5.3.4 [expr.new])
Preceding the type-specifier-seq in a condition (6.4 [stmt.select])
In a for-init-statement that is an expression-statement (6.5 [stmt.iter])
Preceding the type-specifier-seq in a for-range-declaration (6.5 [stmt.iter])
In a reference ptr-operator (8 [dcl.decl])
Preceding the type-specifier-seq in a type-id (8.1 [dcl.name])
Preceding the decl-specifier-seq in a parameter-declaration (8.3.5 [dcl.fct])
In a function-definition (8.4 [dcl.fct.def]) at any of the three locations where they might be expected:
preceding the decl-specifier-seq
following the parameter list (paragraph 2 repeats the syntax from 8.3.5 [dcl.fct] with the conspicuous omission of the attribute-specifier)
preceding the compound-statement of the function-body (this would introduce an ambiguity with the attribute-specifier following the parameter list that would need to be addressed)
Preceding the decl-specifier-seq of a member-declaration (9.2 [class.mem])
Preceding the compound-statement of a try-block or handler (15 [except])
Preceding the type-specifier-seq of an exception-declaration (15 [except])
Another group of problems is the failure to specify to what a given attribute-specifier appertains:
In a condition (6.4 [stmt.select])
In a for-range-declaration (6.5.4 [stmt.ranged])
In a parameter-declaration (8.3.5 [dcl.fct])
In a conversion-type-id (12.3.2 [class.conv.fct])
There is also a problem in the specification of the interpretation of an initial attribute-specifier. 8.3 [dcl.meaning] paragraph 5 says,
In a declaration attribute-specifieropt T attribute-specifieropt D where D is an unadorned identifier the type of this identifier is “T”. The first optional attribute-specifier appertains to the entity being declared.
This wording only covers the case where the declarator is a simple identifier. It leaves unspecified the meaning of the initial attribute-specifier with more complex declarators for pointers, references, functions, and arrays.
Finally, something needs to be said about the case where attribute-specifiers occur in both the initial position and following the declarator-id: is this permitted, and if so, under what constraints?
(See also issue 968.)
Proposed resolution (February, 2010):
See paper PL22.16/10-0023 = WG21 N3033.
The [[ ... ]] notation for attributes was thought to be completely unambiguous. However, it turns out that two [ characters can be adjacent and not be an attribute-introducer: the first could be the beginning of an array bound or subscript operator and the second could be the beginning of a lambda-introducer. This needs to be explored and addressed.
(See also issue 951.)
Proposed resolution (November, 2009):
Add the following paragraph at the end of 8.2 [dcl.ambig.res]:
Two consecutive left square bracket tokens shall appear only when introducing an attribute-specifier. [Note: If two consecutive left square brackets appear where an attribute-specifier is not allowed, the program is ill-formed even if the brackets match an alternative grammar production. —end note] [Example:
int p[10]; void f() { int x = 42; int(p[[x]{return x;}()]); // Error: malformed attribute on a nested // declarator-id and not a function-style cast of // an element of p. new int[[]{return x;}()]; // Error even though attributes are not allowed } // in this context.—end example]
This case is nonstandard by 8.3 [dcl.meaning] paragraph 1 (there is a requirement that the specialization first be declared within the namespace before being defined outside of the namespace), but probably should be allowed:
namespace NS1 {
template<class T>
class CDoor {
public:
int mtd() { return 1; }
};
}
template<> int NS1::CDoor<char>::mtd()
{
return 0;
}
Notes from October 2002 meeting:
There was agreement that we wanted to allow this.
Proposed resolution (February, 2010):
Change 8.3 [dcl.meaning] as follows:
...A declarator-id shall not be qualified except for the definition of a member function (9.3 [class.mfct]) or static data member (9.4 [class.static]) outside of its class, the definition or explicit instantiation of a function or variable member of a namespace outside of its namespace, or the definition of a previously declared an explicit specialization outside of its namespace, or the declaration of a friend function that is a member of another class or namespace (11.4 [class.friend]). When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers (or of an inline namespace within that scope (7.3.1 [namespace.def])) or to a specialization thereof, and the member shall not have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier of the declarator-id. [Note:...
Change 14.8.3 [temp.expl.spec] paragraphs 2-4 as follows:
An explicit specialization shall appear in namespace scope. An explicit specialization whose declarator-id is not qualified shall be declared in the nearest enclosing namespace of the template, or, if the namespace is inline (7.3.1 [namespace.def]), any namespace from its enclosing namespace set. Such a declaration may also be a definition. If the declaration is not a definition, the specialization may be defined later (7.3.1.2 [namespace.memdef]).
A declaration of a function template or class template being explicitly specialized shall be in scope at the point of precede the declaration of an the explicit specialization. [Note: a declaration, but not a definition of the template is required. —end note] The definition of a class or class template shall be in scope at the point of precede the declaration of an explicit specialization for a member template of the class or class template. [Example: ... —end example]
A member function, a member class or a static data member of a class template may be explicitly specialized for a class specialization that is implicitly instantiated; in this case, the definition of the class template shall be in scope at the point of declaration of preced the explicit specialization for the member of the class template. If such an explicit specialization for the member of a class template names an implicitly-declared special member function (Clause 12 [special]), the program is ill-formed.
According to 8.3 [dcl.meaning] paragraph 1,
A declarator-id shall not be qualified except for the definition of a member function (9.3 [class.mfct]) or static data member (9.4 [class.static]) outside of its class, the definition or explicit instantiation of a function or variable member of a namespace outside of its namespace, or the definition of a previously declared explicit specialization outside of its namespace, or the declaration of a friend function that is a member of another class or namespace (11.4 [class.friend]). When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers...
This restriction prohibits examples like the following:
void f();
void ::f(); // error: qualified declarator
namespace N {
void f();
void N::f() { } // error: qualified declarator
}
There doesn't seem to be any good reason for disallowing such declarations, and a number of implementations accept them in spite of the Standard's prohibition. Should the Standard be changed to allow them?
Notes from the April, 2006 meeting:
In discussing issue 548, the CWG agreed that the prohibition of qualified declarators inside their namespace should be removed.
Proposed resolution (October, 2006):
Remove the indicated words from 8.3 [dcl.meaning] paragraph 1:
...An unqualified-id occurring in a declarator-id shall be a simple identifier except for the declaration of some special functions (12.3 [class.conv], 12.4 [class.dtor], 13.5 [over.oper]) and for the declaration of template specializations or partial specializations (). A declarator-id shall not be qualified except for the definition of a member function (9.3 [class.mfct]) or static data member (9.4 [class.static]) outside of its class, the definition or explicit instantiation of a function or variable member of a namespace outside of its namespace, or the definition of a previously declared explicit specialization outside of its namespace, or the declaration of a friend function that is a member of another class or namespace (11.4 [class.friend]). When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers, and the member shall not have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier of the declarator-id...
[Drafting note: The omission of “outside of its class” here does not give permission for redeclaration of class members; that is still prohibited by 9.2 [class.mem] paragraph 1. The removal of the enumeration of the kinds of declarations in which a qualified-id can appear does allow a typedef declaration to use a qualified-id, which was not permitted before; if that is undesirable, the prohibition can be reinstated here.]
The following example appears to be well-formed, with the partial specialization matching the type of Y::f(), even though it is rejected by many compilers:
template<class T> struct X;
template<class R> struct X< R() > {
};
template<class F, class T> void test(F T::* pmf) {
X<F> x;
}
struct Y {
void f() {
}
};
int main() {
test( &Y::f );
}
However, 8.3.5 [dcl.fct] paragraph 4 says,
A cv-qualifier-seq shall only be part of the function type for a non-static member function, the function type to which a pointer to member refers, or the top-level function type of a function typedef declaration. The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type. In the latter case, the cv-qualifiers are ignored.
This specification makes it impossible to write a partial specialization for a const member function:
template<class R> struct X<R() const> {
};
A template argument is not one of the permitted contexts for cv-qualification of a function type. This restriction should be removed.
Notes from the April, 2006 meeting:
During the meeting the CWG was of the opinion that the “R() const” specialization would not match the const member function even if it were allowed and so classified the issue as NAD. Questions have been raised since the meeting, however, suggesting that the template argument in the partial specialization would, in fact, match the type of a const member function (see, for example, the very similar usage via typedefs in 9.3 [class.mfct] paragraph 9). The issue is thus being left open for renewed discussion at the next meeting.
Proposed resolution (June, 2008):
Change 8.3.5 [dcl.fct] paragraph 7 as follows:
A cv-qualifier-seq shall only be part of the function type for a non-static member function, the function type to which a pointer to member refers, or the top-level function type of a function typedef declaration, or the top-level function type of a type-id that is a template-argument for a type template-parameter. The effect... A ref-qualifier shall only be part of the function type for a non-static member function, the function type to which a pointer to member refers, or the top-level function type of a function typedef declaration, or the top-level function type of a type-id that is a template-argument for a type template-parameter. The return type...
According to the definition of value initialization (8.5 [dcl.init] paragraph 5), non-union class types without user-declared constructors are value-initialized by value-initializing each of their members rather than by executing the (generated) default constructor. However, a number of other items in the Standard are described in relationship to the execution of the constructor:
12.4 [class.dtor] paragraph 6: “Bases and members are destroyed in the reverse order of the completion of their constructor.” If a given base or member is value-initialized without running its constructor, is it destroyed? (For that matter, paragraph 10 refers to “constructed” objects; is an object that is value-initialized without invoking a constructor “constructed?”)
15.2 [except.ctor] paragraph 2: “An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed subobjects, that is, for subobjects for which the constructor has completed execution...”
3.8 [basic.life] paragraph 1: The lifetime of an object begins when “the constructor call has completed.” (In the TC1 wording — “if T is a class type with a non-trivial constructor (12.1 [class.ctor]), the constructor call has completed” — the lifetime of some value-initialized objects never began; in the current wording — “the constructor invoked to create the object is non-trivial” — the lifetime begins before any of the members are initialized.)
Proposed resolution (October, 2005):
Add the indicated words to 8.5 [dcl.init] paragraph 6:
A program that calls for default-initialization or value-initialization of an entity of reference type is ill-formed. If T is a cv-qualified type, the cv-unqualified version of T is used for these definitions of zero-initialization, default-initialization, and value-initialization. Even when value-initialization of an object does not call that object's constructor, the object is deemed to have been fully constructed once its initialization is complete and thus subject to provisions of this International Standard applying to “constructed” objects, objects “for which the constructor has completed execution,” etc.
Notes from April, 2006 meeting:
There was some concern about whether this wording covered (or needed to cover) cases where an object is “partially constructed.” Another approach might be simply to define value initialization to be “construction.” Returned to “drafting” status for further investigation.
Proposed resolution (February, 2010):
Change 8.5 [dcl.init] paragraph 7 as follows:
To value-initialize an object of type T means:
...
An object that is value-initialized is deemed to be constructed and thus subject to provisions of this International Standard applying to “constructed” objects, objects “for which the constructor has completed,” etc., even if no constructor is invoked for the object's initialization.
8.5 [dcl.init] paragraph 2 reads,
Automatic, register, static, and external variables of namespace scope can be initialized by arbitrary expressions involving literals and previously declared variables and functions.
Both “automatic” and “static” are used to describe storage durations, “register” is a storage class specifier which indicates the object has automatic storage duration, “external” describes linkage, and “namespace scope” is a kind of scope. Automatic, register, static and external, together with namespace scope, are used to restrict the “variables.”
Register objects are only a sub-set of automatic objects and thus the word “register” is redundant and should be elided. If register objects are to be emphasized, they should be mentioned like “Automatic (including register)...”
Variables having namespace scope can never be automatic; they can only be static, with either external or internal linkage. Therefore, there are in fact no “automatic variables of namespace scope,” and the “static” in “static variables of namespace scope” is useless.
In fact, automatic and static variables already compose all variables with either external linkage or not, and thus the “external” becomes redundant, too, and the quoted sentence seems to mean that all variables of namespace scope can be initialized by arbitrary expressions. But this is not true because not all internal variables of namespace scope can. Therefore, the restrictive “external” is really necessary, not redundant.
As a result, the erroneous restrictive “automatic, register, static” should be removed and the quoted sentence may be changed to:
External variables of namespace scope can be initialized by arbitrary expressions involving literals and previously declared variables and functions.
Notes from the April, 2007 meeting:
This sentence is poorly worded, but the analysis given in the issue description is incorrect. The intent is simply that the storage class of a variable places no restrictions on the kind of expression that can be used to initialize it (in contrast to C, where variables of static storage duration can only be initialized by constant expressions).
Proposed resolution (June, 2008):
Change 8.5 [dcl.init] paragraph 2 as follows:
Automatic, register, static, and external variables of namespace scope Variables of automatic, thread, and static storage duration can be initialized by arbitrary expressions involving literals and previously declared variables and functions...
Notes from the September, 2008 meeting:
The existing wording is intended to exclude block-scope extern declarations but to allow initializers in all other forms of variable declarations. The best way to phrase that is probably to say that all variable definitions (except for function parameters, where the initializer syntax is used for default arguments) can have arbitrary expressions as initializers, regardless of storage duration.
Proposed resolution (February, 2010):
Change 8.5 [dcl.init] paragraph 2 as follows:
Automatic, register, thread_local, static, and namespace-scoped external variables can be initialized by Except for objects declared with the constexpr specifier, for which see 7.1.5 [dcl.constexpr], an initializer in the definition of a variable can consist of arbitrary expressions involving literals and previously declared variables and functions, regardless of the variable's storage duration. [Example:...
According to 8.5.3 [dcl.init.ref] paragraph 5, a reference initialized with a reference-compatible rvalue of class type binds directly to the object. A reference-compatible non-class rvalue reference, however, is first copied to a temporary and the reference binds to that temporary, not to the target of the rvalue reference. This can cause problems when the result of a forwarding function is used in such a way that the address of the result is captured. For example:
struct ref {
explicit ref(int&& i): p(&i) { }
int* p;
};
int&& forward(int&& i) {
return i;
}
void f(int&& i) {
ref r(forward(i));
// Here r.p is a dangling pointer, pointing to a defunct int temporary
}
A formulation is needed so that rvalue references are treated like class and array rvalues.
Notes from the February, 2008 meeting:
You can't just treat scalar rvalues like class and array rvalues, because they might not have an associated object. However, if you have an rvalue reference, you know that there is an object, so probably the best way to address this issue is to specify somehow that binding a reference to an rvalue reference does not introduce a new temporary.
(See also issues 690 and 846.)
Proposed resolution (February, 2010):
See paper N3030.
It should always be possible to use the new brace syntax to value-initialize an object. However, the current rules make the following example ill-formed because of ambiguity:
struct S {
S();
S(std::initializer_list<int>);
S(std::initializer_list<double>);
};
S s{}; // Ambiguous initializer-list constructor reference,
// not value initialization.
Proposed resolution (February, 2010):
Change 8.5.4 [dcl.init.list] paragraph 3 as follows:
List-initialization of an object or reference of type T is defined as follows:
If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
Otherwise, if the initializer list has no elements and T is an aggregate, the initializer list is used to initialize each of the members of T. [Example:
struct A { A(std::initializer_list<int>); // #1 }; struct B { A a; }; B b { }; // OK, uses #1 B b { 1 }; // error—end example]
If Otherwise, if T is an aggregate...
...
[Example:
struct S { S(std::initializer_list<double>); // #1 S(std::initializer_list<int>); // #2 S(); // #3 // ... }; S s1 = { 1.0, 2.0, 3.0 }; // invoke #1 S s2 = { 1, 2, 3 }; // invoke #2 S s3 = { }; // invoke #3 (for value-initialization; see above)—end example]
In looking at a large handful of core issues related to elaborated-type-specifiers and the naming of classes in general, I discovered an odd fact. It turns out that there is exactly one place in the grammar where nested-name-specifier is not immediately preceded by "::opt": in class-head, which is used only for class definitions. So technically, this example is ill-formed, and should evoke a syntax error:
struct A;
struct ::A { };
However, all of EDG, GCC and Microsoft's compiler accept it without a qualm. In fact, I couldn't get any of them to even warn about it.
Suggested resolution:
It would simplify the grammar, and apparently better reflect existing practice, to factor the global-scope operator into the rule for nested-name-specifier.
Proposed resolution (November, 2006):
In 3.4.3 [basic.lookup.qual] paragraph 6, change the grammar snippet as follows:
Delete 5.1.1 [expr.prim.general] paragraph 4 (“The operator :: followed by...”). [Drafting note: It's covered by paragraph 8 (type, lvalue-ness, member-ness, reference to 3.4.3.2 [namespace.qual]) and 3.4.3.2 [namespace.qual] (qualified lookup for namespace members).]
Change the grammar in 5.1.1 [expr.prim.general] paragraph 7 as follows (deleting the :: forms from qualified-id and adding :: as a new production for nested-name-specifier):
Change 5.1.1 [expr.prim.general] paragraph 8 as follows:
A nested-name-specifier that names designates a namespace (7.3 [basic.namespace]), followed by the name of a member of that namespace...
Change 5.1.1 [expr.prim.general] paragraph 10 as follows:
In a qualified-id, if the id-expression unqualified-id is a conversion-function-id...
In 5.2 [expr.post] paragraph 1, change the grammar as follows:
In 5.2.4 [expr.pseudo] paragraph 2, change the grammar snippet as follows:
In 7.1.6.2 [dcl.type.simple] paragraph 1, change the grammar as follows:
In 7.1.6.3 [dcl.type.elab] before paragraph 1, change the grammar as follows:
In 7.1.6.3 [dcl.type.elab] paragraph 1, change the grammar snippet as follows:
In 7.3.2 [namespace.alias] paragraph 1, change the grammar as follows:
In 7.3.3 [namespace.udecl] paragraph 1, change the grammar as follows:
In 7.3.4 [namespace.udir] before paragraph 1, change the grammar as follows:
In 8 [dcl.decl] paragraph 4, change the grammar as follows:
In 8.3.3 [dcl.mptr] paragraph 1, change the grammar snippet as follows:
In 9.2 [class.mem] before paragraph 1, change the grammar as follows:
In 10 [class.derived] paragraph 1, change the grammar as follows:
In 12.6.2 [class.base.init] paragraph 1, change the grammar as follows:
In 14.7 [temp.res] paragraph 3, change the grammar as follows:
[Drafting notes: gcc 4.1.1 rejects the example in the issue description. I still think it's a good idea to make the grammar more uniform, and there ought to be nothing special about the global scope operator. However, there is a slight change in effective grammar with these modification: all places that require a non-optional nested-name-specifier used to required at least one named level of nesting. With these changes, "::" is a valid nested-name-specifier (that denotes the global scope). Any such use needed to protect against non-class (i.e. namespace) scopes in its semantic description anyway, which also covers the "::" case.]
The grammar for member-declaration in 9.2 [class.mem] does not include a production for the alias-declaration form of typedef declarations, meaning that something like
struct S {
using UINT = unsigned int;
};
is ill-formed. This seems like an oversight.
Proposed resolution (February, 2010):
In the grammar in 9 [class], add the indicated production to the definition of member-declaration:
Can a member of a union be of a class that has a user-declared non-default constructor? The restrictions on union membership in 9.5 [class.union] paragraph 1 only mention default and copy constructors:
An object of a class with a non-trivial default constructor (12.1 [class.ctor]), a non-trivial copy constructor (12.8 [class.copy]), a non-trivial destructor (12.4 [class.dtor]), or a non-trivial copy assignment operator (13.5.3 [over.ass], 12.8 [class.copy]) cannot be a member of a union...
(12.1 [class.ctor] paragraph 11 does say, “a non-trivial constructor,” but it's not clear whether that was intended to refer only to default and copy constructors or to any user-declared constructor. For example, 12.2 [class.temporary] paragraph 3 also speaks of a “non-trivial constructor,” but the cross-references there make it clear that only default and copy constructors are in view.)
Note (March, 2008):
This issue was resolved by the adoption of paper J16/08-0054 = WG21 N2544 (“Unrestricted Unions”) at the Bellevue meeting.
The type long long is missing from the list of bit-field types in 9.6 [class.bit] paragraph 3 for which the implementation can choose the signedness. This was presumably an oversight. (If that is the case, we may want to reconsider the handling of 4.5 [conv.prom] paragraph 3: a long long bit-field that the implementation treats as unsigned will — pending the outcome of issue 739 — still promote to signed long long, which can lead to unexpected results for bit-fields with the same number of bits as long long.)
Proposed resolution (February, 2010):
Change 9.6 [class.bit] paragraph 3 as follows:
...It is implementation-defined whether a plain (neither explicitly signed nor unsigned) char, short, int or, long, or long long bit-field is signed or unsigned...
The resolution of issue 372 leaves unclear whether the following are well-formed or not:
class C {
typedef int I; // private
template <int> struct X;
template <int> friend struct Y;
}
template <C::I> struct C::X { }; // C::I accessible to member?
template <C::I> struct Y { }; // C::I accessible to friend?
Presumably the answer to both questions is “yes,” but the new wording does not address template-parameters.
Proposed resolution (June, 2008):
Change 11 [class.access] paragraph 6 as follows:
...For purposes of access control, the base-specifiers of a class, the template-parameters of a template-declaration, and the definitions of class members that appear outside of the class definition are considered to be within the scope of that class...
Notes from the September, 2008 meeting:
The proposed resolution preserves the word “scope” as a holdover from the original specification prior to issue 372, which intended to change access determination from a scope-based model to an entity-based model. The resolution should eliminate all references to scope and simply use the entity-based model.
(See also issue 718.)
Proposed resolution (February, 2010):
Change 11 [class.access] paragraphs 6-7 as follows:
All access controls in Clause 11 [class.access] affect the ability to access a class member name from a declaration of a particular scope entity, including references appearing in those parts of the declaration that precede the name of the entity being declared and implicit references to constructors, conversion functions, and destructors involved in the creation and destruction of a static data member. For purposes of access control, the base-specifiers of a class and the definitions of class members that appear outside of the class definition are considered to be within the scope of that class. In particular, access controls apply as usual to member names accessed as part of a function return type, even though it is not possible to determine the access privileges of that use without first parsing the rest of the function declarator. Similarly, access control for implicit calls to the constructors, the conversion functions, or the destructor called to create and destroy a static data member is performed as if these calls appeared in the scope of the member's class. [Example:
class A { typedef int I; // private member I f(); friend I g(I); static I x; template<int> struct X; template<int> friend struct Y; protected: struct B { }; }; A::I A::f() { return 0; } A::I g(A::I p = A::x); A::I g(A::I p) { return 0; } A::I A::x = 0; template<A::I> struct A::X { }; template<A::I> struct Y { }; struct D: A::B, A { };Here, all the uses of A::I are well-formed because A::f and, A::x, and A::X are members of class A and g is a friend and Y are friends of class A. This implies, for example, that access checking on the first use of A::I must be deferred until it is determined that this use of A::I is as the return type of a member of class A. Similarly, the use of A::B as a base-specifier is well-formed because D is derived from A, so checking of base-specifiers must be deferred until the entire base-specifier-list has been seen. —end example]
Does the restriction in 11.5 [class.protected] apply to upcasts across protected inheritance, too? For instance,
struct B {
int i;
};
struct I: protected B { };
struct D: I {
void f(I* ip) {
B* bp = ip; // well-formed?
bp->i = 5; // aka "ip->i = 5;"
}
};
I think the rationale for the 11.5 [class.protected] restriction applies equally well here — you don't know whether ip points to a D object or not, so D::f can't be trusted to treat the protected B subobject consistently with the policies of its actual complete object type.
The current treatment of “accessible base class” in 11.2 [class.access.base] paragraph 4 clearly makes the conversion from I* to B* well-formed. I think that's wrong and needs to be fixed. The rationale for the accessibility of a base class is whether “an invented public member” of the base would be accessible at the point of reference, although we obscured that a bit in the reformulation; it seems to me that the invented member ought to be considered a non-static member for this purpose and thus subject to 11.5 [class.protected].
(See also issues 385 and 471.).Notes from October 2004 meeting:
The CWG tentatively agreed that casting across protective inheritance should be subject to the additional restriction in 11.5 [class.protected].
Proposed resolution (February, 2010):
Change 11.2 [class.access.base] paragraph 4 as follows:
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. } }; class N2: protected B { }; class P2: public N2 { void f2(N2* n2p) { B* bp = n2p; // error: invented member would be protected and naming // class N2 not the same as or derived from the referencing // class P2 (cf 11.5 [class.protected]) } };—end example]
According to 12.1 [class.ctor] paragraph 1, only function-specifiers are permitted in the declaration of a constructor, and constexpr is not a function-specifier. (See also issue 263, in which the resolution of a similar concern regarding the friend specifier did not change 12.1 [class.ctor] paragraph 1 but perhaps should have done so.)
Proposed resolution (February, 2010):
Change 12.1 [class.ctor] paragraph 1 as follows:
Constructors do not have names. A special declarator syntax using an optional sequence of function-specifiers (7.1.2 [dcl.fct.spec]) followed by the constructor's class name followed by a parameter list is used to declare or define the constructor. The syntax uses
a decl-specifier-seq in which each decl-specifier is either a function-specifier or constexpr,
the constructor's class name, and
a parameter list
in that order. In such a declaration, optional parentheses around the constructor class name are ignored. [Example:...
Split off from issue 86.
Should binding a reference to the result of a "," operation whose second operand is a temporary extend the lifetime of the temporary?
const SFileName &C = ( f(), SFileName("abc") );
Notes from the March 2004 meeting:
We think the temporary should be extended.
Proposed resolution (October, 2004):
Change 12.2 [class.temporary] paragraph 2 as indicated:
... In all these cases, the temporaries created during the evaluation of the expression initializing the reference, except the temporary that is the overall result of the expression [Footnote: For example, if the expression is a comma expression (5.18 [expr.comma]) and the value of its second operand is a temporary, the reference is bound to that temporary.] and 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...
[Note: this wording partially resolves issue 86. See also issue 446.]
Notes from the April, 2005 meeting:
The CWG suggested a different approach from the 10/2004 resolution, leaving 12.2 [class.temporary] unchanged and adding normative wording to 5.18 [expr.comma] specifying that, if the result of the second operand is a temporary, that temporary is the result of the comma expression as well.
Proposed Resolution (November, 2006):
Add the indicated wording to 5.18 [expr.comma] paragraph 1:
... The type and value of the result are the type and value of the right operand; the result is an lvalue if its right operand is an lvalue, and is a bit-field if its right operand is an lvalue and a bit-field. If the value of the right operand is a temporary (12.2 [class.temporary]), the result is that temporary.
The changes for delegating constructors overlooked the need to change 12.6.2 [class.base.init] paragraph 3:
The expression-list in a mem-initializer is used to initialize the base class or non-static data member subobject denoted by the mem-initializer-id. The semantics of a mem-initializer are as follows:
if the expression-list of the mem-initializer is omitted, the base class or member subobject is value-initialized (see 8.5 [dcl.init]);
otherwise, the subobject indicated by mem-initializer-id is direct-initialized using expression-list as the initializer (see 8.5 [dcl.init]).
The initialization of each base and member constitutes a full-expression. Any expression in a mem-initializer is evaluated as part of the full-expression that performs the initialization.
This paragraph deals only with subobjects; it needs to be made more general to apply to the complete object as well when the mem-initializer-id designates the constructor's class.
Proposed resolution (June, 2008):
Change 12.6.2 [class.base.init] paragraph 3 as follows:
The expression-list in a mem-initializer is used to initialize the base class or non-static data member subobject denoted by the mem-initializer-id. The semantics of a mem-initializer are A mem-initializer in which the mem-initializer-id names the constructor's class initializes the object by invoking the selected target constructor with the mem-initializer's expression-list. A mem-initializer in which the mem-initializer-id names a base class or non-static data member initializes the designated subobject as follows:
if the expression-list of the mem-initializer is omitted, the base class or member subobject is value-initialized (see 8.5 [dcl.init]);
otherwise, the subobject indicated by mem-initializer-id is direct-initialized using expression-list as the initializer (see 8.5 [dcl.init]).
...
The initialization of each base and member performed by each mem-initializer constitutes a full-expression. Any expression...
Notes from the September, 2008 meeting:
This text was significantly modified by N2756 (nonstatic data member initializers) and needs to be reworked in light of those changes.
Proposed resolution (February, 2010):
Change 12.6.2 [class.base.init] paragraph 7 as follows:
The expression-list or braced-init-list in a mem-initializer is used to initialize the base class or non-static data member subobject denoted by the mem-initializer-id designated subobject (or, in the case of a delegating constructor, the complete class object) according to the initialization rules of 8.5 [dcl.init] for direct-initialization.
[Example: ...
—end example] The initialization of each base and member performed by each mem-initializer constitutes a full-expression...
Change 12.6.2 [class.base.init] paragraph 8 as follows:
If In a non-delegating constructor, if a given non-static data member or base class is not named by a mem-initializer-id...
Change 12.6.2 [class.base.init] paragraph 10 as follows:
Initialization In a non-delegating constructor, initialization proceeds in the following order:
Change 12.6.2 [class.base.init] paragraph 12 as follows (this is an unrelated change correcting an error noticed while preparing the resolution of this issue):
Names in the expression-list or braced-init-list of a mem-initializer are evaluated in the scope of the constructor...
Change the next-to-last bullet of the note in 8.5.4 [dcl.init.list] paragraph 1 as follows:
as a base-or-member initializer in a mem-initializer (12.6.2 [class.base.init])
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.
Proposed Resolution (November, 2006):
This issue is resolved by the proposed resolution for issue 535.
Footnote 112 (12.8 [class.copy] paragraph 2) says,
Because a template constructor is never a copy constructor, the presence of such a template does not suppress the implicit declaration of a copy constructor. Template constructors participate in overload resolution with other constructors, including copy constructors, and a template constructor may be used to copy an object if it provides a better match than other constructors.
However, many of the stipulations about copy construction are phrased to refer only to “copy constructors.” For example, 12.8 [class.copy] paragraph 14 says,
A program is ill-formed if the copy constructor... for an object is implicitly used and the special member function is not accessible (clause 11 [class.access]).
Does that mean that using an inaccessible template constructor to copy an object is permissible, because it is not a “copy constructor?” Obviously not, but each use of the term “copy constructor” in the Standard should be examined to determine if it applies strictly to copy constructors or to any constructor used for copying. (A similar issue applies to “copy assignment operators,” which have the same relationship to assignment operator function templates.)
Proposed Resolution (February, 2008):
Change 3.2 [basic.def.odr] paragraph 2 as follows:
... [Note: this covers calls to named functions (5.2.2 [expr.call]), operator overloading (clause 13 [over]), user-defined conversions (12.3.2 [class.conv.fct]), allocation function for placement new (5.3.4 [expr.new]), as well as non-default initialization (8.5 [dcl.init]). A copy constructor selected to copy class objects is used even if the call is actually elided by the implementation (12.8 [class.copy]). —end note] ... A copy-assignment function for a class An assignment operator function in a class is used by an implicitly-defined copy-assignment function for another class as specified in 12.8 [class.copy]...
Delete 12.1 [class.ctor] paragraphs 10 and 11:
A copy constructor (12.8 [class.copy]) is used to copy objects of class type.
A union member shall not be of a class type (or array thereof) that has a non-trivial constructor.
Replace the “example” in 12.2 [class.temporary] paragraph 1 with a note as follows:
[Example: even if the copy constructor is not called, all the semantic restrictions, such as accessibility (clause 11 [class.access]), shall be satisfied. —end example] [Note: This includes accessibility (clause 11 [class.access]) for the constructor selected. —end note]
Change 12.8 [class.copy] paragraph 7 as follows:
A non-user-provided copy constructor is implicitly defined if it is used to initialize an object of its class type from a copy of an object of its class type or of a class type derived from its class type (3.2 [basic.def.odr]). [Footnote: See 8.5 [dcl.init] for more details on direct and copy initialization. —end footnote] [Note: the copy constructor is implicitly defined even if the implementation elided its use (12.2 [class.temporary]) the copy operation (12.8 [class.copy]). —end note] A program is ill-formed if the class for which a copy constructor is implicitly defined or explicitly defaulted has:
a non-static data member of class type (or array thereof) with an inaccessible or ambiguous copy constructor, or
a base class with an inaccessible or ambiguous copy constructor.
Before the non-user-provided copy constructor for a class is implicitly defined...
Change 12.8 [class.copy] paragraph 8 as follows:
...Each subobject is copied in the manner appropriate to its type:
if the subobject is of class type, the copy constructor for the class is used direct-initialization (8.5 [dcl.init]) is performed [Note: If overload resolution fails or the constructor selected by overload resolution is inaccessible (11 [class.access]) in the context of X, the program is ill-formed. —end note];
if the subobject is an array...
[Drafting note: 8.5 [dcl.init] paragraph 15 requires “unambiguous” and 13.3 [over.match] paragraph 3 requires “accessible,” thus no need for normative text here.]
Change 12.8 [class.copy] paragraph 12 as follows:
A non-user-provided copy assignment operator is implicitly defined when an object of its class type is assigned a value of its class type or a value of a class type derived from its class type it is used (3.2 [basic.def.odr]). A program is ill-formed if the class for which a copy assignment operator is implicitly defined or explicitly defaulted has: a non-static data member of const or reference type.
a non-static data member of const type, or
a non-static data member of reference type, or
a non-static data member of class type (or array thereof) with an inaccessible copy assignment operator, or
a base class with an inaccessible copy assignment operator.
Change 12.8 [class.copy] paragraph 13 as follows:
... Each subobject is assigned in the manner appropriate to its type:
if the subobject is of class type, the copy assignment operator for the class the assignment operator function selected by overload resolution (13.3 [over.match]) for that class is used (as if by explicit qualification; that is, ignoring any possible virtual overriding functions in more derived classes) [Note: If overload resolution fails or the assignment operator function selected by overload resolution is inaccessible (11 [class.access]) in the context of X, the program is ill-formed. —end note];
if the subobject is an array...
Delete 12.8 [class.copy] paragraph 14:
A program is ill-formed if the copy constructor or the copy assignment operator for an object is implicitly used and the special member function is not accessible (clause 11 [class.access]). [Note: Copying one object into another using the copy constructor or the copy assignment operator does not change the layout or size of either object. —end note]
Change 12.8 [class.copy] paragraph 15 as follows:
When certain criteria are met, an implementation is allowed to omit the copy construction of a class object, even if the copy constructor selected for the copy operation and/or the destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization. [Footnote: Because only one object is destroyed instead of two, and one copy constructor is not executed, there is still one object destroyed for each one constructed. —end footnote] This elision...
Change 13.3.3.1.2 [over.ics.user] paragraph 4 as follows:
A conversion of an expression of class type to the same class type is given Exact Match rank, and a conversion of an expression of class type to a base class of that type is given Conversion rank, in spite of the fact that a copy constructor (i.e., a user-defined conversion function) is called for those cases.
Change 15.1 [except.throw] paragraph 3 as follows:
A throw-expression initializes a temporary object, called the exception object, the type of which by copy-initialization (8.5 [dcl.init]). The type of that temporary object is determined...
Change 15.1 [except.throw] paragraph 5 as follows:
When the thrown object is a class object, the copy constructor selected for the copy-initialization and the destructor shall be accessible, even if the copy operation is elided (12.8 [class.copy]).
Change 15.3 [except.handle] paragraphs 16-17 as follows:
When the exception-declaration specifies a class type, a copy constructor copy-initialization (8.5 [dcl.init]) is used to initialize either the object declared in the exception-declaration or, if the exception-declaration does not specify a name, a temporary object of that type. The object shall not have an abstract class type. The object is destroyed when the handler exits, after the destruction of any automatic objects initialized within the handler. The copy constructor selected for the copy-initialization and the destructor shall be accessible in the context of the handler, even if the copy operation is elided (12.8 [class.copy]). If the copy constructor and destructor are implicitly declared (12.8 [class.copy]), such a use in the handler causes these functions to be implicitly defined; otherwise, the program shall provide a definition for these functions.
The copy constructor and destructor associated with the object shall be accessible even if the copy operation is elided (12.8 [class.copy]).
Change the footnote in 15.5.1 [except.terminate] paragraph 1 as follows:
[Footnote: For example, if the object being thrown is of a class with a copy constructor type, std::terminate() will be called if that copy constructor the constructor selected to copy the object exits with an exception during a throw. —end footnote]
(This resolution also resolves issue 111.)
[Drafting note: The following do not require changes: 5.17 [expr.ass] paragraph 4; 9 [class] paragraph 5; 9.5 [class.union] paragraph 1; 12.2 [class.temporary] paragraph 2; 12.8 [class.copy] paragraphs 1-2; 15.4 [except.spec] paragraph 14.]
Notes from February, 2008 meeting:
These changes overlap those that will be made when concepts are added. This issue will be maintained in “review” status until the concepts proposal is adopted and any conflicts will be resolved at that point.
14.2 [temp.param] paragraph 11 currently says,
If a template-parameter of a class template is a template parameter pack, it shall be the last template-parameter. [Note: These are not requirements for function templates because template arguments might be deduced (14.9.2 [temp.deduct])...
This restriction was only meant to apply to primary class templates, not partial specializations.
Suggested resolution:
If a template-parameter of a primary class template is a template parameter pack, it shall be the last template-parameter. [Note: These are not requirements for function templates or class template partial specializations because template arguments might be deduced (14.9.2 [temp.deduct])...
Proposed resolution (February, 2010):
Change 14.2 [temp.param] paragraph 11 as follows:
If a template-parameter of a class template has a default template-argument, each subsequent template-parameter shall either have a default template-argument supplied or be a template parameter pack. If a template-parameter of a primary class template is a template parameter pack, it shall be the last template-parameter. [Note: These are not requirements for function templates for class template partial specializations because template arguments might can be deduced (14.9.2 [temp.deduct]). [Example:...
The following is the wording from 14.3 [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.
Notes from the October 2003 meeting:
We reviewed John Spicer's paper N1528 and agreed with his recommendations therein.
Proposed resolution (February, 2010):
Change 14.3 [temp.names] paragraph 5 as follows:
If a A name prefixed by the keyword template is not the name of a template, shall be a template-id or the name shall refer to a class template the program is ill-formed. [Note: the keyword template may not be applied to non-template members of class templates. —end note] [Note: as is the case with the typename prefix, the template prefix is allowed in cases where it is not strictly necessary; i.e., when the nested-name-specifier or the expression on the left of the -> or . is not dependent on a template-parameter, or the use does not appear in the scope of a template. —end note] [Example:
template <class T> struct A { void f(int); template <class U> void f(U); }; template <class T> void f(T t) { A<T> a; a.template f<>(t); // OK: calls template a.template f(t); // error: not a template-id } template <class T> struct B {template <class T2> struct C {}; }; // OK: T::template C names a class template: template <class T, template <class X> class TT = T::template C> struct D {}; D<B<int>> db;—end example]
Consider this example:
class Foo {
public:
template< typename T > T *get();
};
template< typename U >
U *testFoo( Foo &foo ) {
return foo.get< U >(); //#1
}
I am under the impression that this should compile without requiring the insertion of the template keyword before get in the expression at //#1. This notion is supported by this note excerpted from 14.3 [temp.names]/5:
[Note: just as is the case with the typename prefix, the template prefix is allowed in cases where it is not strictly necessary; i.e., when the expression on the left of the -> or ., or the nested-name-specifier is not dependent on a template parameter.]
But 14.3 [temp.names]/4 contains this text:
When the name of a member template specialization appears after . or -> in a postfix-expression, or after nested-name-specifier in a qualified-id, and the postfix-expression or qualified-id explicitly depends on a template-parameter (14.6.2), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.
The only way that I can read this to support my assumption above is if I assume that the phrase postfix-expression is used twice above with different meaning. That is I read the first use as referring to the full expression while the second use refers to the subexpression preceding the operator. Is this the correct determination of intent? I find this text confusing. Would it be an improvement if the second occurrence of "postfix-expression" should be replaced by "the subexpression preceding the operator". Of course that begs the question "where is subexpression actually defined in the standard?"
John Spicer: I agree that the code should work, and that we should tweak the wording.
Proposed resolution (February, 2010):
Change 14.3 [temp.names] paragraph 4 as follows:
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 qualified-id or the subexpression of the postfix-expression that precedes the . or -> depends on a template-parameter template parameter (14.7.2 [temp.dep]) but