Date: | 2025-04-13 |
Project: | Programming Language C++ |
Reference: | ISO/IEC 14882:2024 |
Reply to: | Jens Maurer |
jens.maurer@gmx.net |
This document contains the C++ core language issues that have been categorized as Defect Reports by the Committee (PL22.16 + WG21) and other accepted issues, that is, issues with status "DR," "accepted," "DRWP," "WP," "CD1," "CD2," "CD3," "CD4," "CD5," "CD6," "TC1," "C++11," "C++14," "C++17," "C++20," and "C++20," along with their proposed resolutions. Issues with DR, accepted, DRWP, and WP status are NOT part of the International Standard for C++. They are provided for informational purposes only, as an indication of the intent of the Committee. They should not be considered definitive until or unless they appear in an approved Technical Corrigendum or revised International Standard for C++.
This document is part of a group of related documents that together describe the issues that have been raised regarding the C++ Standard. The other documents in the group are:
For more information, including a description of the meaning of the issue status codes and instructions on reporting new issues, please see the Active Issues List.
Section references in this document reflect the section numbering of document WG21 N5008.
(From submission #654.)
Subclause 6.9.2.2 [intro.races] paragraph 22 specifies:
Two accesses to the same object of type volatile std::sig_atomic_t do not result in a data race if both occur in the same thread, even if one or more occurs in a signal handler. ...
This provision applies to bit-fields as well, because bit-fields are objects (6.8.1 [basic.types.general] paragraph 4). However, in practice bit-fields are not updated atomically and are subject to tearing.
Proposed resolution (2025-01-20, approved by CWG 2025-02-14):
Change in 6.9.2.2 [intro.races] paragraph 22 as follows:
Two accesses to the same non-bit-field object of type volatile std::sig_atomic_t do not result in a data race if both occur in the same thread, even if one or more occurs in a signal handler. ...
Consider:
void f() { X x; // Is x an lvalue or an xvalue here? void g(int n = (decltype((throw x, 0))())); // status quo: x is move-eligible here } void f() { X x; struct A { void g() { try { struct Y { // Is x an lvalue or an xvalue here? void h(int n = (decltype((throw x, 0))())); }; } catch (...) { } } }; }
11.9.6 [class.copy.elision] paragraph 3 specifies:
An implicitly movable entity is a variable of automatic storage duration that is either a non-volatile object or an rvalue reference to a non-volatile object type. In the following copy-initialization contexts, a move operation is first considered before attempting a copy operation:
- ...
- if the operand of a throw-expression (7.6.18 [expr.throw]) is a (possibly parenthesized) id-expression that names an implicitly movable entity that belongs to a scope that does not contain the compound-statement of the innermost try-block or function-try-block (if any) whose compound-statement or ctor-initializer contains the throw-expression,
Thus, in the first example above, x is treated as an xvalue, but it is treated as an lvalue in the second example. This outcome is surprising.
(P2266R2 (Simpler implicit move) moved this wording, introduced by P1825R0 (Merged wording for P0527R1 and P1155R3), from 11.9.6 [class.copy.elision] to 7.5.5.2 [expr.prim.id.unqual].)
Proposed resolution [SUPERSEDED]:
Change in 7.5.5.2 [expr.prim.id.unqual] paragraph 4:
An implicitly movable entity is a variableofwith automatic storage duration that is either a non-volatile object or an rvalue reference to a non-volatile object type.In the following contexts, anAn id-expression is move-eligible:if
- it names an implicitly movable entity declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression and
Iftheid-expression(possibly parenthesized) id-expression is the operand of
- a return or co_return statement
, and names an implicitly movable entity declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expressionorif the id-expression (possibly parenthesized) is the operand ofa potentially-evaluated throw-expression,and names an implicitly movable entity that belongs to a scope that does not contain the compound-statement of the innermost lambda-expression, try-block, or function-try-block (if any) whose compound-statement or ctor-initializer encloses the throw-expressionwhere no try-block or function-try-block intervenes between the declaration of the entity and the innermost enclosing scope of the throw-expression.
Additional notes (December, 2024)
Treating potentially-evaluated expressions differently (as opposed to unevaluated ones) is surprising.
Proposed resolution (approved by CWG 2025-02-14):
Change in 7.5.5.2 [expr.prim.id.unqual] paragraph 4:
An implicitly movable entity is a variableofwith automatic storage duration that is either a non-volatile object or an rvalue reference to a non-volatile object type.In the following contexts, anAn id-expression is move-eligible:if
- it names an implicitly movable entity,
Ifit is theid-expression(possibly parenthesized)is theoperand of a return or co_return statement, and names an implicitly movable entity declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expressionorif the id-expression (possibly parenthesized) is the operandof a throw-expression, andnames an implicitly movable entity that belongs to a scope that does not contain the compound-statement of the innermost lambda-expression, try-block, or function-try-block (if any) whose compound-statement or ctor-initializer encloses the throw-expression- each intervening scope between the declaration of the entity and the innermost enclosing scope of the id-expression is a block scope and, for a throw-expression, is not the block scope of a try-block or function-try-block.
(From submission #628.)
A warning is currently, but ought not to be, encouraged for a call to a [[nodiscard]] function with a void return type. Such a situation may arise for dependent return types. For example:
[[nodiscard]] void f(); template<class T> [[nodiscard]] T g(); void h() { f(); // suggested change: warning no longer recommended (void)f(); // warning not recommended g<int>(); // warning recommended g<void>(); // suggested change: warning no longer recommended (void)g<void>(); // warning not recommended }
Proposed resolution (2025-01-21, approved by CWG 2025-02-14):
Change in 9.13.9 [dcl.attr.nodiscard] paragraph 4 as follows:
Recommended practice: Appearance of a nodiscard call as a potentially-evaluated discarded-value expression (7.2 [expr.prop]) of non-void type is discouraged unless explicitly cast to void. Implementations should issue a warning in such cases. The value of a has-attribute-expression for the nodiscard attribute should be 0 unless the implementation can issue such warnings.
Issue 2921 fixed the following issue with namespaces:
export module M; namespace N { // external linkage, attached to global module, not exported void f(); } namespace N { // error: exported namespace, redeclares non-exported namespace export void g(); }
This is considered a CWG consistency / wording fix. However, the change for that issue also allowed:
module; #include "header" export module wrap; export using ::feature; // already allowed previously export extern "C++" void feature(); // newly allowed by CWG2921
The CWG chair had neglected to run this new feature past EWG prior to plenary-approving issue 2921 in Wroclaw. Subsequent discussion on the EWG reflector surfaced concerns about missing syntactic differentiation between an intended export-by-redeclaration and an accidental declaration of a different entity because of a slight signature mismatch.
This issues seeks to limit the change to namespaces only; any additional feature in this area should be presented to EWG via a paper.
Proposed resolution (approved by CWG 2025-02-14):
Change in 10.2 [module.interface] paragraph 6 as follows:
A redeclaration of an entity X is implicitly exported if X was introduced by an exported declaration; otherwise it shall not be exportedif it is attached to a named moduleunless it is a namespace. [ Example:export module M; struct S { int n; }; typedef S S; export typedef S S; // OK, does not redeclare an entity export struct S; // error: exported declaration follows non-exported declaration namespace N { // external linkage, attached to global module, not exported void f(); } namespace N { // OK, exported namespace redeclaring non-exported namespace export void g(); }-- end example ]
The resolution accepted for issue 2539 does not actually address the example in the issue, because overload resolution is never performed for expressions involving only built-in types.
Proposed resolution (2025-01-13, approved by CWG 2025-02-14):
Change in 11.10.3 [class.spaceship] paragraph 1 as follows:
The synthesized three-way comparison of type R (17.12.2 [cmp.categories]) of glvalues a and b of the same type is defined as follows:
- If a <=> b is usable (11.10.1 [class.compare.default]) and can be explicitly converted to R using static_cast, static_cast<R>(a <=> b).
- Otherwise, if a <=> b is usable or overload resolution for a <=> b is performed and finds at least one viable candidate, the synthesized three-way comparison is not defined.
- Otherwise, ...
[Accepted as a DR at the November, 2024 meeting.]
(From editorial issue #7042 and submission #595.)
Subclause 3.65 [defns.undefined] states:
[Note 1 to entry: ... Evaluation of a constant expression (7.7 [expr.const]) never exhibits behavior explicitly specified as undefined in Clause 4 [intro] through Clause 15 [cpp]. —end note]
However, 7.7 [expr.const] bullet 5.8 excludes [[noreturn]] and [[assume]]; see also 7.7 [expr.const] paragraph 6.
Suggested resolution [SUPERSEDED]:
Change in 3.65 [defns.undefined] as follows:
[Note 1 to entry: ... Evaluation of a constant expression (7.7 [expr.const]) never exhibits behavior explicitly specified as undefined in Clause 4 [intro] through Clause 15 [cpp], excluding 9.13 [dcl.attr]. —end note]
CWG 2024-09-13
Admitting unbounded core-language undefined behavior in constant expressions is to be avoided. The quoted note is correct; the semantics of [[noreturn]] and [[assume]] need to be clarified.
Possible resolution [SUPERSEDED]:
Change in 9.13.3 [dcl.attr.assume] paragraph 1 as follows:
... The expression is not evaluated. If the converted expression would evaluate to true at the point where the assumption appears or if the assumption is evaluated in a context that is manifestly constant-evaluated, the assumption has no effect. Otherwise, the behavior is undefined.
Change in 9.13.10 [dcl.attr.noreturn] paragraph 2 as follows:
If a function f is called where f was previously declared with the noreturn attribute, the function call is evaluated in a context that is not manifestly constant-evaluated (7.7 [expr.const]), and f eventually returns, the behavior is undefined.
CWG 2024-09-27
The suggested resolution is circular with the rules in 7.7 [expr.const] paragraph 6.
Possible resolution [SUPERSEDED]:
Change in 9.13.3 [dcl.attr.assume] paragraph 1 as follows:
... The expression is not evaluated. If the converted expression would evaluate to true at the point where the assumption appears, the assumption has no effect. Otherwise, outside of an evaluation to determine whether an expression is a core constant expression (7.7 [expr.const]), the behavior is undefined.
Change in 9.13.10 [dcl.attr.noreturn] paragraph 2 as follows:
If a function f is called where f was previously declared with the noreturn attribute, the function call is evaluated outside of an evaluation to determine whether an expression is a core constant expression (7.7 [expr.const]), and f eventually returns, the behavior is undefined.
CWG 2024-10-11
Implementations have two options: Either a violation of an attribute causes an expressions not to be a constant expression, leading to runtime undefined behavior, or the attribute has no effect during constant evaluation.
Possible resolution [SUPERSEDED]:
Change in 7.7 [expr.const] paragraph 6:
It is implementation-defined whether E is a core constant expression in the situations specified in 9.13.3 [dcl.attr.assume] and 9.13.10 [dcl.attr.noreturn].
It is unspecified whether E is a core constant expression if E satisfies the constraints of a core constant expression, but evaluation of E would evaluate
- an operation that has undefined behavior as specified in Clause 16 [library] through Clause 33 [exec]
,or- an invocation of the va_start macro (17.14.2 [cstdarg.syn])
,.- a call to a function that was previously declared with the noreturn attribute (9.13.10 [dcl.attr.noreturn]) and that call returns to its caller, or
- a statement with an assumption (9.13.3 [dcl.attr.assume]) whose converted conditional-expression, if evaluated where the assumption appears, would not disqualify E from being a core constant expression and would not evaluate to true. [Note 5: E is not disqualified from being a core constant expression if the hypothetical evaluation of the converted conditional-expression would disqualify E from being a core constant expression. —end note]
Change in 9.13.3 [dcl.attr.assume] paragraph 1 as follows:
... The expression is not evaluated.
- If the converted expression would evaluate to true at the point where the assumption appears, the assumption has no effect.
- Otherwise, if the statement with the assumption would be evaluated as part of an evaluation of an expression E that satisfies the constraints of a core constant expression (7.7 [expr.const]):
- If the converted expression, evaluated at the point where the assumption appears, would disqualify E from being a core constant expression, the assumption is ignored.
- Otherwise, it is implementation-defined whether E is a core constant expression; if E is evaluated as a core constant expression, the assumption has no effect.
- Otherwise, the behavior is undefined.
Change in 9.13.10 [dcl.attr.noreturn] paragraph 2 as follows:
If a function f is called where f was previously declared with the noreturn attribute and f eventually returns:
- If the function call would be part of an evaluation of an expression E that satisfies the constraints of a core constant expression (7.7 [expr.const]), it is implementation-defined whether E is a core constant expression; if E is evaluated as a core constant expression, the attribute has no effect.
- Otherwise, the behavior is undefined.
CWG 2024-10-25
CWG prefers an approach suggested by Richard Smith that defines a new term "runtime undefined behavior".
Proposed resolution (approved by CWG 2024-11-08):
Add to Clause 3 [intro.defs]:
constant evaluation [defns.const.eval]
evaluation that is performed as part of evaluating an expression as a core constant expression (7.7 [expr.const])
runtime-undefined behavior [defns.undefined.runtime]
behavior that is undefined except when it occurs during constant evaluation
[Note 1 to entry: During constant evaluation,
- it is implementation-defined whether runtime-undefined behavior results in the expression being deemed non-constant (as specified in 7.7 [expr.const]) and
- runtime-undefined behavior has no other effect.]
Change in 7.7 [expr.const] bullet 5.8 as follows:
- an operation that would have undefined or erroneous behavior as specified in Clause 4 [intro] through Clause 15 [cpp]
, excluding 9.13.3 [dcl.attr.assume] and 9.13.10 [dcl.attr.noreturn];
Add a paragraph after 7.7 [expr.const] paragraph 5 as follows:
It is implementation-defined whether E is a core constant expression if E satisfies the constraints of a core constant expression, but evaluation of E has runtime-undefined behavior.
Change in 7.7 [expr.const] paragraph 6:
It is unspecified whether E is a core constant expression if E satisfies the constraints of a core constant expression, but evaluation of E would evaluate
- an operation that has undefined behavior as specified in Clause 16 [library] through Clause 33 [exec]
,or- an invocation of the va_start macro (17.14.2 [cstdarg.syn])
,.- a call to a function that was previously declared with the noreturn attribute (9.13.10 [dcl.attr.noreturn]) and that call returns to its caller, or
- a statement with an assumption (9.13.3 [dcl.attr.assume]) whose converted conditional-expression, if evaluated where the assumption appears, would not disqualify E from being a core constant expression and would not evaluate to true. [Note 5: E is not disqualified from being a core constant expression if the hypothetical evaluation of the converted conditional-expression would disqualify E from being a core constant expression. —end note]
Change in 9.13.3 [dcl.attr.assume] paragraph 1 as follows:
... If the converted expression would evaluate to true at the point where the assumption appears, the assumption has no effect. Otherwise,the behavior is undefinedevaluation of the assumption has runtime-undefined behavior.
Change in 9.13.10 [dcl.attr.noreturn] paragraph 2 as follows:
If a function f iscalledinvoked where f was previously declared with the noreturn attribute andfthat invocation eventually returns, the behavior is runtime-undefined.
[Accepted as a DR at the March, 2024 meeting.]
The resolution for issue 2518 disallows existing implementation practice, as detailed below:
Suggested resolution [SUPERSEDED]:
Change in 4.1.1 [intro.compliance.general] paragraph 2 as follows:
Furthermore, a conforming implementation
- shall not accept a preprocessing translation unit containing a #error preprocessing directive (15.9 [cpp.error])
,andshall issue at least one diagnostic message for each #warning or #error preprocessing directive not following a #error preprocessing directive in a preprocessing translation unit, and- shall not accept a translation unit with a static_assert-declaration that fails (9.1 [dcl.pre]).
Change in 5.1 [lex.separate] paragraph 1 as follows:
The text of the program is kept in units called source files in this document. A source file together with all the headers (16.4.2.3 [headers]) and source files included (15.3 [cpp.include]) via the preprocessing directive #include, less any source lines skipped by any of the conditional inclusion (15.2 [cpp.cond]) preprocessing directives or by the implementation-defined behavior of any conditionally-supported-directives (15.1 [cpp.pre]), is called a preprocessing translation unit.
CWG 2023-03-03
Permit that #warning can be ignored if another diagnostic is produced.
Proposed resolution (approved by CWG 2024-01-19):
Change in 4.1.1 [intro.compliance.general] bullet 2.3 as follows:
- ...
- Otherwise, if a program contains
a conforming implementation shall issue at least one diagnostic message.
- a violation of any diagnosable rule
or,- a preprocessing translation unit with a #warning preprocessing directive (15.9 [cpp.error]), or
- an occurrence of a construct described in this document as “conditionally-supported” when the implementation does not support that construct,
Change in 4.1.1 [intro.compliance.general] paragraph 2 as follows:
Furthermore, a conforming implementation shall not accept
- a preprocessing translation unit containing a #error preprocessing directive (15.9 [cpp.error])
,orshall issue at least one diagnostic message for each #warning or #error preprocessing directive not following a #error preprocessing directive in a preprocessing translation unit, andshall not accepta translation unit with a static_assert-declaration that fails (9.1 [dcl.pre]).
Change in 5.1 [lex.separate] paragraph 1 as follows:
The text of the program is kept in units called source files in this document. A source file together with all the headers (16.4.2.3 [headers]) and source files included (15.3 [cpp.include]) via the preprocessing directive #include, less any source lines skipped by any of the conditional inclusion (15.2 [cpp.cond]) preprocessing directives, as modified by the implementation-defined behavior of any conditionally-supported-directives (15.1 [cpp.pre]) and pragmas (15.10 [cpp.pragma]), if any, is called a preprocessing translation unit.
Change in 15.9 [cpp.error] as follows:
A preprocessing directive ofeither of the following formsthe form# error pp-tokensopt new-linerenders the program ill-formed. A preprocessing directive of the form# warning pp-tokensopt new-linecausesrequires the implementation to produceaat least one diagnostic message for the preprocessing translation unit (4.1.1 [intro.compliance.general])that.Recommended practice: Any diagnostic message caused by either of these directives should include the specified sequence of preprocessing tokens
; the #error directive renders the program ill-formed.
[Accepted as a DR at the November, 2023 meeting.]
The description of how to handle file not ending in a newline in 5.2 [lex.phases] paragraph 1, phase 2, is:
Each instance of a backslash character (\) immediately followed by a new-line character is deleted, splicing physical source lines to form logical source lines. Only the last backslash on any physical source line shall be eligible for being part of such a splice. If, as a result, a character sequence that matches the syntax of a universal-character-name is produced, the behavior is undefined. 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, shall be processed as if an additional new-line character were appended to the file.
This is not clear regarding what happens if the last character in the file is a backslash. In such a case, presumably the result of adding the newline should not be a line splice but rather a backslash preprocessing-token (that will be diagnosed as an invalid token in phase 7), but that should be spelled out.
CWG 2023-07-14
Addressed by the resolution for issue 2747.
[ Resolved by paper P2621R2 (Undefined behavior in the lexer), adopted in June, 2023. ]
(From National Body comments US 024 and US 025 on the C++20 DIS.)
Subclause 5.2 [lex.phases] bullet 1.2 specifies:
Except for splices reverted in a raw string literal, if a splice results in a character sequence that matches the syntax of a universal-character-name, the behavior is undefined.
Undefined behavior during lexing is not acceptable. The behavior ought to be well-defined, ill-formed, or conditionally-supported.
Additional notes (January, 2023):
Forwarded to SG12 with paper issue 1405, by decision of the CWG and SG12 chairs.
[Accepted as a DR at the November, 2023 meeting.]
(From editorial issue 4903.)
Subclause 5.2 [lex.phases] paragraph 2 specifies:
... Each sequence of a backslash character (\) immediately followed by zero or more whitespace characters other than new-line followed by a new-line character is deleted, splicing physical source lines to form logical source lines. ... A source file that is not empty and that does not end in a new-line character, or that ends in a splice, shall be processed as if an additional new-line character were appended to the file.
This is confusing, because the first sentence deletes all splices, and then the last sentence checks for a splice that has already been deleted.
Proposed resolution (approved by CWG 2023-07-14):
Change in 5.2 [lex.phases] paragraph 2 as follows:
... Each sequence of a backslash character (\) immediately followed by zero or more whitespace characters other than new-line followed by a new-line character is deleted, splicing physical source lines to form logical source lines. ... A source file that is not empty and that (after splicing) does not end in a new-line character, or that ends in a splice,shall be processed as if an additional new-line character were appended to the file.
CWG 2023-07-14
CWG noted that a lone backslash at the end of a file remains (in the status quo and with the proposed change) and turns into an ill-formed preprocessing-token. The wording as amended seems sufficiently clear to consider issue 1698 resolved.
[Accepted as a DR at the November, 2024 meeting.]
(From submission #615.)
Subclause 15.1 [cpp.pre] defines the grammar production preprocessing-file, but nothing in the standard specifies that a translation unit is ill-formed if it fails to match that grammar. Similarly, translation-unit has no effect.
Proposed resolution (approved by CWG 2024-11-08):
Change in 5.2 [lex.phases] bullet 1.4 as follows:
- ...
- The source file is analyzed as a preprocessing-file (15.1 [cpp.pre]). Preprocessing directives are executed, macro invocations are expanded, and _Pragma unary operator expressions are executed. A #include preprocessing directive causes the named header or source file to be processed from phase 1 through phase 4, recursively. All preprocessing directives are then deleted.
- ...
Change in 5.2 [lex.phases] bullet 1.7 as follows:
Whitespace characters separating tokens are no longer significant. Each preprocessing token is converted into a token (5.10 [lex.token]). The resulting tokens constitute a translation unit and are syntactically and semantically analyzed as a translation-unit (6.6 [basic.link]) and translated.
[ Resolved by paper P2621R2 (Undefined behavior in the lexer), adopted in June, 2023. ]
(From National Body comment US 027 on the C++20 DIS.)
Subclause 5.5 [lex.pptoken] paragraph 2 specifies:
If a U+0027 apostrophe or a U+0022 quotation mark character matches the last category, the behavior is undefined.
Undefined behavior during lexing is not acceptable. This ought to be ill-formed.
Additional notes (January, 2023):
Forwarded to SG12 with paper issue 1406, by decision of the CWG and SG12 chairs.
[Accepted as a DR at the June, 2024 meeting.]
Subclause 5.11 [lex.name] paragraph 3 specifies:
In addition, some identifiers appearing as a token or preprocessing-token are reserved for use by C++ implementations and shall not be used otherwise; no diagnostic is required.
- Each identifier that contains a double underscore __ or begins with an underscore followed by an uppercase letter is reserved to the implementation for any use.
- Each identifier that begins with an underscore is reserved to the implementation for use as a name in the global namespace.
That implies that uses of standard-specified predefined macros (15.12 [cpp.predefined]) or feature-test macros (17.3.2 [version.syn]) make the program ill-formed. This does not appear to be the intent.
Proposed resolution (approved by CWG 2024-01-19) [SUPERSEDED]:
Change in 5.11 [lex.name] paragraph 3 and add bullets as follows:
In addition, some identifiers appearing as a token or preprocessing-token are reserved for use by C++ implementations and shall not be used otherwise; no diagnostic is required.
- Each identifier that contains a double underscore __ or begins with an underscore followed by an uppercase letter is reserved to the implementation for any use, except for
- the macros specified in 15.12 [cpp.predefined],
- the function-local predefined variable (9.6.1 [dcl.fct.def.general]), and
- the macros and identifiers declared in the standard library as specified in Clause 17 [support] through Clause 32 [thread], and Clause Annex D [depr]. [ Note: This affects macros in 17.3.2 [version.syn], 31.13.1 [cstdio.syn], and D.11 [depr.c.macros] as well as identifiers in 17.2.2 [cstdlib.syn]. -- end note ]
- Each identifier that begins with an underscore is reserved to the implementation for use as a name in the global namespace.
CWG 2024-03-20
The exceptions should not be specified using a specific list, which might become stale in the future.
Proposed resolution (approved by CWG 2024-03-20):
Change in 5.11 [lex.name] paragraph 3 and add bullets as follows:
In addition, some identifiers appearing as a token or preprocessing-token are reserved for use by C++ implementations and shall not be used otherwise; no diagnostic is required.
- Each identifier that contains a double underscore __ or begins with an underscore followed by an uppercase letter, other than those specified in this document (for example, __cplusplus (15.12 [cpp.predefined])), is reserved to the implementation for any use.
- Each identifier that begins with an underscore is reserved to the implementation for use as a name in the global namespace.
[Accepted as a DR at the June, 2023 meeting.]
Subclause 5.13.2 [lex.icon] paragraph 4 specifies:
If an integer-literal cannot be represented by any type in its list and an extended integer type (6.8.2 [basic.fundamental]) can represent its value, it may have that extended integer type. If all of the types in the list for the integer-literal are signed, the extended integer type shall be signed. If all of the types in the list for the integer-literal are unsigned, the extended integer type shall be unsigned. If the list contains both signed and unsigned types, the extended integer type may be signed or unsigned. A program is ill-formed if one of its translation units contains an integer-literal that cannot be represented by any of the allowed types.
This implies that an integer-literal with a z suffix can be of extended integer type, if the literal is larger than what is representable in std::size_t.
According to the author of the paper P0330R8 (Literal Suffix for (signed) size_t) introducing the feature, this is unintentional; z should only yield std::size_t and its corresponding signed integer type.
See also the corresponding WG14 paper N2998 Literal Suffixes for size_t.
Proposed resolution (reviewed by CWG 2023-03-03, approved by CWG 2023-05-12):
Change in 5.13.2 [lex.icon] paragraph 4 as follows:
IfExcept for integer-literals containing a size-suffix, if the value of an integer-literal cannot be represented by any type in its list and an extended integer type (6.8.2 [basic.fundamental]) can represent its value, it may have that extended integer type. If all of the types in the list for the integer-literal are signed, the extended integer typeshall beis signed. If all of the types in the list for the integer-literal are unsigned, the extended integer typeshall beis unsigned. If the list contains both signed and unsigned types, the extended integer type may be signed or unsigned.A program is ill-formed if one of its translation units containsIf an integer-literalthatcannot be represented by any of the allowed types, the program is ill-formed. [ Note: An integer-literal with a z or Z suffix is ill-formed if it cannot be represented by std::size_t. -- end note ]
Additional notes (February, 2023)
Alerted the chair of SG22 (C/C++ Liaison).
Forwarded to EWG at the request of the EWG chair via cplusplus/papers#1467.
EWG 2023-05-11
The "z" suffixes mean std::size_t (or its corresponding signed type) only. The proposed resolution is accepted by EWG.
[Accepted as a DR at the June, 2024 meeting.]
(From submission #511.)
Subclause 5.13.5 [lex.string] paragraph 7 does not properly handle the case where both encoding-prefixes are absent:
The common encoding-prefix for a sequence of adjacent string-literals is determined pairwise as follows: If two string-literals have the same encoding-prefix , the common encoding-prefix is that encoding-prefix . If one string-literal has no encoding-prefix , the common encoding-prefix is that of the other string-literal. Any other combinations are ill-formed.
Possible resolution [SUPERSEDED]:
Change in 5.13.5 [lex.string] paragraph 7 as follows:
The common encoding-prefix for a sequence of adjacent string-literals is determined pairwise as follows:If two string-literals have the same encoding-prefix, the common encoding-prefix is that encoding-prefix.If one string-literal has no encoding-prefix, the common encoding-prefix is that of the other string-literal or is absent if the other string-literal has no encoding-prefix. Otherwise, both string-literals shall have the same encoding-prefix, and the common encoding-prefix is that encoding-prefix.Any other combinations are ill-formed.
CWG 2024-05-03
CWG preferred a holistic instead of a pairwise approach.
Proposed resolution (approved by CWG 2024-05-17):
Change in 5.13.5 [lex.string] paragraph 7 as follows:
The common encoding-prefix for a sequence of adjacent string-literals is determined pairwise as follows: If two string-literals have the same encoding-prefix , the common encoding-prefix is that encoding-prefix . If one string-literal has no encoding-prefix , the common encoding-prefix is that of the other string-literal. Any other combinations are ill-formed.
The string-literals in any sequence of adjacent string-literals shall have at most one unique encoding-prefix among them. The common encoding-prefix of the sequence is that encoding-prefix, if any.
[Accepted as a DR at the March, 2024 meeting.]
Default template arguments of generic lambdas can refer to local variables. It is unclear whether the potential odr-use is checked when parsing the template definition or when instantiating the template.
There is wide implementation divergence.
Proposed resolution (approved by CWG 2024-03-20):
Insert a new paragraph before 6.3 [basic.def.odr] paragraph 11:
[ Example:
void g() { constexpr int x = 1; auto lambda = [] <typename T, int = ((T)x, 0)> {}; // OK lambda.operator()<int, 1>(); // OK, does not consider x at all lambda.operator()<int>(); // OK, does not odr-use x lambda.operator()<const int&>(); // error: odr-uses x from a context where x is not odr-usable } void h() { constexpr int x = 1; auto lambda = [] <typename T> { (T)x; }; // OK lambda.operator()<int>(); // OK, does not odr-use x lambda.operator()<void>(); // OK, does not odr-use x lambda.operator()<const int&>(); // error: odr-uses x from a context where x is not odr-usable }-- end example ]
Every program shall contain at least one definition of every function or variable ...
[Accepted as a DR at the June, 2024 meeting.]
(From submission #523.)
Consider:
void f() {
int x;
[&] {
return x; // #1
};
}
The odr-use of x is ill-formed, because x is not odr-usable in that scope, because there is a lambda scope (6.4.5 [basic.scope.lambda]) between #1 and the definition of x.
A more involved example exhibits implementation divergence:
struct A {
A() = default;
A(const A &) = delete;
constexpr operator int() { return 42; }
};
void f() {
constexpr A a;
[=]<typename T, int = a> {}; // OK, not odr-usable from a default template argument, and not odr-used
}
Proposed resolution (approved by CWG 2024-05-31):
Change in 6.3 [basic.def.odr] paragraph 10 as follows:
A local entity (6.1 [basic.pre]) is odr-usable in a scope (6.4.1 [basic.scope.scope]) if:If a local entity is odr-used in a scope in which it is not odr-usable, the program is ill-formed.
- either the local entity is not *this, or an enclosing class or non-lambda function parameter scope exists and, if the innermost such scope is a function parameter scope, it corresponds to a non-static member function, and
- for each intervening scope (6.4.1 [basic.scope.scope]) between the point at which the entity is introduced and the scope (where *this is considered to be introduced within the innermost enclosing class or non-lambda function definition scope), either:
- the intervening scope is a block scope, or
- the intervening scope is the function parameter scope of a lambda-expression, or
- the intervening scope is the lambda scope of a lambda-expression that has a simple-capture naming the entity or has a capture-default, and the block scope of the lambda-expression is also an intervening scope.
[Accepted as a DR at the November, 2024 meeting.]
(From submission #561.)
Consider:
bool f() { constexpr int z = 42; return requires { sizeof(int [*&z]); } && requires (int x) { sizeof(int [*&z]); }; }
The second requires-expression introduces a function parameter scope according to 6.4.4 [basic.scope.param]. This affects odr-usability as specified in 6.3 [basic.def.odr] paragraph 10, but the two requires-expression in the example ought to actually behave the same.
Proposed resolution (approved by CWG 2024-11-19):
Change in 6.3 [basic.def.odr] bullet 10.2.2 as follows:
A local entity (6.1 [basic.pre]) is odr-usable in a scope (6.4.1 [basic.scope.scope]) if:
- ...
- for each intervening scope (6.4.1 [basic.scope.scope]) between the point at which the entity is introduced and the scope (where *this is considered to be introduced within the innermost enclosing class or non-lambda function definition scope), either:
- the intervening scope is a block scope, or
- the intervening scope is the function parameter scope of a lambda-expression or requires-expression, or
- the intervening scope is the lambda scope of a lambda-expression that has a simple-capture naming the entity or has a capture-default, and the block scope of the lambda-expression is also an intervening scope.
[Accepted as a DR at the November, 2023 meeting.]
Paper P2169R4 (A nice placeholder with no name), as approved by WG21 in Varna, added a placeholder facility. The intent was that the use of placeholders is sufficiently limited such that they never need to be mangled. Quote from 6.4.1 [basic.scope.scope] paragraph 5 as modified by the paper:
A declaration is name-independent if its name is _ and it declares a variable with automatic storage duration, a structured binding not inhabiting a namespace scope, the variable introduced by an init-capture, or a non-static data member.
The following example does not seem to follow that intent:
struct A { A(); }; inline void f() { static union { A _{}; }; static union { A _{}; }; } void g() { return f(); }
The preceding example needs handling similar to the following example, which is unrelated to the placeholder feature:
struct A { A(); }; inline void f() { { static union { A a{}; }; } { static union { A a{}; }; } } void g() { return f(); }
A similar problem may arise for static or thread_local structured bindings at block scope.
Finally, another example involving placeholders in anonymous unions:
static union { int _ = 42; }; int &ref = _; int foo() { return 13; } static union { int _ = foo(); }; int main() { return ref; }
Possible resolution (reviewed by CWG 2023-08-25) [SUPERSEDED]:
Change in 6.4.1 [basic.scope.scope] paragraph 5 and add bullets as follows:
A class is name-dependent if it is an anonymous union declared at namespace scope or with a storage-class-specifier (11.5.2 [class.union.anon]). A declaration is name-independent if its name is _ and it declares
- a variable with automatic storage duration,
- a structured binding with no storage-class-specifier and not inhabiting a namespace scope,
- the variable introduced by an init-capture, or
- a non-static data member of other than a name-dependent class.
Proposed resolution (approved by CWG 2023-09-15):
Change in 6.4.1 [basic.scope.scope] paragraph 5 and add bullets as follows:
A declaration is name-independent if its name is _ and it declares
- a variable with automatic storage duration,
- a structured binding with no storage-class-specifier and not inhabiting a namespace scope,
- the variable introduced by an init-capture, or
- a non-static data member of other than an anonymous union.
[Accepted as a DR at the November, 2023 meeting.]
Consider:
void f(int i) { extern int i; }
According to 6.4.3 [basic.scope.block] paragraph 2, the target scope of the declaration is relevant (which would be the global scope), but not the scope in which the name is bound. That seems wrong. For comparison, template parameter names use the latter rule (13.8.2 [temp.local] paragraph 6).
Proposed resolution (approved by CWG 2023-09-15):
If a declaration that is not a name-independent declaration andwhose target scope isthat binds a name in the block scope S of apotentially conflicts with a declaration whose target scope is the parent scope of S, the program is ill-formed.
- compound-statement of a lambda-expression, function-body, or function-try-block,
- substatement of a selection or iteration statement that is not itself a selection or iteration statement, or
- handler of a function-try-block
[Accepted as a DR at the March, 2024 meeting.]
Subclause 6.5.4 [basic.lookup.argdep] bullet 3.2 specifies:
- ...
- If T is a class type (including unions), its associated entities are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. ...
- ...
It is unclear what happens if T is incomplete, for example because it was instantiated from a template whose definition was not (yet) available.
Proposed resolution (approved by CWG 2024-03-01)
Change in 6.5.4 [basic.lookup.argdep] bullet 3.2 as follows:
- ...
- If T is a class type (including unions), its associated entities are: the class itself; the class of which it is a member, if any; and, if it is a complete type, its direct and indirect base classes. ...
- ...
[Accepted as a DR at the June, 2024 meeting.]
It is unclear what "can be referred to" means in 6.6 [basic.link] paragraph 2. Paragraph 8 provides precise definitions for "same entity".
Possible resolution [SUPERSEDED]:
Replace in 6.6 [basic.link] paragraph 2 as follows:
A name is said to have linkage when it can denote the same object, reference, function, type, template, namespace or value as a name introduced by a declaration in another scope:
- When a name has external linkage, the entity it denotes can be referred to by names from scopes of other translation units or from other scopes of the same translation unit.
- When a name has module linkage, the entity it denotes can be referred to by names from other scopes of the same module unit (10.1 [module.unit]) or from scopes of other module units of that same module.
- When a name has internal linkage, the entity it denotes can be referred to by names from other scopes in the same translation unit.
- When a name has no linkage, the entity it denotes cannot be referred to by names from other scopes.
A name can have external linkage, module linkage, internal linkage, or no linkage, as determined by the rules below. [ Note: The linkage of a name determines when corresponding declarations (6.4.1 [basic.scope.scope]) that introduce that name can declare the same entity. ---end note]
Change in 11.6 [class.local] paragraph 3 as follows:
If class X is a local class, a nested class Y may be declared in class X and later defined in the definition of class X or be later defined in the same scope as the definition of class X.A class nested within a local class is a local class. A member of a local class X shall be declared only in the definition of X or the nearest enclosing block scope of X.
CWG 2024-05-03
CWG opined to split off the second change (see issue 2890) and clarify the note.
Proposed resolution (approved by CWG 2024-05-17):
Replace in 6.6 [basic.link] paragraph 2 as follows:
A name is said to have linkage when it can denote the same object, reference, function, type, template, namespace or value as a name introduced by a declaration in another scope:
- When a name has external linkage, the entity it denotes can be referred to by names from scopes of other translation units or from other scopes of the same translation unit.
- When a name has module linkage, the entity it denotes can be referred to by names from other scopes of the same module unit (10.1 [module.unit]) or from scopes of other module units of that same module.
- When a name has internal linkage, the entity it denotes can be referred to by names from other scopes in the same translation unit.
- When a name has no linkage, the entity it denotes cannot be referred to by names from other scopes.
A name can have external linkage, module linkage, internal linkage, or no linkage, as determined by the rules below. [ Note: All declarations of an entity with a name with internal linkage appear in the same translation unit. All declarations of an entity with module linkage are attached to the same module. ---end note]
[Accepted as a DR at the November, 2024 meeting.]
According to 6.7.1 [intro.memory] paragraph 3,
A memory location is either an object of scalar type or a maximal sequence of adjacent bit-fields all having non-zero width. [Note: Various features of the language, such as references and virtual functions, might involve additional memory locations that are not accessible to programs but are managed by the implementation. —end note] Two or more threads of execution (6.9.2 [intro.multithread]) can update and access separate memory locations without interfering with each other.
It is not clear how this relates to the permission granted in 11.4 [class.mem] paragraph 18 to inspect the common initial sequence of standard-layout structs that are members of a standard-layout union. If one thread is writing to the common initial sequence and another is reading from it via a different struct, that should constitute a data race, but the current wording does not clearly state that.
Additional notes (October, 2024)
(From submission #621.)
A similar concern arises for the following example:
union U { int x, y; } u; (u.x = 1, 0) + (u.y = 2, 0);
Possible resolution [SUPERSEDED]:
Change in 6.7.1 [intro.memory] paragraph 3 as follows:
A memory location is the storage occupied by the object representation of either an object of scalar type that is not a bit-field or a maximal sequence of adjacent bit-fields all having nonzero width. ...
CWG 2024-10-25
Subclause 6.9.1 [intro.execution] paragraph 10 does not cover unsequenced object creation that does not change any bits of storage, such as a placement new invoking a trivial default constructor. The original concern in this issue was addressed by P0137R1, adding the following in 11.4.2 [class.mfct] paragraph 28:
In a standard-layout union with an active member (11.5 [class.union]) of struct type T1, it is permitted to read a non-static data member m of another union member of struct type T2 provided m is part of the common initial sequence of T1 and T2; the behavior is as if the corresponding member of T1 were nominated.
Proposed resolution (approved by CWG 2024-11-08):
Change in 6.7.1 [intro.memory] paragraph 3 as follows:
A memory location is the storage occupied by the object representation of either an object of scalar type that is not a bit-field or a maximal sequence of adjacent bit-fields all having nonzero width. ...
Change in 6.9.1 [intro.execution] paragraph 10 and add bullets as follows:
... The value computations of the operands of an operator are sequenced before the value computation of the result of the operator.IfThe behavior is undefined ifis unsequenced relative to
- a side effect on a memory location (6.7.1 [intro.memory]) or
- starting or ending the lifetime of an object in a memory location
eitherand
- another side effect on the same memory location,
- starting or ending the lifetime of an object occupying storage that overlaps with the memory location, or
- a value computation using the value of any object in the same memory location,
theythe two evaluations are not potentially concurrent (6.9.2 [intro.multithread]), the behavior is undefined. [ Note: Starting the lifetime of an object in a memory location can end the lifetime of objects in other memory locations (6.7.4 [basic.life]). -- end note ][ Note: ... ]
[ Example:
void g(int i) { i = 7, i++, i++; // i becomes 9 i = i++ + 1; // the value of i is incremented i = i++ + i; // undefined behavior i = i + 1; // the value of i is incremented union U { int x, y; } u; (u.x = 1, 0) + (u.y = 2, 0); // undefined behavior }-- end example ]
Change in 6.9.2.2 [intro.races] paragraph 2 as follows, adding bullets:
Two expression evaluations conflict if one of themand the other one
- modifies (3.1 [defns.access]) a memory location (6.7.1 [intro.memory]) or
- starts or ends the lifetime of an object in a memory location
[Note 2: A modification can still conflict even if it does not alter the value of any bits. —end note]
- reads or modifies the same memory location or
- starts or ends the lifetime of an object occupying storage that overlaps with the memory location.
[Accepted as a DR at the November, 2023 meeting.]
Subclause 6.7.2 [intro.object] paragraph 9 specifies the general principle that two objects with overlapping lifetimes have non-overlapping storage, which can be observed by comparing addresses:
Unless an object is a bit-field or a subobject of zero size, the address of that object is the address of the first byte it occupies. Two objects with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a subobject of zero size and they are of different types; otherwise, they have distinct addresses and occupy disjoint bytes of storage.
After P2752, there are two exceptions: string literal objects and backing arrays for initializer lists.
Subclause 5.13.5 [lex.string] paragraph 9 specifies:
Evaluating a string-literal results in a string literal object with static storage duration (6.7.6 [basic.stc]). Whether all string-literals are distinct (that is, are stored in nonoverlapping objects) and whether successive evaluations of a string-literal yield the same or a different object is unspecified.
Subclause 9.5.4 [dcl.init.ref] paragraph 5, after application of P2752R3 (approved in June, 2023), specifies:
Whether all backing arrays are distinct (that is, are stored in non-overlapping objects) is unspecified.
It is unclear whether a backing array can overlap with a string literal object.
Furthermore, it is unclear whether any such object can overlap with named objects or temporaries, for example:
const char (&r) [] = "foo"; const char a[] = {'f', 'o', 'o', '\0'}; int main() { assert(&r == &a); // allowed not to fail? }
Proposed resolution (approved by CWG 2023-11-09):
Add a new paragraph before 6.7.2 [intro.object] paragraph 9 and change the latter as follows:
An object is a potentially non-unique object if it is a string literal object (5.13.5 [lex.string]), the backing array of an initializer list (9.5.4 [dcl.init.ref]), or a subobject thereof.
Unless an object is a bit-field or a subobject of zero size, the address of that object is the address of the first byte it occupies. Two objects with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a subobject of zero size and they are of different types, or if they are both potentially non-unique objects; otherwise, they have distinct addresses and occupy disjoint bytes of storage.
[Example 2:
static const char test1 = 'x'; static const char test2 = 'x'; const bool b = &test1 != &test2; // always true static const char (&r) [] = "x"; static const char *s = "x"; static std::initializer_list<char> il = { 'x' }; const bool b2 = r != il.begin(); // unspecified result const bool b3 = r != s; // unspecified result const bool b4 = il.begin() != &test1; // always true const bool b5 = r != &test1; // always true-- end example]
Change in subclause 5.13.5 [lex.string] paragraph 9 as follows:
Evaluating a string-literal results in a string literal object with static storage duration (6.7.6 [basic.stc]). [ Note: String literal objects are potentially non-unique (6.7.2 [intro.object]). Whetherall string-literals are distinct (that is, are stored in nonoverlapping objects) and whethersuccessive evaluations of a string-literal yield the same or a different object is unspecified. -- end note ]
Change in subclause 9.5.4 [dcl.init.ref] paragraph 5, after application of P2752R3 (approved in June, 2023), as follows:
Whether all backing arrays are distinct (that is, are stored in non-overlapping objects) is unspecified.[ Note: Backing arrays are potentially non-unique objects (6.7.2 [intro.object]). -- end note ]
CWG 2023-07-14
CWG resolved that a named or temporary object is always disjoint from any other object, and thus cannot overlap with a string literal object or a backing array. The lines b4 and b5 in the example highlight that outcome.
Backing arrays and string literals can arbitrarily overlap among themselves; CWG believes the proposed wording achieves that outcome.
The ancillary question how address comparisons between potentially non-unique objects are treated during constant evaluation is handled in issue 2765.
[Accepted as a DR at the November, 2023 meeting.]
Subclause 6.7.2 [intro.object] paragraph 9 specifies:
... Two objects with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a subobject of zero size and they are of different types; otherwise, they have distinct addresses and occupy disjoint bytes of storage. [ Footnote: ... ]
Types T and const T are different types, but it is unlikely the rule is intending to differentiate along that line.
Suggested resolution [SUPERSEDED]:
Change in 6.7.2 [intro.object] paragraph 9 as follows:
... Two objects with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a subobject of zero size and they are of different types (ignoring top-level cv-qualifiers); otherwise, they have distinct addresses and occupy disjoint bytes of storage. [ Footnote: ... ]
Proposed resolution (approved by CWG 2023-09-15):
(Hypothetically, pointer-to-member types can be empty, but might differ in non-top-level cv-qualification.)
Change in 6.7.2 [intro.object] paragraph 9 as follows:
... Two objects with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a subobject of zero size and they are not ofdifferentsimilar types (7.3.6 [conv.qual]); otherwise, they have distinct addresses and occupy disjoint bytes of storage. [ Footnote: ... ]
[Accepted as a DR at the June, 2023 meeting.]
A non-allocating form of operator new can be used to create an object in storage that is not suitably aligned for the type of the object. Such attempts ought to be undefined behavior.
Proposed resolution (approved by CWG 2023-04-28):
Change in 6.7.3 [basic.align] paragraph 1 as follows:
Object types have alignment requirements (6.8.2 [basic.fundamental], 6.8.4 [basic.compound]) which place restrictions on the addresses at which an object of that type may be allocated. An alignment is an implementation-defined integer value representing the number of bytes between successive addresses at which a given object can be allocated. An object type imposes an alignment requirement on every object of that type; stricter alignment can be requested using the alignment specifier (9.13.2 [dcl.align]). Attempting to create an object (6.7.2 [intro.object]) in storage that does not meet the alignment requirements of the object's type is undefined behavior.
Change in 7.6.2.8 [expr.new] paragraph 22 as follows:
[Note 11: When the allocation function returns a value other than null, it must be a pointer to a block of storage in which space for the object has been reserved. The block of storage is assumed to be appropriately aligned (6.7.3 [basic.align]) and of the requested size. The address of the created object will not necessarily be the same as that of the block if the object is an array. —end note]
[Accepted as a DR at the June, 2023 meeting.]
Subclause 6.7.4 [basic.life] bullet 1.5 specifies:
The lifetime of an object o of type T ends when:
- if T is a non-class type, the object is destroyed, or
- if T is a class type, the destructor call starts, or
- the storage which the object occupies is released, or is reused by an object that is not nested within o (6.7.2 [intro.object]).
Consider the expression new (p) T(x). Does the lifetime of *p end when p is returned from the allocation function, before x is evaluated? Or does the lifetime end when the constructor body starts executing, after x is evaluated?
The second option is conceivable for initialization by constructor and non-class types; for aggregate initialization, the first option must be used, because evaluation of x directly initializes a part of the resulting object. The first option is simpler to implement for constant evaluation.
Proposed resolution (approved by CWG 2023-05-12):
Change in 6.7.4 [basic.life] paragraph 1 as follows:
The lifetime of an object o of type T ends when:When evaluating a new-expression, storage is considered reused after it is returned from the allocation function, but before the evaluation of the new-initializer (7.6.2.8 [expr.new]). [ Example:
- if T is a non-class type, the object is destroyed, or
- if T is a class type, the destructor call starts, or
- the storage which the object occupies is released, or is reused by an object that is not nested within o (6.7.2 [intro.object]).
struct S { int m; }; void f() { S x{1}; new(&x) S(x.m); // undefined behavior }
-- end example ]
Given the following example,
#include <new> int main() { unsigned char buf[sizeof(int)] = {}; int *ip = new (buf) int; return *ip; // 0 or undefined? }
Should the preceding initializsation of the buffer carry over to the value of *ip? According to 6.7.5 [basic.indet] paragraph 1,
When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced (7.6.19 [expr.assign]).
In this case, no new storage is being obtained for the int object created by the new-expression.
Additional notes (August, 2024)
According to the resolution of issue 2721, storage for the elements of buf is considered reused and thus the lifetime of those elements ends when the allocation function returns. Out-of-lifetime objects cannot hold values.
[Accepted as a DR at the March, 2024 meeting.]
In subclause 6.7.2 [intro.object] paragraph 10, operations implicitly creating objects are defined:
Some operations are described as implicitly creating objects within a specified region of storage. For each operation that is specified as implicitly creating objects, that operation implicitly creates and starts the lifetime of zero or more objects of implicit-lifetime types (6.8.1 [basic.types.general]) in its specified region of storage if...
However, the standard does not specify the storage duration that such an implicitly-created object has; this new method of object creation is not mentioned in 6.7.6.1 [basic.stc.general] paragraph 2:
Static, thread, and automatic storage durations are associated with objects introduced by declarations (6.2 [basic.def]) and implicitly created by the implementation (6.7.7 [class.temporary]). The dynamic storage duration is associated with objects created by a new-expression (7.6.2.8 [expr.new]).
With the exception of malloc, the storage duration should probably be that of the object providing storage (if any), similar to the provision for subobjects in 6.7.6.1 [basic.stc.general]:
The storage duration of subobjects and reference members is that of their complete object (6.7.2 [intro.object]).
The storage duration of an object created by a non-allocating form of an allocation function (17.6.3.4 [new.delete.placement]) should be treated similarly.
Possible resolution [SUPERSEDED]:
Change in 6.7.2 [intro.object] paragraph 13 as follows:
Any implicit or explicit invocation of a function named operator new or operator new[] implicitly creates objects with dynamic storage duration in the returned region of storage and returns a pointer to a suitable created object.
Change in 6.7.6.1 [basic.stc.general] paragraph 2 as follows:
Static, thread, and automatic storage durations are associated with objects introduced by declarations (6.2 [basic.def]) and implicitly created by the implementation (6.7.7 [class.temporary]). The dynamic storage duration is associated with objects createdby a new-expression (7.6.2.8 [expr.new])in storage returned by an allocation function (6.7.6.5.2 [basic.stc.dynamic.allocation]) other than a non-allocating form (17.6.3.4 [new.delete.placement]) or by C library memory allocation (20.2.12 [c.malloc]).
Change in 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 3 as follows:
For an allocation functionother than a reserved placement allocation functionother than a non-allocating form (17.6.3.4 [new.delete.placement]), the pointer returned on a successful call shall represent the address of storage that is aligned as follows:
Change in 6.7.6.1 [basic.stc.general] paragraph 1 as follows:
The storage duration ofsubobjects andreference members is that of their complete object. The storage duration of an object nested within another object x is the storage duration of x (6.7.2 [intro.object]).
Change in 7.6.2.8 [expr.new] paragraph 9 as follows:
An object created by a new-expression that invokes an allocation function with a non-allocating form (see below) has the storage duration of the object that used to occupy the region of storage where the new object is created.ObjectsAny other object created by a new-expressionhavehas dynamic storage duration (6.7.6.5 [basic.stc.dynamic]). [Note 5: The lifetime of such an object is not necessarily restricted to the scope in which it is created. —end note]
Change in 20.2.12 [c.malloc] paragraph 4 as follows:
These functions implicitly create objects (6.7.2 [intro.object]) with dynamic storage duration in the returned region of storage and return a pointer to a suitable created object. In the case of calloc and realloc, the objects are created before the storage is zeroed or copied, respectively.
Additional note (December, 2023)
The approach outlined above is incomplete and the wrong direction. The concept of storage duration determines when an object is created and destroyed; for dynamic storage duration, the object is created and destroyed by explicit program action.
Proposed resolution (approved by CWG 2024-01-19):
Change in 6.7.6.1 [basic.stc.general] paragraph 2 as follows:
Static, thread, and automatic storage durations are associated with objects introduced by declarations (6.2 [basic.def]) andimplicitly created by the implementationwith temporary objects (6.7.7 [class.temporary]). The dynamic storage duration is associated with objects created by a new-expression (7.6.2.8 [expr.new]) or with implicitly created objects (6.7.2 [intro.object]).
[Accepted as a DR at the March, 2024 meeting.]
(From submission #490.)
Function parameter objects have automatic storage duration and are not temporary objects (see also issue 2849). However, it is unclear how long the storage for function parameter objects lasts.
Furthermore, for temporary objects that are destroyed at the end of the full-expression, it is unclear how the destruction is ordered with respect to temporary objects destroyed at the end of the full-expression.
Proposed resolution (approved by CWG 2024-03-20):
Change in and combine 6.7.6.4 [basic.stc.auto] paragraph 1 and 2 as follows:
Variables that belong to a block
or parameterscope and are not explicitly declared static, thread_local, or extern have automatic storage duration. The storage forthese entitiessuch variables lasts until the block in which they are created exits. [Note 1: These variables are initialized and destroyed as described in 8.8 [stmt.dcl]. -- end note]Variables that belong to a parameter scope also have automatic storage duration. The storage for a function parameter lasts until immediately after its destruction (7.6.1.3 [expr.call]).
Change in 6.7.7 [class.temporary] paragraph 8 as follows:
The destruction of a temporary whose lifetime is not extended beyond the full-expression in which it was created is sequenced before the destruction of every temporary which is constructed earlier in the same full-expression.Let x and y each be either a temporary object whose lifetime is not extended, or a function parameter. If the lifetimes of x and y end at the end of the same full-expression, and x is initialized before y, then the destruction of y is sequenced before that of x. If the lifetime of two or more temporaries with lifetimes extending beyond the full-expressions in which they were created ends at the same point, these temporaries are destroyed at that point in the reverse order of the completion of their construction. In addition, the destruction of such temporaries shall take into account the ordering of destruction of objects with static, thread, or automatic storage duration (6.7.6.2 [basic.stc.static], 6.7.6.3 [basic.stc.thread], 6.7.6.4 [basic.stc.auto]); that is, if obj1 is an object with the same storage duration as the temporary and created before the temporary is created the temporary shall be destroyed before obj1 is destroyed; if obj2 is an object with the same storage duration as the temporary and created after the temporary is created the temporary shall be destroyed after obj2 is destroyed.
Change in 7.6.1.3 [expr.call] paragraph 6 as follows:
... It is implementation-defined whetherthe lifetime ofa parameterendsis destroyed when the function in which it is definedreturnsexits (8.7.4 [stmt.return], 14.3 [except.ctor]) or at the end of the enclosing full-expression; parameters are always destroyed in the reverse order of their construction. The initialization and destruction of each parameter occurs within the context of the full-expression (6.9.1 [intro.execution]) where the function call appears.
Change in 8.9 [stmt.dcl] paragraph 2 as follows:
A block variable with automatic storage duration (6.7.6.4 [basic.stc.auto]) is active everywhere in the scope to which it belongs after its init-declarator . Upon each transfer of control (including sequential execution of statements) within a function from point P to point Q, all block variables with automatic storage duration that are active at P and not at Q are destroyed in the reverse order of their construction. Then, all block variables with automatic storage duration that are active at Q but not at P are initialized in declaration order; unless all such variables have vacuous initialization (6.7.4 [basic.life]), the transfer of control shall not be a jump. [ Footnote: ... ] When a declaration-statement is executed, P and Q are the points immediately before and after it; when a function returns, Q is after its body.
[Accepted as a DR at the March, 2024 meeting.]
Subclause 6.7.6.1 [basic.stc.general] paragraph 4 seems to suggest that the end of duration of a region of storage causes actual modifications to pointer objects, causing questions about data races (in the abstract machine).
Proposed resolution (approved by CWG 2024-03-20):
Append to 6.7.6.1 [basic.stc.general] paragraph 1:
[ Note: After the duration of a region of storage has ended, the use of pointers to that region of storage is limited (6.8.4 [basic.compound]). -- end note ]
Remove 6.7.6.1 [basic.stc.general] paragraph 4 as follows:
When the end of the duration of a region of storage is reached, the values of all pointers representing the address of any part of that region of storage become invalid pointer values (6.8.4 [basic.compound]). Indirection through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior. Any other use of an invalid pointer value has implementation-defined behavior. [ Footnote: ... ]
Change in 6.8.4 [basic.compound] paragraph 3 as follows:
[Note 2: A pointer past the end of an object (7.6.6 [expr.add]) is not considered to point to an unrelated object of the object's type, even if the unrelated object is located at that address.A pointer value becomes invalid when the storage it denotes reaches the end of its storage duration; see 6.7.6 [basic.stc].—end note]
Insert a new paragraph after 6.8.4 [basic.compound] paragraph 3:
A pointer value P is valid in the context of an evaluation E if P is a null pointer value, or if it is a pointer to or past the end of an object O and E happens before the end of the duration of the region of storage for O. If a pointer value P is used in an evaluation E and P is not valid in the context of E, then the behavior is undefined if E is an indirection (7.6.2.2 [expr.unary.op]) or an invocation of a deallocation function (6.7.6.5.3 [basic.stc.dynamic.deallocation]), and implementation-defined otherwise. [ Footnote: Some implementations might define that copying such a pointer value causes a system-generated runtime fault. -- end footnote ] [ Note: P can be valid in the context of E even if it points to a type unrelated to that of O or if O is not within its lifetime, although further restrictions apply to such pointer values (6.7.4 [basic.life], 7.2.1 [basic.lval], 7.6.6 [expr.add]). —end note]
Change in 7.6.1.9 [expr.static.cast] paragraph 14 as follows:
... If the original pointer value represents the address A of a byte in memory and A does not satisfy the alignment requirement of T, then the resulting pointer value (6.8.4 [basic.compound]) is unspecified. ...
Change in 7.6.1.10 [expr.reinterpret.cast] paragraph 5 as follows:
A value of integral type or enumeration type can be explicitly converted to a pointer. A pointer converted to an integer of sufficient size (if any such exists on the implementation) and back to the same pointer type will have its original value (6.8.4 [basic.compound]); mappings between pointers and integers are otherwise implementation-defined.
[Accepted as a DR at the March, 2024 meeting.]
(From submission #490.)
Parameter objects are not temporary objects, according to 6.7.7 [class.temporary] paragraph 1. An exception hinting at this in 6.7.7 [class.temporary] paragraph 7 should be removed.
Proposed resolution (approved by CWG 2024-02-02):
Change in 6.7.7 [class.temporary] paragraph 7 as follows:
The fourth context is when a temporary objectother than a function parameter objectis created in the for-range-initializer of a range-based for statement. If such a temporary object would otherwise be destroyed at the end of the for-range-initializer full-expression, the object persists for the lifetime of the reference initialized by the for-range-initializer.
[Accepted as a DR at the June, 2024 meeting.]
(From submission #531.)
Paper P1286R2 (Contra CWG DR1778) allowed trivial potentially-throwing special member functions. Whether such a trivial special member function is actually invoked is thus observable via the noexcept operator. Issue 2820 clarified the situation for value-initialization and removed a special case for a trivial default constructor.
Subclause 6.7.7 [class.temporary] paragraph 4 appears to normatively avoid invoking a trivial constructor or destructor, something best left to the as-if rule. There is implementation divergence for this example:
struct C { C() = default; ~C() noexcept(false) = default; }; static_assert(noexcept(C()));
A related question arises from the introduction of temporaries for function parameters and return values in 6.7.7 [class.temporary] paragraph 3. However, this situation can never result in actually throwing an exception during forward evolution: when the constructor becomes non-trivial, the permission to create a temporary object evaporates.
Proposed resolution (approved by CWG 2024-05-31):
Change in 6.7.7 [class.temporary] paragraph 3 as follows:
When an object of class type X is passed to or returned from a potentially-evaluated function call, if X has at least one eligible copy or move constructor (11.4.4 [special]), each such constructor is trivial, and the destructor of X is either trivial or deleted, implementations are permitted to create a temporary object to hold the function parameter or result object. The temporary object is constructed from the function argument or return value, respectively, and the function's parameter or return object is initialized as if by using the eligible trivial constructor to copy the temporary (even if that constructor is inaccessible or would not be selected by overload resolution to perform a copy or move of the object).
Change in 6.7.7 [class.temporary] paragraph 4 as follows:
When an implementation introduces a temporary object of a class that has a non-trivial constructor (11.4.5.2 [class.default.ctor], 11.4.5.3 [class.copy.ctor]), it shall ensure that a constructor is called for the temporary object. Similarly, the destructor shall be called for a temporary with a non-trivial destructor (11.4.7 [class.dtor]).Temporary objects are destroyed as the last step in evaluating the full-expression (6.9.1 [intro.execution]) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception. The value computations and side effects of destroying a temporary object are associated only with the full-expression, not with any specific subexpression.
[Accepted as a DR at the June, 2023 meeting.]
6.7.2 [intro.object] clearly implies that bit-fields are objects; paragraphs 8-9 contain phrases like “unless an object is a bit-field...” and “a non-bit-field subobject”. However, the definition of “object representation” in 6.8.1 [basic.types.general] paragraph 4 is,
The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T).
and thus fails to address bit-fields, which are not necessarily composed of a sequence of complete bytes.
The C Standard (6.2.6.1 paragraph 4) says,
Values stored in bit-fields consist of m bits, where m is the size specified for the bit-field. The object representation is the set of m bits the bit-field comprises in the addressable storage unit holding it.
Presumably similar wording could be adopted for C++.
Proposed resolution (approved by CWG 2023-01-06) [SUPERSEDED]:
Change in 6.8.1 [basic.types.general] paragraph 4 as follows:
The object representation ofan object ofa type T is the sequence of N unsigned char objects taken up bythea non-bit-field complete object of type T, where N equals sizeof(T). The value representation ofan object ofa type T is the set of bits in the object representation of T that participate in representing a value of type T. The object and value representation of a non-bit-field complete object of type T are the bytes and bits, respectively, of the object corresponding to the object and value representation of its type. The object representation of a bit-field object is the sequence of N bits taken up by the object, where N is the width of the bit-field (11.4.10 [class.bit]). The value representation of a bit-field object is the set of bits in the object representation that participate in representing its value. Bits in the object representation of a type or object that are not part of the value representation are padding bits. For trivially copyable 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. [ Footnote: ... ]
CWG 2023-02-06
Additional drafting is needed to constrain the definition to complete object types.
Proposed resolution (approved for C++26 by CWG 2023-02-06):
Change in 6.8.1 [basic.types.general] paragraph 4 as follows:
The object representation ofan object ofa complete object type T is the sequence of N unsigned char objects taken up bythea non-bit-field complete object of type T, where N equals sizeof(T). The value representation ofan object ofa type T is the set of bits in the object representation of T that participate in representing a value of type T. The object and value representation of a non-bit-field complete object of type T are the bytes and bits, respectively, of the object corresponding to the object and value representation of its type. The object representation of a bit-field object is the sequence of N bits taken up by the object, where N is the width of the bit-field (11.4.10 [class.bit]). The value representation of a bit-field object is the set of bits in the object representation that participate in representing its value. Bits in the object representation of a type or object that are not part of the value representation are padding bits. For trivially copyable 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. [ Footnote: ... ]
[Accepted as a DR at the March, 2024 meeting.]
It is unclear whether cv std::nullptr_t is a fundamental type, given that it is declared in a library header and cv-qualifications are not mentioned in 6.8.2 [basic.fundamental] paragraph 15.
Proposed resolution (approved by CWG 2023-12-01):
Change in 6.8.2 [basic.fundamental] paragraph 15 as follows:
The types denoted by cv std::nullptr_t are distinct types. A value of type std::nullptr_t is a null pointer constant (7.3.12 [conv.ptr]). Such values participate in the pointer and the pointer-to-member conversions (7.3.12 [conv.ptr], 7.3.13 [conv.mem]). sizeof(std::nullptr_t) shall be equal to sizeof(void*).The types described in this subclause are called fundamental types. [Note 11: Even if the implementation defines two or more fundamental types to have the same value representation, they are nevertheless different types. —end note]
[Accepted as a DR at the June, 2023 meeting.]
The range of representable values is defined for integer types, but not for floating-point types. This term is used in 5.13.4 [lex.fcon] paragraph 3 as well as in the library, e.g. in 28.2.3 [charconv.from.chars] and 28.3.4.3.2.3 [facet.num.get.virtuals].
The C standard contains a suitable definition that we should inherit.
Proposed resolution (approved by CWG 2023-05-12):
Add a new paragraph after 6.8.2 [basic.fundamental] paragraph 12 as follows:
... Except as specified in 6.8.3 [basic.extended.fp], the object and value representations and accuracy of operations of floating-point types are implementation-defined.
The minimum range of representable values for a floating-point type is the most negative finite floating-point number representable in that type through the most positive finite floating-point number representable in that type. In addition, if negative infinity is representable in a type, the range of that type is extended to all negative real numbers; likewise, if positive infinity is representable in a type, the range of that type is extended to all positive real numbers. [ Note: Since negative and positive infinity are representable in ISO/IEC/IEEE 60559 formats, all real numbers lie within the range of representable values of a floating-point type adhering to ISO/IEC/IEEE 60559. ]
[Accepted as a DR at the June, 2024 meeting.]
Consider:
auto f(long double x, std::float64_t y) { return x + y; }
What is the return type of f?
Suppose an implementation uses the same size and semantics for all of double, long double, and std::float64_t. C23 prefers the IEEE interchange type (i.e. std::float64_t) over long double and double. In contrast, C++ chooses long double, which has a higher rank than double, but std::float64_t is specified to have the same rank as double.
This outcome conflicts with the documented goal of P1467 that C++ and C conversion rules be aligned.
Suggested resolution [SUPERSEDED]:
Change in 6.8.6 [conv.rank] bullet 2.5 as follows:
- ...
- An extended floating-point type with the same set of values as more than one cv-unqualified standard floating-point type has a rank equal to the highest rank
of doubleamong such types.
Additional notes (December, 2023)
The suggested resolution would make a conversion from std::float64_t to double not implicit, which is not desirable.
David Olsen, one of the authors of P1467, asserts that the deviation from C is intentional, and was discussed with the C floating-point study group.
Forwarding to EWG via paper issue 1699 to confirm that the deviation from C is intentional and thus an Annex C entry should be created.
EWG 2024-03-18
This issue should be closed as NAD.
Possible resolution [SUPERSEDED]:
Add a new paragraph in C.7.3 [diff.basic] as follows:
Affected subclause: 6.8.6 [conv.rank]
Change: Conversion rank of same-sized long double vs. std::float64_t
Rationale: Provide implicit conversion from std::float64_t to double.
Effect on original feature: Change of semantically well-defined feature.
Difficulty of converting: Trivial: explicitly convert to the desired type.
How widely used: Rarely.
CWG 2024-04-19
The name std::float64_t is not valid C. CWG resolved to adding a note to 6.8.6 [conv.rank] bullet 2.5 instead.
Proposed resolution (approved by CWG 2024-06-26):
Change in 6.8.6 [conv.rank] bullet 2.5 as follows:
- ...
- An extended floating-point type with the same set of values as more than one cv-unqualified standard floating-point type has a rank equal to the rank of double. [ Note: The treatment of std::float64_t differs from that of the analoguous _Float64 in C, for example on platforms where all of long double, double, and std::float64_t have the same set of values (see ISO/IEC 9899:2024 H.4.2). -- end note ]
[Accepted as a DR at the March, 2024 meeting.]
Subclause 6.9.3.1 [basic.start.main] paragraph 3 specifies:
The function main shall not be used within a program. ...
It is unclear what "use" means. N3214 excluded this appearance from the clarifications of "use" that were turned into "odr-use". For example, it is unclear whether decltype(main) is allowed or not.
CWG 2023-12-01
CWG favored to ban any mention of main.
Proposed resolution (approved by CWG 2023-12-15):
Change in 6.9.3.1 [basic.start.main] paragraph 3 as follows:
The function main shall not beused within a programnamed by an expression. ...
[Accepted as a DR at the November, 2024 meeting.]
(From submission #548.)
Subclause 7.2.1 [basic.lval] paragraph 11 specifies:
... If a program attempts to access (3.1 [defns.access]) the stored value of an object through a glvalue through which it is not type-accessible, the behavior is undefined. ...
Thus, access (read or write) to an int object using an lvalue of type unsigned int is valid. However, 7.3.2 [conv.lval] bullet 3.4 does not specify the resulting value when, for example, the object contains the value -1:
- Otherwise, the object indicated by the glvalue is read (3.1 [defns.access]), and the value contained in the object is the prvalue result. ...
Similarly, 7.6.19 [expr.assign] paragraph 2 is silent for the assignment case:
In simple assignment (=), the object referred to by the left operand is modified (3.1 [defns.access]) by replacing its value with the result of the right operand.
Any concerns about accesses to the object representation are handled in the context of P1839.
Proposed resolution (approved by CWG 2024-09-13):
Change in 7.3.2 [conv.lval] bullet 3.4 as follows:
- Otherwise, the object indicated by the glvalue is read (3.1 [defns.access])
, and the value contained in the object is the prvalue result. Let V be the value contained in the object. If T is an integer type, the prvalue result is the value of type T congruent (6.8.2 [basic.fundamental]) to V, and V otherwise. ...
Change in 7.6.1.6 [expr.post.incr] paragraph 1 as follows:
The value of a postfix ++ expression is the valueofobtained by applying the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) to its operand. [Note 1: The value obtained is a copy of the original value. —end note] ...
Change in 7.6.19 [expr.assign] paragraph 2 as follows:
In simple assignment (=), let V be the result of the right operand; the object referred to by the left operand is modified (3.1 [defns.access]) by replacing its value withthe result of the right operandV or, if the object is of integer type, with the value congruent (6.8.2 [basic.fundamental]) to V.
[Accepted as a DR at the November, 2024 meeting.]
Issue 453 clarified that there are no empty lvalues, and undefined behavior ensues when trying to create one. However, the wording now does not specify the behavior of dangling references, where the storage of the referenced object has gone away.
Proposed resolution (approved by CWG 2024-11-08):
Change in 6.8.2 [basic.fundamental] paragraph 4 as follows:
A pointer value P is valid in the context of an evaluation E if P is a pointer to function or a null pointer value, or if it is a pointer to or past the end of an object O and E happens before the end of the duration of the region of storage for O. If a pointer value P is used in an evaluation E and P is not valid in the context of E, then ...
Change in 7.2.2 [expr.type] paragraph 1 as follows:
If an expression initially has the type “reference to T” (9.3.4.3 [dcl.ref], 9.5.4 [dcl.init.ref]), the type is adjusted to T prior to any further analysis; the value category of the expression is not altered.The expression designatesLet X be the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression. If a pointer to X would be valid in the context of the evalution of the expression (6.8.2 [basic.fundamental]), the result designates X; otherwise, the behavior is undefined.
[Accepted as a DR at the November, 2024 meeting.]
(From editorial issue #7051.)
Consider:
static_assert(sizeof(bool) == 1); // assumption for the example bool f() { char c = 2; bool b = true; memcpy(&b, &c, 1); // #1 return b; // #2 }
Assuming that false and true are represented as 0 and 1, the value representation of b now has a bad value. This should, but does not, result in undefined behavior during the lvalue-to-rvalue conversion at #2.
Proposed resolution (approved by CWG 2024-08-16):
Change in 7.3.2 [conv.lval] paragraph 3 as follows:
The result of the conversion is determined according to the following rules:
- If T is cv std::nullptr_t, the result is a null pointer constant (7.3.12 [conv.ptr]). [Note 1: Since the conversion does not access the object to which the glvalue refers, there is no side effect even if T is volatile-qualified (6.9.1 [intro.execution]), and the glvalue can refer to an inactive member of a union (11.5 [class.union]). —end note]
- Otherwise, if T has a class type, the conversion copy-initializes the result object from the glvalue.
- Otherwise, if the object to which the glvalue refers contains an invalid pointer value (6.7.6.5.3 [basic.stc.dynamic.deallocation]), the behavior is implementation-defined.
- Otherwise, if the bits in the value representation of the object to which the glvalue refers are not valid for the object's type, the behavior is undefined. [ Example:
bool f() { bool b = true; char c = 42; memcpy(&b, &c, 1); return b; // undefined behavior if 42 is not a valid value representation for bool }
-- end example ]- Otherwise, the object indicated by the glvalue is read (3.1 [defns.access]), and the value contained in the object is the prvalue result.
If the result is an erroneous value (6.7.5 [basic.indet]) and the bits in the value representation are not valid for the object's type, the behavior is undefined.
[Accepted as a DR at the June, 2023 meeting.]
According to 7.3.7 [conv.prom] paragraph 5,
A prvalue for an integral bit-field (11.4.10 [class.bit]) can be converted to a prvalue of type int if int can represent all the values of the bit-field; otherwise, it can be converted to unsigned int if unsigned int can represent all the values of the bit-field. If the bit-field is larger yet, no integral promotion applies to it. If the bit-field has an enumerated type, it is treated as any other value of that type for promotion purposes.
This description has several problems. First, the “bit-field” semantic property only makes sense for glvalue expressions, so it's unclear why these rules are described as applying to a prvalue. Perhaps this should be rephrased as something like “An expression that was a bit-field glvalue prior to the application of the lvalue-to-rvalue conversion”?
Second, suppose that char32_t is wider than int. Per paragraph 2, a char32_t prvalue promotes to unsigned long (because unsigned long is necessarily at least 32 bits wide). But per paragraph 5, a char32_t : 32 bitfield does not promote. This seems inconsistent.
Finally, it is not clear that the usual integral promotions are not applied to bit-fields. This should be made explicit.
Proposed resolution (approved by CWG 2023-02-07):
Insert a paragraph before 7.3.7 [conv.prom] paragraph 1 as follows:
For the purposes of 7.3.7 [conv.prom], a converted bit-field is a prvalue that is the result of an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) applied to a bit-field (11.4.10 [class.bit]).
Change in 7.3.7 [conv.prom] paragraph 1 as follows:
A prvalueofthat is not a converted bit-field and has an integer type other than bool, char8_t, char16_t, char32_t, or wchar_t whose integer conversion rank (6.8.6 [conv.rank]) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.
Change in 7.3.7 [conv.prom] paragraph 4 as follows:
A prvalue of an unscoped enumeration type whose underlying type is fixed (9.8.1 [dcl.enum]) can be converted to a prvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, a prvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to a prvalue of the promoted underlying type. [ Note: A converted bit-field of enumeration type is treated as any other value of that type for promotion purposes. -- end note ]
Change in 7.3.7 [conv.prom] paragraph 5 as follows:
Aprvalue for an integral bit-field (11.4.10 [class.bit])converted bit-field of integral type can be converted to a prvalue of type int if int can represent all the values of the bit-field; otherwise, it can be converted to unsigned int if unsigned int can represent all the values of the bit-field.If the bit-field is larger yet, no integral promotion applies to it. If the bit-field has enumeration type, it is treated as any other value of that type for promotion purposes.
Move 7.3.7 [conv.prom] paragraph 2 after paragraph 5 and change as follows:
A prvalue of type char8_t, char16_t, char32_t, or wchar_t (6.8.2 [basic.fundamental]) (including a converted bit-field that was not already promoted to int or unsigned int according to the rules above) can be converted to a prvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long int, unsigned long int, long long int,orunsigned long long int. If none of the types in that list can represent all the values of its underlying type, a prvalue of type char8_t, char16_t, char32_t, or wchar_t can be converted to a prvalue of, or its underlying type.
[Accepted as a DR at the June, 2023 meeting.]
The descriptions of explicit (7.6.1.9 [expr.static.cast] paragraph 9) and implicit (7.3.13 [conv.mem] paragraph 2) pointer-to-member conversions differ in two significant ways:
(This situation cannot arise in an implicit pointer-to-member conversion where the source value is something like &X::f, since you can only implicitly convert from pointer-to-base-member to pointer-to-derived-member. However, if the source value is the result of an explicit "up-cast," the target type of the conversion might still not contain the member referred to by the source value.)
The first difference seems like an oversight. It is not clear whether the latter difference is intentional or not.
(See also issue 794.)
CWG 2022-11-09
The second concern is NAD; implicit conversions allow chaining a pointer-to-member conversion with a qualification conversion.
Proposed resolution (approved by CWG 2023-02-06):
Change in 7.3.13 [conv.mem] paragraph 2 as follows:
A prvalue of type “pointer to member of B of type cv T”, where B is a class type, can be converted to a prvalue of type “pointer to member of D of type cv T”, where D is a complete class derived (11.7 [class.derived]) from B. If B is an inaccessible (11.8 [class.access]), ambiguous (6.5.2 [class.member.lookup]), or virtual (11.7.2 [class.mi]) base class of D, or a base class of a virtual base class of D, a program that necessitates this conversion is ill-formed. If class D does not contain the original member and is not a base class of the class containing the original member, the behavior is undefined. Otherwise,Thethe result of the conversion refers to the same member as the pointer to member before the conversion took place, but it refers to the base class member as if it were a member of the derived class. The result refers to the member in D's instance of B. Since the result has type “pointer to member of D of type cv T”, indirection through it with a D object is valid. The result is the same as if indirecting through the pointer to member of B with the B subobject of D. The null member pointer value is converted to the null member pointer value of the destination type. [ Footnote: ... ]
Change in 7.6.1.9 [expr.static.cast] paragraph 13 as follows:
... If class B contains the original member, or is a baseor derivedclass of the class containing the original member, the resulting pointer to member points to the original member. ...
[Accepted as a DR at the June, 2024 meeting.]
(From submission #533.)
Subclause 7.4 [expr.arith.conv] bullet 1.4.1 specifies:
- If both operands have the same type, no further conversion is needed.
What does "needed" mean?
Proposed resolution (approved by CWG 2024-05-31):
Change in 7.4 [expr.arith.conv] bullet 1.4.1 as follows:
- If both operands have the same type, no further conversion is
neededperformed.
[Accepted as a DR at the June, 2024 meeting.]
(From submission #515.)
Consider:
struct A { static void f() { struct B { void *g() { return this; } }; } };
According to 7.5.3 [expr.prim.this] paragraph 3, this example is ill-formed, because this "appears within" the declaration of a static member function. The qualification "of the current class" can be read as attaching to explicit object member functions only.
Suggested resolution [SUPERSEDED]:
Change in 7.5.3 [expr.prim.this] paragraph 3 as follows:
If a declaration declares a member function or member function template of a class X, the expression this is a prvalue of type “pointer to cv-qualifier-seq X” wherever X is the current class between the optional cv-qualifier-seq and the end of the function-definition, member-declarator , or declarator.It shall not appear within theThe declarationof eitherthat determines the type of this shall declare neither a static member functionornor an explicit object member function of the current class (although its type and value category are defined within such member functions as they are within an implicit object member function).
CWG 2024-05-03
CWG preferred a smaller surgery to avoid the English parsing issue.
Proposed resolution (approved by CWG 2024-05-17):
Change in 7.5.3 [expr.prim.this] paragraph 3 as follows:
If a declaration declares a member function or member function template of a class X, the expression this is a prvalue of type “pointer to cv-qualifier-seq X” wherever X is the current class between the optional cv-qualifier-seq and the end of the function-definition, member-declarator, or declarator. It shall not appear within the declaration ofeithera staticmember functionoranexplicit object member function of the current class (although its type and value category are defined within such member functions as they are within an implicit object member function).
[Accepted as a DR at the June, 2023 meeting.]
According to 7.5.6.2 [expr.prim.lambda.closure] paragraph 3,
The closure type for a lambda-expression has a public inline function call operator (for a non-generic lambda) or function call operator template (for a generic lambda) (12.4.4 [over.call]) whose parameters and return type are described by the lambda-expression's parameter-declaration-clause and trailing-return-type respectively, and whose template-parameter-list consists of the specified template-parameter-list, if any.
This is insufficiently precise because the trailing-return-type might itself contain a parameter-declaration-clause.
Suggested resolution [SUPERSEDED]:
Change in 7.5.6.1 [expr.prim.lambda.general] paragraph 5 as follows:
If a lambda-declarator does notinclude astart with a parenthesized parameter-declaration-clause, it is as if () were inserted at the start of the lambda-declarator. A lambda-expression's parameter-declaration-clause is the (possibly empty) parameter-declaration-clause of the lambda-expression's lambda-declarator. If the lambda-declarator does not include a trailing-return-type, the lambda return type is auto, which is deduced from return statements as described in 9.2.9.7 [dcl.spec.auto].
Proposed resolution (approved by CWG 2023-02-06):
Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 3 as follows:
The closure type for a lambda-expression has a public inline function call operator (for a non-generic lambda) or function call operator template (for a generic lambda) (12.4.4 [over.call]) whose parameters and return type aredescribed bythose of the lambda-expression's parameter-declaration-clause and trailing-return-type respectively, and whose template-parameter-list consists of the specified template-parameter-list, if any.
Change in 7.5.6.1 [expr.prim.lambda.general] paragraph 5 as follows:
If a lambda-declarator does not include a parameter-declaration-clause, it is as if () were inserted at the start of the lambda-declarator.A lambda-expression's parameter-declaration-clause is the parameter-declaration-clause of the lambda-expression's lambda-declarator, if any, or empty otherwise. If the lambda-declarator does not include a trailing-return-type, the lambda return type is auto, which is deduced from return statements as described in 9.2.9.7 [dcl.spec.auto].
[Accepted as a DR at the June, 2023 meeting.]
Consider:
template <auto V> void foo() {} void bar() { foo<[i = 3] { return i; }>(); }
It is unclear whether the data members of a closure type are public or private. This makes a difference, since it affects whether a closure type is a structural type or not (13.2 [temp.param] paragraph 7:
A structural type is one of the following:
- a scalar type, or
- an lvalue reference type, or
- a literal class type with the following properties:
- all base classes and non-static data members are public and non-mutable and
- the types of all bases classes and non-static data members are structural types or (possibly multi-dimensional) array thereof.
Proposed resolution (approved by CWG 2023-03-30):
Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 2 as follows:
... The closure type is not an aggregate type (9.5.2 [dcl.init.aggr]) and not a structural type (13.2 [temp.param]). ...
[Accepted as a DR at the June, 2024 meeting.]
P0847R7 (Deducing this) (approved October, 2021) added explicit-object member functions. Consider:
struct C { C(auto) { } }; void foo() { auto l = [](this C) { return 1; }; void (*fp)(C) = l; fp(1); // same effect as decltype(l){}() or decltype(l){}(1) ? }
Subclause 7.5.6.2 [expr.prim.lambda.closure] paragraph 8 does not address explicit object member functions:
The closure type for a non-generic lambda-expression with no lambda-capture whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage (9.12 [dcl.link]) having the same parameter and return types as the closure type's function call operator. The conversion is to “pointer to noexcept function” if the function call operator has a non-throwing exception specification. The value returned by this conversion function is the address of a function F that, when invoked, has the same effect as invoking the closure type's function call operator on a default-constructed instance of the closure type. F is a constexpr function if...
Suggested resolution [SUPERSEDED]:
Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 8 as follows:
... The value returned by this conversion function isF is a constexpr function if... is an immediate function.
- for a lambda-expression whose parameter-declaration-clause has an explicit object parameter, the address of the function call operator (7.6.2.2 [expr.unary.op];
- otherwise, the address of a function F that, when invoked, has the same effect as invoking the closure type's function call operator on a default-constructed instance of the closure type.
[ Example:
struct C { C(auto) { } }; void foo() { auto a = [](C) { return 0; }; int (*fp)(C) = a; // OK fp(1); // same effect as decltype(a){}(1) auto b = [](this C) { return 1; }; fp = b; // OK fp(1); // same effect as (&decltype(b)::operator())(1) }-- end example ]
Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 11 as follows:
The value returned by any given specialization of this conversion function template isF is a constexpr function if...
- for a lambda-expression whose parameter-declaration-clause has an explicit object parameter, the address of the corresponding function call operator template specialization (7.6.2.2 [expr.unary.op]);
- otherwise, the address of a function F that, when invoked, has the same effect as invoking the generic lambda's corresponding function call operator template specialization on a default-constructed instance of the closure type.
CWG 2023-06-17
Requesting guidance from EWG with paper issue 1689.
Additional notes (October, 2023)
Additional examples demonstrating implementation divergence between clang and MSVC:
struct Any { Any(auto) {} }; auto x = [](this auto self, int x) { return x; }; auto y = [](this Any self, int x) { return x; }; auto z = [](this int (*self)(int), int x) { return x; }; int main() { x(1); y(1); z(1); int (*px)(int) = +x; // MSVC int (*py1)(int) = +y; // MSVC int (*py2)(Any, int) = +y; // Clang int (*pz1)(int) = +z; // MSVC int (*pz2)(int (*)(int), int) = +z; // Clang }
Additional notes (November, 2023)
Additional example:
auto c2 = [](this auto self) { return sizeof(self); }; struct Derived2 : decltype(c) { int value; } d2; struct Derived3 : decltype(c) { int value[10]; } d3;
For MSVC, d2() == 4 and d3() == 40, but +d2 and +d3 both point to functions returning 1.
EWG 2023-11-07
Move forward with option 1 "punt" from D3031 for C++26. A future paper can explore other solutions.
Proposed resolution (approved by CWG 2024-04-19):
Change the example in 7.5.6.1 [expr.prim.lambda.general] paragraph 6 as follows:
int i = [](int i, auto a) { return i; }(3, 4); // OK, a generic lambda int j = []<class T>(T t, int i) { return i; }(3, 4); // OK, a generic lambdaauto x = [](int i, auto a) { return i; }; // OK, a generic lambda auto y = [](this auto self, int i) { return i; }; // OK, a generic lambda auto z = []<class T>(int i) { return i; }; // OK, a generic lambda
Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 9 as follows:
The closure type for a non-generic lambda-expression with no lambda-capture and no explicit object parameter (9.3.4.6 [dcl.fct]) whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage (9.12 [dcl.link]) having the same parameter and return types as the closure type's function call operator. ...
Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 10 as follows:
For a generic lambda with no lambda-capture and no explicit object parameter (9.3.4.6 [dcl.fct]), the closure type has a conversion function template to pointer to function. ...
CWG 2023-11-09
Keeping in review status in anticipation of a paper proposing reasonable semantics for the function pointer conversions.
EWG 2024-03-18
Progress with option #1 of P3031R0, affirming the direction of the proposed resolution.
[Accepted as a DR at the March, 2024 meeting.]
Issue 2542 (approved in June, 2023) made all closure types not be structural types, i.e. unsuitable for use as non-type template parameters. This causes an inconsistency with the treatment of the pointer-to-function conversion for closure types with no captures:
template <auto V> void foo() {} void bar() { foo<[i = 3] { return i; }>(); // #1: error foo<[]{}>(); // #2: error foo<+[]{}>(); // #3: OK, a function pointer is a structural type }
Proposed resolution (approved by CWG 2024-02-02):
Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 3 as follows:
The closure type is not an aggregate type (9.5.2 [dcl.init.aggr])and not; it is a structural type (13.2 [temp.param]) if and only if the lambda has no lambda-capture. An implementation may define the closure type differently from ...
Change in 13.6 [temp.type] paragraph 2 as follows:
Two values are template-argument-equivalent if they are of the same type and
- ...
- they are of a closure type (7.5.6.2 [expr.prim.lambda.closure]), or
- they are of class type and their corresponding direct subobjects and reference members are template-argument-equivalent.
[Accepted as a DR at the June, 2024 meeting.]
Subclause 7.5.6.2 [expr.prim.lambda.closure] paragraph 5 restricts the type of an explicit object parameter of a lambda to the closure type or classes derived from the closure type. It neglects to consider ambiguous or private derivation scenarios.
Proposed resolution (approved by CWG 2024-06-26):
Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 5 as follows:
Given a lambda with a lambda-capture, the type of the explicit object parameter, if any, of the lambda's function call operator (possibly instantiated from a function call operator template) shall be either:
- the closure type
- a class type publicly and unambiguously derived from the closure type, or
- a reference to a possibly cv-qualified such type.
Add a new bullet after 13.10.3.1 [temp.deduct.general] bullet 11.10:
- ...
- Attempting to create a function type in which a parameter has a type of void, or in which the return type is a function type or array type.
- Attempting to give to an explicit object parameter of a lambda's function call operator a type not permitted for such (7.5.6.2 [expr.prim.lambda.closure]).
CWG 2024-06-26
The following example is not supported by the proposed resolution and remains ill-formed:
int main() { int x = 0; auto lambda = [x] (this auto self) { return x; }; using Lambda = decltype(lambda); struct D : private Lambda { D(Lambda l) : Lambda(l) {} using Lambda::operator(); friend Lambda; } d(lambda); d(); }
[Accepted as a DR at the March, 2024 meeting.]
Consider:
template<typename T>
requires requires (T p[10]) { (decltype(p))nullptr; }
int v = 42;
auto r = v<int>; // well-formed?
This example is only well-formed if the type of the parameter p is adjusted to T*, but the provisions in 9.3.4.6 [dcl.fct] paragraph 5 cover function parameters only.
One option is to specify application of the same adjustments as for function parameters. Another option is to specify rules that arguably are more useful in a requires-expression.
Proposed resolution (approved by CWG 2023-11-07):
Change in 7.5.8.1 [expr.prim.req.general] paragraph 3 as follows:
A requires-expression may introduce local parameters using a parameter-declaration-clause(9.3.4.6 [dcl.fct]). A local parameter of a requires-expression shall not have a default argument. The type of such a parameter is determined as specified for a function parameter in 9.3.4.6 [dcl.fct]. These parameters have no linkage, storage, or lifetime; they are only used as notation for the purpose of defining requirements. The parameter-declaration-clause of a requirement-parameter-list shall not terminate with an ellipsis.
[Example 2:template<typename T> concept C = requires(T t, ...) { // error: terminates with an ellipsis t; }; template<typename T> concept C2 = requires(T p[2]) { (decltype(p))nullptr; // OK, p has type "pointer to T" };—end example]
CWG 2023-06-17
There are arguments in favor of both options. Forwarded to EWG with paper issue 1582.
EWG 2023-11-07
Accept the proposed resolution and forward to CWG for inclusion in C++26.
[Accepted as a DR at the November, 2024 meeting.]
(From submission #562.)
Subclause 7.5.8.1 [expr.prim.req.general] paragraph 2 specifies:
A requires-expression is a prvalue of type bool whose value is described below. Expressions appearing within a requirement-body are unevaluated operands (7.2.3 [expr.context]).
A constant-expression used as a non-type template argument "appearing within" the requirement-body should not be considered an "unevaluated operand". Similarly, bodies of lambda-expressions should not be in focus of "appearing within".
Proposed resolution (approved by CWG 2024-10-11):
Change in 7.2.3 [expr.context] paragraph 1 as follows:
In some contexts, unevaluated operands appear (7.5.8 [expr.prim.req]7.5.8.2 [expr.prim.req.simple], 7.5.8.4 [expr.prim.req.compound], 7.6.1.8 [expr.typeid], 7.6.2.5 [expr.sizeof], 7.6.2.7 [expr.unary.noexcept], 9.2.9.6 [dcl.type.decltype], 13.1 [temp.pre], 13.7.9 [temp.concept]). An unevaluated operand is not evaluated.
Change in 7.5.8.1 [expr.prim.req.general] paragraph 2 as follows:
A requires-expression is a prvalue of type bool whose value is described below.Expressions appearing within a requirement-body are unevaluated operands (7.2.3 [expr.context]).
Change in 7.5.8.2 [expr.prim.req.simple] paragraph 1 as follows:
A simple-requirement asserts the validity of an expression. The expression is an unevaluated operand. [Note 1: The enclosing requires-expression will evaluate to false if substitution of template arguments into the expression fails.The expression is an unevaluated operand (7.2.3 [expr.context]).—end note] ...
Change in 7.5.8.4 [expr.prim.req.compound] paragraph 1 as follows:
A compound-requirement asserts properties of the expression E. The expression is an unevaluated operand. Substitution of template arguments (if any) and verification of semantic properties proceed in the following order:
[Accepted as a DR at the June, 2023 meeting.]
Although the note in 7.2.1 [basic.lval] paragraph 1 states that
The discussion of each built-in operator in Clause 7 [expr] indicates the category of the value it yields and the value categories of the operands it expects
in fact, many of the operators that take prvalue operands do not make that requirement explicit. Possible approaches to address this failure could be a blanket statement that an operand whose value category is not stated is assumed to be a prvalue; adding prvalue requirements to each operand description for which it is missing; or changing the description of the usual arithmetic conversions to state that they imply the lvalue-to-rvalue conversion, which would cover the majority of the omissions.
(See also issue 1685, which deals with an inaccurately-specified value category.)
Proposed resolution (approved by CWG 2023-04-28):
Change in 7.2.1 [basic.lval] paragraph 6 as follows:
Whenever a glvalue appears as an operand of an operator thatexpectsrequires a prvalue for that operand, the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), or function-to-pointer (7.3.4 [conv.func]) standard conversions are applied to convert the expression to a prvalue. ...
Change in 7.3.1 [conv.general] paragraph 1 as follows:
... A standard conversion sequence will be applied to an expression if necessary to convert it to an expression having a required destination type and value category. ...
Add to the bulleted list in 7.4 [expr.arith.conv] paragraph 1 as follows:
... This pattern is called the usual arithmetic conversions, which are defined as follows:
- The lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is applied to each operand and the resulting prvalues are used in place of the original operands for the remainder of this section.
- If either operand is of scoped enumeration type (9.8.1 [dcl.enum]), no conversions are performed; if the other operand does not have the same type, the expression is ill-formed.
- ...
Change in 7.6.1.3 [expr.call] paragraph 1 as follows:
... For a call to a non-member function or to a static member function, the postfix expression shall be eitherbean lvalue that refers to a function (in which case the function-to-pointer standard conversion (7.3.4 [conv.func]) is suppressed on the postfix expression), orhavea prvalue of function pointer type.
Change in 7.6.2.2 [expr.unary.op] paragraph 7 as follows:
The operand of the unary + operator shallhavebe a prvalue of arithmetic, unscoped enumeration, or pointer type and the result is the value of the argument. Integral promotion is performed on integral or enumeration operands. ...
Change in 7.6.2.2 [expr.unary.op] paragraph 8 as follows:
The operand of the unary - operator shallhavebe a prvalue of arithmetic or unscoped enumeration type and the result is the negative of its operand. Integral promotion is performed on integral or enumeration operands. ...
Change in 7.6.2.2 [expr.unary.op] paragraph 10 as follows:
The operand of the ~ operator shallhavebe a prvalue of integral or unscoped enumeration type. Integral promotions are performed. ...
Change in 7.6.2.9 [expr.delete] paragraph 1 as follows:
...The operand shall be of pointer to object type or of class type.If the operand is of class type,the operandit is contextually implicitly converted (7.3 [conv]) to a pointer to object type. [ Footnote: ... ] Otherwise, it shall be a prvalue of pointer to object type. The delete-expression has type void.
Change in 7.6.4 [expr.mptr.oper] paragraph 2 and 3 as follows:
The binary operator .* binds its second operand, which shall be a prvalue of type “pointer to member of T” to its first operand, which shall be ...
The binary operator ->* binds its second operand, which shall be a prvalue of type “pointer to member of T” to its first operand, which shall be ...
Change in 7.6.6 [expr.add] paragraph 1 as follows:
The additive operators + and - group left-to-right.TheEach operand shall be a prvalue. If both operands have arithmetic or unscoped enumeration type, the usual arithmetic conversions (7.4 [expr.arith.conv]) are performedfor operands of arithmetic or enumeration type. Otherwise, if one operand has arithmetic or unscoped enumeration type, integral promotion is applied (7.3.7 [conv.prom]) to that operand. A converted or promoted operand is used in place of the corresponding original operand for the remainder of this section. ... For addition, either both operands shall have arithmeticor unscoped enumerationtype, or one operand shall be a pointer to a completely-defined object type and the other shall have integralor unscoped enumerationtype.
Change in 7.6.6 [expr.add] paragraph 2 as follows:
For subtraction, one of the following shall hold:
- both operands have arithmetic
or unscoped enumerationtype; or- both operands are pointers to cv-qualified or cv-unqualified versions of the same completely-defined object type; or
- the left operand is a pointer to a completely-defined object type and the right operand has integral
or unscoped enumerationtype.
Change in 7.6.7 [expr.shift] paragraph 1 as follows:
... The operands shall be prvalues of integral or unscoped enumeration type and integral promotions are performed. ...
Change in 9.5.1 [dcl.init.general] bullet 16.9 as follows:
- ...
- Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression. A standard conversion sequence (7.3 [conv])
will beis used, if necessary,to convert the initializer expression to a prvalue of the cv-unqualified version of the destination type; no user-defined conversions are considered. ...- ...
[Accepted as a DR at the November, 2024 meeting.]
P0135R1 (Wording for guaranteed copy elision through simplified value categories) removes complete type requirements from 7.6.1.3 [expr.call] (under the assumption that subclause 9.5 [dcl.init] has them; apparently it does not) and from 7.6.1.8 [expr.typeid] paragraph 3. These both appear to be bad changes and should presumably be reverted.
Additional notes (October, 2024)
An almost-editorial change (approved by CWG 2021-08-24) restored a consistent complete-type requirement for typeid; see cplusplus/draft#4827.
Proposed resolution (approved by CWG 2024-10-25):
Change in 7.6.1.3 [expr.call] paragraph 13 as follows:
A function call is an lvalue if the result type is an lvalue reference type or an rvalue reference to function type, an xvalue if the result type is an rvalue reference to object type, and a prvalue otherwise. If it is a non-void prvalue, the type of the function call expression shall be complete, except as specified in 9.2.9.6 [dcl.type.decltype].
[Accepted as a DR at the June, 2023 meeting.]
Subclause 7.6.1.3 [expr.call] paragraph 6 specifies:
... The initialization and destruction of each parameter occurs within the context of the calling function. [Example 2: The access of the constructor, conversion functions or destructor is checked at the point of call in the calling function. If a constructor or destructor for a function parameter throws an exception, the search for a handler starts in the calling function; in particular, if the function called has a function-try-block (14.1 [except.pre]) with a handler that can handle the exception, this handler is not considered. —end example]
However, there is no calling function in the case where a function call appears in the initializer of a namespace-scope variable. Likewise, some constant expressions appearing in a type-id do not have calling functions, either. For example:
class C {
private:
constexpr int C(int) {}
friend void foo(int (*a)[1]) noexcept;
};
constexpr int bar(C) { return 1; }
void foo(int (&a)[bar(1)]) noexcept(bar(2) > 0); // presumably OK because of friendship
Proposed resolution (approved by CWG 2023-04-28):
Change in 7.6.1.3 [expr.call] paragraph 6 as follows:
... The initialization and destruction of each parameter occurs within the context of thecalling functionfull-expression (6.9.1 [intro.execution]) where the function call appears. [Example 2: The access (11.8.1 [class.access.general]) of the constructor, conversion functions, or destructor is checked at the point of callin the calling function. If a constructor or destructor for a function parameter throws an exception,the search for a handler starts in the calling function; in particular, if the function called has aany function-try-block (14.1 [except.pre]) of the called function with a handler that can handle the exception, this handleris not considered. —end example]
[Accepted as a DR at the November, 2024 meeting.]
(From submission #536.)
For T{...}, the rule in 7.6.1.4 [expr.type.conv] paragraph 2 yields a prvalue of reference type if T is a reference type:
... Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized (9.4) with the initializer. ...
Also, it should be clarified that void(1, 2) and void{1} are ill-formed.
Possible resolution [SUPERSEDED]:
Change in 7.6.1.4 [expr.type.conv] paragraph 1 and 2 as follows:
A simple-type-specifier (9.2.9.3 [dcl.type.simple]) or typename-specifier (13.8 [temp.res]) followed by a parenthesized optional expression-list or by a braced-init-list (the initializer) constructs a value of the specified type given the initializer. If the type is a placeholder for a deduced class type, it is replaced by the return type of the function selected by overload resolution for class template deduction (12.2.2.9 [over.match.class.deduct]) for the remainder of this subclause. Otherwise, if the type contains a placeholder type, it is replaced by the type determined by placeholder type deduction (9.2.9.7.2 [dcl.type.auto.deduct]). Let T denote the resulting type. [ Example: ... ]
If the initializer is a parenthesized single expression, the type conversion expression is equivalent to the corresponding cast expression (7.6.3 [expr.cast]). Otherwise, if
the typeT is cv voidand, the initializerisshall be () or {} (after pack expansion, if any), and the expression is a prvalue of type void that performs no initialization. Otherwise, the expressionis a prvalue of the specified type whose result object is direct-initialized (9.5 [dcl.init]) with the initializerhas the same effect as direct-initializing an invented variable t of type T from the initializer and then using t as the result of the expression. The result is an lvalue if T is an lvalue reference type or an rvalue reference to function type, an xvalue if T is an rvalue reference to object type, and a prvalue otherwise. If the initializer is a parenthesized optional expression-list,the specified typeT shall not be an array type.
CWG 2024-06-14
The resolution above introduces an additional variable even for a prvalue, which defeats mandatory copy elision. Use the pattern from 7.6.1.9 [expr.static.cast] paragraph 4 instead.
Proposed resolution (approved by CWG 2024-10-25):
Change in 7.6.1.4 [expr.type.conv] paragraph 1 and 2 as follows, move the example to the bottom, and add bullets:
A simple-type-specifier (9.2.9.3 [dcl.type.simple]) or typename-specifier (13.8 [temp.res]) followed by a parenthesized optional expression-list or by a braced-init-list (the initializer) constructs a value of the specified type given the initializer. If the type is a placeholder for a deduced class type, it is replaced by the return type of the function selected by overload resolution for class template deduction (12.2.2.9 [over.match.class.deduct]) for the remainder of this subclause. Otherwise, if the type contains a placeholder type, it is replaced by the type determined by placeholder type deduction (9.2.9.7.2 [dcl.type.auto.deduct]). Let T denote the resulting type.
[ Example: ... ]Then:
If the initializer is a parenthesized optional expression-list,
- If the initializer is a parenthesized single expression, the type conversion expression is equivalent to the corresponding cast expression (7.6.3 [expr.cast]).
- Otherwise, if
the typeT is cv voidand, the initializerisshall be () or {} (after pack expansion, if any), and the expression is a prvalue of type void that performs no initialization.- Otherwise, if T is a reference type, the expression has the same effect as direct-initializing an invented variable t of type T from the initializer and then using t as the result of the expression; the result is an lvalue if T is an lvalue reference type or an rvalue reference to function type and an xvalue otherwise.
- Otherwise, the expression is a prvalue of
the specifiedtype T whose result object is direct-initialized (9.5 [dcl.init]) with the initializer.the specified typeT shall not be an array type.[Example: ... ]
[Accepted as a DR at the November, 2023 meeting.]
Consider:
struct A {
static void f();
static void f(int);
} x;
void (*p)() = x.f; // error
This is ill-formed as confirmed by issue 61. Various other changes (see issue 2241) have put the following example into the same category:
struct B {
static void f();
} y;
void (*q)() = y.f; // error
If this is the intended outcome (although major implementations disagree), then the rules in 7.6.1.5 [expr.ref] should be clarified accordingly.
Proposed resolution (approved by CWG 2023-06-13):
Change in 7.6.1.5 [expr.ref] bullet 6.3 as follows:
- ...
- If E2 is an overload set, the expression shall be the (possibly-parenthesized) left-hand operand of a member function call (7.6.1.3 [expr.call]), and function overload resolution (12.2 [over.match]) is used to select the function to which E2 refers. The type of E1.E2 is the type of E2 and E1.E2 refers to the function referred to by E2.
- If E2 refers to a static member function, E1.E2 is an lvalue.
- Otherwise (when E2 refers to a non-static member function), E1.E2 is a prvalue.
The expression can be used only as the left-hand operand of a member function call (11.4.2 [class.mfct]).[Note 5: Any redundant set of parentheses surrounding the expression is ignored (7.5.4 [expr.prim.paren]). —end note]- ...
This also addresses issue 1038.
[Accepted as a DR at the March, 2024 meeting.]
Consider:
struct C { static int foo; }; C* c = nullptr;
The behavior of (*c).foo is clearly undefined per 7.6.1.5 [expr.ref] paragraph 1:
The postfix expression before the dot or arrow is evaluated ...
However, the treatment of c->foo is less clear, because the transformation to the form (*(E1)).E2 occurs later.
Proposed resolution (approved by CWG 2023-12-15):
Move a part of 7.6.1.5 [expr.ref] paragraph 1 to before paragraph 3 and edit as follows:
A postfix expression followed by a dot . or an arrow ->, optionally followed by the keyword template, and then followed by an id-expression, is a postfix expression.
The postfix expression before the dot or arrow is evaluated; [ Footnote: ... ] the result of that evaluation, together with the id-expression, determines the result of the entire postfix expression.[Note 1: If the keyword template is used, the following unqualified name is considered to refer to a template (13.3 [temp.names]). If a simple-template-id results and is followed by a ::, the id-expression is a qualified-id. —end note]For the first option (dot) the first expression shall be a glvalue. For the second option (arrow) the first expression shall be a prvalue having pointer type. The expression E1->E2 is converted to the equivalent form (*(E1)).E2; the remainder of 7.6.1.5 [expr.ref] will address only the first option (dot). [ Footnote: ... ]
The postfix expression before the dot is evaluated; [ Footnote: ... ] the result of that evaluation, together with the id-expression, determines the result of the entire postfix expression.
[Accepted as a DR at the March, 2024 meeting.]
Subclause 7.6.1.5 [expr.ref] paragraph 2 specifies:
For the first option (dot) the first expression shall be a glvalue. ...
This provision forces a temporary materialization conversion (i.e. a copy) per 7.2.1 [basic.lval] paragraph 7:
Whenever a prvalue appears as an operand of an operator that expects a glvalue for that operand, the temporary materialization conversion (7.3.5 [conv.rval]) is applied to convert the expression to an xvalue.
However, this is limiting in the case that an explicit object member function with a by-value object parameter is invoked for a non-copyable class object, for example:
struct X { X() = default; X(const X&) = delete; X& operator=(const X&) = delete; void f(this X self) { } }; void f() { X{}.f(); // OK? }
The example ought to be well-formed.
Proposed resolution (reviewed by CWG 2024-02-16) [SUPERSEDED]:
Change in 6.7.7 [class.temporary] paragraph 2 as follows:
... [Note 3: Temporary objects are materialized:—end note]
- when binding a reference to a prvalue (9.5.4 [dcl.init.ref], 7.6.1.4 [expr.type.conv], 7.6.1.7 [expr.dynamic.cast], 7.6.1.9 [expr.static.cast], 7.6.1.11 [expr.const.cast], 7.6.3 [expr.cast]),
- when performing certain member
accessaccesses on a class prvalue (7.6.1.5 [expr.ref], 7.6.4 [expr.mptr.oper]),- when invoking an implicit object member function on a class prvalue (7.6.1.3 [expr.call]),
- ...
Change in 7.6.1.3 [expr.call] paragraph 6 as follows:
When a function is called, each parameter (9.3.4.6 [dcl.fct]) is initialized (9.5 [dcl.init], 11.4.5.3 [class.copy.ctor]) with its corresponding argument. If the function is an explicit object member function and there is an implied object argument (12.2.2.2.2 [over.call.func]), the list of provided arguments is preceded by the implied object argument for the purposes of this correspondence. If there is no corresponding argument, the default argument for the parameter is used. [Example 1:template<typename ...T> int f(int n = 0, T ...t); int x = f<int>(); // error: no argument for second function parameter—end example] If the function is a static member function invoked using class member access syntax, the object expression is a discarded-value expression (7.2.3 [expr.context]). If the function is an implicit object member function, the object expression of the class member access shall be a glvalue and the this parameter of the function (7.5.3 [expr.prim.this]) is initialized with a pointer to the object of the call, converted as if by an explicit type conversion (7.6.3 [expr.cast]). [Note 5: There is no access or ambiguity checking on this conversion; the access checking and disambiguation are done as part of the (possibly implicit) class member access operator. See 6.5.2 [class.member.lookup], 11.8.3 [class.access.base], and 7.6.1.5 [expr.ref]. —end note] When a function is called, the type of any parameter shall not be a class type that is either incomplete or abstract. [Note 6: This still allows a parameter to be a pointer or reference to such a type. However, it prevents a passed-by-value parameter to have an incomplete or abstract class type. —end note] It is implementation-defined whether ...
Change in 7.6.1.5 [expr.ref] paragraph 2 as follows:
For the first option (dot), if the id-expression is a data member, the first expression shall be a glvalue. For the second option (arrow), the first expression shall be a prvalue having pointer type. ...
CWG 2024-02-16
When a class member access refers to a static data member or a member enumerator, the object expression should also be treated as a discarded-value expression.
Proposed resolution (approved by CWG 2024-03-01):
Change in 6.7.7 [class.temporary] paragraph 2 as follows:
... [Note 3: Temporary objects are materialized:—end note]
- when binding a reference to a prvalue (9.5.4 [dcl.init.ref], 7.6.1.4 [expr.type.conv], 7.6.1.7 [expr.dynamic.cast], 7.6.1.9 [expr.static.cast], 7.6.1.11 [expr.const.cast], 7.6.3 [expr.cast]),
- when performing certain member
accessaccesses on a class prvalue (7.6.1.5 [expr.ref], 7.6.4 [expr.mptr.oper]),- when invoking an implicit object member function on a class prvalue (7.6.1.3 [expr.call]),
- ...
Change in 7.6.1.3 [expr.call] paragraph 6 as follows:
When a function is called, each parameter (9.3.4.6 [dcl.fct]) is initialized (9.5 [dcl.init], 11.4.5.3 [class.copy.ctor]) with its corresponding argument. If the function is an explicit object member function and there is an implied object argument (12.2.2.2.2 [over.call.func]), the list of provided arguments is preceded by the implied object argument for the purposes of this correspondence. If there is no corresponding argument, the default argument for the parameter is used. [Example 1:template<typename ...T> int f(int n = 0, T ...t); int x = f<int>(); // error: no argument for second function parameter—end example] If the function is an implicit object member function, the object expression of the class member access shall be a glvalue and the this parameter of the function (7.5.3 [expr.prim.this]) is initialized with a pointer to the object of the call, converted as if by an explicit type conversion (7.6.3 [expr.cast]). [Note 5: There is no access or ambiguity checking on this conversion; the access checking and disambiguation are done as part of the (possibly implicit) class member access operator. See 6.5.2 [class.member.lookup], 11.8.3 [class.access.base], and 7.6.1.5 [expr.ref]. —end note] When a function is called, the type of any parameter shall not be a class type that is either incomplete or abstract. [Note 6: This still allows a parameter to be a pointer or reference to such a type. However, it prevents a passed-by-value parameter to have an incomplete or abstract class type. —end note] It is implementation-defined whether ...
Change in 7.6.1.5 [expr.ref] paragraph 2 as follows:
For the first option (dot), if the id-expression names a static member or an enumerator, the first expression is a discarded-value expression (7.2.3 [expr.context]); if the id-expression names a non-static data member, the first expression shall be a glvalue. For the second option (arrow), the first expression shall be a prvalue having pointer type. ...
[Accepted as a DR at the March, 2024 meeting.]
(From submission #479.)
Consider:
int8_t x = 127; x++;
This has undefined behavior, because the resulting value is not representable as an int8_t. In contrast,
int8_t x = 127; ++x;
is well-defined, because it is equivalent to x += 1, which is equivalent to x = (int)x + 1 after the usual arithmetic conversions (7.4 [expr.arith.conv]). No arithmetic overflow occurs. The presence or absence of undefined behavior is detectable in constant evaluation.
Proposed resolution (approved by CWG 2024-02-16):
Change in 7.6.1.6 [expr.post.incr] paragraph 1 as follows:
The value of a postfix ++ expression is the value of its operand. [Note 1: The value obtained is a copy of the original value. —end note] The operand shall be a modifiable lvalue. The type of the operand shall be an arithmetic type other than cv bool, or a pointer to a complete object type. An operand with volatile-qualified type is deprecated; see D.4 [depr.volatile.type]. The value of the operand object is modified (3.1 [defns.access])by adding 1 to itas if it were the operand of the prefix ++ operator (7.6.2.3 [expr.pre.incr]). The value computation of the ++ expression is sequenced before the modification of the operand object. With respect to an indeterminately-sequenced function call, the operation of postfix ++ is a single evaluation. [Note 2: Therefore, a function call cannot intervene between the lvalue-to-rvalue conversion and the side effect associated with any single postfix ++ operator. —end note] The result is a prvalue. The type of the result is the cv-unqualified version of the type of the operand.If the operand is a bit-field that cannot represent the incremented value, the resulting value of the bit-field is implementation-defined. See also 7.6.6 [expr.add] and 7.6.19 [expr.assign].
Change in 7.6.2.3 [expr.pre.incr] as follows:
The operand of prefix ++ or --
is modified (3.1 [defns.access]) by adding 1. The operand shall be a modifiable lvalue. The type of the operandshall not bean arithmetic type other thanof type cv bool, or a pointer to a completely-defined object type. An operand with volatile-qualified type is deprecated; see D.4 [depr.volatile.type].The result is the updated operand; it is an lvalue, and it is a bit-field if the operand is a bit-field.The expression ++x is otherwise equivalent to x+=1 and the expression --x is otherwise equivalent to x-=1. [Note 1: See the discussions of addition (7.6.6 [expr.add]) and assignment operators(7.6.19 [expr.assign])for information on conversions.—end note]
The operand of prefix -- is modified (3.1 [defns.access]) by subtracting 1. The requirements on the operand of prefix -- and the properties of its result are otherwise the same as those of prefix ++.[Note 2: For postfix increment and decrement, see 7.6.1.6 [expr.post.incr]. —end note]
[Accepted as a DR at the June, 2024 meeting.]
(From submission #497.)
Base-to-derived casts and cross-casts need to inspect the vtable of a polymorphic type. However, this is not defined as an "access" and there is no provision for undefined behavior analoguous to 7.2.1 [basic.lval] paragraph 11.
Proposed resolution (approved by CWG 2024-06-26):
Add a new paragraph after 7.6.1.7 [expr.dynamic.cast] paragraph 6 as follows:
If v is a null pointer value, the result is a null pointer value.
If v has type "pointer to cv U" and v does not point to an object whose type is similar (7.3.6 [conv.qual]) to U and that is within its lifetime or within its period of construction or destruction (11.9.5 [class.cdtor]), the behavior is undefined. If v is a glvalue of type U and v does not refer to an object whose type is similar to U and that is within its lifetime or within its period of construction or destruction, the behavior is undefined.
Change in 7.3.12 [conv.ptr] paragraph 3 as follows:
A prvalue v of type “pointer to cv D”, where D is a complete class type, can be converted to a prvalue of type “pointer to cv B”, where B is a base class (11.7 [class.derived]) of D. If B is an inaccessible (11.8 [class.access]) or ambiguous (6.5.2 [class.member.lookup]) base class of D, a program that necessitates this conversion is ill-formed.The result of the conversion is a pointer to the base class subobject of the derived class object. The null pointer value is converted to the null pointer value of the destination type.If v is a null pointer value, the result is a null pointer value. Otherwise, if B is a virtual base class of D and v does not point to an object whose type is similar (7.3.6 [conv.qual]) to D and that is within its lifetime or within its period of construction or destruction (11.9.5 [class.cdtor]), the behavior is undefined. Otherwise, the result is a pointer to the base class subobject of the derived class object.
[Accepted as a DR at the March, 2024 meeting.]
According to 7.6.1.8 [expr.typeid] paragraph 2,
If the glvalue expression is obtained by applying the unary * operator to a pointer69 and the pointer is a null pointer value (7.3.12 [conv.ptr]), the typeid expression throws an exception (14.2 [except.throw]) of a type that would match a handler of type std::bad_typeid exception (17.7.5 [bad.typeid]).
The footnote makes clear that this requirement applies without regard to parentheses, but it is unspecified whether it applies when the dereference occurs in a subexpression of the operand (e.g., in the second operand of the comma operator or the second or third operand of a conditional operator). There is implementation divergence on this question.
Proposed resolution (approved by CWG 2023-11-09):
Insert a new paragraph before 7.6.1.8 [expr.typeid] paragraph 3 and change the latter as follows:
If an expression operand of typeid is a possibly-parenthesized unary-expression whose unary-operator is * and whose operand evaluates to a null pointer value (6.8.4 [basic.compound]), the typeid expression throws an exception (14.2 [except.throw]) of a type that would match a handler of type std::bad_typeid (17.7.5 [bad.typeid]). [ Note: In other contexts, evaluating such a unary-expression results in undefined behavior (7.6.2.2 [expr.unary.op]) -- end note ]
When typeid is applied to a glvalue whose type is a polymorphic class type (11.7.3 [class.virtual]), the result refers to a std::type_info object representing the type of the most derived object (6.7.2 [intro.object]) (that is, the dynamic type) to which the glvalue refers.
If the glvalue is obtained by applying the unary * operator to a pointer [ Footnote: ... ] and the pointer is a null pointer value (6.8.4 [basic.compound]), the typeid expression throws an exception (14.2 [except.throw]) of a type that would match a handler of type std::bad_typeid exception (17.7.5 [bad.typeid]).
[Accepted as a DR at the June, 2023 meeting.]
Issue 2310 clarified class completeness requirements for derived-to-base pointer conversions, but neglected the corresponding lvalue conversion.
Proposed resolution (approved by CWG 2023-04-28):
Change in 7.6.1.9 [expr.static.cast] paragraph 2 as follows:
An lvalue of type “cv1 B”, where B is a class type, can be cast to type “reference to cv2 D”, where D is a complete class derived (11.7 [class.derived]) from B, if cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. ...
[Accepted as a DR at the June, 2024 meeting.]
The ordering of paragraphs in 7.6.1.9 [expr.static.cast] appears to imply that an attempt is made to form an implicit conversion sequence for conversions to void, possibly considering user-defined conversion functions to void. This is not intended.
Proposed resolution (approved by CWG 2024-05-31):
Move 7.6.1.9 [expr.static.cast] paragraph 6 to before paragraph 4, strike paragraph 5, and adjust paragraph 7:
Any expression can be explicitly converted to type cv void, in which case the operand is a discarded-value expression (7.2 [expr.prop]). [Note 3: Such a static_cast has no result as it is a prvalue of type void; see 7.2.1 [basic.lval]. —end note] [Note 4: However, if the value is in a temporary object (6.7.7 [class.temporary]), the destructor for that object is not executed until the usual time, and the value of the object is preserved for the purpose of executing the destructor. —end note]
AnOtherwise, an expression E can be explicitly converted to a type T if there is an implicit conversion sequence (12.2.4.2 [over.best.ics]) from E to T, if overload resolution ...Otherwise, the static_cast shall perform one of the conversions listed below. No other conversion shall be performed explicitly using a static_cast.
Any expression can be explicitly converted to type cv void, in which case the operand is a discarded-value expression (7.2 [expr.prop]). [Note 3: Such a static_cast has no result as it is a prvalue of type void; see 7.2.1 [basic.lval]. —end note] [Note 4: However, if the value is in a temporary object (6.7.7 [class.temporary]), the destructor for that object is not executed until the usual time, and the value of the object is preserved for the purpose of executing the destructor. —end note]
TheOtherwise, the inverse ofanya standard conversion sequence (7.3 [conv]) not containing an lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), function-to-pointer (7.3.4 [conv.func]), null pointer (7.3.12 [conv.ptr]), null member pointer (7.3.13 [conv.mem]), boolean (7.3.15 [conv.bool]), or function pointer (7.3.14 [conv.fctptr]) conversion, can be performed explicitly using static_cast. A program is ill-formed if it uses static_cast to perform the inverse of an ill-formed standard conversion sequence.
Add a new paragraph after 7.6.1.9 [expr.static.cast] paragraph 14:
A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T”, ... Otherwise, the pointer value is unchanged by the conversion. [Example 3: ... —end example]
No other conversion can be performed using static_cast.
[Accepted as a DR at the November, 2023 meeting.]
Subclause 7.6.1.10 [expr.reinterpret.cast] paragraph 11 specifies:
A glvalue of type T1, designating an object x, can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result is that of *reinterpret_cast<T2 *>(p) where p is a pointer to x of type “pointer to T1”. No temporary is created, no copy is made, and no constructors (11.4.5 [class.ctor]) or conversion functions (11.4.8 [class.conv]) are called. [ Footnote: ... ]
The wording does not cover references to function type, only references to object types. All major implementations accept the following example:
void f() {} void(&g())(int) { return reinterpret_cast<void(&)(int)>(f); }
Proposed resolution (approved by CWG 2023-09-15):
Change in 7.6.1.10 [expr.reinterpret.cast] paragraph 11 as follows:
A glvalue of type T1, designating an object or function x, can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result is that of *reinterpret_cast<T2 *>(p) where p is a pointer to x of type “pointer to T1”. No temporary is created, no copy is made, and no constructors (11.4.5 [class.ctor]) or conversion functions (11.4.8 [class.conv]) are called. [ Footnote: ... ]
[Accepted as a DR at the November, 2024 meeting.]
(From submission #617.)
In 7.6.1.10 [expr.reinterpret.cast] paragraph 11, there is an issue similar to the one described in issue 2879: The operand for a cast to reference type can be a "glvalue of type T1", which implies that a prvalue is also acceptable, because it is materialized per 7.2.1 [basic.lval] paragraph 7. However, no implementation accepts reinterpret_cast<double&&>(0).
Suggested resolution [SUPERSEDED]:
Change in 7.6.1.10 [expr.reinterpret.cast] paragraph 1 as follows:
The result of the expression reinterpret_cast<T>(v) is the result of converting the expression v to type T. If T is an lvalue reference type or an rvalue reference to function type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue and the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the expression v. The temporary materialization conversion (7.3.5 [conv.rval]) is not performed on v. Conversions that can be performed explicitly using reinterpret_cast are listed below. No other conversion can be performed explicitly using reinterpret_cast.
Remove 7.6.1.10 [expr.reinterpret.cast] paragraph 3:
[Note 1: The mapping performed by reinterpret_cast might, or might not, produce a representation different from the original value. —end note]
Remove from 7.6.1.10 [expr.reinterpret.cast] paragraph 6 as follows:
...[Note 5: See also 7.3.12 [conv.ptr] for more details of pointer conversions. —end note]
Change in 7.6.1.10 [expr.reinterpret.cast] paragraph 11 as follows:
A glvalue of type T1, designating an object or function x, can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result is that of *reinterpret_cast<T2 *>(p) where p is a pointer to x of type “pointer to T1”. [ Note: No temporary is created, no copy is made, and no constructors (11.4.5 [class.ctor]) or conversion functions (11.4.8 [class.conv]) are called [ Footnote: ... ]. -- end note ]
CWG 2024-10-11
The note in 7.6.1.10 [expr.reinterpret.cast] paragraph 3 should be kept, and the list of exceptions in 7.2.1 [basic.lval] paragraph 7 established by issue 2879 should be amended with a cross-reference to 7.6.1.10 [expr.reinterpret.cast].
Proposed resolution (approved by CWG 2024-11-22):
Change in 7.2.1 [basic.lval] paragraph 7 as follows:
Unless otherwise specified (7.6.1.10 [expr.reinterpret.cast], 7.6.1.11 [expr.const.cast]), whenever a prvalue appears as an operand of an operator that expects a glvalue for that operand, the temporary materialization conversion (7.3.5 [conv.rval]) is applied to convert the expression to an xvalue.
Remove from 7.6.1.10 [expr.reinterpret.cast] paragraph 6 as follows:
...[Note 5: See also 7.3.12 [conv.ptr] for more details of pointer conversions. —end note]
Change in 7.6.1.10 [expr.reinterpret.cast] paragraph 11 as follows:
AIf v is a glvalue of type T1, designating an object or function x, it can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result is that of *reinterpret_cast<T2 *>(p) where p is a pointer to x of type “pointer to T1”. [ Note: No temporary is materialized (7.3.5 [conv.rval]) or created, no copy is made, and no constructors (11.4.5 [class.ctor]) or conversion functions (11.4.8 [class.conv]) are called [ Footnote: ... ]. -- end note ]
[Accepted as a DR at the November, 2024 meeting.]
(From submissions #526, #232, and #342.)
The resolution of issue 891 intended to make const_cast<int&&>(2) ill-formed. However, combined with the temporary materialization conversion turning prvalues into glvalues (7.1 [expr.pre] paragraph 7, this is now well-formed.
Also, the current rules regrettably allow const_cast<int>(0) and const_casts involving function pointers and pointers to member functions. The latter is non-normatively considered disallowed by the note in 7.6.1.11 [expr.const.cast] paragraph 9.
Major implementations except MSVC agree with the proposed direction of this issue.
Suggested resolution [SUPERSEDED]:
Change in 7.6.1.11 [expr.const.cast] paragraph 3 as follows:
For two similar types T1 and T2 (7.3.6 [conv.qual]), a prvalue of type T1 may be explicitly converted to the type T2 using a const_cast if, considering the qualification-decompositions of both types, each Pi1 is the same as Pi2 for all i. The result of a const_cast refers to the original entity.
If T is an object pointer type or pointer to data member type, the type of v shall be similar to T and the corresponding Pi components of the qualification decompositions of T and the type of v shall be the same (7.3.6 [conv.qual]). If v is a null pointer or null member pointer, the result is a null pointer or null member pointer, respectively. Otherwise, the result points to or past the end of the same object or member, respectively, as v.
[ Example: ... ]
Change in 7.6.1.11 [expr.const.cast] paragraph 4 as follows:
For two object types T1 and T2, if a pointer to T1 can be explicitly converted to the type “pointer to T2” using a const_cast, then the following conversions can also be made:
- an lvalue of type T1 can be explicitly converted to an lvalue of type T2 using the cast const_cast<T2&>;
- a glvalue of type T1 can be explicitly converted to an xvalue of type T2 using the cast const_cast<T2&&>; and
- if T1 is a class type, a prvalue of type T1 can be explicitly converted to an xvalue of type T2 using the cast const_cast<T2&&>.
The result of a reference const_cast refers to the original object if the operand is a glvalue and to the result of applying the temporary materialization conversion (7.3.5 [conv.rval]) otherwise.
Otherwise, T shall be a reference type. Let T1 be the type of v and T2 be the type referred to by T. A const_cast from "pointer to T1" to "pointer to T2" shall be valid. If T is an lvalue reference type, v shall be an lvalue. Otherwise, if T2 is a class type and v is a prvalue, the temporary materialization conversion (7.3.5 [conv.rval]) is applied. Otherwise, the temporary materialization conversion is not applied and v shall be a glvalue. The result refers to the same object as the (possibly converted) operand.
Remove 7.6.1.11 [expr.const.cast] paragraph 5:
A null pointer value (6.8.4 [basic.compound]) is converted to the null pointer value of the destination type. The null member pointer value (7.3.13 [conv.mem]) is converted to the null member pointer value of the destination type.
Suggested resolution [SUPERSEDED]:
Change in 7.6.1.11 [expr.const.cast] paragraph 1 as follows:
The result of the expression const_cast<T>(v) is of type T. If T is an lvalue reference to object type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue and the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the expression v. The temporary materialization conversion (7.3.5 [conv.rval]) is not performed on v, other than as specified below. Conversions that can be performed explicitly using const_cast are listed below. No other conversion shall be performed explicitly using const_cast.
Change in 7.6.1.11 [expr.const.cast] paragraph 3 as follows:
For two similar object pointer or pointer to data member types T1 and T2 (7.3.6 [conv.qual]), a prvalue of type T1maycan be explicitly converted to the type T2 using a const_cast if, considering the qualification-decompositions of both types, each Pi1 is the same as Pi2 for all i.The result of a const_cast refers to the original entity.If v is a null pointer or null member pointer, the result is a null pointer or null member pointer, respectively. Otherwise, the result points to or past the end of the same object, or points to the same member, respectively, as v.
Change in 7.6.1.11 [expr.const.cast] paragraph 4 as follows:
For two object types T1 and T2, if a pointer to T1 can be explicitly converted to the type “pointer to T2” using a const_cast, then the following conversions can also be made:The result
- an lvalue of type T1 can be explicitly converted to an lvalue of type T2 using the cast const_cast<T2&>;
- a glvalue of type T1 can be explicitly converted to an xvalue of type T2 using the cast const_cast<T2&&>; and
- if T1 is a class type, a prvalue of type T1 can be explicitly converted to an xvalue of type T2 using the cast const_cast<T2&&>. The temporary materialization conversion is performed on v.
of a reference const_castrefers to theoriginalsame objectifas the (possibly converted) operandis a glvalue and to the result of applying the temporary materialization conversion (7.3.5 [conv.rval]) otherwise.
Remove 7.6.1.11 [expr.const.cast] paragraph 5:
A null pointer value (6.8.4 [basic.compound]) is converted to the null pointer value of the destination type. The null member pointer value (7.3.13 [conv.mem]) is converted to the null member pointer value of the destination type.
CWG 2024-05-31
The existing example in 7.6.1.11 [expr.const.cast] paragraph 3 shows the temporary materialization conversion applied to an array type. The example would be made ill-formed by the suggested resolution above. More investigation is advised.
Suggested resolution [SUPERSEDED]:
Change in 7.6.1.11 [expr.const.cast] paragraph 1 as follows:
The result of the expression const_cast<T>(v) is of type T. If T is an lvalue reference to object type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue and the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the expression v. The temporary materialization conversion (7.3.5 [conv.rval]) is not performed on v, other than as specified below. Conversions that can be performed explicitly using const_cast are listed below. No other conversion shall be performed explicitly using const_cast.
Change in 7.6.1.11 [expr.const.cast] paragraph 3 as follows:
For two similar object pointer or pointer to data member types T1 and T2 (7.3.6 [conv.qual]), a prvalue of type T1maycan be explicitly converted to the type T2 using a const_cast if, considering the qualification-decompositions of both types, each Pi1 is the same as Pi2 for all i.The result of a const_cast refers to the original entity.If v is a null pointer or null member pointer, the result is a null pointer or null member pointer, respectively. Otherwise, the result points to or past the end of the same object, or points to the same member, respectively, as v.[Example 1:typedef int *A[3]; // array of 3 pointer to int typedef const int *const CA[3]; // array of 3 const pointer to const int CA &&r = A{}; // OK, reference binds to temporary array object // after qualification conversion to type CA A &&r1 = const_cast<A>(CA{}); // error: temporary array decayed to pointer A &&r2 = const_cast<A&&>(CA{}); // OK-- end example]
Change in 7.6.1.11 [expr.const.cast] paragraph 4 as follows:
For two object types T1 and T2, if a pointer to T1 can be explicitly converted to the type “pointer to T2” using a const_cast, then the following conversions can also be made:The result
- an lvalue of type T1 can be explicitly converted to an lvalue of type T2 using the cast const_cast<T2&>;
- a glvalue of type T1 can be explicitly converted to an xvalue of type T2 using the cast const_cast<T2&&>; and
- if T1 is a class or array type, a prvalue of type T1 can be explicitly converted to an xvalue of type T2 using the cast const_cast<T2&&>. The temporary materialization conversion is performed on v.
of a reference const_castrefers to theoriginalsame objectifas the (possibly converted) operandis a glvalue and to the result of applying the temporary materialization conversion (7.3.5 [conv.rval]) otherwise.[Example 2:
typedef int *A[3]; // array of 3 pointer to int typedef const int *const CA[3]; // array of 3 const pointer to const int auto &&r2 = const_cast<A&&>(CA{}); // OK, temporary materialization conversion is performed-- end example]
Remove 7.6.1.11 [expr.const.cast] paragraph 5:
A null pointer value (6.8.4 [basic.compound]) is converted to the null pointer value of the destination type. The null member pointer value (7.3.13 [conv.mem]) is converted to the null member pointer value of the destination type.
Change in 9.5.4 [dcl.init.ref] bullet 5.3 as follows:
[Example 5: ...constexpr int f() { const int &x = 42; const_cast<int &>(x) = 1; // undefined behavior return x; } constexpr int z = f(); // error: not a constant expressiontypedef int *AP[3]; // array of 3 pointer to int typedef const int *const ACPC[3]; // array of 3 const pointer to const int ACPC &&r = AP{}; // binds directly-- end example]
This resolution also resolve issue 1965.
CWG 2024-10-11
Subclause 7.2.1 [basic.lval] paragraph 7 should be amended with "unless otherwise specified" and cross-references to the exceptions.
Proposed resolution (approved by CWG 2024-10-25)
Change in 7.2.1 [basic.lval] paragraph 7 as follows:
WheneverUnless otherwise specified (7.6.1.11 [expr.const.cast]), whenever a prvalue appears as an operand of an operator that expects a glvalue for that operand, the temporary materialization conversion (7.3.5 [conv.rval]) is applied to convert the expression to an xvalue.
Change in 7.6.1.11 [expr.const.cast] paragraph 1 as follows:
The result of the expression const_cast<T>(v) is of type T. If T is an lvalue reference to object type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue and the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the expression v. The temporary materialization conversion (7.3.5 [conv.rval]) is not performed on v, other than as specified below. Conversions that can be performed explicitly using const_cast are listed below. No other conversion shall be performed explicitly using const_cast.
Change in 7.6.1.11 [expr.const.cast] paragraph 3 as follows:
For two similar object pointer or pointer to data member types T1 and T2 (7.3.6 [conv.qual]), a prvalue of type T1maycan be explicitly converted to the type T2 using a const_cast if, considering the qualification-decompositions of both types, each Pi1 is the same as Pi2 for all i.The result of a const_cast refers to the original entity.If v is a null pointer or null member pointer, the result is a null pointer or null member pointer, respectively. Otherwise, the result points to or past the end of the same object, or points to the same member, respectively, as v.[Example 1:typedef int *A[3]; // array of 3 pointer to int typedef const int *const CA[3]; // array of 3 const pointer to const int CA &&r = A{}; // OK, reference binds to temporary array object // after qualification conversion to type CA A &&r1 = const_cast<A>(CA{}); // error: temporary array decayed to pointer A &&r2 = const_cast<A&&>(CA{}); // OK-- end example]
Change in 7.6.1.11 [expr.const.cast] paragraph 4 as follows:
For two object types T1 and T2, if a pointer to T1 can be explicitly converted to the type “pointer to T2” using a const_cast, then the following conversions can also be made:The result
- an lvalue of type T1 can be explicitly converted to an lvalue of type T2 using the cast const_cast<T2&>;
- a glvalue of type T1 can be explicitly converted to an xvalue of type T2 using the cast const_cast<T2&&>; and
- if T1 is a class or array type, a prvalue of type T1 can be explicitly converted to an xvalue of type T2 using the cast const_cast<T2&&>. The temporary materialization conversion is performed on v.
of a reference const_castrefers to theoriginalsame objectifas the (possibly converted) operandis a glvalue and to the result of applying the temporary materialization conversion (7.3.5 [conv.rval]) otherwise.[Example 2:
typedef int *A[3]; // array of 3 pointer to int typedef const int *const CA[3]; // array of 3 const pointer to const int auto &&r2 = const_cast<A&&>(CA{}); // OK, temporary materialization conversion is performed-- end example]
Remove 7.6.1.11 [expr.const.cast] paragraph 5:
A null pointer value (6.8.4 [basic.compound]) is converted to the null pointer value of the destination type. The null member pointer value (7.3.13 [conv.mem]) is converted to the null member pointer value of the destination type.
Change in 9.5.4 [dcl.init.ref] bullet 5.3 as follows:
[Example 5: ...constexpr int f() { const int &x = 42; const_cast<int &>(x) = 1; // undefined behavior return x; } constexpr int z = f(); // error: not a constant expressiontypedef int *AP[3]; // array of 3 pointer to int typedef const int *const ACPC[3]; // array of 3 const pointer to const int ACPC &&r = AP{}; // binds directly-- end example]
This resolution also resolve issue 1965.
[Accepted as a DR at the November, 2023 meeting.]
Subclause 7.6.2.2 [expr.unary.op] paragraph 1 specifies:
The unary * operator performs indirection. Its operand shall be a prvalue of type “pointer to T”, where T is an object or function type. The operator yields an lvalue of type T denoting the object or function to which the operand points.
It is unclear what happens if the operand does not point to an object or function.
Proposed resolution (approved by CWG 2023-11-08):
Change in 7.6.2.2 [expr.unary.op] paragraph 1 as follows:
The unary * operator performs indirection. Its operand shall be a prvalue of type “pointer to T”, where T is an object or function type. The operator yields an lvalue of type Tdenoting the object or function to which the operand points. If the operand points to an object or function, the result denotes that object or function; otherwise, the behavior is undefined except as specified in 7.6.1.8 [expr.typeid].
[Accepted as a DR at the March, 2024 meeting.]
Subclause 7.6.2.4 [expr.await] paragraph 2 disallows an await-expression to appear in the body of a lambda-expression:
An await-expression shall appear only in a potentially-evaluated expression within the compound-statement of a function-body outside of a handler (14.1 [except.pre]). ...
This is probably unintended.
Proposed resolution (approved by CWG 2023-11-11):
Change in 7.6.2.4 [expr.await] paragraph 2 as follows:
An await-expression shall appear onlyinas a potentially-evaluated expression within the compound-statement of a function-body or lambda-expression, in either case outside of a handler (14.1 [except.pre]). ...
[Accepted as a DR at the June, 2023 meeting.]
It is unclear whether noexcept(A()) applies the temporary materialization conversion to the prvalue A(). The resolution of issue 1354 suggests that it does so that the destructor is (notionally) invoked.
Proposed resolution (approved by CWG 2023-05-12):
Change in 7.6.2.7 [expr.unary.noexcept] paragraph 3 as follows:
If the operand is a prvalue, the temporary materialization conversion (7.3.5 [conv.rval]) is applied. The result of the noexcept operator is true unless theexpressionfull-expression of the operand is potentially-throwing (14.5 [except.spec]).
[Accepted as a DR at the November, 2023 meeting.]
The introductory sentence "can throw an exception" is misleading, because it might be interpreted to cover exceptions thrown as the result of encountering undefined behavior.
Proposed resolution (approved by CWG 2023-10-06):
Change all of 7.6.2.7 [expr.unary.noexcept] as follows:
The noexcept operator determines whether the evaluation of its operand, which is an unevaluated operand (7.2.3 [expr.context]), can throw an exception (14.2 [except.throw]).noexcept-expression: noexcept ( expression )The operand of the noexcept operator is an unevaluated operand (7.2.3 [expr.context]). If the operand is a prvalue, the temporary materialization conversion (7.3.5 [conv.rval]) is applied.
The result of the noexcept operator is a prvalue of type bool. The result is false if the full-expression of the operand is potentially-throwing (14.5 [except.spec]), and true otherwise.
[Note 1: A noexcept-expression is an integral constant expression (7.7 [expr.const]). —end note]
If the operand is a prvalue, the temporary materialization conversion (7.3.5 [conv.rval]) is applied. The result of the noexcept operator is true unless the full-expression of the operand is potentially-throwing (14.5 [except.spec]).
[Accepted as a DR at the November, 2023 meeting.]
According to 7.6.2.8 [expr.new] paragraph 25,
If the new-expression creates an object or an array of objects of class type, access and ambiguity control are done for the allocation function, the deallocation function (11.4.11 [class.free]), and the constructor (11.4.5 [class.ctor]).
The mention of “the constructor” here is strange. For the “object of class type” case, access and ambiguity control are done when we perform initialization in paragraph 17, and we might not be calling a constructor anyway (for aggregate initialization). This seems wrong.
For the “array of objects of class type” case, it makes slightly more sense (we need to check the trailing array elements can be default-initialized) but again (a) we aren't necessarily using a constructor, (b) we should say which constructor — and we may need overload resolution to find it, and (c) shouldn't this be part of initialization, so we can distinguish between the cases where we should copy-initialize from {} and the cases where we should default-initialize?
Additional notes (May, 2023):
It is unclear whether default-initialization is required to be well-formed even for an array with no elements.
Proposed resolution (approved by CWG 2023-06-16):
Insert a new paragraph before 7.6.2.8 [expr.new] paragraph 9:
If the allocated type is an array, the new-initializer is a braced-init-list, and the expression is potentially-evaluated and not a core constant expression, the semantic constraints of copy-initializing a hypothetical element of the array from an empty initializer list are checked (9.5.5 [dcl.init.list]). [ Note: The array can contain more elements than there are elements in the braced-init-list, requiring initialization of the remainder of the array elements from an empty initializer list. -- end note ]
Objects created by a new-expression have dynamic storage duration (6.7.6.5 [basic.stc.dynamic]). ...
Change in 7.6.2.8 [expr.new] paragraph 25 as follows:
If the new-expression creates an object or an array of objects of class type, access and ambiguity control are done for the allocation function, the deallocation function (6.7.6.5.3 [basic.stc.dynamic.deallocation]), and the constructor (11.4.5 [class.ctor]) selected for the initialization (if any).If the new-expression creates an array of objects of class type, the destructor is potentially invoked (11.4.7 [class.dtor]).
Change in 7.6.2.8 [expr.new] paragraph 28 as follows:
A declaration of a placement deallocation function matches the declaration of a placement allocation function if it has the same number of parameters and, after parameter transformations (9.3.4.6 [dcl.fct]), all parameter types except the first are identical. If the lookup finds a single matching deallocation function, that function will be called; otherwise, no deallocation function will be called. If the lookup finds a usual deallocation function and that function, considered as a placement deallocation function, would have been selected as a match for the allocation function, the program is ill-formed. For a non-placement allocation function, the normal deallocation function lookup is used to find the matching deallocation function (7.6.2.9 [expr.delete]). In any case, the matching deallocation function (if any) shall be non-deleted and accessible from the point where the new-expression appears.
Change in 9.5.1 [dcl.init.general] paragraph 7 as follows:
To default-initialize an object of type T means:
- ...
- If T is an array type, the semantic constraints of default-initializing a hypothetical element shall be met and each element is default-initialized.
- ...
Change in 9.5.1 [dcl.init.general] paragraph 9 as follows:
To value-initialize an object of type T means:
ifIf T is a (possibly cv-qualified) class type (Clause 11 [class]), then
- if T has either no default constructor (11.4.5.2 [class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
- otherwise, the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized
;.ifIf T is an array type, the semantic constraints of value-initializing a hypothetical element shall be met and each element is value-initialized;.otherwiseOtherwise, the object is zero-initialized.
[Accepted as a DR at the June, 2023 meeting.]
Subclause 7.6.2.8 [expr.new] paragraph 1 introduces the grammar non-terminal new-type-id, but never specifies its meaning.
Proposed resolution (approved by CWG 2023-06-12):
Change in 7.6.2.8 [expr.new] paragraph 1 as follows:
The new-expression attempts to create an object of the type-id(9.3.2 [dcl.name])or new-type-id (9.3.2 [dcl.name]) to which it is applied. The type of that object is the allocated type. This type shall be a complete object type (6.8.1 [basic.types.general]), but not an abstract class type (11.7.4 [class.abstract]) or array thereof (6.7.2 [intro.object]).
Change in 9.3.2 [dcl.name] paragraph 1 as follows:
To specify type conversions explicitly, and as an argument of sizeof, alignof, new, or typeid, the name of a type shall be specified. This can be done with a type-id or new-type-id (7.6.2.8 [expr.new]), which is syntactically a declaration for a variable or function of that type that omits the name of the entity.
[Accepted as a DR at the June, 2024 meeting.]
Subclause 7.6.2.9 [expr.delete] paragraph 4 specifies:
The cast-expression in a delete-expression shall be evaluated exactly once.
Due to the reference to the syntactic non-terminal cast-expression, it is unclear whether that includes the conversion to pointer type specified in 7.6.2.9 [expr.delete] paragraph 2.
Proposed resolution (approved by CWG 2024-03-01) [SUPERSEDED]:
Change in 7.6.2.9 [expr.delete] paragraph 1 and paragraph 2 as follows:
... The operand shall be of class type or a prvalue of pointer to object type
or of class type. If of class type, the operand is contextually implicitly converted (7.3 [conv]) to a prvalue pointer to object type. [ Footnote: ... ] The converted operand is used in place of the original operand for the remainder of this subclause. The delete-expression has type void.
If the operand has a class type, the operand is converted to a pointer type by calling the above-mentioned conversion function, and the converted operand is used in place of the original operand for the remainder of this subclause....
Change in 7.6.2.9 [expr.delete] paragraph 4 as follows:
Thecast-expression inoperand of a delete-expression shall be evaluated exactly once.
Proposed resolution (approved by CWG 2024-03-20):
Change in 7.6.2.9 [expr.delete] paragraph 1 and paragraph 2 as follows:
... The first alternative is a single-object delete expression, and the second is an array delete expression. Whenever the delete keyword is immediately followed by empty square brackets, it shall be interpreted as the second alternative. [ Footnote: ... ] If the operand is of class type, it is contextually implicitly converted (7.3 [conv]) to a pointer to object type and the converted operand is used in place of the original operand for the remainder of this subclause.
[ Footnote: ...]Otherwise, it shall be a prvalue of pointer to object type. The delete-expression has type void.
If the operand has a class type, the operand is converted to a pointer type by calling the above-mentioned conversion function, and the converted operand is used in place of the original operand for the remainder of this subclause....
Delete 7.6.2.9 [expr.delete] paragraph 4:
The cast-expression in a delete-expression shall be evaluated exactly once.
[Accepted as a DR at the November, 2023 meeting.]
Subclause 7.6.2.9 [expr.delete] paragraph 12 specifies:
Access and ambiguity control are done for both the deallocation function and the destructor (11.4.7 [class.dtor], 11.4.11 [class.free]).
It is unclear what that means. In particular, ambiguity checking is part of overload resolution, and access checking requires a point of reference.
Proposed resolution (approved by CWG 2023-08-25):
Change in 7.6.2.9 [expr.delete] paragraph 6 as follows:
If the value of the operand of the delete-expression is not a null pointer value and the selected deallocation function (see below) is not a destroying operator delete, evaluating the delete-expressionwill invokeinvokes the destructor (if any) for the object or the elements of the array being deleted. The destructor shall be accessible from the point where the delete-expression appears. In the case of an array, the elementswill beare destroyed in order of decreasing address (that is, in reverse order of the completion of their constructor; see 11.9.3 [class.base.init]).
Change in 7.6.2.9 [expr.delete] paragraph 10
If more than one deallocation function is found, theThe deallocation function to be called is selected as follows:
- ...
Unless the deallocation function is selected at the point of definition of the dynamic type's virtual destructor, the selected deallocation function shall be accessible from the point where the delete-expression appears.
Remove 7.6.2.9 [expr.delete] paragraph 12:
Access and ambiguity control are done for both the deallocation function and the destructor (11.4.7 [class.dtor], 11.4.11 [class.free]).
[Accepted as a DR at the March, 2024 meeting.]
(From editorial issue 5355.)
Consider:
int*** ptr = 0; auto t = (int const*const*const*)ptr;
There is more than one way how this can be interpreted as a static_cast followed by a const_cast, namely:
const_cast<int const * const * const * >(static_cast<int * * const * >(ptr)); const_cast<int const * const * const * >(static_cast<int * const * const * >(ptr));
Subclause 7.6.3 [expr.cast] paragraph 4 makes such a program ill-formed:
... If a conversion can be interpreted in more than one way as a static_cast followed by a const_cast, the conversion is ill-formed.
Proposed resolution (approved by CWG 2024-03-20):
Change in 7.6.3 [expr.cast] paragraph 4 as follows:
... If a conversion can be interpreted in more than one of the ways listed above, the interpretation that appears first in the list is used, even if a cast resulting from that interpretation is ill-formed. If aconversion can be interpreted in more than one way as astatic_cast followed by a const_cast is used and the conversion can be interpreted in more than one way as such, the conversion is ill-formed. [ Example 1 :struct A { }; struct I1 : A { }; struct I2 : A { }; struct D : I1, I2 { }; A* foo( D* p ) { return (A*)( p ); // ill-formed static_cast interpretation } int*** ptr = 0; auto t = (int const*const*const*)ptr; // OK, const_cast interpretation struct S { operator const int*(); operator volatile int*(); }; int *p = (int*)S(); // error: two possible interpretations using static_cast followed by const_cast-- end example ]
[Accepted as a DR at the March, 2024 meeting.]
(From submission #495.)
The phrasing in 7.6.6 [expr.add] bullet 4.2 excludes pointer arithmetic on a pointer pointing to the hypothetical (past-the-end) array element.
Proposed resolution (approved by CWG 2024-02-16):
Change in 7.6.6 [expr.add] bullet 4.2 as follows:
- ...
- Otherwise, if P points to
ana (possibly-hypothetical) array element i of an array object x with n elements (9.3.4.5 [dcl.array]), [ Footnote: ... ] the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) array element i + j of x if 0 <= i + j <= n and the expression P - J points to the (possibly-hypothetical) array element i - j of x if 0 <= i - j <= n.
[Accepted as a DR at the June, 2023 meeting.]
(From editorial issue 6225.)
Subclause 7.6.7 [expr.shift] paragraph 3 specifies:
The value of E1 >> E2 is E1/2E2, rounded down.
It is unclear whether "rounded down" means "towards zero" or "towards negative infinity".
Proposed resolution (approved by CWG 2023-05-12):
Change in 7.6.7 [expr.shift] paragraph 3 as follows:
The value of E1 >> E2 is E1/2E2, roundeddowntowards negative infinity.
[Accepted as a DR at the November, 2023 meeting.]
(From editorial issue 6173.)
Subclause 7.6.9 [expr.rel] paragraph 4 and paragraph 5 specify:
The result of comparing unequal pointers to objects [ Footnote: ] is defined in terms of a partial order consistent with the following rules: ...
[Note 1: A relational operator applied to unequal function pointers or to unequal pointers to void yields an unspecified result. -- end note]
Comparing pointers to objects that are stored in a variable of type "pointer to void" should be fine.
Proposed resolution (approved by CWG 2023-06-16):
Change in 7.6.9 [expr.rel] paragraph 4 and paragraph 5 as follows:
The result of comparing unequal pointers to objects [ Footnote: ... ] is defined in terms of a partial order consistent with the following rules: ...
[Note 1: A relational operator applied to unequal function pointers
or to unequal pointers to voidyields an unspecified result. A pointer value of type "pointer to cv void" can point to an object (6.8.4 [basic.compound]). -- end note]
[Accepted as a DR at the November, 2023 meeting.]
Consider:
void f() {} void g() noexcept {} void q() { bool b1 = f == g; // OK bool b2 = f > g; // error: different types }
For the equality operators, 7.6.10 [expr.eq] paragraph 3 specifies:
If at least one of the operands is a pointer, pointer conversions (7.3.12 [conv.ptr]), function pointer conversions (7.3.14 [conv.fctptr]), and qualification conversions (7.3.6 [conv.qual]) are performed on both operands to bring them to their composite pointer type (7.2.2 [expr.type]). Comparing pointers is defined as follows: ...
In contrast, the corresponding rule for relational operators in 7.6.9 [expr.rel] paragraph 3 specifies:
The usual arithmetic conversions (7.4 [expr.arith.conv]) are performed on operands of arithmetic or enumeration type. If both operands are pointers, pointer conversions (7.3.12 [conv.ptr]) and qualification conversions (7.3.6 [conv.qual]) are performed to bring them to their composite pointer type (7.2.2 [expr.type]). After conversions, the operands shall have the same type.
However, all major implementations accept the example.
Proposed resolution (approved by CWG 2023-10-06):
Change in 7.6.9 [expr.rel] paragraph 3 as follows:
The usual arithmetic conversions (7.4 [expr.arith.conv]) are performed on operands of arithmetic or enumeration type. If both operands are pointers, pointer conversions (7.3.12 [conv.ptr]), function pointer conversions (7.3.14 [conv.fctptr]), and qualification conversions (7.3.6 [conv.qual]) are performed to bring them to their composite pointer type (7.2.2 [expr.type]). After conversions, the operands shall have the same type.
[Accepted as a DR at the June, 2024 meeting.]
Consider:
#include <concepts> template <class T> T get(); template <class T> using X = decltype(true ? get<T const&>() : get<T>()); struct C { }; static_assert(std::same_as<X<int>, int>); static_assert(std::same_as<X<C>, C const>); // #1
Before Issue 1895, #1 was well-formed. With the reformulation based on conversion sequences, #1 is now ill-formed because both conversion sequences can be formed.
Proposed resolution (approved by CWG 2024-05-03):
Change in 7.6.16 [expr.cond] bullet 4.3 as follows:
- ...
- If E2 is a prvalue or if neither of the conversion sequences above can be formed and at least one of the operands has (possibly cv-qualified) class type:
- if T1 and T2 are the same class type (ignoring cv-qualification):
andif T2 is at least as cv-qualified as T1, the target type is T2,- otherwise, no conversion sequence is formed for this operand;
- otherwise, if T2 is a base class of T1, the target type is cv1 T2, where cv1 denotes the cv-qualifiers of T1
,;- otherwise, the target type is the type that E2 would have after applying the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions.
[Accepted as a DR at the November, 2024 meeting.]
(From submission #550.)
There are two known situations where the lvalue-to-rvalue conversion is applied to class types, which can be non-constexpr even if the resulting copy constructor invocation would be constexpr (7.7 [expr.const] bullet 5.9). The other such situation is 7.6.1.3 [expr.call] paragraph 11. Here, the concern is with 7.6.16 [expr.cond] paragraph 7, which can be invoked for class types; for example:
struct S {}; S a; constexpr S b = a; // OK, call to implicitly-declared copy constructor constexpr S d = false ? S{} : a; // error: lvalue-to-rvalue conversion of 'a' is not a constant expression
Major implementations disagree with the ill-formed outcome.
Proposed resolution (approved by CWG 2024-06-26):
Change in 7.6.16 [expr.cond] paragraph 7 as follows:
Otherwise, the result is a prvalue. If the second and third operands do not have the same type, ...
Lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointerArray-to-pointer (7.3.3 [conv.array]),and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the second and third operands. After those conversions, one of the following shall hold:
- The second and third operands have the same type; the result is of that type and the result
objectisinitializedcopy-initialized using the selected operand.- The second and third operands have arithmetic or enumeration type; the usual arithmetic conversions (7.4 [expr.arith.conv]) are performed to bring them to a common type, and the result is of that type.
- One or both of the second and third operands have pointer type; lvalue-to-rvalue (7.3.2 [conv.lval]), pointer
conversions(7.3.12 [conv.ptr]), function pointerconversions(7.3.14 [conv.fctptr]), and qualification conversions (7.3.6 [conv.qual]) are performed to bring them to their composite pointer type (7.2.2 [expr.type]). The result is of the composite pointer type.- One or both of the second and third operands have pointer-to-member type; lvalue-to-rvalue (7.3.2 [conv.lval]), pointer to member
conversions(7.3.13 [conv.mem]), function pointerconversions(7.3.14 [conv.fctptr]), and qualification conversions (7.3.6 [conv.qual]) are performed to bring them to their composite pointer type (7.2.2 [expr.type]). The result is of the composite pointer type.- Both the second and third operands have type std::nullptr_t or one has that type and the other is a null pointer constant. The result is of type std::nullptr_t.
[Accepted as a DR at the June, 2023 meeting.]
Subclause 7.6.18 [expr.throw] paragraph 2 and 3 specify:
Evaluating a throw-expression with an operand throws an exception (14.2 [except.throw]); the type of the exception object is determined by removing any top-level cv-qualifier s from the static type of the operand and adjusting the type from “array of T” or function type T to “pointer to T”.
A throw-expression with no operand rethrows the currently handled exception (14.4 [except.handle]). The exception is reactivated with the existing exception object; no new exception object is created. The exception is no longer considered to be caught.
This means that throwing a value of type const char[3] would throw a char* rather than const char*, which is not intended.
Proposed resolution (approved by CWG 2023-03-03):
Change in 7.6.18 [expr.throw] paragraph 2 through 4 as follows:
Evaluating aA throw-expression with an operand throws an exception (14.2 [except.throw]); the. The array-to-pointer (7.3.3 [conv.array]) and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the operand. The type of the exception object is determined by removing any top-level cv-qualifiers from thestatictype of the (possibly converted) operandand adjusting the type from “array of T” or function type T to “pointer to T”.A throw-expression with no operand rethrows the currently handled exception (14.4 [except.handle]). If no exception is presently being handled, the function std:: terminate is invoked (14.6.2 [except.terminate]).
TheOtherwise, the exception is reactivated with the existing exception object; no new exception object is created. The exception is no longer considered to be caught.
If no exception is presently being handled, evaluating a throw-expression with no operand calls std:: terminate() (14.6.2 [except.terminate]).
[Accepted as a DR at the June, 2023 meeting.]
Neither 14.2 [except.throw] nor 7.6.18 [expr.throw] specifiy the source for copy-initializing the exception object.
Proposed resolution (based on the resolution of issue 2699; approved by CWG 2023-06-12):
Change in 7.6.18 [expr.throw] paragraph 2 as follows:
Evaluating a throw-expression with an operand throws an exception (14.2 [except.throw]); the type of the exception object is determined by removing any top-level cv-qualifiers from the static type of the operand and adjusting the type from “array of T” or function type T to “pointer to T”. The exception object is copy-initialized (9.5.1 [dcl.init.general]) from the (possibly converted) operand.
Change in 14.2 [except.throw] paragraph 3 as follows:
Throwing an exceptioncopy-initializes(9.5 [dcl.init], 11.4.5.3 [class.copy.ctor])a temporary object, called the exception object. If the type of the exception object would be an incomplete type (6.8.1 [basic.types.general]), an abstract class type (11.7.4 [class.abstract]), or a pointer to an incomplete type other than cv void (6.8.4 [basic.compound]), the program is ill-formed.
[Accepted as a DR at the November, 2024 meeting.]
(From submission #626.)
The semantics of unsequenced throw-expressions is unclear. For example:
(throw /* ... */, 0) + (throw /* ... */, 0);
This appears to cause two unsequenced transfers of control, which makes little sense. In contrast, a co_await expression is indivisible (6.9.1 [intro.execution] paragraph 11) per issue 2466.
Proposed resolution (approved by CWG 2024-11-08):
Change in 6.9.1 [intro.execution] paragraph 11 as follows, adding bullets:
When invoking a function (whether or not the function is inline), every argument expression and the postfix expression designating the called function are sequenced before every expression or statement in the body of the called function. For eachF, each evaluation that does not occur within F but is evaluated on the same thread and as part of the same signal handler (if any) is either sequenced before all evaluations that occur within F or sequenced after all evaluations that occur within F; [ Foonote: ... ] if F invokes or resumes a coroutine (7.6.2.4 [expr.await]), only evaluations subsequent to the previous suspension (if any) and prior to the next suspension (if any) are considered to occur within F.
- function invocation,
- evaluation of an await-expression (7.6.2.4 [expr.await]), or
- evaluation of a throw-expression (7.6.18 [expr.throw])
CWG 2024-11-08
Check with implementers (in particular MSVC) that the proposed resolution is acceptable.
[Accepted as a DR at the November, 2023 meeting.]
Consider:
enum class E {E1}; void f() { E e; e = E{0}; // #1 e = {0}; // #2 }
#1 first initializes a temporary of type E and then assigns that to e. For #2, 7.6.19 [expr.assign] bullet 8.1 specifies that #2 is equivalent to #1:
A braced-init-list may appear on the right-hand side of
- an assignment to a scalar, in which case the initializer list shall have at most a single element. The meaning of x = {v}, where T is the scalar type of the expression x, is that of x = T{v}. The meaning of x = {} is x = T{}.
- ...
However, there is no syntactic hint that #2 would invoke direct-initialization, and in fact gcc, icc, and MSVC reject #2, but clang accepts.
Proposed resolution (approved by CWG 2023-11-06):
Change in 7.6.19 [expr.assign] paragraph 8 as follows:
A braced-init-list B may appear on the right-hand side of
- an assignment to a scalar of type T, in which case
the initializer listB shall have at most a single element. The meaning of x ={v}B is x = t, where t is an invented temporary variable declared and initialized as T t = B, where T is the scalar type of the expression x, is that of x = T{v}. The meaning of x = {} is x = T{}.- an assignment to an object of class type, in which case
the initializer listB is passed as the argument to the assignment operator function selected by overload resolution (12.4.3.2 [over.assign], 12.2 [over.match]).
[Accepted as a DR at the June, 2023 meeting.]
Paper P2242 (Non-literal variables (and labels and gotos) in constexpr functions) added 7.7 [expr.const] bullet 5.2:
- a control flow that passes through a declaration of a variable with static (6.7.6.2 [basic.stc.static]) or thread (6.7.6.3 [basic.stc.thread]) storage duration;
It seems that block-scope extern (i.e. non-defining) declarations are covered by the above bullet, but only definitions should be in view here.
Proposed resolution (approved by CWG 2023-06-15):
Change in 7.7 [expr.const] bullet 5.2 as follows:
- a control flow that passes through a declaration of a block variable (6.4.3 [basic.scope.block]) with static (6.7.6.2 [basic.stc.static]) or thread (6.7.6.3 [basic.stc.thread]) storage duration;
[Accepted as a DR at the June, 2023 meeting.]
Iteration statements such as while and for loops are specified by equivalent code involving goto (8.6.2 [stmt.while] paragraph 2, 8.6.4 [stmt.for] paragraph 1, 8.6.5 [stmt.ranged] paragraph 1). The goto statement cannot be evaluated in constant expressions (7.7 [expr.const] bullet 5.30), thus while and for loops cannot be evaluated in constant expressions. Similar concerns arise for continue (8.7.3 [stmt.cont] paragraph 1).
However, that is neither intended nor existing practice.
Suggested resolution [SUPERSEDED]:
Change in 7.7 [expr.const] bullet 5.30 as follows:
An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following:
CWG 2023-03-30
Keep the rule non-normative and non-exhaustive.
Proposed resolution (approved by CWG 2023-04-28):
Change in 7.7 [expr.const] bullet 5.30 as follows:
An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following:
- ...
- a goto statement (8.7.6 [stmt.goto]). [ Note: A goto statement introduced by equivalence (Clause 8 [stmt]) is not in scope. For example, a while statement (8.6.2 [stmt.while]) can be executed during constant evaluation. -- end note ].
[Accepted as a DR at the June, 2023 meeting.]
Subclause 7.7 [expr.const] paragraph 6 talks about invoking the "underlying constructor", but there is no constructor when creating an aggregate or a scalar type.
Proposed resolution:
For the purposes of determining whether an expression E is a core constant expression, the evaluation of the body of a member function of std::allocator<T> as defined in 20.2.10.2 [allocator.members], where T is a literal type, is ignored. Similarly, the evaluation of the body of std::construct_at or std::ranges::construct_at is considered to include only theunderlying constructor callinitialization of the T object if the first argument (of type T*) points to storage allocated with std::allocator<T> or to an object whose lifetime began within the evaluation of E.
[Accepted as a DR at the November, 2023 meeting.]
P2738R1 (constexpr cast from void*: towards constexpr type-erasure) applied incorrect wording to 7.7 [expr.const] bullet 5.14:
- ...
- a conversion from a prvalue P of type ”pointer to cv void” to a pointer-to-object type T unless P points to an object whose type is similar to T;
- ...
The issue is that T is defined to be a pointer type, but the "similar to" phrasing uses it as the pointee type.
Proposed resolution (approved by CWG 2023-07-14):
Change in 7.7 [expr.const] bullet 5.14 as follows:
- ...
- a conversion from a prvalue P of type ”pointer to cv void” to a
pointer-to-objecttypeT"cv1 pointer to T", where T is not cv2 void, unless P points to an object whose type is similar to T;- ...
[Accepted as a DR at the November, 2023 meeting.]
Consider:
consteval int f(int);
struct S {
int x = f(0);
S() = default;
};
int main() {
S s; // OK?
}
Is S an immediate function?
The relevant specification is in 7.7 [expr.const] paragraph 18:
An immediate function is a function or constructor that is
- declared with the consteval specifier, or
- an immediate-escalating function F whose function body contains an immediate-escalating expression E such that E's innermost enclosing non-block scope is F's function parameter scope.
Suggested resolution [SUPERSEDED]:
Change in 7.7 [expr.const] paragraph 18 as follows:
An immediate function is a function or constructor that is
- declared with the consteval specifier, or
- an immediate-escalating function F whose function body contains an immediate-escalating expression E such that E's innermost enclosing non-block scope is F's function parameter scope, or
- an immediate-escalating default constructor of a class which has at least one non-static data member with an immediate-escalating default member initializer.
Proposed resolution (approved by CWG 2023-08-25):
Change in 7.7 [expr.const] paragraph 18 as follows:
An immediate function is a function or constructor that is
- declared with the consteval specifier, or
- an immediate-escalating function F whose function body contains an immediate-escalating expression E such that E's innermost enclosing non-block scope is F's function parameter scope. [ Note: Default member initializers used to initialize a base or member subobject (11.9.3 [class.base.init]) are considered to be part of the function body (9.6.1 [dcl.fct.def.general]). -- end note ]
Change in 9.6.1 [dcl.fct.def.general] paragraph 1 as follows:
Any informal reference to the body of a function should be interpreted as a reference to the non-terminal function-body, including, for a constructor, default member initializers or default initialization used to initialize a base or member subobject in the absence of a mem-initializer-id (11.9.3 [class.base.init]).
[Accepted as a DR at the November, 2023 meeting.]
Subclause 9.13.10 [dcl.attr.noreturn] paragraph 2 specifies:
If a function f is called where f was previously declared with the noreturn attribute and f eventually returns, the behavior is undefined.
Undefineed behavior is, in general, detected during constant evaluation, thus requiring an implementation to actually support the noreturn attribute, such as in the following example:
[[noreturn]] constexpr void f() {} constexpr int x = (f(), 0);
It might be desirable to treat the assume and noreturn attributes alike in that regard.
Proposed resolution (approved by CWG 2023-07-14) [SUPERSEDED]:
Split into a separate paragraph and change 7.7 [expr.const] paragraph 5 as follows:
It is unspecified whether E is a core constant expression if E satisfies the constraints of a core constant expression, but evaluation of E would evaluate
- an operation that has undefined behavior as specified in Clause 16 through Clause 33,
- an invocation of the va_start macro (17.14.2 [cstdarg.syn]),
or- a return statement in a function that was previously declared with the noreturn attribute (9.13.10 [dcl.attr.noreturn]), or
- a statement with an assumption (9.13.3 [dcl.attr.assume]) whose converted conditional-expression, if evaluated where the assumption appears, would not disqualify E from being a core constant expression and would not evaluate to true. [ Note: .... ]
CWG 2023-07-14
As an alternative, all of 9.13 [dcl.attr] could be added to the "library undefined behavior" bullet. However, CWG felt that a case-by-case consideration is warranted, given that assumptions set precedent in requiring special treatment.
Possible resolution (reviewed by CWG 2023-08-25) [SUPERSEDED]:
Split into a separate paragraph and change 7.7 [expr.const] paragraph 5 as follows:
It is unspecified whether E is a core constant expression if E satisfies the constraints of a core constant expression, but evaluation of E would evaluate
- ...
- an operation that would have undefined behavior as specified in Clause 4 [intro] through Clause 15 [cpp], excluding 9.13.3 [dcl.attr.assume] and 9.13.10 [dcl.attr.noreturn]; [ Footnote: ...]
- ...
- an operation that has undefined behavior as specified in Clause 16 through Clause 33,
- an invocation of the va_start macro (17.14.2 [cstdarg.syn]),
or- a return statement in a function that was previously declared with the noreturn attribute (9.13.10 [dcl.attr.noreturn]), or
- a statement with an assumption (9.13.3 [dcl.attr.assume]) whose converted conditional-expression, if evaluated where the assumption appears, would not disqualify E from being a core constant expression and would not evaluate to true. [ Note: .... ]
Proposed resolution (approved by CWG 2023-11-09):
Split into a separate paragraph and change 7.7 [expr.const] paragraph 5 as follows:
It is unspecified whether E is a core constant expression if E satisfies the constraints of a core constant expression, but evaluation of E would evaluate
- ...
- an operation that would have undefined behavior as specified in Clause 4 [intro] through Clause 15 [cpp], excluding 9.13.3 [dcl.attr.assume] and 9.13.10 [dcl.attr.noreturn]; [ Footnote: ...]
- ...
- an operation that has undefined behavior as specified in Clause 16 through Clause 33,
- an invocation of the va_start macro (17.14.2 [cstdarg.syn]),
or- a call to a function that was previously declared with the noreturn attribute (9.13.10 [dcl.attr.noreturn]) and that call returns to its caller, or
- a statement with an assumption (9.13.3 [dcl.attr.assume]) whose converted conditional-expression, if evaluated where the assumption appears, would not disqualify E from being a core constant expression and would not evaluate to true. [ Note: .... ]
[Accepted as a DR at the November, 2023 meeting.]
The message of a static_assert declaration is a conditional-expression and thus is not manifestly constant evaluated. Consider this example:
struct X { std::string s; const char *p; }; consteval X f() { return {.s = "some long string that requires a heap allocation", .p = "hello"}; } static_assert(cond, f().p);
The example is ill-formed, because the immediate invocation f() lets a pointer to the heap escape.
Proposed resolution (approved by CWG 2023-10-06):
Change in 7.7 [expr.const] paragraph 19 as follows:
[Note 11: Except for a static_assert-message, aAmanifestly constant-evaluated expression is evaluated even in an unevaluated operand (7.2.3 [expr.context]). —end note]
Change the grammar in 9.1 [dcl.pre] as follows:
static_assert-message: unevaluated-stringconditional-expressionconstant-expression
Change in 9.1 [dcl.pre] bullet 11.2 as follows:
- ...
- if the static_assert-message is a
conditional-expressionconstant-expression M, ...
[Accepted as a DR at the March, 2024 meeting.]
(From submission #456.)
With the adoption of P1907R1, non-type template parameters of floating-point types have been introduced. It is surprising that a double value cannot be passed as a template argument for a template parameter of type long double, because floating-point conversions are not allowed in converted constant expressions.
Proposed resolution (approved by CWG 2024-02-16):
Change in 7.7 [expr.const] paragraph 12 as follows:
A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains onlyand where the reference binding (if any) binds directly.
- user-defined conversions,
- lvalue-to-rvalue conversions (7.3.2 [conv.lval]),
- array-to-pointer conversions (7.3.3 [conv.array]),
- function-to-pointer conversions (7.3.4 [conv.func]),
- qualification conversions (7.3.6 [conv.qual]),
- integral promotions (7.3.7 [conv.prom]),
- integral conversions (7.3.9 [conv.integral]) other than narrowing conversions (9.5.5 [dcl.init.list]),
- floating-point promotions (7.3.8 [conv.fpprom]),
- floating-point conversions (7.3.10 [conv.double]) where the source value can be represented exactly in the destination type,
- null pointer conversions (7.3.12 [conv.ptr]) from std::nullptr_t,
- null member pointer conversions (7.3.13 [conv.mem]) from std::nullptr_t, and
- function pointer conversions (7.3.14 [conv.fctptr]),
[Accepted as a DR at the November, 2024 meeting.]
(From submission #215.)
Consider:
void f() { std::nullptr_t np; // uninitialized, thus np contains an erroneous value constexpr void *p1 = np; // error: converted initializer is not a constant expression }
The lvalue-to-rvalue conversion on np does not actually read the value of np (7.3.2 [conv.lval] bullet 3.1), yet the situation is made ill-formed by 7.7 [expr.const] bullet 5.9.
Proposed resolution (approved by CWG 2024-10-11):
Change in 7.7 [expr.const] bullet 5.9 as follows:
An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following:
- ...
- an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) unless it is applied to
- a glvalue of type cv std::nullptr_t,
- a non-volatile glvalue that refers to an object that is usable in constant expressions, or
- a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;
- ...
[Accepted as a DR at the November, 2024 meeting.]
Subclause 7.7 [expr.const] paragraph 2 defines "constant-initialized" using the following rule:
A variable or temporary object o is constant-initialized if
- either it has an initializer or its default-initialization results in some initialization being performed, and
- ...
However, the rules for constexpr are slightly different, per 9.2.6 [dcl.constexpr] paragraph 6:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. ...
The difference manifests for an example such as:
struct S {}; int main() { constexpr S s; // OK constexpr S s2 = s; // error: s is not constant-initialized }
Is the difference intentional?
Proposed resolution (approved by CWG 2024-10-25):
Change in 7.7 [expr.const] paragraph 2 as follows:
A variable or temporary object o is constant-initialized if
- either it has an initializer or its
default-initialization results in some initialization being performedtype is const-default-constructible (9.5.1 [dcl.init.general]), and- ...
[Accepted as a DR at the November, 2024 meeting.]
constexpr placement new requires (just) pointer-interconvertibility for the argument pointer, whereas static_cast from void* to T* requires similarity. Requiring pointer-interconvertibility would not allow to differentiate two members of some union of the same type; such differentiation is required to diagnose access to inactive union members.
Proposed resolution (approved by CWG 2024-08-16):
Change in 7.7 [expr.const] bullet 5.18.2 as follows:
- the selected allocation function is a non-allocating form (17.6.3.4 [new.delete.placement]) with an allocated type T, where
- the placement argument to the new-expression points to an object
that is pointer-interconvertible with an object ofwhose type is similar to T (7.3.6 [conv.qual]) or, if T is an array type,withto the first element of an object of a type similar to T, and- the placement argument points to storage whose duration began within the evaluation of E;
CWG 2024-11-19
The proposed resolution does not address the ambiguity with different union members of the same type, but is a good fix to increase consistency with static_cast regardless.
[Accepted as a DR at the March, 2024 meeting.]
Consider:
for (int i : { 1, 2, 3 }) // argument-dependent lookup for begin(std::initializer_list<int>)
/* ... */;
This is undesirable; instead, a member function begin should be preferred.
Proposed resolution (approved by CWG 2023-11-09):
Change in 8.6.5 [stmt.ranged] bullet 1.3 as follows:
begin-expr and end-expr are determined as follows:
- if the
for-range-initializer is an expression oftype of range is a reference to an array type R, begin-expr and end-expr are range and range + N, respectively, where N is the array bound. If R is an array of unknown bound or an array of incomplete type, the program is ill-formed;- if the
for-range-initializer is an expression oftype of range is a reference to a class type C, and searches in the scope of C (6.5.2 [class.member.lookup]) for the names begin and end each find at least one declaration, begin-expr and end-expr are range .begin() and range .end(), respectively;- otherwise, begin-expr and end-expr are begin(range ) and end(range ), respectively, where begin and end undergo argument-dependent lookup (6.5.4 [basic.lookup.argdep]). [Note 1: Ordinary unqualified lookup (6.5.3 [basic.lookup.unqual]) is not performed. —end note]
Additional notes (November, 2023)
Forwarded to EWG via paper issue 1694 for confirmation of the design direction.
EWG 2023-11-09
Approved by EWG.
[Accepted as a DR at the November, 2023 meeting.]
In 8.7.4 [stmt.return] and 8.7.5 [stmt.return.coroutine], the standard uses the phrasing "returns to its caller" when specifying return or co_return. It would be better to talk about transfer of control, which is a term used elsewhere in the standard.
Proposed resolution (approved by CWG 2023-10-06):
Change in 7.6.2.4 [expr.await] paragraph 1 as follows:
The co_await expression is used to suspend evaluation of a coroutine (9.6.4 [dcl.fct.def.coroutine]) while awaiting completion of the computation represented by the operand expression. Suspending the evaluation of a coroutine transfers control to its caller or resumer.
Change 8.7.4 [stmt.return] paragraph 1 as follows:
A function returns control to its caller by the return statement.
Change 8.7.5 [stmt.return.coroutine] paragraph 1 as follows:
A coroutine returns to its caller or resumer (9.6.4 [dcl.fct.def.coroutine]) by the co_return statement or when suspended (7.6.2.4 [expr.await]).A co_return statement transfers control to the caller or resumer of a coroutine (9.6.4 [dcl.fct.def.coroutine]). A coroutine shall not enclose a return statement (8.7.4 [stmt.return]).
Change in 9.6.4 [dcl.fct.def.coroutine] paragraph 10 as follows:
If the allocation function returns nullptr, the coroutinereturnstransfers control to the caller of the coroutine and the return value is obtained by a call to T::get_return_object_on_allocation_failure(), where T is the promise type.
[Accepted as a DR at the November, 2023 meeting.]
Subclause 8.7.5 [stmt.return.coroutine] paragraph 3 specifies:
If p.return_void() is a valid expression, flowing off the end of a coroutine's function-body is equivalent to a co_return with no operand; otherwise flowing off the end of a coroutine's function-body results in undefined behavior.
However, 9.6.4 [dcl.fct.def.coroutine] paragraph 6 suggests:
If searches for the names return_void and return_value in the scope of the promise type each find any declarations, the program is ill-formed. [Note: If return_void is found, flowing off the end of a coroutine is equivalent to a co_return with no operand. Otherwise, flowing off the end of a coroutine results in undefined behavior (8.7.5 [stmt.return.coroutine]). —end note]
The difference is between the conditions "valid expression" and "found by name lookup". Effectively, it means that undefined behavior might result where the implementation could instead diagnose an ill-formed use of return_void (for example, because it is inaccessible, deleted, or the function call requires arguments).
Proposed resolution (approved by CWG 2023-06-17):
Change in 8.7.5 [stmt.return.coroutine] paragraph 3 as follows:
Ifp.return_void() is a valid expressiona search for the name return_void in the scope of the promise type finds any declarations, flowing off the end of a coroutine's function-body is equivalent to a co_return with no operand; otherwise flowing off the end of a coroutine's function-body results in undefined behavior.
Because the restriction that a trailing-return-type can appear only in a declaration with “the single type-specifier auto” (9.3.4.6 [dcl.fct] paragraph 2) is a semantic, not a syntactic, restriction, it does not influence disambiguation, which is “purely syntactic” (8.10 [stmt.ambig] paragraph 3). Consequently, some previously unambiguous expressions are now ambiguous. For example:
struct A { A(int *); A *operator()(void); int B; }; int *p; typedef struct BB { int C[2]; } *B, C; void foo() { // The following line becomes invalid under C++0x: A (p)()->B; // ill-formed function declaration // In the following, // - B()->C is either type-id or class member access expression // - B()->C[1] is either type-id or subscripting expression // N3126 subclause 8.2 [dcl.ambig.res] does not mention an ambiguity // with these forms of expression A a(B ()->C); // function declaration or object declaration sizeof(B ()->C[1]); // sizeof(type-id) or sizeof on an expression }
Notes from the March, 2011 meeting:
CWG agreed that the presence of auto should be considered in disambiguation, even though it is formally handled semantically rather than syntactically.
CWG 2023-05-12
Both 8.10 [stmt.ambig] and 9.3.3 [dcl.ambig.res] need to be adjusted.
CWG 2023-06-13
Addressed by paper P2915R0, approved in June, 2023.
[Accepted as a DR at the November, 2023 meeting.]
C++17 made constexpr static data members implicitly inline (9.2.6 [dcl.constexpr] paragraph 1):
A function or static data member declared with the constexpr or consteval specifier is implicitly an inline function or variable (9.2.8 [dcl.inline]).
However, that makes the following well-formed C++14 program ill-formed, no diagnostic required, per 9.2.8 [dcl.inline] paragraph 5:
If a function or variable with external or module linkage is declared inline in one definition domain, an inline declaration of it shall be reachable from the end of every definition domain in which it is declared; no diagnostic is required.
// x.hh struct X { static const int x; }; // TU 1 #include "x.hh" constexpr int X::x{}; // TU 2 #include "x.hh" int main() { return !&X::x; }
Proposed resolution (reviewed by CWG 2023-02-07, approved by CWG 2023-11-07):
Change 9.2.6 [dcl.constexpr] paragraph 1 as follows:
A function or static data member declared with the constexpr or consteval specifier on its first declaration is implicitly an inline function or variable (9.2.8 [dcl.inline]).
Drafting note: Functions must be declared constexpr on every declaration if on any, so this isn't a change for them.
[Accepted as a DR at the March, 2024 meeting.]
Consider:
auto f(struct X* ptr) { struct D { private: int d; friend class X; // #1 }; return D{}; } X* b = 0; struct X { void show() { auto t = f(0); t.d = 10; // #2 error: ::X is not a friend of f::D } };
The target scope for #2 is f's block scope, making ::X not a friend of f::D. Thus the access at #2 is ill-formed. Clang disagrees.
Subclause 9.2.9.5 [dcl.type.elab] paragraph 3 specifies:
... If E contains an identifier but no nested-name-specifier and (unqualified) lookup for the identifier finds nothing, E shall not be introduced by the enum keyword and declares the identifier as a class-name. The target scope of E is the nearest enclosing namespace or block scope.
If an elaborated-type-specifier appears with the friend specifier as an entire member-declaration, the member-declaration shall have one of the following forms:
friend class-key nested-name-specifieropt identifier ; ...Any unqualified lookup for the identifier (in the first case) does not consider scopes that contain the target scope; no name is bound.
This specification is circular in that the target scope that limits unqualified lookup is defined only if the identifier is actually declared, but the identifier is declared only if lookup finds nothing.
Proposed resolution (approved by CWG 2023-11-11):
Change in 9.2.9.5 [dcl.type.elab] paragraph 4 as follows:
... Any unqualified lookup for the identifier (in the first case) does not consider scopes that contain thetargetnearest enclosing namespace or block scope; no name is bound. [ Note: ... ]
[Accepted as a DR at the June, 2024 meeting.]
(From submission #521.)
Consider:
namespace N { template<typename T> struct A; } template<> struct N::A<int>; // OK template<typename T> struct N::A<T*>; // error: invalid use of elaborated-type-specifier with a qualified-id
However, all major implementations support this.
Proposed resolution (approved by CWG 2024-05-17):
Change in 9.2.9.5 [dcl.type.elab] paragraph 2 as follows:
If an elaborated-type-specifier is the sole constituent of a declaration, the declaration is ill-formed unless it is an explicit specialization (13.9.4 [temp.expl.spec]), a partial specialization (13.7.6 [temp.spec.partial]), an explicit instantiation (13.9.3 [temp.explicit]), or it has one of the following forms:class-key attribute-specifier-seqopt identifier ; class-key attribute-specifier-seqopt simple-template-id ;In the first case, the elaborated-type-specifier declares the identifier as a class-name. The second case shall appear only in an explicit-specialization (13.9.4 [temp.expl.spec]) or in a template-declaration (where it declares a partial specialization(13.7 [temp.decls]). The attribute-specifier-seq, if any, appertains to the class or template being declared.
[Accepted as a DR at the March, 2024 meeting.]
According to 9.2.9.7.1 [dcl.spec.auto.general] paragraph 3,
The placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such a declarator is valid. If the function declarator includes a trailing-return-type (9.3.4.6 [dcl.fct]), that trailing-return-type specifies the declared return type of the function. Otherwise, the function declarator shall declare a function.
This wording disallows a declaration like
int f(); auto (*fp)()=f;
The requirement to declare a function was introduced by the resolution of issue 1892.
Proposed resolution (April, 2021) [SUPERSEDED]:
Change 9.2.9.7.1 [dcl.spec.auto.general] paragraph 3 as follows:
The placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such a declarator is validif the function declarator includes a trailing-return-type T (9.3.4.6 [dcl.fct]) or declares a function.If the function declarator includes a trailing-return-type (9.3.4.6 [dcl.fct]), that trailing-return-type specifiesIn the former case, T is the declared return type of the function.Otherwise, the function declarator shall declare a function.If the declared return type ofthea function contains a placeholder type, the return type of the function is deduced from non-discarded return statements, if any, in the body of the function (8.5.2 [stmt.if]).
Additional notes (May, 2021):
It was observed that the proposed resolution above does not address the example in the issue, since fp neither has a trailing-return-type nor declares a function. Presumably another case in which a function declarator with a placeholder return type should be permitted is in the declaration of a variable in which the type is deduced from its initializer.
It was also noted in passing that the deduction in the example is only partial: the parameter-type-list is specified by the declarator and only the return type is deduced from the initializer. Although this example is supported by current implementations, there is implementation divergence in the support of another case in which only part of the variable's type is deduced:
auto (&ar)[2] = L"a"; // Array bound declared, element type deduced
This issue is related to issue 1892, which prohibited cases like
std::vector<auto(*)()> v;
The ultimate outcome of the two issues should be:
int f(); auto (*fp1)() = f; // OK auto (*fp2)()->int = f; // OK auto (*fp3)()->auto = f; // OK template<typename T> struct C { }; C<auto(*)()> c1; // Not OK C<auto(*)()->int> c2; // OK C<auto(*)()->auto> c3; // Not OK
Proposed resolution (January, 2023) [SUPERSEDED]:
Change in 9.2.9.7.1 [dcl.spec.auto.general] paragraph 1 as follows:
A placeholder-type-specifier designates a placeholder type that will be replaced later, typically by deduction from an initializer.
Change and split 9.2.9.7.1 [dcl.spec.auto.general] paragraph 3 as follows:
A placeholder type can appear
with a function declaratorin the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where suchfor a function declaratoris validthat includes a trailing-return-type (9.3.4.6 [dcl.fct]).If the function declarator includes a trailing-return-type (9.3.4.6 [dcl.fct]), that trailing-return-type specifies the declared return type of the function.
Otherwise, theA placeholder type can appear in the decl-specifier-seq or type-specifier-seq in the declared return type of a function declaratorshall declarethat declares a function. If the declared return type of the function contains a placeholder type,; the return type of the function is deduced from non-discarded return statements, if any, in the body of the function (8.5.2 [stmt.if]).
Change in 9.2.9.7.1 [dcl.spec.auto.general] paragraph 4 as follows:
The type of a variable declared using a placeholder type is deduced from its initializer. This use is allowed in an initializing declaration (9.5 [dcl.init]) of a variable. The placeholder type shall appear as one of the decl-specifiers in the decl-specifier-seqandor as one of the type-specifiers in a trailing-return-type that specifies the type that replaces such a decl-specifier; the decl-specifier-seq shall be followed by one or more declarators, each of which shall be followed by a non-empty initializer. [ Example:... auto f() -> int; // OK, f returns int auto (*fp)() -> auto = f; // OK ...-- end example ]
Change in 9.3.4.6 [dcl.fct] paragraph 1 as follows:
In a declaration T D where D has the formD1 ( parameter-declaration-clause ) cv-qualifier-seqopt ref-qualifieropt noexcept-specifieropt attribute-specifier-seqopt trailing-return-typeoptand the type of the contained declarator-id in the declaration T D1 is "derived-declarator-type-list T",:
- If the trailing-return-type is present, T shall be the single type-specifier auto, and the declared return type of the function type is the type specified by the trailing-return-type.
- Otherwise, the declared return type of the function type is T.
theThe type of the declarator-id in D is "derived-declarator-type-list noexceptopt function of parameter-type-list cv-qualifier-seqopt ref-qualifieropt returningTU", whereThe optional attribute-specifier-seq appertains to the function type.
- the parameter-type-list is derived from the parameter-declaration-clause as described below,
- U is the declared return type, and
- the optional noexcept is present if and only if the exception specification (14.5 [except.spec]) is non-throwing.
Remove 9.3.4.6 [dcl.fct] paragraph 2:
In a declaration T D where D has the formand the type ... The optional attribute-specifier-seq appertains to the function type.D1 ( parameter-declaration-clause ) cv-qualifier-seqopt ref-qualifieropt noexcept-specifieropt attribute-specifier-seqopt trailing-return-type
Change in 11.4.8.3 [class.conv.fct] paragraph 1 as follows:
A declaration whose declarator-id has an unqualified-id that is a conversion-function-id declares a conversion function; its declarator shall be a function declarator (9.3.4.6 [dcl.fct]) of the formwhere theptr-declaratornoptr-declarator( parameter-declaration-clause ) cv-qualifier-seqopt ref-qualifier-seqopt noexcept-specifieropt attribute-specifier-seqoptparameters-and-qualifiersptr-declaratornoptr-declarator consists solely of an id-expression, an optional attribute-specifier-seq, and optional surrounding parentheses, and the id-expression has one of the following forms: ...
Change in 11.4.8.3 [class.conv.fct] paragraph 2 as follows:
A conversion function shall have no non-object parameters and shall be a non-static member function of a class or class template X; its declared return type is the conversion-type-id and it specifies a conversion from X to the type specified by the conversion-type-id interpreted as a type-id (9.3.2 [dcl.name]). A decl-specifier in the decl-specifier-seq of a conversion function (if any) shall not be a defining-type-specifier .
Remove 11.4.8.3 [class.conv.fct] paragraph 3:
The type of the conversion function is “noexceptopt function taking no parameter cv-qualifier-seq opt ref-qualifier opt returning conversion-type-id”.
CWG 2023-06
This does not address void f2(auto (*)() -> auto);
Proposed resolution (approved by CWG 2023-11-10):
Change in 9.2.9.7.1 [dcl.spec.auto.general] paragraph 1 as follows:
A placeholder-type-specifier designates a placeholder type that will be replaced later, typically by deduction from an initializer.
Change 9.2.9.7.1 [dcl.spec.auto.general] paragraph 2 as follows:
AThe type of a parameter-declaration of a function declaration (9.3.4.6 [dcl.fct]), lambda-expression (7.5.6 [expr.prim.lambda]), or template-parameter (13.2 [temp.param]) can be declared using a placeholder-type-specifier of the form type-constraintopt autocan be used as a decl-specifier of the decl-specifier-seq of a parameter-declaration of a function declaration or lambda-expression and, if it is not the auto type-specifier introducing. The placeholder type shall appear as one of the decl-specifiers in the decl-specifier-seq or as one of the type-specifiers in a trailing-return-type,that specifies the type that replaces such a decl-specifier (see below); the placeholder type is a generic parameter type placeholder of the function declarationor, lambda-expression, or template-parameter, respectively.
Change and split 9.2.9.7.1 [dcl.spec.auto.general] paragraph 3 as follows:
A placeholder type can appear
with a function declaratorin the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where suchfor a function declaratoris validthat includes a trailing-return-type (9.3.4.6 [dcl.fct]).If the function declarator includes a trailing-return-type (9.3.4.6 [dcl.fct]), that trailing-return-type specifies the declared return type of the function.
Otherwise, theA placeholder type can appear in the decl-specifier-seq or type-specifier-seq in the declared return type of a function declaratorshall declarethat declares a function. If the declared return type of the function contains a placeholder type,; the return type of the function is deduced from non-discarded return statements, if any, in the body of the function (8.5.2 [stmt.if]).
Change in 9.2.9.7.1 [dcl.spec.auto.general] paragraph 4 as follows:
The type of a variable declared using a placeholder type is deduced from its initializer. This use is allowed in an initializing declaration (9.5 [dcl.init]) of a variable. The placeholder type shall appear as one of the decl-specifiers in the decl-specifier-seqandor as one of the type-specifiers in a trailing-return-type that specifies the type that replaces such a decl-specifier; the decl-specifier-seq shall be followed by one or more declarators, each of which shall be followed by a non-empty initializer. [ Example:... auto f() -> int; // OK, f returns int auto (*fp)() -> auto = f; // OK ...-- end example ]
Change and split 9.2.9.7.1 [dcl.spec.auto.general] paragraph 5 as follows:
A placeholder type can also be used in the type-specifier-seq
inof the new-type-id or in the type-id of a new-expression (7.6.2.8 [expr.new])and as a decl-specifier of the parameter-declaration's decl-specifier-seq in a template-parameter (13.2 [temp.param]). In such a type-id, the placeholder type shall appear as one of the type-specifiers in the type-specifier-seq or as one of the type-specifiers in a trailing-return-type that specifies the type that replaces such a type-specifier.The auto type-specifier can also be used as the simple-type-specifier in an explicit type conversion (functional notation) (7.6.1.4 [expr.type.conv]).
Change in 9.3.4.6 [dcl.fct] paragraph 1 as follows:
In a declaration T D where T may be empty and D has the formD1 ( parameter-declaration-clause ) cv-qualifier-seqopt ref-qualifieropt noexcept-specifieropt attribute-specifier-seqopt trailing-return-typeopta derived-declarator-type-list is determined as follows:andThe declared return type U of the function type is determined as follows:
- If the unqualified-id of the declarator-id is a conversion-function-id, the derived-declarator-type-list is empty.
- Otherwise, the derived-declarator-type-list is as appears in the type "derived-declarator-type-list T" of the contained declarator-id in the declaration T D1
is "derived-declarator-type-list T".
- If the trailing-return-type is present, T shall be the single type-specifier auto, and U is the type specified by the trailing-return-type.
- Otherwise, if the declaration declares a conversion function, see 11.4.8.3 [class.conv.fct].
- Otherwise, U is T.
theThe type of the declarator-id in D is "derived-declarator-type-list noexceptopt function of parameter-type-list cv-qualifier-seqopt ref-qualifieropt returningTU", whereThe optional attribute-specifier-seq appertains to the function type.
- the parameter-type-list is derived from the parameter-declaration-clause as described below and
- the optional noexcept is present if and only if the exception specification (14.5 [except.spec]) is non-throwing.
Remove 9.3.4.6 [dcl.fct] paragraph 2:
In a declaration T D where D has the formand the type ... The optional attribute-specifier-seq appertains to the function type.D1 ( parameter-declaration-clause ) cv-qualifier-seqopt ref-qualifieropt noexcept-specifieropt attribute-specifier-seqopt trailing-return-type
Change in 11.4.8.3 [class.conv.fct] paragraph 1 as follows:
A declaration whose declarator-id has an unqualified-id that is a conversion-function-id declares a conversion function; its declarator shall be a function declarator (9.3.4.6 [dcl.fct]) of the formwhere theptr-declaratornoptr-declarator( parameter-declaration-clause ) cv-qualifier-seqopt ref-qualifier-seqopt noexcept-specifieropt attribute-specifier-seqoptparameters-and-qualifiersptr-declaratornoptr-declarator consists solely of an id-expression, an optional attribute-specifier-seq, and optional surrounding parentheses, and the id-expression has one of the following forms: ...
Change in 11.4.8.3 [class.conv.fct] paragraph 2 as follows:
A conversion function shall have no non-object parameters and shall be a non-static member function of a class or class template X; its declared return type is the conversion-type-id and it specifies a conversion from X to the type specified by the conversion-type-id interpreted as a type-id (9.3.2 [dcl.name]). A decl-specifier in the decl-specifier-seq of a conversion function (if any) shall not be a defining-type-specifier .
Remove 11.4.8.3 [class.conv.fct] paragraph 3:
The type of the conversion function is “noexceptopt function taking no parameter cv-qualifier-seq opt ref-qualifier opt returning conversion-type-id”.
[Accepted as a DR at the March, 2024 meeting.]
(From editorial issue 3936.)
Subclause 9.3.1 [dcl.decl.general] paragraph 4 specifies:
The optional requires-clause in an init-declarator or member-declarator shall be present only if the declarator declares a templated function (13.1 [temp.pre]). ...
This rule does not address function definitions, because those have neither an init-declarator nor a member-declarator.
Proposed resolution (approved by CWG 2024-03-20):
Change in 9.6.1 [dcl.fct.def.general] paragraph 1 as follows:
... The optional attribute-specifier-seq in a function-definition appertains to the function. Avirt-specifier-seq can be part of a function-definition only if it isfunction-definition with a virt-specifier-seq shall be a member-declaration (11.4 [class.mem]). A function-definition with a requires-clause shall define a templated function.
[Accepted as a DR at the March, 2024 meeting.]
9.3.4.3 [dcl.ref] paragraph 4 says:
A reference shall be initialized to refer to a valid object or function. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the "object" obtained by dereferencing a null pointer, which causes undefined behavior ...]
What is a "valid" object? In particular the expression "valid object" seems to exclude uninitialized objects, but the response to Core Issue 363 clearly says that's not the intent. This is an example (overloading construction on constness of *this) by John Potter, which I think is supposed to be legal C++ though it binds references to objects that are not initialized yet:
struct Fun { int x, y; Fun (int x, Fun const&) : x(x), y(42) { } Fun (int x, Fun&) : x(x), y(0) { } }; int main () { const Fun f1 (13, f1); Fun f2 (13, f2); cout << f1.y << " " << f2.y << "\n"; }
Suggested resolution: Changing the final part of 9.3.4.3 [dcl.ref] paragraph 4 to:
A reference shall be initialized to refer to an object or function. From its point of declaration on (see 6.4.2 [basic.scope.pdecl]) its name is an lvalue which refers to that object or function. The reference may be initialized to refer to an uninitialized object but, in that case, it is usable in limited ways (6.7.4 [basic.life], paragraph 6) [Note: On the other hand, a declaration like this:int & ref = *(int*)0;is ill-formed because ref will not refer to any object or function ]
I also think a "No diagnostic is required." would better be added (what about something like int& r = r; ?)
Proposed Resolution (October, 2004) [SUPERSEDED]:
(Note: the following wording depends on the proposed resolution for issue 232.)
Change 9.3.4.3 [dcl.ref] paragraph 4 as follows:
A reference shall be initialized to refer to a valid object or function.If an lvalue to which a reference is directly bound designates neither an existing object or function of an appropriate type (9.5.4 [dcl.init.ref]), nor a region of memory of suitable size and alignment to contain an object of the reference's type (6.7.2 [intro.object], 6.7.4 [basic.life], 6.8 [basic.types]), the behavior is undefined. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the“object”empty lvalue obtained by dereferencing a null pointer, whichcauses undefined behavior. Asdoes not designate an object or function. Also, as described in 11.4.10 [class.bit], a reference cannot be bound directly to a bit-field. ]The name of a reference shall not be used in its own initializer. Any other use of a reference before it is initialized results in undefined behavior. [Example:
int& f(int&); int& g(); extern int& ir3; int* ip = 0; int& ir1 = *ip; // undefined behavior: null pointer int& ir2 = f(ir3); // undefined behavior: ir3 not yet initialized int& ir3 = g(); int& ir4 = f(ir4); // ill-formed: ir4 used in its own initializer—end example]
Rationale: The proposed wording goes beyond the specific concerns of the issue. It was noted that, while the current wording makes cases like int& r = r; ill-formed (because r in the initializer does not "refer to a valid object"), an inappropriate initialization can only be detected, if at all, at runtime and thus "undefined behavior" is a more appropriate treatment. Nevertheless, it was deemed desirable to continue to require a diagnostic for obvious compile-time cases.
It was also noted that the current Standard does not say anything about using a reference before it is initialized. It seemed reasonable to address both of these concerns in the same wording proposed to resolve this issue.
Notes from the April, 2005 meeting:
The CWG decided that whether to require an implementation to diagnose initialization of a reference to itself should be handled as a separate issue (504) and also suggested referring to “storage” instead of “memory” (because 6.7.2 [intro.object] defines an object as a “region of storage”).
Proposed Resolution (April, 2005) [SUPERSEDED]:
(Note: the following wording depends on the proposed resolution for issue 232.)
Change 9.3.4.3 [dcl.ref] paragraph 4 as follows:
A reference shall be initialized to refer to a valid object or function.If an lvalue to which a reference is directly bound designates neither an existing object or function of an appropriate type (9.5.4 [dcl.init.ref]), nor a region of storage of suitable size and alignment to contain an object of the reference's type (6.7.2 [intro.object], 6.7.4 [basic.life], 6.8 [basic.types]), the behavior is undefined. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the“object”empty lvalue obtained by dereferencing a null pointer, whichcauses undefined behavior. Asdoes not designate an object or function. Also, as described in 11.4.10 [class.bit], a reference cannot be bound directly to a bit-field. ]Any use of a reference before it is initialized results in undefined behavior. [Example:
int& f(int&); int& g(); extern int& ir3; int* ip = 0; int& ir1 = *ip; // undefined behavior: null pointer int& ir2 = f(ir3); // undefined behavior: ir3 not yet initialized int& ir3 = g(); int& ir4 = f(ir4); // undefined behavior: ir4 used in its own initializer—end example]
Note (February, 2006):
The word “use” in the last paragraph of the proposed resolution was intended to refer to the description in 6.3 [basic.def.odr] paragraph 2. However, that section does not define what it means for a reference to be “used,” dealing only with objects and functions. Additional drafting is required to extend 6.3 [basic.def.odr] paragraph 2 to apply to references.
Additional note (May, 2008):
The proposed resolution for issue 570 adds wording to define “use” for references.
Note, January, 2012:
The resolution should also probably deal with the fact that the “one-past-the-end” address of an array does not designate a valid object (even if such a pointer might “point to” an object of the correct type, per 6.8.4 [basic.compound]) and thus is not suitable for the lvalue-to-rvalue conversion.
CWG 2023-11-06
We need a (possibly out-of-lifetime) object, not just a region of storage here. Empty lvalues do not exist. Otherwise, the direction is confirmed.
Proposed resolution (approved by CWG 2023-11-10) [SUPERSEDED]:
Change in 7.2.1 [basic.lval] paragraph 11 as follows:
An object of dynamic type Tobj is type-accessible through a glvalue of type Tref if Tref is similar (7.3.6 [conv.qual]) to:
If a program attempts to access (3.1 [defns.access]) the stored value of an object through a glvalue
- Tobj,
- a type that is the signed or unsigned type corresponding to Tobj, or
- a char, unsigned char, or std:byte type.
whose type is not similar (7.3.6 [conv.qual]) to one of the following typesthrough which it is not type-accessible, the behavior is undefined:.[ Footnote: ... ]If a program invokes a defaulted copy/move constructor or copy/move assignment operator for a union of type U with a glvalue argument that does not denote an object of type cv U within its lifetime, the behavior is undefined.
- the dynamic type of the object,
- a type that is the signed or unsigned type corresponding to the dynamic type of the object, or
- a char, unsigned char, or std::byte type.
Change in 7.6.1.3 [expr.call] paragraph 5 as follows:
A type Tcall is call-compatible with a function type Tfunc if Tcall is the same type as Tfunc or if the type "pointer to Tfunc" can be converted to type "pointer to Tcall" via a function pointer conversion (7.3.14 [conv.fctptr]). Calling a function through an expression whose function typeE is different from the functionis not call-compatible with the typeFof the called function's definition results in undefined behaviorunless the type “pointer to F” can be converted to the type “pointer to E” via a function pointer conversion (7.3.14 [conv.fctptr]). [Note 4:The exception appliesThis requirement allows the case when the expression has the type of a potentially-throwing function, but the called function has a non-throwing exception specification, and the function types are otherwise the same. —end note]
Change and split in 9.3.4.3 [dcl.ref] paragraph 5 as follows:
There shall be no references to references, no arrays of references, and no pointers to references. The declaration of a reference shall contain an initializer (9.5.4 [dcl.init.ref]) except when the declaration contains an explicit extern specifier (9.2.2 [dcl.stc]), is a class member (11.4 [class.mem]) declaration within a class definition, or is the declaration of a parameter or a return type (9.3.4.6 [dcl.fct]); see 6.2 [basic.def].
A reference shall be initialized to refer to a valid object or function.Attempting to bind a reference to a function where the initializer is a glvalue whose type is not call-compatible (7.6.1.3 [expr.call]) with the type of the function's definition results in undefined behavior. Attempting to bind a reference to an object where the initializer is a glvalue through which the object is not type-accessible (7.2.1 [basic.lval]) results in undefined behavior. [Note 2:
In particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by indirection through a null pointer, which causes undefined behavior.The object designated by such a glvalue can be outside its lifetime (6.7.4 [basic.life]). Because a null pointer value or a pointer past the end of an object does not point to an object, a reference in a well-defined program cannot refer to such things; see 7.6.2.2 [expr.unary.op]. As described in 11.4.10 [class.bit], a reference cannot be bound directly to a bit-field. —end note] An odr-use (6.3 [basic.def.odr]) of a reference that does not happen after (6.9.2.2 [intro.races]) its initialization results in undefined behavior. [ Example:int &f(int&); int &g(); extern int &ir3; int *ip = 0; int &ir1 = *ip; // undefined behavior: null pointer int &ir2 = f(ir3); // undefined behavior: ir3 not yet initialized int &ir3 = g(); int &ir4 = f(ir4); // undefined behavior: ir4 used in its own initializer-- end example ]
Additional notes (November, 2023):
An odr-use is a property of the program, not an evaluation that participates in the "happens before" relation.
Proposed resolution (approved by CWG 2024-03-20):
Change in 7.2.1 [basic.lval] paragraph 11 as follows:
An object of dynamic type Tobj is type-accessible through a glvalue of type Tref if Tref is similar (7.3.6 [conv.qual]) to:
If a program attempts to access (3.1 [defns.access]) the stored value of an object through a glvalue
- Tobj,
- a type that is the signed or unsigned type corresponding to Tobj, or
- a char, unsigned char, or std:byte type.
whose type is not similar (7.3.6 [conv.qual]) to one of the following typesthrough which it is not type-accessible, the behavior is undefined:.[ Footnote: ... ]If a program invokes a defaulted copy/move constructor or copy/move assignment operator for a union of type U with a glvalue argument that does not denote an object of type cv U within its lifetime, the behavior is undefined.
- the dynamic type of the object,
- a type that is the signed or unsigned type corresponding to the dynamic type of the object, or
- a char, unsigned char, or std::byte type.
Change in 7.6.1.3 [expr.call] paragraph 5 as follows:
A type Tcall is call-compatible with a function type Tfunc if Tcall is the same type as Tfunc or if the type "pointer to Tfunc" can be converted to type "pointer to Tcall" via a function pointer conversion (7.3.14 [conv.fctptr]). Calling a function through an expression whose function typeE is different from the functionis not call-compatible with the typeFof the called function's definition results in undefined behaviorunless the type “pointer to F” can be converted to the type “pointer to E” via a function pointer conversion (7.3.14 [conv.fctptr]). [Note 4:The exception appliesThis requirement allows the case when the expression has the type of a potentially-throwing function, but the called function has a non-throwing exception specification, and the function types are otherwise the same. —end note]
Change and split in 9.3.4.3 [dcl.ref] paragraph 5 as follows:
There shall be no references to references, no arrays of references, and no pointers to references. The declaration of a reference shall contain an initializer (9.5.4 [dcl.init.ref]) except when the declaration contains an explicit extern specifier (9.2.2 [dcl.stc]), is a class member (11.4 [class.mem]) declaration within a class definition, or is the declaration of a parameter or a return type (9.3.4.6 [dcl.fct]); see 6.2 [basic.def].
A reference shall be initialized to refer to a valid object or function.Attempting to bind a reference to a function where the converted initializer is a glvalue whose type is not call-compatible (7.6.1.3 [expr.call]) with the type of the function's definition results in undefined behavior. Attempting to bind a reference to an object where the converted initializer is a glvalue through which the object is not type-accessible (7.2.1 [basic.lval]) results in undefined behavior. [Note 2:
In particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by indirection through a null pointer, which causes undefined behavior.The object designated by such a glvalue can be outside its lifetime (6.7.4 [basic.life]). Because a null pointer value or a pointer past the end of an object does not point to an object, a reference in a well-defined program cannot refer to such things; see 7.6.2.2 [expr.unary.op]. As described in 11.4.10 [class.bit], a reference cannot be bound directly to a bit-field. —end note] The behavior of an evaluation of a reference (7.5.5 [expr.prim.id], 7.6.1.5 [expr.ref]) that does not happen after (6.9.2.2 [intro.races]) the initialization of the reference is undefined. [ Example:int &f(int&); int &g(); extern int &ir3; int *ip = 0; int &ir1 = *ip; // undefined behavior: null pointer int &ir2 = f(ir3); // undefined behavior: ir3 not yet initialized int &ir3 = g(); int &ir4 = f(ir4); // undefined behavior: ir4 used in its own initializer char x alignas(int); int &ir5 = *reinterpret_cast<int *>(&x); // undefined behavior: initializer refers to char object-- end example ]
[Accepted as a DR at the June, 2023 meeting.]
9.3.4.3 [dcl.ref] paragraph 1 specifies:
A declarator that specifies the type “reference to cv void” is ill-formed.
A declarator does not contain the leading decl-specifier-seq of a declaration, so the following example is not covered by the prohibition:
void f(void& x);
Proposed resolution (approved by CWG 2023-06-15):
Change in 9.3.4.3 [dcl.ref] paragraph 1 as follows:
A declarator that specifiesForming the type “reference to cv void” is ill-formed.
[Accepted as a DR at the March, 2024 meeting.]
(From submission #493.)
Consider:
struct A {
void f(this A&);
};
void A::f(this A&) { } // #1
This is accepted by all major implementations. However, 9.3.4.6 [dcl.fct] paragraph 6 specifies:
An explicit-object-parameter-declaration is a parameter-declaration with a this specifier. An explicit-object-parameter-declaration shall appear only as the first parameter-declaration of a parameter-declaration-list of either:A member-declarator with an explicit-object-parameter-declaration shall not include a ref-qualifier or a cv-qualifier-seq and shall not be declared static or virtual.
- a member-declarator that declares a member function (11.4 [class.mem]), or
- a lambda-declarator (7.5.6 [expr.prim.lambda]).
The function-definition at #1 is neither of the two allowed options.
Similar concerns arise for explicit instantiations and explicit specializations.
Proposed resolution (approved by CWG 2024-02-16):
Change in 9.3.4.6 [dcl.fct] paragraph 6 as follows:
An explicit-object-parameter-declaration is a parameter-declaration with a this specifier. An explicit-object-parameter-declaration shall appear only as the first parameter-declaration of a parameter-declaration-list ofeitherone of:A member-declarator with an explicit-object-parameter-declaration shall not include a ref-qualifier or a cv-qualifier-seq and shall not be declared static or virtual.
- a
member-declarator that declaresdeclaration of a member function or member function template (11.4 [class.mem]), or- an explicit instantiation (13.9.3 [temp.explicit]) or explicit specialization (13.9.4 [temp.expl.spec]) of a templated member function, or
- a lambda-declarator (7.5.6 [expr.prim.lambda]).
[Accepted as a DR at the November, 2024 meeting.]
(From submission #578.)
Consider:
struct A { void f(this void); };
This ought to be an ill-formed parameter of type void.
Proposed resolution (approved by CWG 2024-08-16):
Change in 9.3.4.6 [dcl.fct] paragraph 3 as follows:
If the parameter-declaration-clause is empty, the function takes no arguments. A parameter list consisting of a single unnamed non-object parameter of non-dependent type void is equivalent to an empty parameter list. Except for this special case, a parameter shall not have type cv void.
[Accepted as a DR at the June, 2023 meeting.]
Subclause 9.3.4.7 [dcl.fct.default] paragraph 6 specifies:
Except for member functions of class templates, the default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition; the program is ill-formed if a default constructor (11.4.5.2 [class.default.ctor]), copy or move constructor (11.4.5.3 [class.copy.ctor]), or copy or move assignment operator (11.4.6 [class.copy.assign]) is so declared. Default arguments for a member function of a class template shall be specified on the initial declaration of the member function within the class template.
That rule appears to allow adding default arguments for member functions of classes that are nested within class templates, for example:
template<class> struct A { struct B { void c(int); }; }; template<class T> void A<T>::B::c(int = 0) {}
MSVC accepts; gcc and clang reject.
Proposed resolution (approved by CWG 2023-03-03):
Change in 9.3.4.7 [dcl.fct.default] paragraph 6 as follows:
Except for member functions ofclass templatestemplated classes, the default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition; the program is ill-formed if a default constructor (11.4.5.2 [class.default.ctor]), copy or move constructor (11.4.5.3 [class.copy.ctor]), or copy or move assignment operator (11.4.6 [class.copy.assign]) is so declared. Default arguments for a member function of a templated classtemplateshall be specified on the initial declaration of the member function within the templated classtemplate.
[Accepted as a DR at the June, 2023 meeting.]
Consider:
const int arr[2](1,2);
This is accepted by all major implementations, yet 9.5.1 [dcl.init.general] paragraph 13 prohibits it:
If the entity being initialized does not have class type, the expression-list in a parenthesized initializer shall be a single expression.
Presumably, this was an oversight when adding parenthesized aggregate initialization.
Proposed resolution (approved by CWG 2023-04-28):
Change in 9.5.1 [dcl.init.general] paragraph 13 as follows:
If the entity being initialized does not have class or array type, the expression-list in a parenthesized initializer shall be a single expression.
[Accepted as a DR at the March, 2024 meeting.]
Subclause 9.5.1 [dcl.init.general] paragraph 9 specifies:
To value-initialize an object of type T means:
- if T is a (possibly cv-qualified) class type (Clause Clause 11 [class]), then
- if T has either no default constructor (11.4.5.2 [class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
- otherwise, the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
- if T is an array type, ...
The specification about checking the semantic constraints and invoking only non-trivial default constructors is overly convoluted. Omitting a call to a trivial constructor is an as-if optimization that should not be prescribed by the standard.
Proposed resolution (approved by CWG 2024-01-19):
Change in 9.5.1 [dcl.init.general] bullet 9.1.2 as follows:
- ...
- otherwise, the object is zero-initialized and
the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object isthen default-initialized;
[Accepted as a DR at the March, 2024 meeting.]
Consider:
std::string arr[] = "some string";
Prior to the application of paper P0960R3 (Allow initializing aggregates from a parenthesized list of values), this was ill-formed, but now it is well-formed. However, the specification talks about an expression-list as part of the initializer, which does not exist in this case.
Proposed resolution (approved by CWG 2023-11-07):
Change in 9.5.1 [dcl.init.general] bullet 16.5 as follows:
Otherwise, if the destination type is an array, the object is initialized as follows. The initializer shall be of the form ( expression-list ). Let x1 , . . . , xk be the elements of the expression-list. If the destination type is an array of unknown bound, it is defined as having k elements. Let n denote the array size after this potential adjustment. If k is greater than n, the program is ill-formed. Otherwise, ...
[Accepted as a DR at the June, 2024 meeting.]
(From submission #501.)
Subclause 9.5.1 [dcl.init.general] paragraph 9 specifies (as modified by issue 2820):
To value-initialize an object of type T means:
- If T is a (possibly cv-qualified) class type (Clause 11 [class]), then
- if T has either no default constructor (11.4.5.2 [class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
- otherwise, the object is zero-initialized and then default-initialized.
- ...
Per the specification, no zero-initialization occurs if the class has any user-provided default constructor, even if that constructor is not actually selected for the default initialization. This is observable in a constant expression, where reads of uninitialized data members are ill-formed.
Proposed resolution (approved by CWG 2024-04-05):
Change in 6.7.4 [basic.life] paragraph 1 as follows:
The lifetime of an object or reference is a runtime property of the object or reference. A variable is said to have vacuous initialization if it is default-initialized, no other initialization is performed, and, if it is of class type or a (possibly multidimensional) array thereof, a trivial constructor of that class typehas a trivial default constructoris selected for the default-initialization. The lifetime of an object of type T begins when: ...
Change in 9.5.1 [dcl.init.general] paragraph 9 as follows:
To value-initialize an object of type T means:
- If T is a (possibly cv-qualified) class type (Clause 11 [class]), then
let C be the constructor selected to default-initialize the object, if any. If C is not user-provided, the object is first zero-initialized. In all cases, the object is then default-initialized.
- if T has either no default constructor (11.4.5.2 [class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
- otherwise, the object is zero-initialized and then default-initialized.
- ...
[Accepted as a DR at the June, 2024 meeting.]
(From submission #541.)
Initialization of cv T follows the same rules as initialization of T. Issue 2830 clarified this for list-initialization. Further amendments are needed to properly handle cv-qualified versions of char8_t and char16_t as well as bool.
Proposed resolution (approved by CWG 2024-06-14):
Change in 9.5.1 [dcl.init.general] paragraph 16 as follows:
The semantics of initializers are as follows. The destination type is the cv-unqualified type of the object or reference being initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the source type is not defined.
Change in 9.5.1 [dcl.init.general] bullet 16.6 as follows:
- Otherwise, if the destination type is a
(possibly cv-qualified)class type:
- If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same
class as the class ofas the destination type, the initializer expression is used to initialize the destination object. [Example 2: T x = T(T(T())); value-initializes x. —end example]- Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same
class as, or a derived class of, the class ofas or is derived from the destination type, constructors are considered. The applicable constructors are enumerated (12.2.2.4 [over.match.ctor]), and the best one is chosen through overload resolution (12.2 [over.match]). Then:
Change in 9.5.1 [dcl.init.general] bullet 16.9 as follows:
- Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression. A standard conversion sequence (7.3 [conv]) is used to convert the initializer expression to a prvalue of
the cv-unqualified version ofthe destination type; no user-defined conversions are considered. If the conversion cannot be done, the initialization is ill-formed. When initializing a bit-field with a value that it cannot represent, the resulting value of the bit-field is implementation-defined. [ Note: ... ]
[Accepted as a DR as paper P3106R1 at the March, 2024 meeting.]
According to 9.5.2 [dcl.init.aggr] paragraph 4,
An array of unknown size initialized with a brace-enclosed initializer-list containing n initializer-clauses, where n shall be greater than zero, is defined as having n elements (9.3.4.5 [dcl.array]).
However, the interaction of this with brace elision is not clear. For instance, in the example in paragraph 7,
struct X { int i, j, k = 42; }; X a[] = { 1, 2, 3, 4, 5, 6 }; X b[2] = { { 1, 2, 3 }, { 4, 5, 6 } };
a and b are said to have the same value, even though there are six initializer-clauses in the initializer list in a's initializer and two in b's initializer.
Similarly, 13.10.3.2 [temp.deduct.call] paragraph 1 says,
in the P'[N] case, if N is a non-type template parameter, N is deduced from the length of the initializer list
Should that take into account the underlying type of the array? For example,
template<int N> void f1(const X(&)[N]); f1({ 1, 2, 3, 4, 5, 6 }); // Is N deduced to 2 or 6? template<int N> void f2(const X(&)[N][2]); f2({ 1, 2, 3, 4, 5, 6 }); // Is N deduced to 1 or 6?
Additional notes (April, 2024)
The situation with arrays of unknown bound was clarified by P3106R1. The concern about template argument deduction was left untouched; the existing wording in 13.10.3.2 [temp.deduct.call] paragraph 1 seems to be clear, rendering the two examples shown above ill-formed, because deduction of P' against the integer arguments fails.
[Accepted as a DR at the June, 2024 meeting.]
There is an inconsistency in the handling of references vs pointers in user defined conversions and overloading. The reason for that is that the combination of 9.5.4 [dcl.init.ref] and 7.3.6 [conv.qual] circumvents the standard way of ranking conversion functions, which was probably not the intention of the designers of the standard.
Let's start with some examples, to show what it is about:
struct Z { Z(){} }; struct A { Z x; operator Z *() { return &x; } operator const Z *() { return &x; } }; struct B { Z x; operator Z &() { return x; } operator const Z &() { return x; } }; int main() { A a; Z *a1=a; const Z *a2=a; // not ambiguous B b; Z &b1=b; const Z &b2=b; // ambiguous }
So while both classes A and B are structurally equivalent, there is a difference in operator overloading. I want to start with the discussion of the pointer case (const Z *a2=a;): 12.2.4 [over.match.best] is used to select the best viable function. Rule 4 selects A::operator const Z*() as best viable function using 12.2.4.3 [over.ics.rank] since the implicit conversion sequence const Z* -> const Z* is a better conversion sequence than Z* -> const Z*.
So what is the difference to the reference case? Cv-qualification conversion is only applicable for pointers according to 7.3.6 [conv.qual]. According to 9.5.4 [dcl.init.ref] paragraphs 4-7 references are initialized by binding using the concept of reference-compatibility. The problem with this is, that in this context of binding, there is no conversion, and therefore there is also no comparing of conversion sequences. More exactly all conversions can be considered identity conversions according to 12.2.4.2.5 [over.ics.ref] paragraph 1, which compare equal and which has the same effect. So binding const Z* to const Z* is as good as binding const Z* to Z* in terms of overloading. Therefore const Z &b2=b; is ambiguous. [12.2.4.2.5 [over.ics.ref] paragraph 5 and 12.2.4.3 [over.ics.rank] paragraph 3 rule 3 (S1 and S2 are reference bindings ...) do not seem to apply to this case]
There are other ambiguities, that result in the special treatment of references: Example:
struct A {int a;}; struct B: public A { B() {}; int b;}; struct X { B x; operator A &() { return x; } operator B &() { return x; } }; main() { X x; A &g=x; // ambiguous }
Since both references of class A and B are reference compatible with references of class A and since from the point of ranking of implicit conversion sequences they are both identity conversions, the initialization is ambiguous.
So why should this be a defect?
So overall I think this was not the intention of the authors of the standard.
So how could this be fixed? For comparing conversion sequences (and only for comparing) reference binding should be treated as if it was a normal assignment/initialization and cv-qualification would have to be defined for references. This would affect 9.5.4 [dcl.init.ref] paragraph 6, 7.3.6 [conv.qual] and probably 12.2.4.3 [over.ics.rank] paragraph 3.
Another fix could be to add a special case in 12.2.4 [over.match.best] paragraph 1.
CWG 2023-06-13
It was noted that the second example is not ambiguous, because a derived-to-base conversion is compared against an identity conversion. However, 12.2.4.2.5 [over.ics.ref] paragraph 1 needs a wording fix so that it applies to conversion functions as well. CWG opined that the first example be made valid, by adding a missing tie-breaker for the conversion function case.
Proposed resolution (approved by CWG 2024-04-19):
Change in 12.2.4.1 [over.match.best.general] bullet 2.2 as follows:
- ...
- the context is an initialization by user-defined conversion (see 9.5 [dcl.init], 12.2.2.6 [over.match.conv], and 12.2.2.7 [over.match.ref]) and the standard conversion sequence from the
return typeresult of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from thereturn typeresult of F2 to the destination type- ...
Add a new sub-bullet to 12.2.4.3 [over.ics.rank] bullet 3.2 as follows:
- ...
- S1 and S2 include reference bindings (9.5.4 [dcl.init.ref]), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers
.[ Example: ... -- end example ] or, if not that,- S1 and S2 bind the same reference type "reference to T" and have source types V1 and V2, respectively, where the standard conversion sequence from V1* to T* is better than the standard conversion sequence from V2* to T*. [ Example:
struct Z {}; struct A { operator Z&(); operator const Z&(); // #1 }; struct B { operator Z(); operator const Z&&(); // #2 }; const Z& r1 = A(); // OK, uses #1 const Z&& r2 = B(); // OK, uses #2--- end example]
[Accepted as a DR at the March, 2024 meeting.]
Core issue 2481 was resolved by clarifying that the temporary object in p5.4.2 is cv-qualified if the reference being initialized is cv-qualified. However, this is not the right bullet point for the example given,
constexpr const int &r = 42;
Such an initialization would actually use bullet 5.3.1 instead. (5.4.2 would be used if the initializer were, for example, 3.14.) We therefore need to make a similar clarification in bullet 5.3, and ideally using the same language.
Proposed resolution (approved by CWG 2024-03-20):
Change in 9.5.4 [dcl.init.ref] bullet 5.3 as follows:
Otherwise, if the initializer expressionthen the initializer expression in the first case and the converted expression in the second case is called the converted initializer. If the converted initializer is a prvalue, let its type be denoted by T4; the temporary materialization conversion (7.3.5 [conv.rval]) is applied, considering the type of the prvalue to be
- is an rvalue (but not a bit-field) or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
- has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an rvalue or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (see 12.2.2.7 [over.match.ref]),
is adjusted to type“cv1 T4” (7.3.6 [conv.qual])and the temporary materialization conversion (7.3.5 [conv.rval]) is applied. In any case, the reference binds to the resulting glvalue (or to an appropriate base class subobject).
Append to the example in 9.5.4 [dcl.init.ref] bullet 5.3 as follows:
B&& rrb = x; // binds directly to the result of operator B constexpr int f() { const int &x = 42; const_cast<int &>(x) = 1; // undefined behavior return x; } constexpr int z = f(); // error: not a constant expression
Change the example in 9.5.4 [dcl.init.ref] bullet 5.4 as follows:
const double& rcd2 = 2; // rcd2 refers to temporary with type const double and value 2.0
[Accepted as a DR at the November, 2023 meeting.]
Consider:
int* p; const int*&& r = static_cast<int*&&>(p);
The intent of core issues 2018 and 2352 was to make this example ill-formed, because it surprisingly introduces a temporary.
Proposed resolution (approved by CWG 2023-10-20):
Change in 9.5.4 [dcl.init.ref] bullet 5.4 as follows:
- Otherwise
:, T1 shall not be reference-related to T2.
- If T1 or T2 is a class type
and T1 is not reference-related to T2, user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1 T1” by user-defined conversion (9.5 [dcl.init], 12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv]); the program is ill-formed if the corresponding non-reference copy-initialization would be ill-formed. The result of the call to the conversion function, as described for the non-reference copy-initialization, is then used to direct-initialize the reference. For this direct-initialization, user-defined conversions are not considered.- Otherwise, the initializer expression is implicitly converted to a prvalue of type “T1”. The temporary materialization conversion is applied, considering the type of the prvalue to be “cv1 T1”, and the reference is bound to the result.
If T1 is reference-related to T2:
cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2; andif the reference is an rvalue reference, the initializer expression shall not be an lvalue. [Note 3: This can be affected by whether the initializer expression is move-eligible (7.5.5.2 [expr.prim.id.unqual]). —end note]
[Accepted as a DR at the November, 2023 meeting.]
According to 9.5.5 [dcl.init.list] bullet 3.8,
Otherwise, if T is an enumeration with a fixed underlying type (9.8.1 [dcl.enum]), the initializer-list has a single element v, and the initialization is direct-list-initialization, the object is initialized with the value T(v) (7.6.1.4 [expr.type.conv]); if a narrowing conversion is required to convert v to the underlying type of T , the program is ill-formed.
This could be read as requiring that there be a conversion from v to the underlying type of T, leaving the status of an example like the following unclear:
enum class E {};
struct X { operator E(); };
E{X()}; // ok?
Notes from the March, 2018 meeting:
CWG disagreed that the existing wording requires such a conversion, only that if such a conversion is possble, it must not narrow. A formulation along the lines of “if that initialization involves a narrowing conversion to the underlying type of T...” was suggested to clarify the intent. This will be handled editorially, and the issue will be left in "review" status until the change has been verified.
Additional notes (August, 2023)
Issue 2374 has meanwhile clarified that v is required to implicitly convert to the underlying type of the enumeration for 9.5.5 [dcl.init.list] bullet 3.8 to apply. Now, the logic falls through to 9.5.5 [dcl.init.list] bullet 3.9 for the above example, making it well-formed.
CWG 2023-09-15
Class types with conversions to scalar types were not in view when the wording in this bullet was conceived.
Proposed resolution (approved by CWG 2023-10-06):
Change in 9.5.5 [dcl.init.list] bullet 3.8 as follows:
Otherwise, if T is an enumeration with a fixed underlying type (9.8.1 [dcl.enum]) U, the initializer-list has a single element v of scalar type, v can be implicitly converted to U, and the initialization is direct-list-initialization, the object is initialized with the value T(v) (7.6.1.4 [expr.type.conv]); if a narrowing conversion is required to convert v to U, the program is ill-formed.
[Accepted as a DR at the March, 2024 meeting.]
Issue 2137 amended the rules for initialization by initializer list, but neglected to add an example.
Proposed resolution (approved by CWG 2023-11-11):
Change the example in 9.5.5 [dcl.init.list] bullet 3.7 as follows:
struct S { S(std::initializer_list<double>); // #1 S(std::initializer_list<int>); // #2 S(std::initializer_list<S>); // #3 S(); //#3#4 // ... }; S s1 = { 1.0, 2.0, 3.0 }; // invoke #1 S s2 = { 1, 2, 3 }; // invoke #2 S s3{s2}; // invoke #3 (not the copy constructor) Ss3s4 = { }; // invoke#3#4
[Accepted as a DR at the June, 2023 meeting.]
Aggregates can be initialized by a designated initializer list, but references to aggregates cannot, although list-initialization of such with a regular braced-init-list is fine.
Subclause 9.5.5 [dcl.init.list] paragraph 3 specifies:
List-initialization of an object or reference of type T is defined as follows:
- If the braced-init-list contains a designated-initializer-list, T shall be an aggregate class. ...
- ...
- Otherwise, if T is a reference type, a prvalue is generated. The prvalue initializes its result object by copy-list-initialization from the initializer list. The prvalue is then used to direct-initialize the reference. The type of the prvalue is the type referenced by T, unless ...
- ...
Subclause 12.2.4.2.6 [over.ics.list] paragraph 2 specifies:
If the initializer list is a designated-initializer-list, a conversion is only possible if the parameter has an aggregate type that can be initialized from the initializer list according to the rules for aggregate initialization (9.5.2 [dcl.init.aggr]), in which case the implicit conversion sequence is a user-defined conversion sequence whose second standard conversion sequence is an identity conversion.
Proposed resolution (approved by CWG 2023-04-28):
Change in 9.5.5 [dcl.init.list] bullet 3.1 as follows:
- If the braced-init-list contains a designated-initializer-list and T is not a reference type, T shall be an aggregate class. ...
- ...
Change in 9.5.5 [dcl.init.list] bullet 3.9 as follows:
Otherwise, if the initializer list is not a designated-initializer-list and has a single element of type E and ...
Change in 9.5.5 [dcl.init.list] bullet 3.10 as follows:
[ Example:... const B& b2{a}; // error: cannot copy-list-initialize B temporary from A struct C { int x; }; C&& c = { .x = 1 }; // OK-- end example ]
Change in 12.2.4.2.6 [over.ics.list] paragraph 2 as follows:
If the initializer list is a designated-initializer-list and the parameter is not a reference, a conversion is only possible if the parameter has an aggregate type that can be initialized from the initializer list according to the rules for aggregate initialization (9.5.2 [dcl.init.aggr]), in which case the implicit conversion sequence is a user-defined conversion sequence whose second standard conversion sequence is an identity conversion.
[Accepted as a DR at the March, 2024 meeting.]
(From editorial issue 3492.)
Subclause 9.5.5 [dcl.init.list] paragraph 3 specifies:
List-initialization of an object or reference of type T is defined as follows:
- ...
Top-level cv-qualifiers should be ignored when comparing T to other types, e.g. in 9.5.5 [dcl.init.list] bullet 3.2.
Proposed resolution (approved by CWG 2024-01-19):
Change in 9.5.5 [dcl.init.list] paragraph 3 as follows:
List-initialization of an object or reference of type cv T is defined as follows:
- ...
- If T is an aggregate class and the initializer list has a single element of type
cvcv1 U, where U is T or a class derived from T, the object is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization).- ...
[Accepted as a DR at the June, 2024 meeting.]
Consider:
float f = {1e100};
This is rejected as narrowing on all implementations. Issue 2723 made the example non-narrowing, which seems incorrect on an IEEE platform.
Proposed resolution (approved by CWG 2024-04-19):
Change in 9.5.5 [dcl.init.list] paragraph 7 as follows:
A narrowing conversion is an implicit conversion
- from a floating-point type to an integer type, or
- from a floating-point type T to another floating-point type whose floating-point conversion rank is neither greater than nor equal to that of T, except where the
sourceresult of the conversion is a constant expression andthe actualeither its valueafter conversionis finite andwithin the range of values that can be represented (even if it cannot be represented exactly)the conversion did not overflow, or the values before and after the conversion are not finite, or- from an integer type ...
Additional notes (April, 2024)
According to the proposed wording, since NaNs are not finite, conversion of NaNs is always non-narrowing. However, the payload of the NaN might not be preserved when converting to a smaller floating-point type.
[Accepted as a DR at the June, 2024 meeting.]
The following fragment,
int f() {};
is syntactically ambiguous. It could be either a function-definition followed by an empty-declaration, or it could be a simple-declaration whose init-declarator has the brace-or-equal-initializer {}. The same is true of a variable declaration
int a {};
since function-definition simply uses the term declarator in its production.
Additional notes (May, 2024)
Issue 2876 introduced a framework to distinguish the parsing. Its resolution was extended to also resolve this issue.
[Accepted as a DR at the March, 2024 meeting.]
(See editorial issue 5337.)
Subclause 9.6.2 [dcl.fct.def.default] paragraph 1 specifies:
A function definition whose function-body is of the form = default ; is called an explicitly-defaulted definition. A function that is explicitly defaulted shall
- be a special member function or a comparison operator function (12.4.3 [over.binary]), and
- not have default arguments.
There seem to be no further restrictions on which comparison operator functions are allowed to be defaulted. For example,
enum E { };
bool operator==(E, E) = default; // well-formed?
Subclause 11.10.1 [class.compare.default] paragraph 1 applies only to comparison operator functions "for some class":
A defaulted comparison operator function (12.4.3 [over.binary]) for some class C shall be a non-template function that is
- a non-static const non-volatile member of C having one parameter of type const C& and either no ref-qualifier or the ref-qualifier &, or
- a friend of C having either two parameters of type const C& or two parameters of type C.
Proposed resolution [SUPERSEDED]:
A function definition whose function-body is of the form = default ; is called an explicitly-defaulted definition. A function that is explicitly defaulted shall
- be a special member function (11.4.4 [special]) or a comparison operator function (12.4.3 [over.binary], 11.10.1 [class.compare.default]), and
- not have default arguments (9.3.4.7 [dcl.fct.default]).
A defaulted comparison operator function (12.4.3 [over.binary])for some class Cshall be a non-template function that is
- a non-static const non-volatile member of some class C having one parameter of type const C& and either no ref-qualifier or the ref-qualifier &, or
- a friend of some class C having either two parameters of type const C& or two parameters of type C.
Such a comparison operator function is termed a comparison operator function for class C. A comparison operator function for class C that is defaulted on its first declaration ...
CWG 2023-12-01
A defaulted comparison function for an incomplete class later declared a friend for that class should be made ill-formed.
Proposed resolution (approved by CWG 2023-12-15):
A function definition whose function-body is of the form = default ; is called an explicitly-defaulted definition. A function that is explicitly defaulted shall
- be a special member function (11.4.4 [special]) or a comparison operator function (12.4.3 [over.binary], 11.10.1 [class.compare.default]), and
- not have default arguments (9.3.4.7 [dcl.fct.default]).
Change in 11.10.1 [class.compare.default] paragraph 1 as follows:
A defaulted comparison operator function (12.4.3 [over.binary])for some class Cshall be a non-template function thatisSuch a comparison operator function is termed a defaulted comparison operator function for class C. Name lookups in the implicit definition (9.6.2 [dcl.fct.def.default]) of a comparison operator function are performed from a context equivalent to its function-body. A definition of a comparison operator as defaulted that appears in a class shall be the first declaration of that function. [ Example:
- is a non-static member or friend of some class C,
- is defined as defaulted in C or in a context where C is complete, and
- either has two parameters of type const C& or two parameters of type C, where the implicit object parameter (if any) is considered to be the first parameter.
struct S; bool operator==(S, S) = default; // error: S is not complete struct S { friend bool operator==(S, const S&) = default; // error: parameters of different types }; enum E { }; bool operator==(E, E) = default; // error: not a member or friend of a class-- end example ]
[Accepted as a DR at the November, 2023 meeting.]
After the application of P2448R2, 9.6.2 [dcl.fct.def.default] paragraph 3 reads:
A function explicitly defaulted on its first declaration is implicitly inline (9.2.8 [dcl.inline]), and is implicitly constexpr (9.2.6 [dcl.constexpr]) if it satisfies the requirements for a constexpr function.
It is unclear that no other such defaulted function is implicitly constexpr.
Proposed resolution (approved by CWG 2023-06-17):
A function explicitly defaulted on its first declaration is implicitly inline (9.2.8 [dcl.inline]), and is implicitly constexpr (9.2.6 [dcl.constexpr]) if it satisfies the requirements for a constexpr function. [Note: Other defaulted functions are not implicitly constexpr. -- end note ]
[Accepted as a DR at the March, 2024 meeting.]
Subclause 9.6.2 [dcl.fct.def.default] paragraph 5 specifies:
... A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is implicitly defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. A non-user-provided defaulted function (i.e., implicitly declared or explicitly defaulted in the class) that is not defined as deleted is implicitly defined when it is odr-used (6.3 [basic.def.odr]) or needed for constant evaluation (7.7 [expr.const]).
In the first case, there is a second point of declaration for the function, wherever the user wrote the definition. In contrast, there is no redeclaration for the second case, where the function is not user-provided. A note would clarify.
Proposed resolution (approved by CWG 2024-03-20):
Insert a paragraph break before the quoted section and change in 9.6.2 [dcl.fct.def.default] paragraph 5 as follows:
... A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is implicitly defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [Note 1: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. —end note] A non-user-provided defaulted function (i.e., implicitly declared or explicitly defaulted in the class) that is not defined as deleted is implicitly defined when it is odr-used (6.3 [basic.def.odr]) or needed for constant evaluation (7.7 [expr.const]).[Note 1: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. —end note][ Note: The implicit definition of a non-user-provided defaulted function does not bind any names. -- end note ]
[Accepted as a DR at the November, 2023 meeting.]
Subclause 9.6.4 [dcl.fct.def.coroutine] paragraph 4 specifies:
In the following, pi is an lvalue of type Pi , where p1 denotes the object parameter and pi+1 denotes the ith non-object function parameter for a non-static member function, and pi denotes the ith function parameter otherwise. For a non-static member function, q1 is an lvalue that denotes *this; any other qi is an lvalue that denotes the parameter copy corresponding to pi , as described below.
An explicit object member function is a non-static member function, but there is no this.
Proposed resolution (approved by CWG 2023-07-14):
Change in 9.6.4 [dcl.fct.def.coroutine] paragraph 4 as follows:
In the following, pi is an lvalue of type Pi , where p1 denotes the object parameter and pi+1 denotes the ith non-object function parameter fora non-statican implicit object member function, and pi denotes the ith function parameter otherwise. Fora non-statican implicit object member function, q1 is an lvalue that denotes *this; any other qi is an lvalue that denotes the parameter copy corresponding to pi, as described below.
[Accepted as a DR at the June, 2024 meeting.]
Consider:
auto [a, b] = f(X{});
If X is a tuple-like type, this is transformed to approximately the following:
auto e = f(X{}); T1 &a = get<0>(std::move(e)); T2 &b = get<1>(std::move(e));
However, the sequencing of the initializations of e, a, and b is not specified. Further, the temporary X{} should be destroyed after the initializations of a and b.
Possible resolution [SUPERSEDED]:
Change in 6.9.1 [intro.execution] paragraph 5 as follows:
A full-expression is...
- an unevaluated operand (7.2.3 [expr.context]),
- a constant-expression (7.7 [expr.const]),
- an immediate invocation (7.7 [expr.const]),
- an init-declarator (9.3 [dcl.decl]) or a mem-initializer (11.9.3 [class.base.init]), including the constituent expressions of the initializer,
- the initializers for all uniquely-named variables introduced by a structured binding declaration (9.7 [dcl.struct.bind]), including the constituent expressions of all initializers,
- an invocation of a destructor generated at the end of the lifetime of an object other than a temporary object (6.7.7 [class.temporary]) whose lifetime has not been extended, or
- an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.
Change in 9.7 [dcl.struct.bind] paragraph 4 as follows:
... Each vi is the name of an lvalue of type Ti that refers to the object bound to ri; the referenced type is Ti. The initialization of e is sequenced before the initialization of any ri. The initialization of ri is sequenced before the initialization of rj if i < j.
CWG 2024-05-03
CWG tentatively agreed that all temporaries in a structured binding (including those from default arguments of get invocations) should persist until the semicolon on the declaration as written.
Possible resolution [SUPERSEDED]:
Change in 6.9.1 [intro.execution] paragraph 5 as follows:
A full-expression is...
- an unevaluated operand (7.2.3 [expr.context]),
- a constant-expression (7.7 [expr.const]),
- an immediate invocation (7.7 [expr.const]),
- an init-declarator (9.3 [dcl.decl]) , an initializer of a structured binding declaration (9.1 [dcl.pre]), or a mem-initializer (11.9.3 [class.base.init]), including the constituent expressions of the initializer,
- an invocation of a destructor generated at the end of the lifetime of an object other than a temporary object (6.7.7 [class.temporary]) whose lifetime has not been extended, or
- an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.
Change in 9.7 [dcl.struct.bind] paragraph 4 as follows:
... Each vi is the name of an lvalue of type Ti that refers to the object bound to ri; the referenced type is Ti. The initialization of e is sequenced before the initialization of any ri. The initialization of each ri is sequenced before the initialization of any rj where i < j.
Append a new paragraph at the end of 9.7 [dcl.struct.bind] as follows:
... [ Example: ... The type of the id-expression x is "int", the type of the id-expression y is "const volatile double". -- end example ]
The initialization of e and of any ri are sequenced before the destruction of any temporary object introduced by the initializer for e or by the initializers for the ri. The temporary objects are destroyed in the reverse order of their construction.
CWG 2024-06-14
The specification for the lifetime of temporaries should be moved to 6.7.7 [class.temporary].)
Proposed resolution (approved by CWG 2024-06-28):
Change in 6.7.7 [class.temporary] paragraph 5 as follows:
There arefourfive contexts in which temporaries are destroyed at a different point than the end of the full-expression. ...
Insert a new paragraph after 6.7.7 [class.temporary] paragraph 7 as follows:
The fourth context is when a temporary object is created in the for-range-initializer of a range-based for statement. If such a temporary object would otherwise be destroyed at the end of the for-range-initializer full-expression, the object persists for the lifetime of the reference initialized by the for-range-initializer.
The fifth context is when a temporary object is created in a structured binding declaration (9.7 [dcl.struct.bind]). Any temporary objects introduced by the initializers for the variables with unique names are destroyed at the end of the structured binding declaration.
Change in 6.9.1 [intro.execution] paragraph 5 as follows:
A full-expression is...
- an unevaluated operand (7.2.3 [expr.context]),
- a constant-expression (7.7 [expr.const]),
- an immediate invocation (7.7 [expr.const]),
- an init-declarator (9.3 [dcl.decl]) (including such introduced by a structured binding (9.7 [dcl.struct.bind])) or a mem-initializer (11.9.3 [class.base.init]), including the constituent expressions of the initializer,
- an invocation of a destructor generated at the end of the lifetime of an object other than a temporary object (6.7.7 [class.temporary]) whose lifetime has not been extended, or
- an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.
Change in 9.7 [dcl.struct.bind] paragraph 4 as follows:
... Each vi is the name of an lvalue of type Ti that refers to the object bound to ri; the referenced type is Ti. The initialization of e is sequenced before the initialization of any ri. The initialization of each ri is sequenced before the initialization of any rj where i < j.
[Accepted as a DR at the June, 2024 meeting.]
Issue 2621 claimed to ask the question whether lookup for using enum declarations was supposed to be type-only, but the example actually highlighted the difference between elaborated-type-specifier lookup (where finding nothing but typedef names is ill-formed) and ordinary lookup.
However, consider:
enum A { x, y }; void f() { int A; using enum A; // #1, error: names non-type int A using T = enum A; // #2, OK, names ::A }
The two situations should both be type-only lookups for consistency, although #2 would not find typedefs.
Proposed resolution (reviewed by CWG 2024-05-17) [SUPERSEDED]:
Change in 9.8.2 [enum.udecl] paragraph 1 as follows:
A using-enum-declarator names the set of declarations found by type-only lookup (6.5.1 [basic.lookup.general]6.5.3 [basic.lookup.unqual], 6.5.5 [basic.lookup.qual]) for the using-enum-declarator (6.5.3 [basic.lookup.unqual], 6.5.5 [basic.lookup.qual]). The using-enum-declarator shall designate a non-dependent type with a reachable enum-specifier.
Additional notes (May, 2024)
An example is desirable. Also, the treatment of the following example is unclear:
template<class T> using AA = T; enum AA<E> e; // ill-formed elaborated-type-specifier using enum AA<E>; // Clang and MSVC reject, GCC and EDG accept
Proposed resolution (approved by CWG 2024-06-26):
Change in 9.8.2 [enum.udecl] paragraph 1 as follows:
A using-enum-declarator names the set of declarations found by type-only lookup (6.5.1 [basic.lookup.general]6.5.3 [basic.lookup.unqual], 6.5.5 [basic.lookup.qual]) for the using-enum-declarator (6.5.3 [basic.lookup.unqual], 6.5.5 [basic.lookup.qual]). The using-enum-declarator shall designate a non-dependent type with a reachable enum-specifier. [ Example:enum E { x }; void f() { int E; using enum E; // OK } using F = E; using enum F; // OK template<class T> using EE = T; void g() { using enum EE<E>; // OK }-- end example ]
[Accepted as a DR at the June, 2023 meeting.]
Issue 36 was resolved by P1787R6, but no example was added.
Proposed resolution (approved by CWG 2023-06-13):
Add an example to 9.10 [namespace.udecl] paragraph 8 as follows:
[ Example:struct C { int i; }; struct D1 : C { }; struct D2 : C { }; struct D3 : D1, D2 { using D1::i; // OK, equivalent to using C::i using D1::i; // error: duplicate using D2::i; // error: duplicate, also names C::i };-- end example ]
Change the example in 9.10 [namespace.udecl] paragraph 10 as follows:
using B::x; using A::x; // OK, hides struct B::x using A::x; // OK, does not conflict with previous using A::x x = 99; // assigns to A::x struct x x1; // x1 has class type B::x }
CWG 2023-01-06
There is implementation divergence in handling this example.
CWG 2023-02-07
P1787R6 clarified that the example added to 9.10 [namespace.udecl] paragraph 10 is accepted, even in the non-function case.
[Accepted as a DR at the November, 2023 meeting.]
Subclause 9.13.8 [dcl.attr.unused] paragraph 2 specifies:
The attribute may be applied to the declaration of a class, a typedef-name, a variable (including a structured binding declaration), a non-static data member, a function, an enumeration, or an enumerator.
Absent from that list are labels, but both gcc and clang accept [[maybe_unused]] on a label, and behave accordingly.
Proposed resolution (approved by CWG 2023-07-14)
Change in 9.13.8 [dcl.attr.unused] as follows:
The attribute-token maybe_unused indicates that a name, label, or entity is possibly intentionally unused. No attribute-argument-clause shall be present.
The attribute may be applied to the declaration of a class,
atypedef-name,avariable (including a structured binding declaration),anon-static data member,afunction,anenumeration, oranenumerator, or to an identifier label (8.2 [stmt.label]).A name or entity declared without the maybe_unused attribute can later be redeclared with the attribute and vice versa. An entity is considered marked after the first declaration that marks it.
Recommended practice: For an entity marked maybe_unused, implementations should not emit a warning that the entity or its structured bindings (if any) are used or unused. For a structured binding declaration not marked maybe_unused, implementations should not emit such a warning unless all of its structured bindings are unused. For a label to which maybe_unused is applied, implementations should not emit a warning that the label is used or unused.
[Example 1:[[maybe_unused]] void f([[maybe_unused]] bool thing1, [[maybe_unused]] bool thing2) { [[maybe_unused]] bool b = thing1 && thing2; assert(b); #ifdef NDEBUG goto x; #endif [[maybe_unused]] x: }Implementations should not warn that b or x is unused, whether or not NDEBUG is defined. — end example]
CWG 2023-07-14
CWG has reviewed and approved the proposed resolution. However, this is a new (albeit small) feature, thus forwarding to EWG via paper issue 1585 for approval.
EWG 2023-11-07
Accept the proposed resolution, forward to CWG for inclusion in C++26.
[Accepted as a DR at the November, 2024 meeting.]
Consider:
export module mod; extern "C++" void func(); export extern "C++" { void func(); }
Per 10.2 [module.interface] paragraph 6, this is currently ill-formed, but implementation treatment varies and there seems to be little rationale why this should be ill-formed for entities not attached to a named module.
The rule also makes the following example ill-formed, which was intended to be well-formed:
export module M; namespace N { // external linkage, attached to global module, not exported void f(); } namespace N { // error: exported namespace, redeclares non-exported namespace export void g(); }
The mental model here is that for an entity not attached to a named module, exportedness is not a meaningful property of that entity.
Proposed resolution (approved by CWG 2024-08-16):
Change in 10.2 [module.interface] paragraph 6 as follows:
A redeclaration of an entity X is implicitly exported if X was introduced by an exported declaration; otherwise it shall not be exported if it is attached to a named module.
Additional notes (January, 2025)
Partially reverted in issue 2990.
[Accepted as a DR at the June, 2023 meeting.]
Subclause 10.3 [module.import] paragraph 5 specifies:
A module-import-declaration that specifies a header-name H imports a synthesized header unit, which is a translation unit formed by applying phases 1 to 7 of translation (5.2 [lex.phases]) to the source file or header nominated by H, which shall not contain a module-declaration. [Note 2: All declarations within a header unit are implicitly exported (10.2 [module.interface]), and are attached to the global module (10.1 [module.unit]). —end note]
It is unclear whether the contents of header units can vary depending on the set of defined macros at the point where the import (or #include) appears.
Proposed resolution (approved by CWG 2023-06-13):
Change in 10.3 [module.import] paragraph 5 as follows:
[Note 2: A header unit is a separate translation unit with an independent set of defined macros. All declarations within a header unit are implicitly exported (10.2 [module.interface]), and are attached to the global module (10.1 [module.unit]). —end note]
[Accepted as a DR at the November, 2023 meeting.]
Consider:
// header "S.h" template<class T> struct S { S(const T*); }; template<class T> S(T*) -> S<T> // translation unit module; #include "S.h" export module M; export using ::S;
Obviously, the using-declaration referring to the class template S is exported by M, but what about the deduction guide of S?
Proposed resolution (approved by CWG 2023-08-25) [SUPERSEDED]:
Add a new bullet after 10.4 [module.global.frag] bullet 3.5.7 as follows:
- ...
- there exists a declaration M that is not a namespace-definition for which M is decl-reachable from S and either
- ...
- one of M and D declares a template and the other declares a partial or explicit specialization or an implicit or explicit instantiation of that template, or
- one of M and D declares a class template and the other declares a deduction guide for that template, or
Proposed resolution (approved by CWG 2023-10-06):
Add a new bullet after 10.4 [module.global.frag] bullet 3.5.7 as follows:
- ...
- there exists a declaration M that is not a namespace-definition for which M is decl-reachable from S and either
- ...
- one of M and D declares a template and the other declares a partial or explicit specialization or an implicit or explicit instantiation of that template, or
- M declares a class template and D is a deduction guide for that template, or
[Accepted as a DR at the March, 2024 meeting.]
Issue 2237 sought to disallow simple-template-ids as constructor names, by referring to the injected-class-name. However, 11.1 [class.pre] paragraph 2 specifies:
The class-name is also bound in the scope of the class (template) itself; this is known as the injected-class-name.
The grammar non-terminal class-name includes the option of a simple-template-id (for declaring a partial specialization).
Proposed resolution (approved by CWG 2023-11-11):
Change in 11.1 [class.pre] paragraph 2 as follows:
The component name of the class-name is also bound in the scope of the class (template) itself; this is known as the injected-class-name. ...
[Accepted as a DR at the March, 2024 meeting.]
Subclause 11.4.1 [class.mem.general] has this grammar:
member-declarator: declarator virt-specifier-seq[opt] pure-specifier[opt] declarator brace-or-equal-initializer[opt] pure-specifier: = 0
The primary issue is that foo = 0 matches both member-declarator productions. Secondarily, a declarator by itself is also ambiguous.
Code such as virtual FunctionType f = 0; can be valid, so disambiguation on the syntactic form of the declarator is not possible.
Proposed resolution (approved by CWG 2024-02-16):
Change and add before 11.4.1 [class.mem.general] paragraph 1 as follows:
member-declarator: declarator virt-specifier-seqopt pure-specifieropt declarator brace-or-equal-initializeropt
In the absence of a virt-specifier-seq, the token sequence = 0 is treated as a pure-specifier if the type of the declarator-id (9.3.4.1 [dcl.meaning.general]) is a function type, and is otherwise treated as a brace-or-equal-initializer. [ Note: If the member declaration acquires a function type through template instantiation, the program is ill-formed; see 13.9.1 [temp.spec.general]. --end note ]
[Accepted as a DR at the November, 2023 meeting.]
The interaction of [[no_unique_address]] and the definition of common initial sequence is still problematic. Subclause 11.4.1 [class.mem.general] bullet 23.3 specifies that corresponding members in a common initial sequence are not allowed to differ with respect to the presence or absence of a [[no_unique_address]] attribute. However, the Itanium ABI will not allocate two successive data members of the same empty class type at the same address, causing non-conforming behavior for the following example:
struct A {}; struct B {}; struct C { [[no_unique_address]] A a; [[no_unique_address]] B b; }; struct D { [[no_unique_address]] A a1; [[no_unique_address]] A a2; }; static_assert(offsetof(C, b) == offsetof(D, a2));
Since "common initial sequence" and "layout compatible" are concepts mostly used for C compatibility, but [[no_unique_address]] does not exist in C, it seems reasonable to terminate a common initial sequence at the first data member that is declared [[no_unique_address]].
Another concern is the behavior of std::is_layout_compatible on implementations that ignore [[no_unique_address]]. On such an implementation, the following example would be considered layout-compatible, although it actually is not:
struct E {}; struct A { E e; int i; }; struct B { [[no_unique_address]] E e; int i; }; static_assert( std::is_layout_compatible_v<A, B> );
Alternative possible resolution [SUPERSEDED]:
Change in 11.4.1 [class.mem.general] paragraph 23 as follows:
The common initial sequence of two standard-layout struct (11.2 [class.prop]) types is the longest sequence of non-static data members and bit-fields in declaration order, starting with the first such entity in each of the structs, such that
- corresponding entities have layout-compatible types (6.8 [basic.types]),
- corresponding entities have the same alignment requirements (6.7.3 [basic.align]),
either both entities are declared with the no_unique_address attribute (9.13.11 [dcl.attr.nouniqueaddr]) or neither is,neither entity is declared with the no_unique_address attribute (9.13.11 [dcl.attr.nouniqueaddr]), and- either both entities are bit-fields with the same width or neither is a bit-field.
Proposed resolution (approved by CWG 2023-08-25):
Change in 11.4.1 [class.mem.general] paragraph 23 as follows:
The common initial sequence of two standard-layout struct (11.2 [class.prop]) types is the longest sequence of non-static data members and bit-fields in declaration order, starting with the first such entity in each of the structs, such that
- corresponding entities have layout-compatible types (6.8 [basic.types]),
- corresponding entities have the same alignment requirements (6.7.3 [basic.align]),
either both entities are declared with the no_unique_address attribute (9.13.11 [dcl.attr.nouniqueaddr]) or neither is,if a has-attribute-expression (15.2 [cpp.cond]) is not 0 for the no_unique_address attribute, then neither entity is declared with the no_unique_address attribute (9.13.11 [dcl.attr.nouniqueaddr]), and- either both entities are bit-fields with the same width or neither is a bit-field.
[Accepted as a DR at the March, 2024 meeting.]
Consider:
struct A{
int a;
void show(){
int* r = &a; // #1
}
};
According to 11.4.3 [class.mfct.non.static] paragraph 2, the transformation to class member access does not happen for the id-expression a, because it is the unparenthesized operand of &:
When an id-expression (7.5.5 [expr.prim.id]) that is neither part of a class member access syntax (7.6.1.5 [expr.ref]) nor the un-parenthesized operand of the unary & operator (7.6.2.2 [expr.unary.op]) is used where the current class is X (7.5.3 [expr.prim.this]), if name lookup (6.5 [basic.lookup]) resolves the name in the id-expression to a non-static non-type member of some class C, and if either the id-expression is potentially evaluated or C is X or a base class of X, the id-expression is transformed into a class member access expression (7.6.1.5 [expr.ref]) using (*this) as the postfix-expression to the left of the . operator. [Note 1: If C is not X or a base class of X, the class member access expression is ill-formed. —end note] This transformation does not apply in the template definition context (13.8.3.2 [temp.dep.type]).
Proposed resolution (approved by CWG 2024-03-20):
This resolution moves the transformation to 7.5.5.1 [expr.prim.id.general], where the similar transformation for anonymous unions is already described.
Change in 6.3 [basic.def.odr] paragraph 7 as follows:
*this is odr-used if this appears as a potentially-evaluated expression (including as the result oftheany implicit transformationin the body of a non-static member functionto a class member access expression (11.4.3 [class.mfct.non.static]7.5.5.1 [expr.prim.id.general])).
Add a new paragraph before 7.5.5.1 [expr.prim.id.general] paragraph 2:
If an id-expression E denotes a non-static non-type member of some class C at a point where the current class (7.5.3 [expr.prim.this]) is X and
the id-expression is transformed into a class member access expression using (*this) as the object expression. [Note 1: If C is not X or a base class of X, the class member access expression is ill-formed. Also, if the id-expression occurs within a static or explicit object member function, the class member access is ill-formed. —end note] This transformation does not apply in the template definition context (13.8.3.2 [temp.dep.type]).
- E is potentially evaluated or C is X or a base class of X, and
- E is not the id-expression of a class member access expression (7.6.1.5 [expr.ref]), and
- if E is a qualified-id, E is not the un-parenthesized operand of the unary & operator (7.6.2.2 [expr.unary.op]),
If an id-expression E denotes a member M of an anonymous union (11.5.2 [class.union.anon]) U:
- If U is a non-static data member, E refers to M as a member of the lookup context of the terminal name of E (after any implicit transformation to a class member access expression
(11.4.3 [class.mfct.non.static])).- ...
Change in 7.5.5.1 [expr.prim.id.general] paragraph 3 as follows:
An id-expression that denotes a non-static data member or implicit object member function of a class can only be used:
- as part of a class member access (
7.6.1.5 [expr.ref]after any implicit transformation (see above)) in which the object expression refers to the member's class[ Footnote: This also applies when the object expression is an implicit (*this) (11.4.3 [class.mfct.non.static]).]or a class derived from that class, or- ...
Change in 7.5.5.2 [expr.prim.id.unqual] paragraph 1 as follows:
... [ Note: ...Within the definition of a non-static member function, an identifier that names a non-static member is transformed to a class member access expression (11.4.3 [class.mfct.non.static]).—end note]
Remove 11.4.3 [class.mfct.non.static] paragraph 2, including the example:
When an id-expression (7.5.5 [expr.prim.id]) that is neither part of a class member access syntax (7.6.1.5 [expr.ref]) nor the un-parenthesized operand of the unary & operator (7.6.2.2 [expr.unary.op]) is used where the current class is X (7.5.3 [expr.prim.this]), if name lookup (6.5 [basic.lookup]) resolves the name in the id-expression to a non-static non-type member of some class C, and if either the id-expression is potentially evaluated or C is X or a base class of X, the id-expression is transformed into a class member access expression (7.6.1.5 [expr.ref]) using (*this) as the postfix-expression to the left of the . operator. [Note 1: If C is not X or a base class of X, the class member access expression is ill-formed. —end note] This transformation does not apply in the template definition context (13.8.3.2 [temp.dep.type]). [ Example: ... ]
Change in 13.8.3.2 [temp.dep.type] paragraph 6 as follows:
[ Example: ...template int C<B>::g(); // OK, transformation to class member access syntax // does not occur in the template definition context; see-- end example ]11.4.3 [class.mfct.non.static]7.5.5.1 [expr.prim.id.general]
[Accepted as a DR at the November, 2023 meeting.]
Consider:
#include <type_traits> template<typename T> concept Int = std::is_same_v<T, int>; template<typename T> concept Float = std::is_same_v<T, float>; template<typename T> struct Foo { Foo() requires Int<T> = default; // #1 Foo() requires Int<T> || Float<T> = default; // #2 };
Per the wording, #1 is not eligible for Foo<float>, because the constraints are not satisfied. But #2 also is not eligible, because #1 is more constrained than #2. The intent is that #2 is eligible.
Proposed resolution (approved by CWG 2023-06-17):
Change in 11.4.4 [special] paragraph 6 as follows:
An eligible special member function is a special member function for which:
- the function is not deleted,
- the associated constraints (13.5 [temp.constr]), if any, are satisfied, and
- no special member function of the same kind whose associated constraints, if any, are satisfied is more constrained (13.5.5 [temp.constr.order]).
[Accepted as a DR at the June, 2023 meeting.]
The specification of when a defaulted special member function is to be defined as deleted sometimes overlooks variant and array members.
Proposed resolution (approved by CWG 2023-02-07):
Change in 11.4.5.2 [class.default.ctor] paragraph 2 as follows:
A defaulted default constructor for class X is defined as deleted if:
X is a union that has a variant member with a non-trivial default constructor and no variant member of X has a default member initializer,X is a non-union class that has a variant member M with a non-trivial default constructor and no variant member of the anonymous union containing M has a default member initializer,- any non-static data member with no default member initializer (11.4 [class.mem]) is of reference type,
- any non-variant non-static data member of const-qualified type (or possibly multi-dimensional array thereof) with no brace-or-equal-initializer is not const-default-constructible (9.5 [dcl.init]),
- X is a union and all of its variant members are of const-qualified type (or possibly multi-dimensional array thereof),
- X is a non-union class and all members of any anonymous union member are of const-qualified type (or possibly multi-dimensional array thereof),
- any potentially constructed subobject, except for a non-static data member with a brace-or-equal-initializer or a variant member of a union where another non-static data member has a brace-or-equal-initializer, has class type M (or possibly multi-dimensional array thereof) and
either M has no default constructor oroverload resolution (12.2 [over.match]) as applied to find M's corresponding constructorresults in an ambiguity or in a function that is deleted or inaccessible from the defaulted default constructoreither does not result in a usable candidate (12.2.1 [over.match.general]) or, in the case of a variant member, selects a non-trivial function, or- any potentially constructed subobject has
a type withclass type M (or possibly multi-dimensional array thereof) and M has a destructor that is deleted or inaccessible from the defaulted default constructor.
Change in 11.4.5.3 [class.copy.ctor] paragraph 10 as follows:
... A defaulted copy/move constructor for a class X is defined as deleted (9.6.3 [dcl.fct.def.delete]) if X has:
- a potentially constructed subobject of type M (or possibly multi-dimensional array thereof)
that cannot be copied/moved becausefor which overload resolution (12.2 [over.match]), as applied to find M's corresponding constructor,results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructoreither does not result in a usable candidate (12.2.1 [over.match.general]) or, in the case of a variant member, selects a non-trivial function,a variant member whose corresponding constructor as selected by overload resolution is non-trivial,- any potentially constructed subobject of
a type withclass type M (or possibly multi-dimensional array thereof) where M has a destructor that is deleted or inaccessible from the defaulted constructor, or,- for the copy constructor, a non-static data member of rvalue reference type.
Change in 11.4.6 [class.copy.assign] paragraph 7 as follows:
A defaulted copy/move assignment operator for class X is defined as deleted if X has:
a variant member with a non-trivial corresponding assignment operator and X is a union-like class, or- a non-static data member of const non-class type (or possibly multi-dimensional array thereof), or
- a non-static data member of reference type, or
- a direct non-static data member of class type M (or possibly multi-dimensional array thereof) or a direct base class M that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to find M's corresponding assignment operator,
results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operatoreither does not result in a usable candidate (12.2.1 [over.match.general]) or, in the case of a variant member, selects a non-trivial function.
Change in 11.4.7 [class.dtor] paragraph 7 as follows:
A 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 potentially constructed subobject has class type M (or possibly multi-dimensional array thereof) and M has a
deleteddestructor that is deleted ora destructor thatis inaccessible from the defaulted destructor or, in the case of a variant member, is non-trivial,- or, for a virtual destructor, lookup of the non-array deallocation function results in an ambiguity or in a function that is deleted or inaccessible from the defaulted destructor.
[Accepted as a DR at the June, 2024 meeting.]
(From submission #510.)
Subclause 11.4.5.2 [class.default.ctor] paragraph 1 does not, but should, consider user-declared constructor templates.
Proposed resolution (approved by CWG 2024-04-05):
Change in 11.4.5.2 [class.default.ctor] paragraph 1 as follows:
... If there is no user-declared constructor or constructor template for class X, a non-explicit constructor having no parameters is implicitly declared as defaulted (9.6 [dcl.fct.def]). ...
[ Resolved by the changes for issue 1353, accepted as a DR at the June, 2023 meeting. ]
Bullet 4 of 11.4.5.3 [class.copy.ctor] paragraph 23 says that a defaulted copy/move assignment operator is defined as deleted if the class has
a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to M's corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator
The intent of this is that if overload resolution fails to find a corresponding copy/move assignment operator that can validly be called to copy/move a member, the class's assignment operator will be defined as deleted. However, this wording does not cover an example like the following:
struct A { A(); }; struct B { B(); const A a; }; typedef B& (B::*pmf)(B&&); pmf p =&B::operator=;
Here, the problem is simply that overload resolution failed to find a callable function, which is not one of the cases listed in the current wording. A similar problem exists for base classes in the fifth bullet.
Additional note (January, 2013):
A similar omission exists in paragraph 11 for copy constructors.
[Accepted as a DR at the November, 2023 meeting.]
Consider:
struct S{
~S() {}
};
struct A {
union {
S arr_;
};
~A(); // user-provided!
};
auto foo() {
return A{S()};
}
Does the destructor of A attempt to destroy the (unnamed) data member that is the anonymous union? The latter has a deleted destructor per 11.4.7 [class.dtor]. For the default constructor, 11.9.3 [class.base.init] paragraph 9.2 prevents the corresponding construction.
Proposed resolution (approved by CWG 2023-08-25):
Change in 9.5.2 [dcl.init.aggr] paragraph 9 as follows:
The destructor for each element of class type other than an anonymous union member is potentially invoked (11.4.7 [class.dtor]) from the context where the aggregate initialization occurs
Change in 11.4.7 [class.dtor] paragraph 13 as follows:
After executing the body of the destructor and destroying any objects with automatic storage duration allocated within the body, a destructor for class X calls the destructors for X's direct non-variant non-static data members other than anonymous unions, the destructors for X's non-virtual direct base classes and, if X is the most derived class (11.9.3 [class.base.init]), its destructor calls the destructors for X's virtual base classes. All destructors are called as if they were referenced with a qualified name, that is, ignoring any possible virtual overriding destructors in more derived classes. Bases and members are destroyed in the reverse order of the completion of their constructor (see 11.9.3 [class.base.init]).
Change in 14.3 [except.ctor] paragraph 3 as follows:
A subobject is known to be initialized if it is not an anonymous union member and its initialization is specified
- in 11.9.3 [class.base.init] for initialization by constructor,
- in 11.4.5.3 [class.copy.ctor] for initialization by defaulted copy/move constructor,
- in 11.9.4 [class.inhctor.init] for initialization by inherited constructor,
- in 9.5.2 [dcl.init.aggr] for aggregate initialization,
- in 7.5.6.3 [expr.prim.lambda.capture] for the initialization of the closure object when evaluating a lambda-expression,
- in 9.5.1 [dcl.init.general] for default-initialization, value-initialization, or direct-initialization of an array.
[Accepted as a DR at the November, 2023 meeting.]
There is a conflict between 9.2.6 [dcl.constexpr] paragraph 2
A destructor, an allocation function, or a deallocation function shall not be declared with the consteval specifier.
and 11.4.7 [class.dtor] paragraph 1
Each decl-specifier of the decl-specifier-seq of a prospective destructor declaration (if any) shall be friend, inline, virtual, constexpr, or consteval.
Proposed resolution (approved by CWG 2023-10-20):
Change in 11.4.7 [class.dtor] paragraph 1 as follows:
Each decl-specifier of the decl-specifier-seq of a prospective destructor declaration (if any) shall be friend, inline, virtual, or constexpr, or consteval.
[Accepted as a DR at the June, 2023 meeting.]
The rule in 11.4.8.3 [class.conv.fct] paragraph 4 is normatively redundant explanation:
A conversion function is never used to convert a (possibly cv-qualified) object to the (possibly cv-qualified) same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to it), or to cv void. [ Footnote: These conversions are considered as standard conversions for the purposes of overload resolution (12.2.4.2 [over.best.ics], 12.2.4.2.5 [over.ics.ref]) and therefore initialization (9.5 [dcl.init]) and explicit casts (7.6.1.9 [expr.static.cast]). A conversion to void does not invoke any conversion function (7.6.1.9 [expr.static.cast]). Even though never directly called to perform a conversion, such conversion functions can be declared and can potentially be reached through a call to a virtual conversion function in a base class. -- end footnote ]
Proposed resolution (approved by CWG 2023-04-28):
Change in 11.4.8.3 [class.conv.fct] paragraph 4 as follows:
[ Note: A conversion function is neverused to convertinvoked for implicit or explicit conversions of ana (possibly cv-qualified)object to the(possibly cv-qualified)same object type (or a reference to it), to a(possibly cv-qualified)base class of that type (or a reference to it), or to cv void.[ Footnote:These conversions are considered as standard conversions for the purposes of overload resolution (12.2.4.2 [over.best.ics], 12.2.4.2.5 [over.ics.ref]) and therefore initialization (9.5 [dcl.init]) and explicit casts (7.6.1.9 [expr.static.cast]). A conversion to void does not invoke any conversion function (7.6.1.9 [expr.static.cast]).Even though never directly called to perform a conversion, such conversion functions can be declared and can potentially be reached through a call to a virtual conversion function in a base class.-- end footnote ]-- end note ][ Example: ... ]
[Accepted as a DR at the November, 2023 meeting.]
Subclause 11.5.1 [class.union.general] paragraph 6 describes how union member subobjects are implicitly created by certain assignment operations that assign to union members. However, this description does not appear to properly handle the case of an anonymous union appearing within a union:
union A { int x; union { int y; }; }; void f() { A a = {.x = 1}; a.y = 2; }
Here, the expectation is that the assignment to a.y starts the lifetime of the anonymous union member subobject within A and also the int member subobject of the anonymous union member subobject. But the algorithm for computing S(a.y) determines that it is {a.y} and does not include the anonymous union member subobject.
Proposed resolution (approved by CWG 2023-06-17):
Change in 11.5.1 [class.union.general] paragraph 6 as follows:
In an assignment expression of the form E1 = E2 that uses either the built-in assignment operator (7.6.19 [expr.assign]) or a trivial assignment operator (11.4.6 [class.copy.assign]), for each element X of S(E1) and each anonymous union member X (11.5.2 [class.union.anon]) that is a member of a union and has such an element as an immediate subobject (recursively), if modification of X would have undefined behavior under 6.7.4 [basic.life], an object of the type of X is implicitly created in the nominated storage; no initialization is performed and the beginning of its lifetime is sequenced after the value computation of the left and right operands and before the assignment.
Editing note: Adding this rule into the definition of S would be more logical, but S(E) is a set of subexpressions of E and there is no form of expression that names an anonymous union member. Redefining S(E) to be a set of objects might be a better option.
[Accepted as a DR at the November, 2024 meeting.]
Subclause 11.6 [class.local] paragraph 3 establishes restrictions on the definition of classes nested within local classes, but it is unclear which restrictions exist for other members of local classes.
Possible resolution [SUPERSEDED]:
Change in 11.6 [class.local] paragraph 3 as follows:
If class X is a local class, a nested class Y may be declared in class X and later defined in the definition of class X or be later defined in the same scope as the definition of class X.A class nested within a local class is a local class. A member of a local class X shall be declared only in the definition of X or the nearest enclosing block scope of X.
CWG 2024-06-14
The implementation status quo is that no members of local classes other than nested classes can be defined at block scope.
Proposed resolution (approved by CWG 2024-11-08):
Change in 11.6 [class.local] paragraph 3 as follows:
If class X is a local class, a nested class Y may be declared in class X and later defined in the definition of class X or be later defined in the same scope as the definition of class X.A class nested within a local class is a local class. A member of a local class X shall be declared only in the definition of X or, if the member is a nested class, in the nearest enclosing block scope of X.
[Accepted as a DR at the June, 2024 meeting.]
Consider:
export module Foo;
class X {
friend void f(X); // #1 linkage?
};
Subclause 11.8.4 [class.friend] paragraph 4 gives #1 external linkage:
A function first declared in a friend declaration has the linkage of the namespace of which it is a member (6.6 [basic.link]).
(There is no similar provision for friend classes first declared in a class.)
However, 6.6 [basic.link] bullet 4.8 gives it module linkage:
... otherwise, if the declaration of the name is attached to a named module (10.1 [module.unit]) and is not exported (10.2 [module.interface]), the name has module linkage;
Subclause 10.2 [module.interface] paragraph 2 does not apply:
A declaration is exported if it is declared within an export-declaration and inhabits a namespace scope or it is
- a namespace-definition that contains an exported declaration, or
- a declaration within a header unit (10.3 [module.import]) that introduces at least one name.
Also consider this related example:
export module Foo; export class Y; // maybe many lines later, or even a different partition of Foo class Y { friend void f(Y); // #2 linkage? };
See issue 2607 for a similar question about enumerators.
Additional note (May, 2022):
Forwarded to EWG with paper issue 1253, by decision of the CWG chair.
EWG telecon 2022-06-09
Consensus: "A friend's linkage should be affected by the presence/absence of export on the containing class definition itself, but ONLY if the friend is a definition", pending confirmation by electronic polling.
Proposed resolution (June, 2022) (approved by CWG 2023-01-27) [SUPERSEDED]:
Change in 6.6 [basic.link] paragraph 4 as follows:
... The name of an entity that belongs to a namespace scope that has not been given internal linkage above and that is the name ofhas its linkage determined as follows:
- a variable; or
- a function; or
- a named class (11.1 [class.pre]), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes (9.2.4 [dcl.typedef]); or
- a named enumeration (9.8.1 [dcl.enum]), or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes (9.2.4 [dcl.typedef]); or
- an unnamed enumeration that has an enumerator as a name for linkage purposes (9.8.1 [dcl.enum]); or
- a template
- if the entity is a function or function template first declared in a friend declaration and that declaration is a definition, the name has the same linkage, if any, as the name of the enclosing class (11.8.4 [class.friend]);
- otherwise, if the entity is a function or function template declared in a friend declaration and a corresponding non-friend declaration is reachable, the name has the linkage determined from that prior declaration,
- otherwise, if the enclosing namespace has internal linkage, the name has internal linkage;
- otherwise, if the declaration of the name is attached to a named module (10.1 [module.unit]) and is not exported (10.2 [module.interface]), the name has module linkage;
- otherwise, the name has external linkage.
Remove 11.8.4 [class.friend] paragraph 4:
A function first declared in a friend declaration has the linkage of the namespace of which it is a member (6.6 [basic.link]). Otherwise, the function retains its previous linkage (9.2.2 [dcl.stc]).
EWG electronic poll 2022-06
Consensus for "A friend's linkage should be affected by the presence/absence of export on the containing class definition itself, but ONLY if the friend is a definition (option #2, modified by Jason's suggestion). This resolves CWG2588." See vote.
Proposed resolution (approved by CWG 2024-03-20):
Change in 6.6 [basic.link] paragraph 4 as follows:
... The name of an entity that belongs to a namespace scope that has not been given internal linkage above and that is the name ofhas its linkage determined as follows:
- a variable; or
- a function; or
- a named class (11.1 [class.pre]), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes (9.2.4 [dcl.typedef]); or
- a named enumeration (9.8.1 [dcl.enum]), or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes (9.2.4 [dcl.typedef]); or
- an unnamed enumeration that has an enumerator as a name for linkage purposes (9.8.1 [dcl.enum]); or
- a template
- if the entity is a function or function template first declared in a friend declaration and that declaration is a definition and the enclosing class is defined within an export-declaration, the name has the same linkage, if any, as the name of the enclosing class (11.8.4 [class.friend]);
- otherwise, if the entity is a function or function template declared in a friend declaration and a corresponding non-friend declaration is reachable, the name has the linkage determined from that prior declaration,
- otherwise, if the enclosing namespace has internal linkage, the name has internal linkage;
- otherwise, if the declaration of the name is attached to a named module (10.1 [module.unit]) and is not exported (10.2 [module.interface]), the name has module linkage;
- otherwise, the name has external linkage.
Remove 11.8.4 [class.friend] paragraph 4:
A function first declared in a friend declaration has the linkage of the namespace of which it is a member (6.6 [basic.link]). Otherwise, the function retains its previous linkage (9.2.2 [dcl.stc]).
[Accepted as a DR at the November, 2023 meeting.]
According to 11.9.4 [class.inhctor.init] paragraph 1,
When a constructor for type B is invoked to initialize an object of a different type D (that is, when the constructor was inherited (9.10 [namespace.udecl])), initialization proceeds as if a defaulted default constructor were used to initialize the D object and each base class subobject from which the constructor was inherited, except that the B subobject is initialized by the invocation of the inherited constructor. The complete initialization is considered to be a single function call; in particular, the initialization of the inherited constructor's parameters is sequenced before the initialization of any part of the Dobject.
First, this assumes that the base class constructor will be invoked from the derived class constructor, which will not be true if the base is virtual and initialized by a more-derived constructor.
If the call to the virtual base constructor is omitted, the last sentence is unclear whether the initialization of the base class constructor's parameters by the inheriting constructor occurs or not. There is implementation divergence in the initialization of V's parameter in the following example:
struct NonTriv { NonTriv(int); ~NonTriv(); }; struct V { V() = default; V(NonTriv); }; struct Q { Q(); }; struct A : virtual V, Q { using V::V; A() : A(42) { } }; struct B : A { }; void foo() { B b; }
CWG telecon 2022-09-23:
Inheriting constructors from a virtual base class ought to be ill-formed. Inform EWG accordingly.
Possible resolution [SUPERSEDED]:
Change in 9.10 [namespace.udecl] paragraph 3 as follows:
... If a using-declarator names a constructor, its nested-name-specifier shall name a direct non-virtual base class of the current class. If the immediate (class) scope is associated with a class template, it shall derive from the specified base class or have at least one dependent base class.
Change the example in 11.9.4 [class.inhctor.init] paragraph 1 as follows:
D2 f(1.0); // error: B1 hasa deletedno default constructorstruct W { W(int); }; struct X : virtual W { using W::W; X() = delete; }; struct Y : X { using X::X; }; struct Z : Y, virtual W { using Y::Y; }; Z z(0); // OK, initialization of Y does not invoke default constructor of X
Change the example in 11.9.4 [class.inhctor.init] paragraph 2 as follows:
struct V1 : virtual B { using B::B; }; struct V2 : virtual B { using B::B; }; struct D2 : V1, V2 { using V1::V1; using V2::V2; };D1 d1(0); // error: ambiguousD2 d2(0); // OK, initializes virtual B base class, which initializes the A base class // then initializes the V1 and V2 base classes as if by a defaulted default constructor
CWG telecon 2022-10-07:
Given that there are examples that discuss inheriting constructors from virtual base classes and given the existing normative wording, making it clear that NonTriv is not constructed, CWG felt that the implementation divergence is best addressed by amending the examples.
Possible resolution [SUPERSEDED]:
Add another example before 11.9.4 [class.inhctor.init] paragraph 2 as follows:
[ Example:
struct NonTriv { NonTriv(int); ~NonTriv(); }; struct V { V() = default; V(NonTriv); }; struct Q { Q(); }; struct A : virtual V, Q { using V::V; A() : A(42) { } // #1, A(42) is equivalent to V(42) }; struct B : A { }; void foo() { B b; }In this example, the V subobject of b is constructed using the defaulted default constructor. The mem-initializer naming the constructor inherited from V at #1 is not evaluated and thus no object of type NonTriv is constructed. -- end example ]
If the constructor was inherited from multiple base class subobjects of type B, the program is ill-formed.
Proposed resolution (approved by CWG 2023-11-06):
Change in 11.9.4 [class.inhctor.init] paragraph 1 as follows:
When a constructor for type B is invoked to initialize an object of a different type D (that is, when the constructor was inherited (9.10 [namespace.udecl])), initialization proceeds as if a defaulted default constructor were used to initialize the D object and each base class subobject from which the constructor was inherited, except that the B subobject is initialized bythe invocation ofthe inherited constructor if the base class subobject were to be initialized as part of the D object (11.9.3 [class.base.init]). The invocation of the inherited constructor, including the evaluation of any arguments, is omitted if the B subobject is not to be initialized as part of the D object. The complete initialization is considered to be a single function call; in particular, unless omitted, the initialization of the inherited constructor's parameters is sequenced before the initialization of any part of the Dobject.
Add another example before 11.9.4 [class.inhctor.init] paragraph 2 as follows:
[ Example:
struct V { V() = default; V(int); }; struct Q { Q(); }; struct A : virtual V, Q { using V::V; A() = delete; }; int bar() { return 42; } struct B : A { B() : A(bar()) {} // ok }; struct C : B {}; void foo() { C c; } // bar is not invoked, because the V subobject is not initialized as part of B-- end example ]
CWG telecon 2022-10-21:
This is an ABI break for implementations when transitioning to the C++17 model for inheriting constructors.
[Accepted as a DR at the November, 2024 meeting.]
The specification of copy elision in 11.9.6 [class.copy.elision] uses the undefined term "copy/move operation", even though the constructor actually selected might not be a copy or move constructor as specified in 11.4.5.3 [class.copy.ctor]. It is thus unclear whether copy elision can be applied even in the case a non-copy/move constructor is selected.
Proposed resolution (approved by CWG 2024-11-22):
Change in 11.9.6 [class.copy.elision] paragraph 1 through 3 as follows:
When certain criteria are met, an implementation is allowed to omit thecopy/move construction ofcreation of a class object from a source object of the same type (ignoring cv-qualification), even if the selected constructorselected for the copy/move operationand/or the destructor for the object have side effects. In such cases, the implementation treats the source and target of the omittedcopy/move operationinitialization as simply two different ways of referring to the same object. If the first parameter of the selected constructor is an rvalue reference to the object's type, the destruction of that object occurs when the target would have been destroyed; otherwise, the destruction occurs at the later of the times when the two objects would have been destroyed without the optimization. [Footnote:Note: Because only one object is destroyed instead of two, andone copy/move constructor is not executedthe creation of one object is omitted, there is still one object destroyed for each one constructed. -- endfootnotenote ] This elision ofcopy/move operationsobject creation, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
- in a return statement (8.7.4 [stmt.return]) in a function with a class return type, when the expression is the name of a non-volatile object o with automatic storage duration (other than a function parameter or a variable introduced by the exception-declaration of a handler (14.4 [except.handle]))
with the same type (ignoring cv-qualification) as the function return type, thecopy/move operationcopy-initialization of the result object can be omitted by constructingthe objecto directly into the function call'sreturnresult object;- in a throw-expression (7.6.18 [expr.throw]), when the operand is the name of a non-volatile object o with automatic storage duration (other than a function
or catch-clauseparameter or a variable introduced by the exception-declaration of a handler) that belongs to a scope that does not contain the innermost enclosing compound-statement associated with a try-block (if there is one), thecopy/move operationcopy-initialization of the exception object can be omitted by constructingthe objecto directly into the exception object;- in a coroutine (9.6.4 [dcl.fct.def.coroutine]), a copy of a coroutine parameter can be omitted and references to that copy replaced with references to the corresponding parameter if the meaning of the program will be unchanged except for the execution of a constructor and destructor for the parameter copy object;
- when the exception-declaration of a handler (14.4 [except.handle]) declares an object o
of the same type (except for cv-qualification) as the exception object (14.2 [except.throw]), thecopy operationcopy-initialization of o can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration. [Note 1: There cannot be a move from the exception object because it is always an lvalue. —end note]
[Accepted as a DR at the March, 2024 meeting.]
Consider:
struct Base {
protected:
bool operator==(const Base& other) const = default;
};
struct Child : Base {
int i;
bool operator==(const Child& other) const = default;
};
bool b = Child() == Child(); //error: deleted operator==
Per 11.10.1 [class.compare.default] paragraph 6,
Let xi be an lvalue denoting the i-th element in the expanded list of subobjects for an object x (of length n), where xi is formed by a sequence of derived-to-base conversions (12.2.4.2 [over.best.ics]), class member access expressions (7.6.1.5 [expr.ref]), and array subscript expressions (7.6.1.2 [expr.sub]) applied to x.
The derived-to-base conversion for this loses the context of access to the protected Base::operator==, violating 11.8.5 [class.protected] paragraph 1. The example is rejected by implementations, but ought to work.
For this related example, there is implementation divergence:
struct B { protected: constexpr operator int() const { return 0; } }; struct D : B { constexpr bool operator==(const D&) const = default; }; template<typename T> constexpr auto comparable(T t) -> decltype(t == t) { return t == t; } constexpr bool comparable(...) { return false; } static_assert(comparable(D{}));
Is D::operator== deleted, because its defaulted definition violates the protected access rules? Is D::operator== not deleted, but synthesis fails on use because of the proctected access rules? Is the synthesis not in the immediate context, making the expression comparable(D{}) ill-formed?
CWG 2023-06-17
There is no implementation divergence; the first example yields a deleted operator== in the derived class.
Proposed resolution (approved by CWG 2023-12-01):
Change in 11.10.1 [class.compare.default] paragraph 1 as follows:
... Name lookups and access checks in the implicit definition (9.6.2 [dcl.fct.def.default]) of a comparison operator function are performed from a context equivalent to its function-body . A definition of a comparison operator as defaulted that appears in a class shall be the first declaration of that function.
Additional notes (March, 2025)
Despite the intent, the applied resolution does not make the original example well-formed, because the object expression in the synthesized body of Child::operator== is of type Base, violating 11.8.5 [class.protected] paragraph 1. See issue 3007 for the next attempt.
[Accepted as a DR at the March, 2024 meeting.]
(See also editorial issues 5335 and 5336.)
Consider the example in 11.10.4 [class.compare.secondary] paragraph 3:
struct HasNoLessThan { };
struct C {
friend HasNoLessThan operator<=>(const C&, const C&);
bool operator<(const C&) const = default; // OK, function is deleted
};
While the comment may reflect the intent, it does not follow from the wording. 11.10.4 [class.compare.secondary] paragraph 2 specifies:
The operator function with parameters x and y is defined as deleted if
- overload resolution (12.2 [over.match]), as applied to x @ y, does not result in a usable candidate, or
- the candidate selected by overload resolution is not a rewritten candidate.
Otherwise, the operator function yields x @ y. The defaulted operator function is not considered as a candidate in the overload resolution for the @ operator.
Overload resolution applied to x < y results in a usable candidate operator<=> (12.2.1 [over.match.general]) and that candidate is a rewritten candidate (12.2.2.3 [over.match.oper] bullet 3.4), thus operator< in the above example is not deleted. However, its definition is ill-formed, because the rewrite (x <=> y) < 0 is ill-formed (12.2.2.3 [over.match.oper] paragraph 8).
There is implementation divergence.
Subclause 11.10.3 [class.spaceship] paragraph 1 seems to prefer an ill-formed program for similar synthesized situations:
[Note 1: A synthesized three-way comparison is ill-formed if overload resolution finds usable candidates that do not otherwise meet the requirements implied by the defined expression. —end note]
Proposed resolution (approved by CWG 2024-03-20):
Change in 11.10.4 [class.compare.secondary] paragraph 2 as follows:The operator function with parameters x and y is defined as deleted if
- a first overload resolution (12.2 [over.match]), as applied to x @ y,
- does not result in a usable candidate, or
- the selected candidate
selected by overload resolutionis not a rewritten candidate., or- a second overload resolution for the expression resulting from the interpretation of x @ y using the selected rewritten candidate (12.2.2.3 [over.match.oper]) does not result in a usable candidate (for example, that expression might be (x <=> y) @ 0), or
- x @ y cannot be implicitly converted to bool.
In any of the two overload resolutions above, the defaulted operator function is not considered as a candidate for the @ operator. Otherwise, the operator function yields x @ y.
The defaulted operator function is not considered as a candidate in the overload resolution for the @ operator.
[Accepted as a DR at the November, 2023 meeting.]
Subclause 12.2.2.1 [over.match.funcs.general] paragraph 4 specifies:
For implicit object member functions, the type of the implicit object parameter iswhere X is the class of which the function is a member and cv is the cv-qualification on the member function declaration.
- “lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
- “rvalue reference to cv X” for functions declared with the && ref-qualifier
Since a member of some class C is also a member of any class derived from C, this specification is unclear.
Proposed resolution (approved by CWG 2023-08-25):
Change in 12.2.2.1 [over.match.funcs.general] paragraph 4 as follows:
For implicit object member functions, the type of the implicit object parameter iswhere X is the class of which the function is a direct member and cv is the cv-qualification on the member function declaration.
- “lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
- “rvalue reference to cv X” for functions declared with the && ref-qualifier
[Accepted as a DR at the June, 2023 meeting.]
Subclause 12.2.2.3 [over.match.oper] paragraph 5 specifies:
For the built-in assignment operators, conversions of the left operand are restricted as follows:
- no temporaries are introduced to hold the left operand, and
- no user-defined conversions are applied to the left operand to achieve a type match with the left-most parameter of a built-in candidate.
The first bullet is redundant, because standard conversion sequences cannot bind "vq T&" (the type of the first parameter of a built-in assignment operator) to a temporary.
Proposed resolution (approved by CWG 2023-03-30):
Change in 12.2.2.3 [over.match.oper] paragraph 5 as follows:
For the first parameter of the built-in assignment operators, only standard conversion sequences (12.2.4.2.2 [over.ics.scs]) are considered.conversions of the left operand are restricted as follows:
no temporaries are introduced to hold the left operand, andno user-defined conversions are applied to the left operand to achieve a type match with the left-most parameter of a built-in candidate.
[Accepted as a DR at the November, 2024 meeting.]
There is implementation divergence handling the following example:
struct A { A(const A&) = delete; }; struct B { operator A&&(); }; const A& r = B();
Conversion to an lvalue pursuant to 9.5.4 [dcl.init.ref] bullet 5.1 fails due to the attempt to invoke a deleted function, but conversion to an rvalue according to 9.5.4 [dcl.init.ref] bullet 5.3 would succeed, except that 12.2.2.7 [over.match.ref] bullet 1.1 hinges on the target type of the initialization, not the target type of the conversion.
Proposed resolution (approved by CWG 2024-08-16):
Change in 12.2.2.7 [over.match.ref] bullet 1.1 as follows:
Let R be a set of types includingfor any T2.
- “lvalue reference to cv2 T2” (when
initializingconverting to an lvaluereference or an rvalue reference to function) and- “cv2 T2” and “rvalue reference to cv2 T2” (when
initializingconverting to an rvaluereferenceor an lvaluereference toof function type)
[Accepted as a DR at the March, 2024 meeting.]
(From submission #486.)
Consider:
struct A { explicit A(int = 10); A() = default; // converting constructor (11.4.8.2 [class.conv.ctor] paragraph 1) }; A a = {}; // #1, copy-initialization int f(A); int x = f({}); // #2 A b; // #3
#1 and #2 are accepted by MSVC and EDG, but considered ambiguous by gcc and clang. #3 is rejected as ambiguous by all major implementations.
#1 is copy-list-initialization (9.5.5 [dcl.init.list] paragraph 1), and A has a default constructor, thus a is value-initialized (9.5.5 [dcl.init.list] bullet 3.4). The default constructors are user-provided, thus a is default-initialized (9.5.1 [dcl.init.general] bullet 8.1, 9.5.1 [dcl.init.general] bullet 7.1). Overload resolution then chooses a constructor according to 12.2.2.4 [over.match.ctor] paragraph 1:
... For copy-initialization (including default initialization in the context of copy-initialization), the candidate functions are all the converting constructors (11.4.8.2 [class.conv.ctor]) of that class. ...
Thus, the explicit constructor is not a candidate; #1 chooses the converting constructor.
In contrast, #2 uses the special rules for forming a list-initialization sequence (12.2.4.2.6 [over.ics.list] paragraph 7), which perform overload resolution according to 12.2.2.8 [over.match.list] paragraph 1, which considers all constructors for overload resolution (and makes the program ill-formed if an explicit constructor is chosen). For the example, overload resolution is ambiguous.
#3 performs default-initialization, and overload resolution then chooses a constructor according to 12.2.2.4 [over.match.ctor] paragraph 1:
... For direct-initialization or default-initialization that is not in the context of copy-initialization, the candidate functions are all the constructors of the class of the object being initialized. ...
In this case, the overload resolution is ambiguous.
Proposed resolution (approved by CWG 2024-02-16):
Change in 12.2.2.4 [over.match.ctor] paragraph 1 as follows:
When objects of class type are direct-initialized (9.5 [dcl.init]), copy-initialized from an expression of the same or a derived class type (9.5 [dcl.init]), or default-initialized (9.5 [dcl.init]), overload resolution selects the constructor. For direct-initialization or default-initializationthat is not in the context of copy-initialization(including default-initialization in the context of copy-list-initialization), the candidate functions are all the constructors of the class of the object being initialized.For copy-initialization (including default initialization in the context of copy-initialization)Otherwise, the candidate functions are all the converting constructors (11.4.8.2 [class.conv.ctor]) of that class. The argument list is the expression-list or assignment-expression of the initializer. For default-initialization in the context of copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.
CWG 2024-02-16
The fact that #2 considers all constructors was discussed (and established) in the C++11 timeframe when brace-initialization was first introduced. #1 should be consistent with that, even though the = is usually a clear sign that explicit constructors are not considered.
[Accepted as a DR at the November, 2023 meeting.]
Consider:
template<class T> concept True = true;
template<class T> struct X {
template<class U> requires True<T> X(T, U(&)[3]);
};
template<typename T, typename U> X(T, U(&)[3]) -> X<T>;
int arr3[3];
X z(3, arr3); // #1
According to 12.2.2.9 [over.match.class.deduct] bullet 1.1, the requires-clause of the constructor is not propagated to the function template synthesized for the implicit deduction guide. Thus, instead of favoring the more-constrained implicit deduction guide per 12.2.4.1 [over.match.best.general] bullet 2.6, the user-declared deduction-guide is preferred per 12.2.4.1 [over.match.best.general] bullet 2.11.
Proposed resolution (approved by CWG 2023-10-20):
Change in 12.2.2.9 [over.match.class.deduct] bullet 1.1 as follows:
- If C is defined, for each constructor of C, a function template with the following properties:
- The template parameters are the template parameters of C followed by the template parameters (including default template arguments) of the constructor, if any.
- The associated constraints (13.5.3 [temp.constr.decl]) are the conjunction of the associated constraints of C and the associated constraints of the constructor.
- The types of the function parameters are those of the constructor.
- The return type is the class template specialization designated by C and template arguments corresponding to the template parameters of C.
[Accepted as a DR at the March, 2024 meeting.]
Subclause 12.2.2.9 [over.match.class.deduct] bullet 1.1.2 specifies:
- The types of the function parameters are those of the constructor.
However, this does not consider default arguments or variadic constructors.
Proposed resolution (approved by CWG 2023-04-28):
(This also resolves issue 2628.)
Change in 12.2.2.9 [over.match.class.deduct] paragraph 1 as follows:
- If C is defined, for each constructor of C, a function template with the following properties:
- The template parameters are the template parameters of C followed by the template parameters (including default template arguments) of the constructor, if any.
- The associated constraints (13.5.3 [temp.constr.decl]) are the conjunction of the associated constraints of C and the associated constraints of the constructor, if any. [ Note: A constraint-expression in the template-head of C is checked for satisfaction before any constraints from the template-head or trailing-requires-clause of the constructor. -- end note ]
- The
types of the function parameters are thoseparameter-declaration-clause is that of the constructor.- The return type is the class template specialization designated by C and template arguments corresponding to the template parameters of C.
- If C is not defined or does not declare any constructors, an additional function template derived as above from a hypothetical constructor C().
- An additional function template derived as above from a hypothetical constructor C(C), called the copy deduction candidate.
- For each deduction-guide, a function or function template with the following properties:
- The
template parameterstemplate-head, if any, andfunction parametersparameter-declaration-clause are those of the deduction-guide.- The return type is the simple-template-id of the deduction-guide.
[Accepted as a DR at the November, 2023 meeting.]
Consider:
template <typename T = int> struct S { constexpr void f(); // #1 constexpr void f(this S&) requires true; // #2 }; void test() { S<> s; s.f(); // #3 }
With the current rules, the call at #3 is ambiguous, even though #2 is more constrainted.
Proposed resolution (approved by CWG 2023-11-07):
Change in 12.2.4.1 [over.match.best.general] bullet 2.6 as follows:
- ...
- F1 and F2 are non-template functions
withandor if not that,
- they have the same
parameter-type-listsnon-object-parameter-type-lists (9.3.4.6 [dcl.fct]), and- if they are member functions, both are direct members of the same class, and
- if both are non-static member functions, they have the same types for their object parameters, and
- F1 is more constrained than F2 according to the partial ordering of constraints described in 13.5.5 [temp.constr.order],
[ Example:
template <typename T = int> struct S { constexpr void f(); // #1 constexpr void f(this S&) requires true; // #2 }; void test() { S<> s; s.f(); // calls #2 }-- end example ]
- ...
[Accepted as a DR at the March, 2024 meeting.]
Consider:
int foo(int*& r); // #1 int foo(const int* const& r); // #2 int *p; int x = foo(p);
Both #1 and #2 perform direct reference binding; no qualification conversions are involved. Despite the lack of a rule, implementations prefer #1 over #2.
Proposed resolution (approved by CWG 2023-11-10):
Change in 12.2.4.2.5 [over.ics.ref] paragraph 1 as follows:
When a parameter ofreferencetype "reference to cv T" binds directly (9.5.4 [dcl.init.ref]) to an argument expression, the implicit conversion sequence is the identity conversion, unless:
- If the argument expression has a type that is a derived class of the parameter type,
in which casethe implicit conversion sequence is a derived-to-base conversion (12.2.4.2 [over.best.ics]).- Otherwise, if T is a function type, or if the type of the argument is possibly cv-qualified T, or if T is an array type of unknown bound with element type U and the argument has an array type of known bound whose element type is possibly cv-qualified U, the implicit conversion sequence is the identity conversion. [ Note: When T is a function type, the type of the argument may differ only by the presence of noexcept. -- end note]
- Otherwise, the implicit conversion sequence is a qualification conversion.
[Example 1: ... —end example]
If the parameter binds directly to the result of applying a conversion function to the argument expression, the implicit conversion sequence is a user-defined conversion sequence (12.2.4.2.3 [over.ics.user]) whose second standard conversion sequence iseither an identity conversion or, if the conversion function returns an entity of a type that is a derived class of the parameter type, a derived-to-base conversiondetermined by the above rules.
Change in 12.2.4.3 [over.ics.rank] bullet 3.2.5 as follows:
- S1 and S2 differ only in their qualification conversion (7.3.6 [conv.qual]) and yield similar types T1 and T2, respectively (where a standard conversion sequence that is a reference binding is considered to yield the cv-unqualified referenced type), where T1
can be converted to T2 by a qualification conversionand T2 are not the same type, and const T2 is reference-compatible with T1 (9.5.4 [dcl.init.ref]). [Example 5:int f(const volatile int *); int f(const int *); int i; int j = f(&i); // calls f(const int*) int g(const int*); int g(const volatile int* const&); int* p; int k = g(p); // calls g(const int*)-- end example] or, if not that,
Change in 12.2.4.3 [over.ics.rank] bullet 3.2.6 as follows:
- S1 and S2
include reference bindingsbind "reference to T1" and "reference to T2", respectively (9.5.4 [dcl.init.ref]),and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 referswhere T1 and T2 are not the same type, and T2 is reference-compatible with T1. [Example 6: ...int h1(int (&)[]); int h1(int (&)[1]); int h2(void (&)()); int h2(void (&)() noexcept); void g2() { int a[1]; h1(a); // calls h1(int (&)[1]) extern void f2() noexcept; h2(f2); // calls h2(void (&)() noexcept) }-- end example ]
[Accepted as a DR at the November, 2024 meeting.]
Consider:
void f() noexcept {}
void g(void (*)() noexcept) {}
void g(void (&)()) {}
int main() {
g(f); // error: ambiguous
}
In contrast:
void f() noexcept {} void g(void (*)()) {} void g(void (&)()) {} // #1 int main() { g(f); // OK, calls #1 }
In both cases, binding void(&)() to void() noexcept is considered an identity conversion, without further disambiguation by 12.2.4.3 [over.ics.rank].
CWG 2024-06-26
Binding a reference to a function should not be considered an identity conversion if it strips a non-throwing exception specification. This amendment removes the ambiguity for the first example and makes the second example ambiguous, which is desirable.
Proposed resolution (approved by CWG 2024-10-11):
Change in 12.2.4.2.5 [over.ics.ref] paragraph 1 as follows:
When a parameter of type “reference to cvT” binds directly (9.5.4 [dcl.init.ref]) to an argument expression:[Example 1:
- If the argument expression has a type that is a derived class of the parameter type, the implicit conversion sequence is a derived-to-base conversion (12.2.4.2 [over.best.ics]).
- Otherwise,
if T is a function type, orif the type of the argument is possibly cv-qualified T, or if T is an array type of unknown bound with element type U and the argument has an array type of known bound whose element type is possibly cv-qualified U, the implicit conversion sequence is the identity conversion.[Note 1: When T is a function type, the type of the argument can differ only by the presence of noexcept. —end note]- Otherwise, if T is a function type, the implicit conversion sequence is a function pointer conversion.
- Otherwise, the implicit conversion sequence is a qualification conversion.
struct A {}; struct B : public A {} b; int f(A&); int f(B&); int i = f(b); // calls f(B&), an exact match, rather than f(A&), a conversion
void g() noexcept; int h(void (&)() noexcept); // #1 int h(void (&)()); // #2 int j = h(g); // calls #1, an exact match, rather than #2, a function pointer conversion—end example]
Change in 12.2.4.3 [over.ics.rank] bullet 3.2.6 as follows:
int f(const int &); int f(int &); int g(const int &); int g(int); int i; int j = f(i); // calls f(int &) int k = g(i); // ambiguous struct X { void f() const; void f(); }; void g(const X& a, X b) { a.f(); // calls X::f() const b.f(); // calls X::f() } int h1(int (&)[]); int h1(int (&)[1]);int h2(void (&)()); int h2(void (&)() noexcept);void g2() { int a[1]; h1(a);extern void f2() noexcept; h2(f2);}
[Accepted as a DR at the November, 2023 meeting.]
The Standard is not clear whether the following example is well-formed or not:
struct S { static void f(int); static void f(double); }; S s; void (*pf)(int) = &s.f;
According to 7.6.1.5 [expr.ref] bullet 4.3, you do function overload resolution to determine whether x.f is a static or non-static member function. 7.6.2.2 [expr.unary.op] paragraph 6 says that you can only take the address of an overloaded function in a context that determines the overload to be chosen, and the initialization of a function pointer is such a context (12.3 [over.over] paragraph 1) . The problem is that 12.3 [over.over] is phrased in terms of “an overloaded function name,” and this is a member access expression, not a name.
There is variability among implementations as to whether this example is accepted; some accept it as written, some only if the & is omitted, and some reject it in both forms.
Additional note (October, 2010):
A related question concerns an example like
struct S { static void g(int*) {} static void g(long) {} } s; void foo() { (&s.g)(0L); }
Because the address occurs in a call context and not in one of the contexts mentioned in 12.3 [over.over] paragraph 1, the call expression in foo is presumably ill-formed. Contrast this with the similar example
void g1(int*) {} void g1(long) {} void foo1() { (&g1)(0L); }
This call presumably is well-formed because 12.2.2.2 [over.match.call] applies to “the address of a set of overloaded functions.” (This was clearer in the wording prior to the resolution of issue 704: “...in this context using &F behaves the same as using the name F by itself.”) It's not clear that there's any reason to treat these two cases differently.
This question also bears on the original question of this issue, since the original wording of 12.2.2.2 [over.match.call] also described the case of an ordinary member function call like s.g(0L) as involving the “name” of the function, even though the postfix-expression is a member access expression and not a “name.” Perhaps the reference to “name” in 12.3 [over.over] should be similarly understood as applying to member access expressions?
Additional notes (February, 2023)
This appears to be resolved, in part by P1787R6 (accepted November, 2020).
CWG 2023-06-12
The clarifications in P1787R6 did not address the core of this issue, so it is kept open. In order to avoid confusion, a wording change to clarify the treatment (regardless of direction) seems advisable. CWG felt that the first and second examples should be treated consistently, and expressed a mild preferences towards making those ill-formed. It was noted that the reference to id-expression in 12.3 [over.over] can be understood to refer to the id-expression of a class member access.
This issue is resolved by issue 2725.
[Accepted as a DR at the November, 2024 meeting.]
Consider:
template<bool B> struct X { void f(short) requires B; void f(long); template<typename> void g(short) requires B; template<typename> void g(long); }; void test(X<true> x) { x.f(0); // #1, ambiguous x.g<int>(0); // #2, ambiguous &X<true>::f; // #3, OK! &X<true>::g<int>; // #4, ambiguous }
For the function call cases, 12.2.4.1 [over.match.best.general] bullet 2.6 and 13.7.7.3 [temp.func.order] paragraph 6 specify that constraints are only considered if the competing overloads are otherwise basically the same. There is no corresponding restriction when taking the address of a function.
For a second issue, the treatment of placeholder type deduction is unclear:
template<bool B> struct X { void f(short) requires B; void f(short); template<typename> void g(short) requires B; template<typename> void g(short); }; void test(X<true> x) { auto r = &X<true>::f; // #5 auto s = &X<true>::g<int>; // #6 }
When the address of the overload set is resolved, there is a target, but the target type is auto, which is not properly handled.
See also issue 2572.
Proposed resolution (September, 2024) [SUPERSEDED]:
Change in 12.2.4.1 [over.match.best.general] bullet 2.6 as follows:
- F1 and F2 are non-template functions and F1 is more partial-ordering-constrained than F2 (13.5.5 [temp.constr.order])
or if not that,
- they have the same non-object-parameter-type-lists (9.3.4.6 [dcl.fct]), and
- if they are member functions, both are direct members of the same class, and
- if both are non-static member functions, they have the same types for their object parameters, and
- F1 is more constrained than F2 according to the partial ordering of constraints described in 13.5.5 [temp.constr.order],
Change in 12.3 [over.over] paragraph 1 as follows:
... The target can beIf the target type contains a placeholder type, placeholder type deduction is performed (9.2.9.7.2 [dcl.type.auto.deduct]), and the remainder of this subclause uses the target type so deduced.
- an object or reference being initialized (9.5 [dcl.init], 9.5.4 [dcl.init.ref], 9.5.5 [dcl.init.list]),
- the left side of an assignment (7.6.19 [expr.assign]),
- a parameter of a function (7.6.1.3 [expr.call]),
- a parameter of a user-defined operator (12.4 [over.oper]),
- the return value of a function, operator function, or conversion (8.7.4 [stmt.return]),
- an explicit type conversion (7.6.1.4 [expr.type.conv], 7.6.1.9 [expr.static.cast], 7.6.3 [expr.cast]), or
- a non-type template-parameter (13.4.3 [temp.arg.nontype]).
Change in 12.3 [over.over] paragraph 5 as follows:
All functions with associated constraints that are not satisfied (13.5.3 [temp.constr.decl]) are eliminated from the set of selected functions. If more than one function in the set remains, all function template specializations in the set are eliminated if the set also contains a function that is not a function template specialization. Any given non-template function F0 is eliminated if the set contains a second non-template function that is moreconstrainedpartial-ordering-constrained than F0according to the partial ordering rules of(13.5.5 [temp.constr.order]). Any given function template specialization F1 is eliminated if the set contains a second function template specialization whose function template is more specialized than the function template of F1 according to the partial ordering rules of 13.7.7.3 [temp.func.order]. After such eliminations, if any, there shall remain exactly one selected function.
Split and change in 12.3 [over.over] paragraph 6 as follows:
[Example 1: ...
The initialization of pfe is ill-formed because no f() with type int(...) has been declared, and not because of any ambiguity.
For another example,-- end example][Example:
...
-- end example]
[Example:
template<bool B> struct X { void f(short) requires B; void f(long); template<typename> void g(short) requires B; template<typename> void g(long); }; void test() { &X<true>::f; // error: ambiguous; constraints are not considered &X<true>::g<int>; // error: ambiguous; constraints are not considered }-- end example]
Add a paragraph at the end of 13.5.5 [temp.constr.order]:
A declaration D1 is more constrained than another declaration D2 when D1 is at least as constrained as D2, and D2 is not at least as constrained as D1. [ Example: ... ]
A non-template function F1 is more partial-ordering-constrained than a non-template function F2 if
- they have the same non-object-parameter-type-lists (9.3.4.6 [dcl.fct], and
- if they are member functions, both are direct members of the same class, and
- if both are non-static member functions, they have the same types for their object parameters, and
- the declaration of F1 is more constrained than the declaration of F2.
Change in 13.10.3.2 [temp.deduct.call] paragraph 6 as follows:
When P is a function type, function pointer type, or pointer-to-member-function type:
- If the argument is an overload set containing one or more function templates, the parameter is treated as a non-deduced context.
- If the argument is an overload set (not containing function templates), trial argument deduction is attempted using each of the members of the set.
If deduction succeeds for only one of the overload set members, that member is used as the argument value for the deduction. If deduction succeeds for more than one member of the overload setIf all successful deductions yield the same deduced A, that deduced A is the result of deduction; otherwise, the parameter is treated as a non-deduced context.
Add a new paragraph at the end of 13.10.3.2 [temp.deduct.call] as follows:
[ Example:// All arguments for placeholder type deduction (9.2.9.7.2 [dcl.type.auto.deduct]) yield the same deduced type. template<bool B> struct X { void f(short) requires B; // #1 void f(short); // #2 }; void test() { auto x = &X<true>::f; // OK, deduces void(*)(short), selects #1 auto y = &X<false>::f; // OK, deduces void(*)(short), selects #2 }-- end example]
Change in 13.10.3.6 [temp.deduct.type] bullet 5.6 as follows:
- A function parameter for which the associated argument is an overload set
(12.3 [over.over]), and one or more of the following apply:
more than one function matchesfunctions that do not all have the same function type match the function parameter type (resulting in an ambiguous deduction), or- no function matches the function parameter type, or
- the overload set supplied as an argument contains one or more function templates.
Add a section to C.1.5 [diff.cpp23.temp]:
Affected subclause: 13.10.3.2 [temp.deduct.call]
Change: Template argument deduction from overload sets succeeds in more cases.
Rationale: Allow consideration of constraints to disambiguate overload sets used as parameters in function calls.
Effect on original feature: Valid C++ 2023 code may become ill-formed.
[Example 1:template <typename T> void f(T &&, void (*)(T &&)); void g(int &); inline namespace A { void g(short &&); } inline namespace B { void g(short &&); } void q() { int x; f(x, g); // ill-formed; previously well-formed, deducing T=int& }
-- end example]
CWG 2024-09-27
Functions whose constraints are not satisfied should be excluded from the overload set before attempting deduction. Also, clarify that 12.3 [over.over] applies after deduction is complete.
Proposed resolution (approved by CWG 2024-11-19):
Change in 12.2.4.1 [over.match.best.general] bullet 2.6 as follows:
- F1 and F2 are non-template functions and F1 is more partial-ordering-constrained than F2 (13.5.5 [temp.constr.order])
or if not that,
- they have the same non-object-parameter-type-lists (9.3.4.6 [dcl.fct]), and
- if they are member functions, both are direct members of the same class, and
- if both are non-static member functions, they have the same types for their object parameters, and
- F1 is more constrained than F2 according to the partial ordering of constraints described in 13.5.5 [temp.constr.order],
Change in 12.3 [over.over] paragraph 1 as follows:
... The target can beIf the target type contains a placeholder type, placeholder type deduction is performed (9.2.9.7.2 [dcl.type.auto.deduct]), and the remainder of this subclause uses the target type so deduced.
- an object or reference being initialized (9.5 [dcl.init], 9.5.4 [dcl.init.ref], 9.5.5 [dcl.init.list]),
- the left side of an assignment (7.6.19 [expr.assign]),
- a parameter of a function (7.6.1.3 [expr.call]),
- a parameter of a user-defined operator (12.4 [over.oper]),
- the return value of a function, operator function, or conversion (8.7.4 [stmt.return]),
- an explicit type conversion (7.6.1.4 [expr.type.conv], 7.6.1.9 [expr.static.cast], 7.6.3 [expr.cast]), or
- a non-type template-parameter (13.4.3 [temp.arg.nontype]).
Change in 12.3 [over.over] paragraph 5 as follows:
All functions with associated constraints that are not satisfied (13.5.3 [temp.constr.decl]) are eliminated from the set of selected functions. If more than one function in the set remains, all function template specializations in the set are eliminated if the set also contains a function that is not a function template specialization. Any given non-template function F0 is eliminated if the set contains a second non-template function that is moreconstrainedpartial-ordering-constrained than F0according to the partial ordering rules of(13.5.5 [temp.constr.order]). Any given function template specialization F1 is eliminated if the set contains a second function template specialization whose function template is more specialized than the function template of F1 according to the partial ordering rules of 13.7.7.3 [temp.func.order]. After such eliminations, if any, there shall remain exactly one selected function.
Split and change in 12.3 [over.over] paragraph 6 as follows:
[Example 1: ...
The initialization of pfe is ill-formed because no f() with type int(...) has been declared, and not because of any ambiguity.
For another example,-- end example][Example:
...
-- end example]
[Example:
template<bool B> struct X { void f(short) requires B; void f(long); template<typename> void g(short) requires B; template<typename> void g(long); }; void test() { &X<true>::f; // error: ambiguous; constraints are not considered &X<true>::g<int>; // error: ambiguous; constraints are not considered }-- end example]
Add a paragraph at the end of 13.5.5 [temp.constr.order]:
A declaration D1 is more constrained than another declaration D2 when D1 is at least as constrained as D2, and D2 is not at least as constrained as D1. [ Example: ... ]
A non-template function F1 is more partial-ordering-constrained than a non-template function F2 if
- they have the same non-object-parameter-type-lists (9.3.4.6 [dcl.fct], and
- if they are member functions, both are direct members of the same class, and
- if both are non-static member functions, they have the same types for their object parameters, and
- the declaration of F1 is more constrained than the declaration of F2.
Change in 13.10.3.2 [temp.deduct.call] paragraph 6 as follows:
When P is a function type, function pointer type, or pointer-to-member-function type:
- If the argument is an overload set containing one or more function templates, the parameter is treated as a non-deduced context.
- If the argument is an overload set (not containing function templates), trial argument deduction is attempted using each of the members of the set whose associated constraints (13.5.2 [temp.constr.constr]) are satisfied.
If deduction succeeds for only one of the overload set members, that member is used as the argument value for the deduction. If deduction succeeds for more than one member of the overload setIf all successful deductions yield the same deduced A, that deduced A is the result of deduction; otherwise, the parameter is treated as a non-deduced context.
Add a new paragraph at the end of 13.10.3.2 [temp.deduct.call] as follows:
[ Example:// All arguments for placeholder type deduction (9.2.9.7.2 [dcl.type.auto.deduct]) yield the same deduced type. template<bool B> struct X { static void f(short) requires B; // #1 static void f(short); // #2 }; void test() { auto x = &X<true>::f; // OK, deduces void(*)(short), selects #1 auto y = &X<false>::f; // OK, deduces void(*)(short), selects #2 }-- end example]
Change in 13.10.3.6 [temp.deduct.type] bullet 5.6 as follows:
- A function parameter for which the associated argument is an overload set
(12.3 [over.over]), andsuch that one or more of the following apply:[ Note: A particular function from the overload set is selected (12.3 [over.over]) after template argument deduction has succeeded (13.10.4 [temp.over]). -- end note ]
more than one function matchesfunctions whose associated constraints are satisfied and that do not all have the same function type match the function parameter type (resulting in an ambiguous deduction), or- no function whose associated constraints are satisfied matches the function parameter type, or
- the overload set supplied as an argument contains one or more function templates.
Add a section to C.1.5 [diff.cpp23.temp]:
Affected subclause: 13.10.3.2 [temp.deduct.call]
Change: Template argument deduction from overload sets succeeds in more cases.
Rationale: Allow consideration of constraints to disambiguate overload sets used as parameters in function calls.
Effect on original feature: Valid C++ 2023 code may become ill-formed.
[Example 1:template <typename T> void f(T &&, void (*)(T &&)); void g(int &); // #1 inline namespace A { void g(short &&); // #2 } inline namespace B { void g(short &&); // #3 } void q() { int x; f(x, g); // ill-formed; previously well-formed, deducing T=int& }There is no change to the applicable deduction rules for the individual g candidates: Type deduction from #1 does not succeed; type deductions from #2 and #3 both succeed. -- end example]
[Accepted as a DR at the November, 2024 meeting.]
(From submission #600.)
With the introduction of explicit object member functions, the restrictions on operator functions became inconsistent. Subclause 12.4.1 [over.oper.general] paragraph 7 specifies:
An operator function shall either
- be a member function or
- be a non-member function that has at least one non-object parameter whose type is a class, a reference to a class, an enumeration, or a reference to an enumeration.
Talking about non-object parameters in a bullet discussing non-member functions makes no sense. The following example ought to be prohibited, for consistency with operator==(int, int):
struct B { bool operator==(this int, int); operator int() const; };
Proposed resolution (approved by CWG 2024-11-22):
Change in 12.4.1 [over.oper.general] paragraph 7 as follows, removing the bullets:
An operator function shalleither
be a member function orbe a non-member function that hashave at least onenon-objectfunction parameter or implicit object parameter whose type is a class, a reference to a class, an enumeration, or a reference to an enumeration.
[Accepted as a DR at the March, 2024 meeting.]
The type of a template parameter object is specified to be const T in 13.2 [temp.param] paragraph 8:
An id-expression naming a non-type template-parameter of class type T denotes a static storage duration object of type const T, known as a template parameter object, whose value is that of the corresponding template argument after it has been converted to the type of the template-parameter. ...
However, it is unclear what the type of an id-expression is that refers to such an object. There is implementation divergence in the treatment of the following example:
struct A {}; template<auto a, auto x> // also consider A a and const auto x int f() { decltype(a) b; // also consider decltype((a)) A& rb = b; decltype(x) y; int& ry = y; } int x = f<A{}, 42>();
Note that non-type template parameters are handled specially for decltype, as specified in 9.2.9.6 [dcl.type.decltype] paragraph 1:
For an expression E, the type denoted by decltype(E) is defined as follows:
- ...
- otherwise, if E is an unparenthesized id-expression naming a non-type template-parameter (13.2 [temp.param]), decltype(E) is the type of the template-parameter after performing any necessary type deduction (9.2.9.7 [dcl.spec.auto], 9.2.9.8 [dcl.type.class.deduct]);
- ...
Proposed resolution (approved by CWG 2024-03-01):
Change in 7.5.5.2 [expr.prim.id.unqual] paragraph 3 as follows:
...[Note 4:If the entity is a template parameter object for a template parameter of type T (13.2 [temp.param]), the type of the expression is const T.—end note]In all other cases, the type of the expression is the type of the entity.
[ Resolved by paper P2308R1 (Template parameter initialization), adopted in November, 2023. ]
Since non-type template parameters can now have class types, it would seem to make sense to allow a braced-init-list as a template-argument, but the grammar does not permit it.
See also issues 2049 and 2459.
Possible resolution:
The resolution also addresses issue 2049.
Change in 7.3.1 [conv.general] paragraph 3 as follows:
An expression or braced-init-list E can be implicitly converted to a type T if and only if the declaration T t = E; is well-formed, for some invented temporary variable t (9.5 [dcl.init]).
Change in 7.7 [expr.const] paragraph 12 as follows:
A converted constant expression of type T is an expression or braced-init-list, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only (12.1 [over.pre])
- user-defined conversions,
- ...
Change in 13.3 [temp.names] paragraph 1 as follows:
template-argument: constant-expression type-id id-expression braced-init-list
Change in 13.4.2 [temp.arg.type] paragraph 4 as follows:
template<auto n> struct B { /* ... */ }; B<5> b1; // OK, template parameter type is int B<'a'> b2; // OK, template parameter type is char B<2.5> b3; // OK, template parameter type is double B<void(0)> b4; // error: template parameter type cannot be void template<int i> struct C { /* ... */ }; C<{ 42 }> c1; // OK
[ Resolved by paper P2308R1 (Template parameter initialization), adopted in November, 2023. ]
According to 13.4.3 [temp.arg.nontype] paragraph 1,
A template-argument for a non-type template-parameter shall be a converted constant expression (7.7 [expr.const]) of the type of the template-parameter.
This does not permit an example like:
template <int* x = {}> struct X {};
which seems inconsistent.
See also issues 2450 and 2459.
[ Resolved by paper P2308R1 (Template parameter initialization), adopted in November, 2023. ]
The initialization of template parameters is severely underspecified. The only descriptions in the existing wording that apply are that the argument is “[converted] to the type of the template-parameter” (13.6 [temp.type] bullet 1.3) and, in 13.4.3 [temp.arg.nontype] paragraph 2,
A template-argument for a non-type template-parameter shall be a converted constant expression (7.7 [expr.const]) of the type of the template-parameter.
This omission is particularly important for template parameters of class type with lvalue template parameter objects whose addresses can be examined during construction. See also issue 2450.
Suggested resolution:
To avoid address-based paradoxes, template arguments for a template parameter of class type C that are not of that type or a derived type are converted to C to produce an exemplar. No restrictions are imposed on the conversion from a template argument to a constructor parameter, since explicit and list-initialization may already be used to limit conversions in a similar fashion. Template arguments that are of such a type are used directly as the exemplar (potentially after a materialization conversion); the effect is as if the template parameter were of type const C& (except that temporaries are allowed). (In the latter case, we must impose some restrictions on glvalue template parameters to interpret them.) Each exemplar is used to copy-initialize the template parameter object to which it is (to be) template-argument-equivalent; the initialization is required to produce a template-argument-equivalent value. The multiple initializations of the template parameter object are (required to be) all equivalent and produce no side effects, so it is unobservable which happen.
[Accepted as a DR at the June, 2023 meeting.]
It is unclear whether deduction guides can be expressed using abbreviated function syntax. Subclause 13.7.2.3 [temp.deduct.guide] paragraph 3 refers to the restrictions of a function's parameter-declaration-clause:
The same restrictions apply to the parameter-declaration-clause of a deduction guide as in a function declaration (9.3.4.6 [dcl.fct]). ...
However, that subclause is silent on the meaning of abbreviated function syntax when used for deduction guides. Furthermore, 9.3.4.6 [dcl.fct] paragraph 22 explicitly restricts the definition to function templates, which deduction guides are not:
An abbreviated function template is a function declaration that has one or more generic parameter type placeholders (9.2.9.7 [dcl.spec.auto]). ...
Arguably, the lack of template parameter names in abbreviated function syntax makes it less suitable to specifiy deduction guides.
CWG 2023-02-11
CWG solicits input from EWG whether abbreviated function syntax is intended to be used for deduction guides. See cplusplus/papers#1465.
EWG 2023-05-11
CWG should clarify that abbreviated function syntax should not be permitted in deduction guides.
Proposed resolution (approved by CWG 2023-05-12):
Change in 13.7.2.3 [temp.deduct.guide] paragraph 3 as follows:
The same restrictions apply to the parameter-declaration-clause of a deduction guide as in a function declaration (9.3.4.6 [dcl.fct]), except that a generic parameter type placeholder (9.2.9.7 [dcl.spec.auto]) shall not appear in the parameter-declaration-clause of a deduction guide. The simple-template-id shall name a class template specialization. The template-name shall be the same identifier as the template-name of the simple-template-id. A deduction-guide shall inhabit the scope to which the corresponding class template belongs and, for a member class template, have the same access. Two deduction guide declarations for the same class template shall not have equivalent parameter-declaration-clauses if either is reachable from the other.
[Accepted as a DR at the March, 2024 meeting.]
The grammar for deduction-guide does not, but should, allow a trailing requires-clause:
deduction-guide: explicit-specifieropt template-name ( parameter-declaration-clause ) -> simple-template-id ;
Proposed resolution (approved by CWG 2023-11-11):
Change the grammar in 13.7.2.3 [temp.deduct.guide] paragraph 1 as follows:
deduction-guide: explicit-specifieropt template-name ( parameter-declaration-clause ) requires-clauseopt -> simple-template-id ;
[Accepted as a DR at the November, 2024 meeting.]
(From submission #576.)
Issue 2707 added the missing requires-clause to the grammar for deduction-guide, but the position is inconsistent with that of function declarations.
Proposed resolution (approved by CWG 2024-08-16):
Change in 13.7.2.3 [temp.deduct.guide] paragraph 1 as follows:
deduction-guide : explicit-specifieropt template-name ( parameter-declaration-clause )requires-clauseopt-> simple-template-id requires-clauseopt ;
[Accepted as a DR at the June, 2023 meeting.]
Subclause 13.7.4 [temp.variadic] paragraph 11 specifies:
The instantiation of any other pack expansion produces a list of elements E1, E2, ... , EN.
Consider this example:
template<class ...T> struct Align{ alignas(T...) unsigned char buffer[128]; }; Align<int, short> a;
The pack expansion of the alignment-specifier yields alignas(int), alignas(short), an ill-formed list per the grammar in 9.13.1 [dcl.attr.grammar].
Proposed resolution (approved by CWG 2023-04-28):
Insert a new paragraph after 13.7.4 [temp.variadic] paragraph 9 as follows:
The instantiation of a sizeof... expression (7.6.2.5 [expr.sizeof]) produces an integral constant with value N.
The instantiation of an alignment-specifier with an ellipsis produces E1 E2 ... EN.
[Accepted as a DR at the June, 2023 meeting.]
Subclause 13.8.1 [temp.res.general] paragraph 6 specifies rules to determine when a template is valid, but the specification is in terms of "instantiation". However, some kinds of templates (namely alias templates) are not instantiated.
Further, the rule discusses only templates, but should apply to any templated entity.
Proposed resolution (approved by CWG 2023-05-12):
Change in 13.8.1 [temp.res.general] paragraph 6 as follows:
The validity of a
templatetemplated entity may be checked prior to any instantiation. [Note 3: Knowing which names are type names allows the syntax of every template to be checked in this way. —end note]The program is ill-formed, no diagnostic required, if:
- no valid specialization, ignoring static_assert-declarations that fail (9.1 [dcl.pre]), can be generated for a
templatetemplated entity or a substatement of a constexpr if statement (8.5.2 [stmt.if]) within atemplatetemplated entity and the innermost enclosing template is not instantiated, or- no specialization of an alias template (13.7.8 [temp.alias]) is valid and no specialization of the alias template is named in the program, or
- any constraint-expression in the program, introduced or otherwise, has (in its normal form) an atomic constraint A where no satisfaction check of A could be well-formed and no satisfaction check of A is performed, or
- every valid specialization of a variadic template requires an empty template parameter pack, or
- a hypothetical instantiation of a
templatetemplated entity immediately following its definition would be ill-formed due to a construct (other than a static_assert-declaration that fails) that does not depend on a template parameter, or- the interpretation of such a construct in the hypothetical instantiation is different from the interpretation of the corresponding construct in any actual instantiation of the
templatetemplated entity.
[Accepted as a DR at the March, 2024 meeting.]
Consider:
static int x = 1; template<auto y = x> void f() {}
Is the definition of f well-formed? Since x is not a constant expression, any use of the default template argument is ill-formed, but for example f<5>() does not actually use it.
Are implementations allowed or required to reject this situation, even if the template is never instantiated? If the default template argument is dependent, checking may need to be deferred to instantiations in any case.
Proposed resolution (approved by CWG 2024-03-01):
Change in 13.8.1 [temp.res.general] paragraph 6 as follows:
The validity of a template may be checked prior to any instantiation. [Note : ... —end note]
The program is ill-formed, no diagnostic required, if:
- no valid specialization, ignoring static_assert-declarations that fail, can be generated for a template or a substatement of a constexpr if statement (8.5.2 [stmt.if]) within a template and the template is not instantiated, or
- no valid specialization, ignoring static_assert-declarations that fail, can be generated for a default template-argument and the default template-argument is not used in any instantiation, or
- any constraint-expression in the program, introduced or otherwise, has (in its normal form) an atomic constraint A where no satisfaction check of A could be well-formed and no satisfaction check of A is performed, or
- every valid specialization of a variadic template requires an empty template parameter pack, or
- a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, or
- the interpretation of such a construct in the hypothetical instantiation is different from the interpretation of the corresponding construct in any actual instantiation of the template.
[Accepted as a DR at the November, 2023 meeting.]
Consider:
template <typename T>
concept C = requires {
typename T::type<void>; // template required?
};
There is implementation divergence: gcc accepts, clang and MSVC reject.
A type-requirement ought to be a type-only context.
Proposed resolution (approved by CWG 2023-10-20):
Change in 7.5.8.3 [expr.prim.req.type] paragraph 1 as follows:
A type-requirement asserts the validity of a type. The component names of a type-requirement are those of its nested-name-specifier (if any) and type-name. [Note 1: The enclosing requires-expression will evaluate to false if substitution of template arguments fails. —end note]
Change in 13.8.1 [temp.res.general] paragraph 4 as follows:
A qualified or unqualified name is said to be in a type-only context if it is the terminal name of
- a typename-specifier, type-requirement, nested-name-specifier, elaborated-type-specifier, class-or-decltype, or
- ...
[Accepted as a DR at the March, 2024 meeting.]
Subclause 13.8.1 [temp.res.general] paragraph 6 specifies:
The program is ill-formed, no diagnostic required, if: ... Otherwise, no diagnostic shall be issued for a template for which a valid specialization can be generated.
The "Otherwise..." part is misleading; it could be interpreted to mean that warnings must be suppressed in templates.
Proposed resolution (approved by CWG 2023-12-01):
Change in 13.8.1 [temp.res.general] paragraph 6 as follows:
Otherwise, no diagnostic shall be issued for a template for which a valid specialization can be generated.
[Accepted as a DR at the November, 2024 meeting.]
Consider:
template<class T>
void f()
{
struct Y {
using type = int;
};
Y::type y; // error; missing typename
}
Since lookup of Y always finds the local class, regardless of T, the requirement to apply typename is too strict.
Proposed resolution (approved by CWG 2024-11-08):
Change in 13.8.3.2 [temp.dep.type] paragraph 1 as follows:
A name or template-id refers to the current instantiation if it is
- ...
- in the definition of a nested class of a class template, the name of the nested class referenced as a member of the current instantiation,
or- in the definition of a class template partial specialization or a member of a class template partial specialization, the name of the class template followed by a template argument list equivalent to that of the partial specialization (13.7.6 [temp.spec.partial]) enclosed in <> (or an equivalent template alias specialization)
., or- in the definition of a templated function, the name of a local class (11.6 [class.local].
[Accepted as a DR at the November, 2023 meeting.]
Subclause 13.8.3.2 [temp.dep.type] paragraph 7 has a list of types considered to be dependent. This list covers placeholder types only insofar as it has an entry about decltype(expression). Subclause 13.8.3.3 [temp.dep.expr] paragraph 3 has a list of expression forms not considered dependent unless specific types named by the expressions are dependent. This list includes forms where placeholder types are allowed. For example, the wording does not say that the new-expression at #1 (below) is dependent, but it ought to be:
template <typename T> struct A { A(bool, T); };
void g(...);
template <typename T>
auto f(T t) { return g(new A(t, 0)); } // #1
int g(A<int> *);
int h() { return f<void *>(nullptr); }
Some implementation even treats an obviously non-dependent case as dependent:
template <typename T, typename U> struct A { A(T, U); }; void g(...); // #1 template <typename T> auto f() { return g(new A(0, 0)); } // #1 or #2? int g(A<int, int> *); // #2 void h() { return f<void *>(); }
A similar example that is non-dependent:
template <typename T, typename U = T> struct A { A(T, U); }; void g(...); template <typename T> auto f() { return g(new A(0, 0)); } int g(A<int> *); void h() { return f<void *>(); }
And another non-dependent one:
template <typename T, typename U = T> struct A { A(T); }; void g(...); template <typename T> auto f() { return g(new A(0)); } int g(A<int> *); void h() { return f<void *>(); }
And here is an example that is dependent:
template<class T>
struct S {
template<class U = T> struct A { A(int); };
auto f() { return new A(0); } // dependent return type
};
Proposed resolution (November, 2022) [SUPERSEDED]:
Change in 7.6.2.8 [expr.new] paragraph 2 as follows:
If a placeholder type (9.2.9.7 [dcl.spec.auto]) or a placeholder for a deduced class type (9.2.9.8 [dcl.type.class.deduct]) appears in the type-specifier-seq of a new-type-id or type-id of a new-expression, the allocated type is deduced as follows: Let init be the new-initializer , if any, and T be the new-type-id or type-id of the new-expression, then the allocated type is the type deduced for the variable x in the invented declaration (9.2.9.7 [dcl.spec.auto]):T x init ;
Insert new paragraphs before 13.8.3.2 [temp.dep.type] paragraph 7 and change the latter as follows:
An initializer is dependent if any constituent expression (6.9.1 [intro.execution]) of the initializer is type-dependent. A placeholder type (9.2.9.7.1 [dcl.spec.auto.general]) is dependent if it designates a type deduced from a dependent initializer.
A placeholder for a deduced class type (9.2.9.8 [dcl.type.class.deduct]) is dependent if
- it has a dependent initializer or
- any default template-argument of the primary class template named by the placeholder is dependent when considered in the scope enclosing the primary class template.
A type is dependent if it is
- ...
- a function type whose exception specification is value-dependent,
- denoted by a dependent placeholder type,
- denoted by a dependent placeholder for a deduced class type,
- ...
Proposed resolution (approved by CWG 2023-06-12):
Change in 7.6.2.8 [expr.new] paragraph 2 as follows:
If a placeholder type (9.2.9.7 [dcl.spec.auto]) or a placeholder for a deduced class type (9.2.9.8 [dcl.type.class.deduct]) appears in the type-specifier-seq of a new-type-id or type-id of a new-expression, the allocated type is deduced as follows: Let init be the new-initializer , if any, and T be the new-type-id or type-id of the new-expression, then the allocated type is the type deduced for the variable x in the invented declaration (9.2.9.7 [dcl.spec.auto]):T x init ;
Insert new paragraphs before 13.8.3.2 [temp.dep.type] paragraph 7 and change the latter as follows:
An initializer is dependent if any constituent expression (6.9.1 [intro.execution]) of the initializer is type-dependent. A placeholder type (9.2.9.7.1 [dcl.spec.auto.general]) is dependent if it designates a type deduced from a dependent initializer.
A placeholder for a deduced class type (9.2.9.8 [dcl.type.class.deduct]) is dependent if
- it has a dependent initializer, or
- it refers to an alias template that is a member of the current instantiation and whose defining-type-id is dependent after class template argument deduction (12.2.2.9 [over.match.class.deduct]) and substitution (13.7.8 [temp.alias]).
[ Example:
template<class T, class V> struct S { S(T); }; template<class U> struct A { template<class T> using X = S<T, U>; template<class T> using Y = S<T, int>; void f() { new X(1); // dependent new Y(1); // not dependent } };-- end example ]
A type is dependent if it is
- ...
- a function type whose exception specification is value-dependent,
- denoted by a dependent placeholder type,
- denoted by a dependent placeholder for a deduced class type,
- ...
[Accepted as a DR at the November, 2023 meeting.]
(Split off from issue 2774.)
Subclause 13.8.3.3 [temp.dep.expr] is lacking specifiation about the type-dependence of requires-expressions.
Proposed resolution (approved by CWG 2023-08-25):
Change in 13.8.3.3 [temp.dep.expr] paragraph 4 as follows:
Expressions of the following forms are never type-dependent (because the type of the expression cannot be dependent):... noexcept ( expression ) requires-expression
[Accepted as a DR at the November, 2024 meeting.]
(From submission #554.)
The following examples of noexcept-expressions are not specified to be value-dependent, but ought to be, because value-initialization of T might throw an exception.
template<typename T> void f() { noexcept(static_cast<int>(T{})); noexcept(typeid(*T{})); noexcept(delete T{}); }
Proposed resolution (approved by CWG 2024-11-19):
Change in 13.8.3.4 [temp.dep.constexpr] paragraph 2 as follows:
Expressions of the following form are value-dependent if the unary-expression or expression is type-dependent or the type-id is dependent:sizeof unary-expression sizeof ( type-id ) typeid ( expression ) typeid ( type-id ) alignof ( type-id )noexcept ( expression )
Change in 13.8.3.4 [temp.dep.constexpr] paragraph 3 as follows:
Expressions of the following form are value-dependent if either the type-idor, simple-type-specifier, or typename-specifier is dependent or the expression or cast-expression is value-dependent or any expression in the expression-list is value-dependent or any assignment-expression in the braced-init-list is value-dependent:simple-type-specifier ( expression-listopt ) typename-specifier ( expression-listopt ) simple-type-specifier braced-init-list typename-specifier braced-init-list static_cast < type-id > ( expression ) const_cast < type-id > ( expression ) reinterpret_cast < type-id > ( expression ) dynamic_cast < type-id > ( expression ) ( type-id ) cast-expression
Add a new paragraph before 13.8.3.4 [temp.dep.constexpr] paragraph 5 as follows:
A noexcept-expression (7.6.2.7 [expr.unary.noexcept]) is value-dependent if its expression involves a template parameter.
An expression of the form &qualified-id where ...
[Accepted as a DR at the March, 2024 meeting.]
(From submission #489.)
Subclause 13.9.3 [temp.explicit] paragraph 8 specifies:
A trailing template-argument can be left unspecified in an
explicit instantiation of a function template specialization or of a
member function template specialization provided it can be deduced
(13.10.3.7 [temp.deduct.decl]). If all template arguments can be
deduced, the empty template argument list <> may be omitted.
[Example 3:
template<class T> class Array { /* ... */ };
template<class T> void sort(Array<T>& v) { /* ... */ }
// instantiate sort(Array<int>&) -- template-argument deduced
template void sort<>(Array<int>&);
—end example]
This paragraph is redundant with a more general provision on explicitly specifying template arguments in 13.10.2 [temp.arg.explicit] paragraph 4:
Trailing template arguments that can be deduced (13.10.3 [temp.deduct]) or obtained from default template-arguments may be omitted from the list of explicit template-arguments. [Note 1: A trailing template parameter pack (13.7.4 [temp.variadic]) not otherwise deduced will be deduced as an empty sequence of template arguments. —end note] If all of the template arguments can be deduced or obtained from default template-arguments, they may all be omitted; in this case, the empty template argument list <> itself may also be omitted. ...
A similar duplication is in 13.9.4 [temp.expl.spec] paragraph 10.
Proposed resolution (approved by CWG 2024-02-16):
Change the example in 13.9.3 [temp.explicit] paragraph 4 as follows:
...
template<class T> void sort(Array<T>& v) { /* ... */ }
template void sort(Array<char>&); // argument is deduced here (13.10.2 [temp.arg.explicit])
...
Remove 13.9.3 [temp.explicit] paragraph 8:
A trailing template-argument can be left unspecified in an
explicit instantiation of a function template specialization or of a
member function template specialization provided it can be deduced
(13.10.3.7 [temp.deduct.decl]). If all template arguments can be
deduced, the empty template argument list <> may be omitted.
[Example 3:
template<class T> class Array { /* ... */ };
template<class T> void sort(Array<T>& v) { /* ... */ }
// instantiate sort(Array<int>&) -- template-argument deduced
template void sort<>(Array<int>&);
—end example]
Change the example in 13.9.4 [temp.expl.spec] paragraph 1 as follows:
... [ Example:template<class T> class stream; template<> class stream<char> { /* ... */ }; // #1 template<class T> class Array { /* ... */ }; template<class T> void sort(Array<T>& v) { /* ... */ } template<> void sort<int>(Array<int>&); // #2 template<> void sortGiven these declarations,<char*>(Array<char*>&); // #3 template argument is deduced (13.10.2 [temp.arg.explicit]) ...stream<char>#1 will be used as the definition of streams of chars; other streams will be handled by class template specializations instantiated from the class template. Similarly, #2 will be used as the sort function for arguments of type Array<int> and #3sort<char*>will be usedas the sort functionfor arguments of type Array<char*>; other Array types will be sorted by functions generated from the function template. —end example]
Remove 13.9.4 [temp.expl.spec] paragraph 10:
A trailing template-argument can be left unspecified in the template-id naming an explicit function template specialization provided it can be deduced (13.10.3.7 [temp.deduct.decl]). [Example 6:template<class T> class Array { /* ... */ }; template<class T> void sort(Array<T>& v); // explicit specialization for sort(Array<int>&) // with deduced template-argument of type int template<> void sort(Array<int>&);—end example]
[Accepted as a DR at the November, 2023 meeting.]
Presumably something like the following should be well-formed, where a deduction failure in a partial specialization is handled as a SFINAE case as it is with function templates and not a hard error:
template <class T, class U> struct X { typedef char member; }; template<class T> struct X<T, typename enable_if<(sizeof(T)>sizeof( float)), float>::type> { typedef long long member; }; int main() { cout << sizeof(X<double, float>::member); }
However, this does not appear to be described anywhere in the Standard.
Additional notes (January, 2023)
The section on SFINAE (13.10.3.1 [temp.deduct.general] paragraph 8) is not specific to function templates, and 13.7.6.2 [temp.spec.partial.match] paragraph 2 hands off the "matching" determination for partial specializations to 13.10.3 [temp.deduct] in general. However, the definition of deduction substitution loci in 13.10.3.1 [temp.deduct.general] paragraph 7 does not account for the template argument list of a partial specialization.
Proposed resolution (approved by CWG 2023-11-08):
Change in 13.10.3.1 [temp.deduct.general] paragraph 7 as follows:
The deduction substitution loci areThe substitution occurs in all types and expressions that are used in the deduction substitution loci. ...
- the function type outside of the noexcept-specifier,
- the explicit-specifier,
and- the template parameter declarations
., and- the template argument list of a partial specialization (13.7.6.1 [temp.spec.partial.general]).
[Accepted as a DR at the November, 2023 meeting.]
Subclause 13.10.3.1 [temp.deduct.general] paragraph 9 specifies:
A lambda-expression appearing in a function type or a template parameter is not considered part of the immediate context for the purposes of template argument deduction. [Note 7: The intent is to avoid requiring implementations to deal with substitution failure involving arbitrary statements. ... -- end note ]
However, the intent of the note is not satisfied by the normative rule, because a lambda-expression appearing in a requires-expression has the same concerns as one in a function signature.
Suggested resolution: Change the rule to say that substitution into the body of a lambda is never in the immediate context of substitution into the lambda-expression and move the rule somewhere more general.
Possible resolution (reviewed by CWG 2023-08-25) [SUPERSEDED]:
Change in 13.5.2.3 [temp.constr.atomic] paragraph 3 as follows:
To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression in the immediate context of the atomic constraint (13.10.3.1 [temp.deduct.general]), the constraint is not satisfied. Otherwise, the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is performed if necessary, and E shall be a constant expression of type bool. The constraint is satisfied if and only if evaluation of E results in true.
Change in 13.10.3.1 [temp.deduct.general] paragraph 9 as follows:
A lambda-expression appearing in a function type or a template parameterSubstituting into the body of a lambda-expression isnot considered part ofnever in the immediate contextfor the purposes of template argument deductionof substitution into the lambda-expression. [Note 7: The intent is to avoid requiring implementations to deal with substitution failure involving arbitrary statements.
Proposed resolution (approved by CWG 2023-11-09):
Change in 7.5.8.1 [expr.prim.req.general] paragraph 5 as follows:
The substitution of template arguments into a requires-expressionmaycan result in the formation of invalid types or expressions in the immediate context of its requirements (13.10.3.1 [temp.deduct.general]) or the violation of the semantic constraints of those requirements. In such cases, the requires-expression evaluates to false; it does not cause the program to be ill-formed. The substitution and semantic constraint checking proceeds in lexical order and stops when a condition that determines the result of the requires-expression is encountered. If substitution (if any) and semantic constraint checking succeed, the requires-expression evaluates to true.
Change in 13.5.2.3 [temp.constr.atomic] paragraph 3 as follows:
To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression in the immediate context of the atomic constraint (13.10.3.1 [temp.deduct.general]), the constraint is not satisfied. Otherwise, the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is performed if necessary, and E shall be a constant expression of type bool. The constraint is satisfied if and only if evaluation of E results in true.
Change in 13.10.3.1 [temp.deduct.general] paragraph 9 as follows:
A lambda-expression appearing in a function type or a template parameter is not considered part of the immediate context for the purposes of template argument deduction. When substituting into a lambda-expression, substitution into its body is not in the immediate context. [Note 7: The intent is to avoid requiring implementations to deal with substitution failure involving arbitrary statements.
[Accepted as a DR at the March, 2024 meeting.]
Subclause 14.2 [except.throw] paragraph 5 specifies:
When the thrown object is a class object, the constructor selected for the copy-initialization as well as the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible, even if the copy/move operation is elided (11.9.6 [class.copy.elision]). The destructor is potentially invoked (11.4.7 [class.dtor]).
This provision is for capturing a copy constructor for implementations not using reference-counted std::exception_ptrs, but that ought to be described separately from the "thrown object", which can be interpreted as the operand of the throw-expression.
Proposed resolution (approved by CWG 2023-09-15) [SUPERSEDED]:
Change in 14.2 [except.throw] paragraph 5 as follows:
When the thrown object is a class object, the constructor selected for the copy-initialization as well as the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible, even if the copy/move operation is elided (11.9.6 [class.copy.elision]).Let T denote the type of the exception object. Copy-initialization of an object of type T from an lvalue of type const T in a context unrelated to any class shall be well-formed. If T is a class type,Thethe destructor of T is potentially invoked (11.4.7 [class.dtor]).
CWG 2023-11-09
The drafting should also consider odr-use of the constructor potentially invoked for the copy-initialization.
Proposed resolution (approved by CWG 2023-11-10) [SUPERSEDED]:
Change in 6.3 [basic.def.odr] paragraph 9 as follows:
An assignment operator function in a class is odr-used by an implicitly-defined copy assignment or move assignment function for another class as specified in 11.4.6 [class.copy.assign]. A constructor for a class is odr-used as specified in 9.5 [dcl.init] and when selected for the potential copy-initialization as specified in 14.2 [except.throw]. A destructor for a class is odr-used if it is potentially invoked (11.4.7 [class.dtor]).
Change in 14.2 [except.throw] paragraph 5 as follows:
When the thrown object is a class object, the constructor selected for the copy-initialization as well as the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible, even if the copy/move operation is elided (11.9.6 [class.copy.elision]).Let T denote the type of the exception object. Copy-initialization of an object of type T from an lvalue of type const T in a context unrelated to T shall be well-formed. If T is a class type,Thethe destructor of T is potentially invoked (11.4.7 [class.dtor]).
Proposed resolution (approved by CWG 2024-03-20):
Change in 14.2 [except.throw] paragraph 5 as follows:
When the thrown object is a class object, the constructor selected for the copy-initialization as well as the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible, even if the copy/move operation is elided (11.9.6 [class.copy.elision]).Let T denote the type of the exception object. Copy-initialization of an object of type T from an lvalue of type const T in a context unrelated to T shall be well-formed. If T is a class type, the selected constructor is odr-used (6.3 [basic.def.odr]) andThethe destructor of T is potentially invoked (11.4.7 [class.dtor]).
[Accepted as a DR at the March, 2024 meeting.]
(From submission #492.)
According to 14.2 [except.throw] paragraph 3, the exception object is a temporary object. But none of the possible storage durations of temporary objects matches the behavior of exception objects.
Proposed resolution (approved by CWG 2024-02-16):
Change in 6.7.7 [class.temporary] paragraph 1 as follows:
Temporary objects are created...
- when a prvalue is converted to an xvalue (7.3.5 [conv.rval])
,and- when needed by the implementation to pass or return an object of trivially copyable type (see below)
, and.- when throwing an exception (14.2 [except.throw]). [Note 1: The lifetime of exception objects is described in 14.2 [except.throw]. —end note]
Change in 14.2 [except.throw] paragraph 3 as follows:
Throwing an exception initializesa temporaryan object with dynamic storage duration, called the exception object. If the type of the exception object would be an incomplete type (6.8.1 [basic.types.general]), an abstract class type (11.7.4 [class.abstract]), or a pointer to an incomplete type other than cv void (6.8.4 [basic.compound]) the program is ill-formed.
[Accepted as a DR at the November, 2024 meeting.]
Consider:
using module = int; module i; int foo() { return i; }
Is this a valid translation unit?
It is not a valid module-file, because the translation unit does not start with module or export module (15.5 [cpp.module]). It is also not a valid traditional preprocessing-file (15.1 [cpp.pre]), because module i is considered a preprocessing directive (15.1 [cpp.pre] bullet 1.3, which is never a text-line (15.1 [cpp.pre] paragraph 2):
A sequence of preprocessing tokens is only a text-line if it does not begin with a directive-introducing token. ...
A clarifying note would be appreciated.
Possible resolution (August, 2024):
Change in 15.1 [cpp.pre] paragraph 2 as follows:
A sequence of preprocessing tokens is only a text-line if it does not begin with a directive-introducing token. [ Note: A source line starting with module identifier is always interpreted as a preprocessing directive, not as a text-line, even if parsing the source file as a preprocessing-file subsequently fails. -- end note ]
A sequence of preprocessing tokens is only a conditionally-supported-directive if ...
CWG 2024-09-13
A source line starting with module identifier appearing in a macro argument may or may not be interpreted as a preprocessing directive (15.7.2 [cpp.subst] paragraph 13), thus the note as written is incorrect.
Proposed resolution (approved by CWG 2024-09-27):
Change in 15.1 [cpp.pre] paragraph 2 as follows:
A sequence of preprocessing tokens is only a text-line if it does not begin with a directive-introducing token. [ Example:
using module = int; module i; // not a text-line and not a control-line int foo() { return i; }
The example is not a valid preprocessing-file. -- end example]
[Accepted as a DR at the November, 2024 meeting.]
Given the existing implementation divergence, it should be clarified that __LINE__ counts physical source lines, not logical ones.
Proposed resolution (approved by CWG 2024-08-16):
Change in 15.8 [cpp.line] paragraph 2 as follows:
The line number of the current source line is the line number of the current physical source line, i.e. it is one greater than the number of new-line characters read or introduced in translation phase 1 (5.2 [lex.phases]) while processing the source file to the current token.
[Accepted as a DR at the June, 2024 meeting.]
It is unclear whether Clause Annex B [implimits] is normative, and what its normative content is. The Annex was editorially switched from "informative" to "normative" in September 2020 with this change description:
[intro.compliance.general, implimits] Cite Annex B normatively.
This change also promotes Annex B [implimits] to a "normative" annex. The existing wording in the annex is already normative in character.
On the other hand, this sentence in Clause Annex B [implimits] paragraph 2 seems to say otherwise:
... However, these quantities are only guidelines and do not determine compliance.
If it is indeed intentional that the actual quantitative limits are just guidelines, then the normative (conformance-related) content is limited to Clause Annex B [implimits] paragraph 1, which establishes documentation requirements only:
Because computers are finite, C++ implementations are inevitably limited in the size of the programs they can successfully process. Every implementation shall document those limitations where known. This documentation may cite fixed limits where they exist, say how to compute variable limits as a function of available resources, or say that fixed limits do not exist or are unknown.
However, those general documentation requirements are best expressed in 4.1.1 [intro.compliance.general], leaving Clause Annex B [implimits] with just an informative list of minimum suggested quantities.
Possible resolution [SUPERSEDED]:
Change in 4.1.1 [intro.compliance.general] bullet 2.1 as follows:
If a program contains no violations of the rules in Clause 5 through Clause 33 and Annex D, a conforming implementation shall, within its resource limitsas described in Annex B(see below), accept and correctly execute that program.
Insert a new paragraph before 4.1.1 [intro.compliance.general] paragraph 8 as follows:
An implementation shall document its limitations in the size of the programs it can successfully process, where known. This documentation may cite fixed limits where they exist, say how to compute variable limits as a function of available resources, or say that fixed limits do not exist or are unknown. Clause Annex B [implimits] lists some quantities that can be subject to limitations, and recommends a minimum value for each quantity.
A conforming implementation may have extensions (including additional library functions), ...
Change in Clause Annex B [implimits] paragraph 1 and paragraph 2 as follows:
Annex B
(normativeinformative)Because computers are finite, C++ implementations are inevitably limited in the size of the programs they can successfully process. Every implementation shall document those limitations where known. This documentation may cite fixed limits where they exist, say how to compute variable limits as a function of available resources, or say that fixed limits do not exist or are unknown.
The limits may constrain quantities that include those described below or others.Implementations can exhibit limitations, among others for the quantities in the following list. The bracketed number following each quantity is recommended as the minimum value for that quantity.However, these quantities are only guidelines and do not determine compliance.
CWG 2024-05-17
CWG was divided whether to relax the documentation requirement or not. As a next step, the wording for a relaxation should be reviewed.
Proposed resolution (approved by CWG 2024-06-26):
Change in 4.1.1 [intro.compliance.general] bullet 2.1 as follows:
If a program contains no violations of the rules in Clause 5 through Clause 33 and Annex D, a conforming implementation shall, within its resource limits as described in Annex B,accept and correctly execute that program, except when the implementation's limitations (see below) are exceeded.
Insert a new paragraph before 4.1.1 [intro.compliance.general] paragraph 8 as follows:
An implementation is encouraged to document its limitations in the size or complexity of the programs it can successfully process, if possible and where known. Clause Annex B [implimits] lists some quantities that can be subject to limitations and a potential minimum supported value for each quantity.
A conforming implementation may have extensions (including additional library functions), ...
Change in Clause Annex B [implimits] paragraph 1 and paragraph 2 as follows:
Annex B
(normativeinformative)Because computers are finite, C++ implementations are inevitably limited in the size of the programs they can successfully process. Every implementation shall document those limitations where known. This documentation may cite fixed limits where they exist, say how to compute variable limits as a function of available resources, or say that fixed limits do not exist or are unknown.
The limits may constrain quantities that include those described below or others.Implementations can exhibit limitations for various quantities; some possibilities are presented in the following list. The bracketed number following each quantity isrecommended as thea potential minimum value for that quantity.However, these quantities are only guidelines and do not determine compliance.
[Accepted as a DR at the June, 2024 meeting.]
(From submission #528.)
Paper N3055 (A Taxonomy of Expression Value Categories) introduced xvalues. This changed the behavior of well-formed code for typeid and the conditional operator, but corresponding entries in Annex C are missing.
Proposed resolution (approved by CWG 2024-05-31):
Add a new paragraph to C.6.3 [diff.cpp03.expr] as follows:
Affected subclause: 7.6.1.8 [expr.typeid]
Change: Evaluation of operands in typeid.
Rationale: Introduce additional expression value categories.
Effect on original feature: Valid C++ 2003 code that uses xvalues as operands for typeid may change behavior. For example,
void f() { struct B { B() {} virtual ~B() { } }; struct C { B b; }; typeid(C().b); // unevaluated in C++03, evaluated in C++11 }
Add a new paragraph to C.6.3 [diff.cpp03.expr] as follows:
Affected subclause: 7.6.16 [expr.cond]
Change: Fewer copies in the conditional operator.
Rationale: Introduce additional expression value categories.
Effect on original feature: Valid C++ 2003 code that uses xvalues as operands for the conditional operator may change behavior. For example,
void f() { struct B { B() {} B(const B&) { } }; struct D : B {}; struct BB { B b; }; struct DD { D d; }; true ? BB().b : DD().d; // additional copy in C++03, no copy or move in C++11 }
[Accepted as a DR at the November, 2023 meeting.]
With C++11, anonymous namespaces changed from external linkage (with a unique namespace name) to internal linkage. That implies that extern "C", which affects names with external linkage only, no longer has an effect inside anonymous namespaces.
However, a corresponding Annex C entry is missing.
Proposed resolution (approved by CWG 2023-09-15):
Add a new paragraph in C.6.4 [diff.cpp03.dcl.dcl] as follows:
Affected subclause: 9.12 [dcl.link]
Change: Names declared in an anonymous namespace changed from external linkage to internal linkage; language linkage applies to names with external linkage only.
Rationale: Alignment with user expectations.
Effect on original feature: Valid C++ 2003 code may violate the one-definition rule (6.3 [basic.def.odr]) in this revision of C++. For example:namespace { extern "C" { extern int x; } } // #1, previously external linkage and C language linkage, now internal linkage and C++ language linkage namespace A { extern "C" int x = 42; } // #2, external linkage and C language linkage int main(void) { return x; }This code is valid in C++ 2003, but #2 is not a definition for #1 in this revision of C++, violating the one-definition rule.
[Accepted at the June, 2024 meeting.]
(From submission #499.)
Subclause 7.5.5.3 [expr.prim.id.qual] paragraph 2 specifies:
... A declarative nested-name-specifier shall not have a decltype-specifier. ...
This should have been adjusted by P2662R3 (Pack Indexing) to read computed-type-specifier.
Proposed resolution (approved by CWG 2024-04-05):
Change in 7.5.5.3 [expr.prim.id.qual] paragraph 2 as follows:
... A declarative nested-name-specifier shall not have adecltype-specifiercomputed-type-specifier. ...
CWG 2024-06-26
This is not a DR.
[Resolved by paper P3144R2, approved at the June, 2024 meeting.]
Subclause 7.6.2.9 [expr.delete] paragraph 6 specifies:
... The destructor shall be accessible from the point where the delete-expression appears. ...
This check cannot be performed in case the operand of the delete-expression is a pointer to incomplete type.
Possible resolution:
Change in 7.6.2.9 [expr.delete] paragraph 6 as follows:
...TheUnless the object being deleted has incomplete class type, the destructor shall be accessible from the point where the delete-expression appears. ...
Additional notes (September, 2024)
Paper P3144R2 (Deleting a Pointer to an Incomplete Type Should be Ill-formed), approved in June, 2024 made the deletion of pointer-to-incomplete class types ill-formed, thus the situation cannot appear anymore.
[Accepted at the June, 2024 meeting.]
Subclause 7.7 [expr.const] bullet 5.14 was amended by P2738R1 to support certain casts from void* to object pointer types. The bullet specifies:
- ...
- a conversion from a prvalue P of type “pointer to cv void” to a pointer-to-object type T unless P points to an object whose type is similar to T;
- ...
This wording does not, but should, support null pointer values. The implementation burden is negligible.
Proposed resolution (approved by CWG 2023-12-01):
Change in 7.7 [expr.const] bullet 5.14 as follows:
- ...
- a conversion from a prvalue P of type “pointer to cv void” to a pointer-to-object type T unless P is a null pointer value or points to an object whose type is similar to T;
- ...
CWG 2023-12-01
CWG seeks approval from EWG for the design direction. See paper issue 1698.
EWG 2024-03-18
EWG approves.
CWG 2024-04-19
This issue is not a DR.
[Accepted at the June, 2024 meeting.]
P2573R2 (= delete("should have a reason");), adopted in Tokyo, does not disambiguate the following syntax:
using T = void (); using U = int; T a = delete ("hello"); U b = delete ("hello");
Either may be parsed as a (semantically ill-formed) simple-declaration whose initializer is a delete-expression or as a function-definition.
Proposed resolution (approved by CWG 2024-05-31):
Change and split 9.1 [dcl.pre] paragraph 9 as follows:
An object definition causes storage of appropriate size and alignment to be reserved and any appropriate initialization (9.5 [dcl.init]) to be done.
Syntactic components beyond those found in the general form of simple-declaration are added to a function declaration to make a function-definition. A token sequence starting with { or = is treated as a function-body (9.6.1 [dcl.fct.def.general]) if the type of the declarator-id (9.3.4.1 [dcl.meaning.general]) is a function type, and is otherwise treated as a brace-or-equal-initializer (9.5.1 [dcl.init.general]). [ Note: If the declaration acquires a function type through template instantiation, the program is ill-formed; see 13.9.1 [temp.spec.general]. The function type of a function definition cannot be specified with a typedef-name (9.3.4.6 [dcl.fct]). --end note ]
An object declaration, however, is also a definition unless it contains the extern specifier and has no initializer (6.2 [basic.def]). An object definition causes storage of appropriate size and alignment to be reserved and any appropriate initialization (9.5 [dcl.init]) to be done.
This drafting also resolves issue 2144.
[Voted into the WP at the June, 2008 meeting.]
The C99 and C++ Standards disagree about the validity of two Cyrillic characters for use in identifiers. C++ (_N2691_.E [extendid]) says that 040d is valid in an identifier but that 040e is not; C99 (Annex D) says exactly the opposite. In fact, both characters should be accepted in identifiers; see the Unicode chart.
Proposed resolution (February, 2008):
The reference in paragraph 2 should be changed to ISO/IEC TR 10176:2003 and the table should be changed to conform to the one in that document (beginning on page 34).
[Moved to DR at 10/01 meeting.]
_N4567_.5.1.1 [expr.prim.general] paragraph 11 reads,
A template-id shall be used as an unqualified-id only as specified in 13.9.3 [temp.explicit] , 13.9 [temp.spec] , and 13.7.6 [temp.spec.partial] .
What uses of template-ids as unqualified-ids is this supposed to prevent? And is the list of referenced sections correct/complete? For instance, what about 13.10.2 [temp.arg.explicit], "Explicit template argument specification?" Does its absence from the list in _N4567_.5.1.1 [expr.prim.general] paragraph 11 mean that "f<int>()" is ill-formed?
This is even more confusing when you recall that unqualified-ids are contained in qualified-ids:
qualified-id: ::opt nested-name-specifier templateopt unqualified-id
Is the wording intending to say "used as an unqualified-id that is not part of a qualified-id?" Or something else?
Proposed resolution (10/00):
Remove the referenced sentence altogether.
[Voted into WP at March 2004 meeting.]
The example below is ambiguous.
struct A{ struct B{}; }; A::B C(); namespace B{ A C(); } struct Test { friend A::B ::C(); };Here, it is not clear whether the friend declaration denotes A B::C() or A::B C(), yet the standard does not resolve this ambiguity.
The ambiguity arises since both the simple-type-specifier (9.2.9.3 [dcl.type.simple] paragra 1) and an init-declararator (9.3 [dcl.decl] paragraph 1) contain an optional :: and an optional nested-name-specifier (_N4567_.5.1.1 [expr.prim.general] paragraph 1) . Therefore, two different ways to analyse this code are possible:
simple-type-specifier = A::Bor
init-declarator = ::C()
simple-declaration = friend A::B ::C();
simple-type-specifier = ASince it is a friend declaration, the init-declarator may be qualified, and start with a global scope.
init-declarator = ::B::C()
simple-declaration = friend A ::B::C();
Suggested Resolution: In the definition of nested-name-specifier, add a sentence saying that a :: token immediately following a nested-name-specifier is always considered as part of the nested-name-specifier. Under this interpretation, the example is ill-formed, and should be corrected as either
friend A (::B::C)(); //or friend A::B (::C)();
An alternate suggestion — changing 9.2 [dcl.spec] to say that
The longest sequence of tokens that could possibly be a type name is taken as the decl-specifier-seq of a declaration.
— is undesirable because it would make the example well-formed rather than requiring the user to disambiguate the declaration explicitly.
Proposed resolution (04/01):
(See below for problem with this, from 10/01 meeting.)
In _N4567_.5.1.1 [expr.prim.general] paragraph 7,
Before the grammar for qualified-id, start a new paragraph 7a with the text
A qualified-id is an id-expression that contains the scope resolution operator ::.
Following the grammar fragment, insert the following:
The longest sequence of tokens that could form a qualified-id constitutes a single qualified-id. [Example:
// classes C, D; functions F, G, namespace N; non-class type T friend C ::D::F(); // ill-formed, means friend (C::D::F)(); friend C (::D::F)(); // well-formed friend N::T ::G(); // ill-formed, means friend (N::T::G)(); friend N::T (::G)(); // well-formed—end example]
Start a new paragraph 7b following the example.
(This resolution depends on that of issue 215.)
Notes from 10/01 meeting:
It was pointed out that the proposed resolution does not deal with cases like X::Y where X is a type but not a class type. The working group reaffirmed its decision that the disambiguation should be syntactic only, i.e., it should depend only on whether or not the name is a type.
Jason Merrill :At the Seattle meeting, I suggested that a solution might be to change the class-or-namespace-name in the nested-name-specifier rule to just be "identifier"; there was some resistance to this idea. FWIW, I've tried this in g++. I had to revise the idea so that only the second and subsequent names were open to being any identifier, but that seems to work just fine.
So, instead of
it would be
Or some equivalent but right-associative formulation, if people feel that's important, but it seems irrelevant to me.
Clark Nelson :
Personally, I prefer the left-associative rule. I think it makes it easier to understand. I was thinking about this production a lot at the meeting, considering also some issues related to 301. My formulation was getting kind of ugly, but with a left-associative rule, it gets a lot nicer.
Your proposal isn't complete, however, as it doesn't allow template arguments without an explicit template keyword. You probably want to add an alternative for:
There is admittedly overlap between this alternative and
but I think they're both necessary.
Notes from the 4/02 meeting:
The changes look good. Clark Nelson will merge the two proposals to produce a single proposed resolution.
Proposed resolution (April 2003):
nested-name-specifier is currently defined in _N4567_.5.1.1 [expr.prim.general] paragraph 7 as:
The proposed definition is instead:
Issue 215 is addressed by using type-name instead of class-name in the first alternative. Issue 125 (this issue) is addressed by using identifier instead of anything more specific in the third alternative. Using left association instead of right association helps eliminate the need for class-or-namespace-name (or type-or-namespace-name, as suggested for issue 215).
It should be noted that this formulation also rules out the possibility of A::template B::, i.e. using the template keyword without any template arguments. I think this is according to the purpose of the template keyword, and that the former rule allowed such a construct only because of the difficulty of formulation of a right-associative rule that would disallow it. But I wanted to be sure to point out this implication.
Notes from April 2003 meeting:
See also issue 96.
The proposed change resolves only part of issue 215.
[Voted into WP at April, 2006 meeting.]
_N4778_.7.6.1.4 [expr.pseudo] paragraph 2 says both:
The type designated by the pseudo-destructor-name shall be the same as the object type.and also:
The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type.Which is it? "The same" or "the same up to cv-qualifiers"? The second sentence is more generous than the first. Most compilers seem to implement the less restrictive form, so I guess that's what I think we should do.
Proposed resolution (October, 2005):
Change _N4778_.7.6.1.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 is the object type.The type designated by the pseudo-destructor-name shall be the same as the object type.The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type. Furthermore, the two type-names in a pseudo-destructor-name of the form::opt nested-name-specifieropt type-name ::~ type-name
shall designate the same scalar type.The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type.
[Voted into the WP at the June, 2008 meeting.]
_N4868_.6.5.6 [basic.lookup.classref] paragraph 1 says,
In a class member access expression (7.6.1.5 [expr.ref] ), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (13.3 [temp.names] ) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a class or function template.
There do not seem to be any circumstances in which use of a non-member template function would be well-formed as the id-expression of a class member access expression.
Proposed Resolution (November, 2006):
Change _N4868_.6.5.6 [basic.lookup.classref] paragraph 1 as follows:
In a class member access expression (7.6.1.5 [expr.ref]), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (13.3 [temp.names]) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a classor functiontemplate...
[Voted into WP at the October, 2006 meeting.]
I believe this program is invalid:
struct A { }; struct C { struct A {}; void f (); }; void C::f () { ::A *a; a->~A (); }The problem is that _N4868_.6.5.6 [basic.lookup.classref] says that you have to look up A in both the context of the pointed-to-type (i.e., ::A), and in the context of the postfix-expression (i.e., the body of C::f), and that if the name is found in both places it must name the same type in both places.
The EDG front end does not issue an error about this program, though.
Am I reading the standardese incorrectly?
John Spicer: I think you are reading it correctly. I think I've been hoping that this would get changed. Unlike other dual lookup contexts, this is one in which the compiler already knows the right answer (the type must match that of the left hand of the -> operator). So I think that if either of the types found matches the one required, it should be sufficient. You can't say a->~::A(), which means you are forced to say a->::A::~A(), which disables the virtual mechanism. So you would have to do something like create a local typedef for the desired type.
See also issues 244, 399, and 466.
Proposed resolution (April, 2006):
Remove the indicated text from _N4868_.6.5.6 [basic.lookup.classref] paragraph 2:
If the id-expression in a class member access (7.6.1.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...
Change _N4868_.6.5.6 [basic.lookup.classref] paragraph 3 as indicated:
If the unqualified-id is ~type-name, the type-name is looked up in the context of the entire postfix-expression.andIf the type T of the object expression is of a class type C(or of pointer to a class type C), the type-name is also looked upin the context of the entire postfix-expression andin 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.At least one of the lookups shall find a name that refers to (possibly cv-qualified) T. [Example:struct A { }; struct B { struct A { }; void f(::A* a); }; void B::f(::A* a) { a->~A(); // OK, lookup in *a finds the injected-class-name }—end example]
[Note: this change also resolves issue 414.]
[Voted into WP at October 2004 meeting.]
The example in _N4868_.6.5.6 [basic.lookup.classref] paragraph 4 is wrong (see 11.8.3 [class.access.base] paragraph 5; the cast to the naming class can't be done) and needs to be corrected. This was noted when the final version of the algorithm for issue 39 was checked against it.
Proposed Resolution (October 2003):
Remove the entire note at the end of _N4868_.6.5.6 [basic.lookup.classref] paragraph 4, including the entire example.
[Voted into WP at the October, 2006 meeting.]
By _N4868_.6.5.6 [basic.lookup.classref] paragraph 3, the following is ill-formed because the two lookups of the destructor name (in the scope of the class of the object and in the surrounding context) find different Xs:
struct X {}; int main() { X x; struct X {}; x.~X(); // Error? }
This is silly, because the compiler knows what the type has to be, and one of the things found matches that. The lookup should require only that one of the lookups finds the required class type.
Proposed resolution (April, 2005):
This issue is resolved by the resolution of issue 305.
[Voted into WP at July, 2007 meeting.]
_N4868_.11.4.3.2 [class.this] paragraph 1, which specifies the meaning of the keyword 'this', seems to limit its usage to the *body* of non-static member functions. However 'this' is also usable in ctor-initializers which, according to the grammar in 9.6 [dcl.fct.def] par. 1, are not part of the body.
Proposed resolution: Changing the first part of _N4868_.11.4.3.2 [class.this] par. 1 to:
In the body of a nonstatic (9.3) member function or in a ctor-initializer (12.6.2), the keyword this is a non-lvalue expression whose value is the address of the object for which the function is called.
NOTE: I'm talking of constructors as functions that are "called"; there have been discussions on c.l.c++.m as to whether constructors are "functions" and to whether this terminology is correct or not; I think it is both intuitive and in agreement with the standard wording.
Steve Adamczyk: See also issue 397, which is defining a new syntax term for the body of a function including the ctor-initializers.
Notes from the March 2004 meeting:
This will be resolved when issue 397 is resolved.
Proposed resolution (October, 2005):
Change 9.6 [dcl.fct.def] paragraph 1 as indicated:
Function definitions have the form
function-definition:
decl-specifier-seqopt declarator
ctor-initializeroptfunction-bodyfunction-body:
decl-specifier-seqopt declarator function-try-blockctor-initializeropt compound-statement
function-try-block
An informal reference to the body of a function should be interpreted as a reference to the nonterminal function-body.
Change the definition of function-try-block in Clause 14 [except] paragraph 1:
function-try-block:
try ctor-initializeropt
function-bodycompound-statement handler-seq
Change 6.4.7 [basic.scope.class] paragraph 1, point 1, as indicated:
The potential scope of a name declared in a class consists not only of the declarative region following the name's point of declaration, but also of all functionbodies,bodies and default arguments, and constructor ctor-initializersin that class (including such things in nested classes).
Change 6.4.7 [basic.scope.class] paragraph 1, point 5, as indicated:
The potential scope of a declaration that extends to or past the end of a class definition also extends to the regions defined by its member definitions, even if the members are defined lexically outside the class (this includes static data member definitions, nested class definitions, member function definitions (including the member function bodyand, for constructor functions (11.4.5 [class.ctor]), the ctor-initializer (11.9.3 [class.base.init])) and any portion of the declarator part of such definitions which follows the identifier, including a parameter-declaration-clause and any default arguments (9.3.4.7 [dcl.fct.default]). [Example:...
Change footnote 32 in 6.5.3 [basic.lookup.unqual] paragraph 8 as indicated:
That is, an unqualified name that occurs, for instance, in a type or default argument expression in theparameter-declaration-clause,parameter-declaration-clause or in the function body, or in an expression of a mem-initializer in a constructor definition.
Change _N4567_.5.1.1 [expr.prim.general] paragraph 3 as indicated:
...The keyword this shall be used only inside a non-static class member function body (11.4.2 [class.mfct])or in a constructor mem-initializer (11.9.3 [class.base.init])...
Change 11.4 [class.mem] paragraph 2 as indicated:
...Within the class member-specification, the class is regarded as complete within function bodies, default arguments, and exception-specifications, and constructor ctor-initializers(including such things in nested classes)...
Change 11.4 [class.mem] paragraph 9 as indicated:
Each occurrence in an expression of the name of a non-static data member or non-static member function of a class shall be expressed as a class member access (7.6.1.5 [expr.ref]), except when it appears in the formation of a pointer to member (7.6.2.2 [expr.unary.op]), oror when it appears in the body of a non-static member function of its class or of a class derived from its class (11.4.3 [class.mfct.non.static]), or when it appears in a mem-initializer for a constructor for its class or for a class derived from its class (11.9.3 [class.base.init]).
Change the note in 11.4.2 [class.mfct] paragraph 5 as indicated:
[Note: a name used in a member function definition (that is, in the parameter-declaration-clause including the default arguments (9.3.4.7 [dcl.fct.default]), oror in the member function body, or, for a constructor function (11.4.5 [class.ctor]), in a mem-initializer expression (11.9.3 [class.base.init])) is looked up as described in 6.5 [basic.lookup]. —end note]
Change 11.4.3 [class.mfct.non.static] paragraph 1 as indicated:
...A non-static member function may also be called directly using the function call syntax (7.6.1.3 [expr.call], 12.2.2.2 [over.match.call]) from within the body of a member function of its class or of a class derived from its class.
- from within the body of a member function of its class or of a class derived from its class, or
- from a mem-initializer (11.9.3 [class.base.init]) for a constructor for its class or for a class derived from its class.
Change 11.4.3 [class.mfct.non.static] paragraph 3 as indicated:
When an id-expression (_N4567_.5.1.1 [expr.prim.general]) that is not part of a class member access syntax (7.6.1.5 [expr.ref]) and not used to form a pointer to member (7.6.2.2 [expr.unary.op]) is used in the body of a non-static member function of class Xor used in the mem-initializer for a constructor of class X, if name lookup (6.5.3 [basic.lookup.unqual]) resolves the name in the id-expression to a non-static non-type member of class X or of a base class of X, the id-expression is transformed into a class member access expression (7.6.1.5 [expr.ref]) using (*this) (_N4868_.11.4.3.2 [class.this]) as the postfix-expression to the left of the . operator...
Change 11.4.5 [class.ctor] paragraph 7 as indicated:
...The implicitly-defined default constructor performs the set of initializations of the class that would be performed by a user-written default constructor for that class withan empty mem-initializer-listno ctor-initializer (11.9.3 [class.base.init]) and an emptyfunction bodycompound-statement...
Change 11.9.3 [class.base.init] paragraph 4 as indicated:
...After the call to a constructor for class X has completed, if a member of X is neither specified in the constructor's mem-initializers, nor default-initialized, nor value-initialized, nor given a value during execution of the compound-statement of the body of the constructor, the member has indeterminate value.
Change the last bullet of 11.9.3 [class.base.init] paragraph 5 as indicated:
Finally, the body compound-statement of the
constructor body is executed.
Change Clause 14 [except] paragraph 4 as indicated:
A function-try-block associates a handler-seq with the ctor-initializer, if present, and the
function-bodycompound-statement. An exception thrown during the execution of the initializer expressions in the ctor-initializer or during the execution of thefunction-bodycompound-statement transfers control to a handler in a function-try-block in the same way as an exception thrown during the execution of a try-block transfers control to other handlers. [Example:int f(int); class C { int i; double d; public: C(int, double); }; C::C(int ii, double id) try : i(f(ii)), d(id) { // constructorfunction bodystatements } catch (...) { // handles exceptions thrown from the ctor-initializer // and from the constructorfunction bodystatements }—end example]
Change 14.3 [except.ctor] paragraph 2 as indicated:
When an exception is thrown, control is transferred to the nearest handler with a matching type (14.4 [except.handle]); “nearest” means the handler for which thecompound-statement,compound-statement or ctor-initializer, or function-bodyfollowing the try keyword was most recently entered by the thread of control and not yet exited.
[Voted into WP at March 2004 meeting.]
The example in _N4868_.13.8.6 [temp.inject] paragraph 2 is incorrect:
template<typename T> class number { number(int); //... friend number gcd(number& x, number& y) { /* ... */ } //... }; void g() { number<double> a(3), b(4); //... a = gcd(a,b); // finds gcd because number<double> is an // associated class, making gcd visible // in its namespace (global scope) b = gcd(3,4); // ill-formed; gcd is not visible }
Regardless of the last statement ("b = gcd(3,4);"), the above code is ill-formed:
a) number's constructor is private;
b) the definition of (non-void) friend 'gcd' function does not contain a return statement.
Proposed resolution (April 2003):
Replace the example in _N4868_.13.8.6 [temp.inject] paragraph 2
bytemplate<typename T> class number { number(int); //... friend number gcd(number& x, number& y) { /* ... */ } //... }; void g() { number<double> a(3), b(4); //... a = gcd(a,b); // finds gcd because number<double> is an // associated class, making gcd visible // in its namespace (global scope) b = gcd(3,4); // ill-formed; gcd is not visible }
template<typename T> class number { public: number(int); //... friend number gcd(number x, number y) { return 0; } private: //... }; void g() { number<double> a(3), b(4); //... a = gcd(a,b); // finds gcd because number<double> is an // associated class, making gcd visible // in its namespace (global scope) b = gcd(3,4); // ill-formed; gcd is not visible }
Drafting note: Added "return" to the friend function, removed references in gcd arguments, added access specifiers.
[Voted into WP at April, 2007 meeting.]
Section Clause 3 [intro.defs], definition of "signature" omits the function name as part of the signature. Since the name participates in overload resolution, shouldn't it be included in the definition? I didn't find a definition of signature in the ARM, but I might have missed it.
Fergus Henderson: I think so. In particular, _N4140_.17.6.4.3.2 [global.names] reserves certain "function signatures" for use by the implementation, which would be wrong unless the signature includes the name.
-2- Each global function signature declared with external linkage in a header is reserved to the implementation to designate that function signature with external linkage.
-5- Each function signature from the Standard C library declared with external linkage is reserved to the implementation for use as a function signature with both extern "C" and extern "C++" linkage, or as a name of namespace scope in the global namespace.
Other uses of the term "function signature" in the description of the standard library also seem to assume that it includes the name.
James Widman:
Names don't participate in overload resolution; name lookup is separate from overload resolution. However, the word “signature” is not used in Clause 12 [over]. It is used in linkage and declaration matching (e.g., 13.7.7.2 [temp.over.link]). This suggests that the name and scope of the function should be part of its signature.
Proposed resolution (October, 2006):
Replace Clause 3 [intro.defs] “signature” with the following:
the name and the parameter-type-list (9.3.4.6 [dcl.fct]) of a function, as well as the class or namespace of which it is a member. If a function or function template is a class member its signature additionally includes the cv-qualifiers (if any) on the function or function template itself. The signature of a function template additionally includes its return type and its template parameter list. The signature of a function template specialization includes the signature of the template of which it is a specialization and its template arguments (whether explicitly specified or deduced). [Note: Signatures are used as a basis for name-mangling and linking. —end note]
Delete paragraph 3 and replace the first sentence of 13.7.7.2 [temp.over.link] as follows:
The signature of a function template specialization consists of the signature of the function template and of the actual template arguments (whether explicitly specified or deduced).The signature of a function template
consists of its function signature, its return type and its template parameter listis defined in Clause 3 [intro.defs]. The names of the template parameters are significant...
(See also issue 537.)
[Voted into WP at April, 2007 meeting.]
The standard defines “signature” in two places: Clause 3 [intro.defs] and 13.7.7.2 [temp.over.link] paragraphs 3-4. The former seems to be meant as a formal definition (I think it's the only place covering the nontemplate case), yet it lacks some bits mentioned in the latter (specifically, the notion of a “signature of a function template,” which is part of every signature of the associated function template specializations).
Also, I think the Clause 3 [intro.defs] words “the information about a function that participates in overload resolution” isn't quite right either. Perhaps, “the information about a function that distinguishes it in a set of overloaded functions?”
Eric Gufford:
In Clause 3 [intro.defs] the definition states that “Function signatures do not include return type, because that does not participate in overload resolution,” while 13.7.7.2 [temp.over.link] paragraph 4 states “The signature of a function template consists of its function signature, its return type and its template parameter list.” This seems inconsistent and potentially confusing. It also seems to imply that two identical function templates with different return types are distinct signatures, which is in direct violation of 12.2 [over.match]. 13.7.7.2 [temp.over.link] paragraph 4 should be amended to include verbiage relating to overload resolution.
Either return types are included in function signatures, or they're not, across the board. IMHO, they should be included as they are an integral part of the function declaration/definition irrespective of overloads. Then verbiage should be added about overload resolution to distinguish between signatures and overload rules. This would help clarify things, as it is commonly understood that overload resolution is based on function signature.
In short, the term “function signature” should be made consistent, and removed from its (implicit, explicit or otherwise) linkage to overload resolution as it is commonly understood.
James Widman:
The problem is that (a) if you say the return type is part of the signature of a non-template function, then you have overloading but not overload resolution on return types (i.e., what we have now with function templates). I don't think anyone wants to make the language uglier in that way. And (b) if you say that the return type is not part of the signature of a function template, you will break code. Given those alternatives, it's probably best to maintain the status quo (which the implementors appear to have rendered faithfully).
Proposed resolution (September, 2006):
This issue is resolved by the resolution of issue 357.
[Voted into WP at March 2004 meeting.]
Should this program do what its author obviously expects? As far as I can tell, the standard says that the point of instantiation for Fib<n-1>::Value is the same as the point of instantiation as the enclosing specialization, i.e., Fib<n>::Value. What in the standard actually says that these things get initialized in the right order?
template<int n> struct Fib { static int Value; }; template <> int Fib<0>::Value = 0; template <> int Fib<1>::Value = 1; template<int n> int Fib<n>::Value = Fib<n-1>::Value + Fib<n-2>::Value; int f () { return Fib<40>::Value; }
John Spicer: My opinion is that the standard does not specify the behavior of this program. I thought there was a core issue related to this, but I could not find it. The issue that I recall proposed tightening up the static initialization rules to make more cases well defined.
Your comment about point of instantiation is correct, but I don't think that really matters. What matters is the order of execution of the initialization code at execution time. Instantiations don't really live in "translation units" according to the standard. They live in "instantiation units", and the handling of instantiation units in initialization is unspecified (which should probably be another core issue). See 5.2 [lex.phases] paragraph 8.
Notes from October 2002 meeting:
We discussed this and agreed that we really do mean the the order is unspecified. John Spicer will propose wording on handling of instantiation units in initialization.
Proposed resolution (April 2003):
TC1 contains the following text in 6.9.3.2 [basic.start.static] paragraph 1:
Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.
This was revised by issue 270 to read:
Dynamic initialization of an object is either ordered or unordered. Explicit specializations and definitions of class template static data members have ordered initialization. Other class template static data member instances have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.
This addresses this issue but while reviewing this issue some additional changes were suggested for the above wording:
Dynamic initialization of an object is either ordered or unordered. Definitions of explicitly specializedExplicit specializations and definitions ofclass template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations)instanceshave unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.
[Moved to DR at October 2007 meeting.]
C99 and C++ differ in their approach to universal character names (UCNs).
Issue 248 already covers the differences in UCNs allowed for identifiers, but a more fundamental issue is that of UCNs that correspond to codes reserved by ISO 10676 for surrogate pair forms.
Specifically, C99 does not allow UCNs whose short names are in the range 0xD800 to 0xDFFF. I think C++ should have the same constraint. If someone really wants to place such a code in a character or string literal, they should use a hexadecimal escape sequence instead, for example:
wchar_t w1 = L'\xD900'; // Okay. wchar_t w2 = L'\uD900'; // Error, not a valid character.
(Compare 6.4.3 paragraph 2 in ISO/IEC 9899/1999 with 5.3.1 [lex.charset] paragraph 2 in the C++ standard.)
Proposed resolution (October, 2007):
This issue is resolved by the adoption of paper J16/07-0030 = WG21 N2170.
[Voted into WP at the October, 2006 meeting.]
The current wording of 5.13.3 [lex.ccon] paragraph 3 states,
If the character following a backslash is not one of those specified, the behavior is undefined.
Paper J16/04-0167=WG21 N1727 suggests that such character escapes be ill-formed. In discussions at the Lillehammer meeting, however, the CWG felt that the newly-approved category of conditionally-supported behavior would be more appropriate.
Proposed resolution (April, 2006):
Change the next-to-last sentence of 5.13.3 [lex.ccon] paragraph 3 from:
If the character following a backslash is not one of those specified, the behavior is undefined.
to:
Escape sequences in which the character following the backslash is not listed in Table 6 are conditionally-supported, with implementation-defined semantics.
[Voted into the WP at the June, 2008 meeting.]
6.1 [basic.pre] paragraph 10, while not incorrect, does not allow for linkage of operators and conversion functions. It says:
An identifier used in more than one translation unit can potentially refer to the same entity in these translation units depending on the linkage (6.6 [basic.link]) of the identifier specified in each translation unit.
Proposed Resolution (November, 2006):
This issue is resolved by the proposed resolution of issue 485.
[Voted into the WP at the June, 2008 meeting.]
6.1 [basic.pre] paragraph 4 says:
A name is a use of an identifier (5.11 [lex.name]) that denotes an entity or label (8.7.6 [stmt.goto], 8.2 [stmt.label]).
Just three paragraphs later, it says
Two names are the same if
- they are identifiers composed of the same character sequence; or
- they are the names of overloaded operator functions formed with the same operator; or
- they are the names of user-defined conversion functions formed with the same type.
The last two bullets contradict the definition of name in paragraph 4 because they are not identifiers.
This definition affects other parts of the Standard, as well. For example, in 6.5.4 [basic.lookup.argdep] paragraph 1,
When an unqualified name is used as the postfix-expression in a function call (7.6.1.3 [expr.call]), other namespaces not considered during the usual unqualified lookup (6.5.3 [basic.lookup.unqual]) may be searched, and in those namespaces, namespace-scope friend function declarations (11.8.4 [class.friend]) not otherwise visible may be found.
With the current definition of name, argument-dependent lookup apparently does not apply to function-notation calls to overloaded operators.
Another related question is whether a template-id is a name or not and thus would trigger an argument-dependent lookup. Personally, I have always viewed a template-id as a name, just like operator+.
Proposed Resolution (November, 2006):
Change 6.1 [basic.pre] paragraphs 3-8 as follows:
An entity is a value, object,
subobject, base class subobject, array element,variable, reference, function,instance of a function,enumerator, type, class member, template, template specialization, namespace, or parameter pack.A name is a use of an
identifieridentifier (5.11 [lex.name]), operator-function-id (12.4 [over.oper]), conversion-function-id (11.4.8.3 [class.conv.fct]), or template-id (13.3 [temp.names]) that denotes an entity or label (8.7.6 [stmt.goto], 8.2 [stmt.label]).A variable is introduced by the declaration of an object. The variable's name denotes the object.Every name that denotes an entity is introduced by a declaration. Every name that denotes a label is introduced either by a goto statement (8.7.6 [stmt.goto]) or a labeled-statement (8.2 [stmt.label]).
A variable is introduced by the declaration of an object. The variable's name denotes the object.
Some names denote types
, classes, enumerations,or templates. In general, it is necessary to determine whether or not a name denotes one of these entities before parsing the program that contains it. The process that determines this is called name lookup (6.5 [basic.lookup]).Two names are the same if
they are
identifiersidentifiers composed of the same character sequence; orthey are
the names of overloaded operator functionsoperator-function-ids formed with the same operator; orthey are
the names of user-defined conversion functionsconversion-function-ids formed with the same type., orthey are template-ids that refer to the same class or function (13.6 [temp.type]).
An identifierA name used in more than one translation unit can potentially refer to the same entity in these translation units depending on the linkage (6.6 [basic.link]) of theidentifiername specified in each translation unit.
Change 6.4.7 [basic.scope.class] paragraph 1 item 5 as follows:
The potential scope of a declaration that extends to or past the end of a class definition also extends to the regions defined by its member definitions, even if the members are defined lexically outside the class (this includes static data member definitions, nested class definitions, member function definitions (including the member function body and any portion of the declarator part of such definitions which follows theidentifierdeclarator-id, including a parameter-declaration-clause and any default arguments (9.3.4.7 [dcl.fct.default]).
[Drafting note: This last change is not really mandated by the issue, but it's another case of “identifier” confusion.]
(This proposed resolution also resolves issue 309.)
[Moved to DR at October 2002 meeting.]
6.3 [basic.def.odr] paragraph 2 says that a deallocation function is "used" by a new-expression or delete-expression appearing in a potentially-evaluated expression. 6.3 [basic.def.odr] paragraph 3 requires only that "used" functions be defined.
This wording runs afoul of the typical implementation technique for polymorphic delete-expressions in which the deallocation function is invoked from the virtual destructor of the most-derived class. The problem is that the destructor must be defined, because it's virtual, and if it contains an implicit reference to the deallocation function, the deallocation function must also be defined, even if there are no relevant new-expressions or delete-expressions in the program.
For example:
struct B { virtual ~B() { } }; struct D: B { void operator delete(void*); ~D() { } };
Is it required that D::operator delete(void*) be defined, even if no B or D objects are ever created or deleted?
Suggested resolution: Add the words "or if it is found by the lookup at the point of definition of a virtual destructor (11.4.7 [class.dtor])" to the specification in 6.3 [basic.def.odr] paragraph 2.
Notes from 04/01 meeting:
The consensus was in favor of requiring that any declared non-placement operator delete member function be defined if the destructor for the class is defined (whether virtual or not), and similarly for a non-placement operator new if a constructor is defined.
Proposed resolution (10/01):
In 6.3 [basic.def.odr] paragraph 2, add the indicated text:
An allocation or deallocation function for a class is used by a new expression appearing in a potentially-evaluated expression as specified in 7.6.2.8 [expr.new] and 11.4.11 [class.free]. A deallocation function for a class is used by a delete expression appearing in a potentially-evaluated expression as specified in 7.6.2.9 [expr.delete] and 11.4.11 [class.free]. A non-placement allocation or deallocation function for a class is used by the definition of a constructor of that class. A non-placement deallocation function for a class is used by the definition of the destructor of that class, or by being selected by the lookup at the point of definition of a virtual destructor (11.4.7 [class.dtor]). [Footnote: An implementation is not required to call allocation and deallocation functions from constructors or destructors; however, this is a permissible implementation technique.]
[Moved to DR at October 2002 meeting.]
6.3 [basic.def.odr] paragraph 4 has a note listing the contexts that require a class type to be complete. It does not list use as a base class as being one of those contexts.
Proposed resolution (10/01):
In 6.3 [basic.def.odr] paragraph 4 add a new bullet at the end of the note as the next-to-last bullet:
[Voted into WP at March 2004 meeting.]
Consider the following translation unit:
template<class T> struct S { void f(union U*); // (1) }; template<class T> void S<T>::f(union U*) {} // (2) U *p; // (3)
Does (1) introduce U as a visible name in the surrounding namespace scope?
If not, then (2) could presumably be an error since the "union U" in that definition does not find the same type as the declaration (1).
If yes, then (3) is OK too. However, we have gone through much trouble to allow template implementations that do not pre-parse the template definitions, but requiring (1) to be visible would change that.
A slightly different case is the following:
template<typename> void f() { union U *p; } U *q; // Should this be valid?
Notes from October 2003 meeting:
There was consensus that example 1 should be allowed. (Compilers already parse declarations in templates; even MSVC++ 6.0 accepts this case.) The vote was 7-2.
Example 2, on the other hand, is wrong; the union name goes into a block scope anyway.
Proposed resolution:
In 6.4.2 [basic.scope.pdecl] change the second bullet of paragraph 5 as follows:
for an elaborated-type-specifier of the formclass-key identifierif the elaborated-type-specifier is used in the decl-specifier-seq or parameter-declaration-clause of a function defined in namespace scope, the identifier is declared as a class-name in the namespace that contains the declaration; otherwise, except as a friend declaration, the identifier is declared in the smallest non-class, non-function-prototype scope that contains the declaration. [Note: These rules also apply within templates.] [Note: ...]
[Voted into WP at March 2004 meeting.]
Consider the following example (inspired by a question from comp.lang.c++.moderated):
template<typename> struct B {}; template<typename T> struct D: B<D> {};
Most (all?) compilers reject this code because D is handled as a template name rather than as the injected class name.
Clause 11 [class]/2 says that the injected class name is "inserted into the scope of the class."
6.4.7 [basic.scope.class]/1 seems to be the text intended to describe what "scope of a class" means, but it assumes that every name in that scope was introduced using a "declarator". For an implicit declaration such as the injected-class name it is not clear what that means.
So my questions:
John Spicer: I do not believe the injected class name should be available in the base specifier. I think the semantics of injected class names should be as if a magic declaration were inserted after the opening "{" of the class definition. The injected class name is a member of the class and members don't exist at the point where the base specifiers are scanned.
John Spicer: I believe the 6.4.7 [basic.scope.class] wording should be updated to reflect the fact that not all names come from declarators.
Notes from October 2003 meeting:
We agree with John Spicer's suggested answers above.
Proposed Resolution (October 2003):
The answer to question 1 above is No and no change is required.
For question 1, change 6.4.7 [basic.scope.class] paragraph 1 rule 1 to:
1) The potential scope of a name declared in a class consists not only of the declarative region following the name's point of declarationdeclarator, but also of all function bodies, default arguments, and constructor ctor-initializers in that class (including such things in nested classes). The point of declaration of an injected-class-name (Clause 11 [class]) is immediately following the opening brace of the class definition.
(Note that this change overlaps a change in issue 417.)
Also change 6.4.2 [basic.scope.pdecl] by adding a new paragraph 8 for the injected-class-name case:
The point of declaration for an injected-class-name (Clause 11 [class]) is immediately following the opening brace of the class definition.
Alternatively this paragraph could be added after paragraph 5 and before the two note paragraphs (i.e. it would become paragraph 5a).
[Voted into WP at April 2005 meeting.]
The ambiguity text in 6.5.2 [class.member.lookup] may not say what we intended. It makes the following example ill-formed:
struct A { int x(int); }; struct B: A { using A::x; float x(float); }; int f(B* b) { b->x(3); // ambiguous }This is a name lookup ambiguity because of 6.5.2 [class.member.lookup] paragraph 2:
... Each of these declarations that was introduced by a using-declaration is considered to be from each sub-object of C that is of the type containing the declaration designated by the using-declaration. If the resulting set of declarations are not all from sub-objects of the same type, or the set has a nonstatic member and includes members from distinct sub-objects, there is an ambiguity and the program is ill-formed.This contradicts the text and example in paragraph 12 of 9.10 [namespace.udecl] .
Proposed Resolution (10/00):
Replace the two cited sentences from 6.5.2 [class.member.lookup] paragraph 2 with the following:
The resulting set of declarations shall all be from sub-objects of the same type, or there shall be a set of declarations from sub-objects of a single type that contains using-declarations for the declarations found in all other sub-object types. Furthermore, for nonstatic members, the resulting set of declarations shall all be from a single sub-object, or there shall be a set of declarations from a single sub-object that contains using-declarations for the declarations found in all other sub-objects. Otherwise, there is an ambiguity and the program is ill-formed.
Replace the examples in 6.5.2 [class.member.lookup] paragraph 3 with the following:
struct A { int x(int); static int y(int); }; struct V { int z(int); }; struct B: A, virtual V { using A::x; float x(float); using A::y; static float y(float); using V::z; float z(float); }; struct C: B, A, virtual V { }; void f(C* c) { c->x(3); // ambiguous -- more than one sub-object A c->y(3); // not ambiguous c->z(3); // not ambiguous }
Notes from 04/01 meeting:
The following example should be accepted but is rejected by the wording above:
struct A { static void f(); }; struct B1: virtual A { using A::f; }; struct B2: virtual A { using A::f; }; struct C: B1, B2 { }; void g() { C::f(); // OK, calls A::f() }
Notes from 10/01 meeting (Jason Merrill):
The example in the issues list:
struct A { int x(int); }; struct B: A { using A::x; float x(float); }; int f(B* b) { b->x(3); // ambiguous }Is broken under the existing wording:
... Each of these declarations that was introduced by a using-declaration is considered to be from each sub-object of C that is of the type containing the declaration designated by the using-declaration. If the resulting set of declarations are not all from sub-objects of the same type, or the set has a nonstatic member and includes members from distinct sub-objects, there is an ambiguity and the program is ill-formed.Since the two x's are considered to be "from" different objects, looking up x produces a set including declarations "from" different objects, and the program is ill-formed. Clearly this is wrong. The problem with the existing wording is that it fails to consider lookup context.
The first proposed solution:
The resulting set of declarations shall all be from sub-objects of the same type, or there shall be a set of declarations from sub-objects of a single type that contains using-declarations for the declarations found in all other sub-object types. Furthermore, for nonstatic members, the resulting set of declarations shall all be from a single sub-object, or there shall be a set of declarations from a single sub-object that contains using-declarations for the declarations found in all other sub-objects. Otherwise, there is an ambiguity and the program is ill-formed.breaks this testcase:
struct A { static void f(); }; struct B1: virtual A { using A::f; }; struct B2: virtual A { using A::f; }; struct C: B1, B2 { }; void g() { C::f(); // OK, calls A::f() }because it considers the lookup context, but not the definition context; under this definition of "from", the two declarations found are the using-declarations, which are "from" B1 and B2.
The solution is to separate the notions of lookup and definition context. I have taken an algorithmic approach to describing the strategy.
Incidentally, the earlier proposal allows one base to have a superset of the declarations in another base; that was an extension, and my proposal does not do that. One algorithmic benefit of this limitation is to simplify the case of a virtual base being hidden along one arm and not another ("domination"); if we allowed supersets, we would need to remember which subobjects had which declarations, while under the following resolution we need only keep two lists, of subobjects and declarations.
Proposed resolution (October 2002):
Replace 6.5.2 [class.member.lookup] paragraph 2 with:
The following steps define the result of name lookup for a member name f in a class scope C.
The lookup set for f in C, called S(f,C), consists of two component sets: the declaration set, a set of members named f; and the subobject set, a set of subobjects where declarations of these members (possibly including using-declarations) were found. In the declaration set, using-declarations are replaced by the members they designate, and type declarations (including injected-class-names) are replaced by the types they designate. S(f,C) is calculated as follows.
If C contains a declaration of the name f, the declaration set contains every declaration of f in C (excluding bases), the subobject set contains C itself, and calculation is complete.
Otherwise, S(f,C) is initially empty. If C has base classes, calculate the lookup set for f in each direct base class subjobject Bi, and merge each such lookup set S(f,Bi) in turn into S(f,C).
The following steps define the result of merging lookup set S(f,Bi) into the intermediate S(f,C):
- If each of the subobject members of S(f,Bi) is a base class subobject of at least one of the subobject members of S(f,C), S(f,C) is unchanged and the merge is complete. Conversely, if each of the subobject members of S(f,C) is a base class subobject of at least one of the subobject members of S(f,Bi), the new S(f,C) is a copy of S(f,Bi).
- Otherwise, if the declaration sets of S(f,Bi) and S(f,C) differ, the merge is ambiguous: the new S(f,C) is a lookup set with an invalid declaration set and the union of the subobject sets. In subsequent merges, an invalid declaration set is considered different from any other.
- Otherwise, consider each declaration d in the set, where d is a member of class A. If d is a nonstatic member, compare the A base class subobjects of the subobject members of S(f,Bi) and S(f,C). If they do not match, the merge is ambiguous, as in the previous step. [Note: It is not necessary to remember which A subobject each member comes from, since using-declarations don't disambiguate. ]
- Otherwise, the new S(f,C) is a lookup set with the shared set of declarations and the union of the subobject sets.
The result of name lookup for f in C is the declaration set of S(f,C). If it is an invalid set, the program is ill-formed.
[Example:
struct A { int x; }; // S(x,A) = {{ A::x }, { A }} struct B { float x; }; // S(x,B) = {{ B::x }, { B }} struct C: public A, public B { }; // S(x,C) = { invalid, { A in C, B in C }} struct D: public virtual C { }; // S(x,D) = S(x,C) struct E: public virtual C { char x; }; // S(x,E) = {{ E::x }, { E }} struct F: public D, public E { }; // S(x,F) = S(x,E) int main() { F f; f.x = 0; // OK, lookup finds { E::x } }S(x,F) is unambiguous because the A and B base subobjects of D are also base subobjects of E, so S(x,D) is discarded in the first merge step. --end example]
Turn 6.5.2 [class.member.lookup] paragraphs 5 and 6 into notes.
Notes from October 2003 meeting:
Mike Miller raised some new issues in N1543, and we adjusted the proposed resolution as indicated in that paper.
Further information from Mike Miller (January 2004):
Unfortunately, I've become aware of a minor glitch in the proposed resolution for issue 39 in N1543, so I'd like to suggest a change that we can discuss in Sydney.
A brief review and background of the problem: the major change we agreed on in Kona was to remove detection of multiple-subobject ambiguity from class lookup (6.5.2 [class.member.lookup]) and instead handle it as part of the class member access expression. It was pointed out in Kona that 11.8.3 [class.access.base]/5 has this effect:
If a class member access operator, including an implicit "this->," is used to access a nonstatic data member or nonstatic member function, the reference is ill-formed if the left operand (considered as a pointer in the "." operator case) cannot be implicitly converted to a pointer to the naming class of the right operand.
After the meeting, however, I realized that this requirement is not sufficient to handle all the cases. Consider, for instance,
struct B { int i; }; struct I1: B { }; struct I2: B { }; struct D: I1, I2 { void f() { i = 0; // not ill-formed per 11.2p5 } };
Here, both the object expression ("this") and the naming class are "D", so the reference to "i" satisfies the requirement in 11.8.3 [class.access.base]/5, even though it involves a multiple-subobject ambiguity.
In order to address this problem, I proposed in N1543 to add a paragraph following 7.6.1.5 [expr.ref]/4:
If E2 is a non-static data member or a non-static member function, the program is ill-formed if the class of E1 cannot be unambiguously converted (10.2) to the class of which E2 is directly a member.
That's not quite right. It does diagnose the case above as written; however, it breaks the case where qualification is used to circumvent the ambiguity:
struct D2: I1, I2 { void f() { I2::i = 0; // ill-formed per proposal } };
In my proposed wording, the class of "this" can't be converted to "B" (the qualifier is ignored), so the access is ill-formed. Oops.
I think the following is a correct formulation, so the proposed resolution we discuss in Sydney should contain the following paragraph instead of the one in N1543:
If E2 is a nonstatic data member or a non-static member function, the program is ill-formed if the naming class (11.2) of E2 cannot be unambiguously converted (10.2) to the class of which E2 is directly a member.
This reformulation also has the advantage of pointing readers to 11.8.3 [class.access.base], where the the convertibility requirement from the class of E1 to the naming class is located and which might otherwise be overlooked.
Notes from the March 2004 meeting:
We discussed this further and agreed with these latest recommendations. Mike Miller has produced a paper N1626 that gives just the final collected set of changes.
(This resolution also resolves isssue 306.)
[Voted into WP at April 2005 meeting.]
Is the following well-formed?
struct A { struct B { }; }; struct C : public A, public A::B { B *p; };The lookup of B finds both the struct B in A and the injected B from the A::B base class. Are they the same thing? Does the standard say so?
What if a struct is found along one path and a typedef to that struct is found along another path? That should probably be valid, but does the standard say so?
This is resolved by issue 39
February 2004: Moved back to "Review" status because issue 39 was moved back to "Review".
[Moved to DR at 10/01 meeting.]
The example in 6.5.3 [basic.lookup.unqual] paragraph 3 is incorrect:
typedef int f; struct A { friend void f(A &); operator int(); void g(A a) { f(a); } };Regardless of the resolution of other issues concerning the lookup of names in friend declarations, this example is ill-formed (the function and the typedef cannot exist in the same scope).
One possible repair of the example would be to make f a class with a constructor taking either A or int as its parameter.
(See also issues 95, 136, 138, 143, 165, and 166.)
Proposed resolution (04/01):
Change the example in 6.5.3 [basic.lookup.unqual] paragraph 3 to read:
typedef int f; namespace N { struct A { friend int f(A &); operator int(); void g(A a) { int i = f(a); // f is the typedef, not the friend function: // equivalent to int(a) } }; }
Delete the sentence immediately following the example:
The expression f(a) is a cast-expression equivalent to int(a).
[Voted into WP at the October, 2006 meeting.]
Is the following code well-formed?
namespace N { int i; extern int j; } int N::j = i;
The question here is whether the lookup for i in the initializer of N::j finds the declaration in namespace N or not. Implementations differ on this question.
If N::j were a static data member of a class, the answer would be clear: both 6.5.3 [basic.lookup.unqual] paragraph 12 and 9.5 [dcl.init] paragraph 11 say that the initializer “is in the scope of the member's class.” There is no such provision for namespace members defined outside the namespace, however.
The reasoning given in 6.5.3 [basic.lookup.unqual] may be instructive:
A name used in the definition of a static data member of class X (11.4.9.3 [class.static.data]) (after the qualified-id of the static member) is looked up as if the name was used in a member function of X.
It is certainly the case that a name used in a function that is a member of a namespace is looked up in that namespace (6.5.3 [basic.lookup.unqual] paragraph 6), regardless of whether the definition is inside or outside that namespace. Initializers for namespace members should probably be looked up the same way.
Proposed resolution (April, 2006):
Add a new paragraph following 6.5.3 [basic.lookup.unqual] paragraph 12:
If a variable member of a namespace is defined outside of the scope of its namespace then any name used in the definition of the variable member (after the declarator-id) is looked up as if the definition of the variable member occurred in its namespace. [Example:
namespace N { int i = 4; extern int j; } int i = 2; int N::j = i; // N::j == 4—end example]
[Moved to DR at 4/02 meeting.]
Paragraphs 1 and 2 of 6.5.4 [basic.lookup.argdep] say, in part,
When an unqualified name is used as the postfix-expression in a function call (7.6.1.3 [expr.call] )... namespace-scope friend function declarations (11.8.4 [class.friend] ) not otherwise visible may be found... the set of declarations found by the lookup of the function name [includes] the set of declarations found in the... classes associated with the argument types.The most straightforward reading of this wording is that if a function of namespace scope (as opposed to a class member function) is declared as a friend in a class, and that class is an associated class in a function call, the friend function will be part of the overload set, even if it is not visible to normal lookup.
Consider the following example:
namespace A { class S; }; namespace B { void f(A::S); }; namespace A { class S { int i; friend void B::f(S); }; } void g() { A::S s; f(s); // should find B::f(A::S) }This example would seem to satisfy the criteria from 6.5.4 [basic.lookup.argdep] : A::S is an associated class of the argument, and A::S has a friend declaration of the namespace-scope function B::f(A::S), so Koenig lookup should include B::f(A::S) as part of the overload set in the call.
Another interpretation is that, instead of finding the friend declarations in associated classes, one only looks for namespace-scope functions, visible or invisible, in the namespaces of which the the associated classes are members; the only use of the friend declarations in the associated classes is to validate whether an invisible function declaration came from an associated class or not and thus whether it should be included in the overload set or not. By this interpretation, the call f(s) in the example will fail, because B::f(A::S) is not a member of namespace A and thus is not found by the lookup.
Notes from 10/99 meeting: The second interpretation is correct. The wording should be revised to make clear that Koenig lookup works by finding "invisible" declarations in namespace scope and not by finding friend declarations in associated classes.
Proposed resolution (04/01): The "associated classes" are handled adequately under this interpretation by 6.5.4 [basic.lookup.argdep] paragraph 3, which describes the lookup in the associated namespaces as including the friend declarations from the associated classes. Other mentions of the associated classes should be removed or qualified to avoid the impression that there is a lookup in those classes:
In 6.5.4 [basic.lookup.argdep], change
When an unqualified name is used as the postfix-expression in a function call (7.6.1.3 [expr.call]), other namespaces not considered during the usual unqualified lookup (6.5.3 [basic.lookup.unqual]) may be searched, and namespace-scope friend function declarations (11.8.4 [class.friend]) not otherwise visible may be found.
to
When an unqualified name is used as the postfix-expression in a function call (7.6.1.3 [expr.call]), other namespaces not considered during the usual unqualified lookup (6.5.3 [basic.lookup.unqual]) may be searched, and in those namespaces, namespace-scope friend function declarations (11.8.4 [class.friend]) not otherwise visible may be found.
In 6.5.4 [basic.lookup.argdep] paragraph 2, delete the words and classes in the following two sentences:
If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespacesand classesare not considered. Otherwise the set of declarations found by the lookup of the function name is the union of the set of declarations found using ordinary unqualified lookup and the set of declarations found in the namespacesand classesassociated with the argument types.
(See also issues 95, 136, 138, 139, 165, 166, and 218.)
[Voted into WP at April, 2007 meeting.]
The original intent of the Committee when Koenig lookup was added to the language was apparently something like the following:
This approach is not reflected in the current wording of the Standard. Instead, the following appears to be the status quo:
John Spicer: Argument-dependent lookup was created to solve the problem of looking up function names within templates where you don't know which namespace to use because it may depend on the template argument types (and was then expanded to permit use in nontemplates). The original intent only concerned functions. The safest and simplest change is to simply clarify the existing wording to that effect.
Bill Gibbons: I see no reason why non-function declarations should not be found. It would take a special rule to exclude "function objects", as well as pointers to functions, from consideration. There is no such rule in the standard and I see no need for one.
There is also a problem with the wording in 6.5.4 [basic.lookup.argdep] paragraph 2:
If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered.
This implies that if the ordinary lookup of the name finds the declaration of a data member which is a pointer to function or function object, argument-dependent lookup is still done.
My guess is that this is a mistake based on the incorrect assumption that finding any member other than a member function would be an error. I would just change "class member function" to "class member" in the quoted sentence.
Mike Miller: In light of the issue of "short-circuiting" Koenig lookup when normal lookup finds a non-function, perhaps it should be written as "...finds the declaration of a class member, an object, or a reference, the associated namespaces..."?
Andy Koenig: I think I have to weigh in on the side of extending argument-dependent lookup to include function objects and pointers to functions. I am particularly concerned about [function objects], because I think that programmers should be able to replace functions by function objects without changing the behavior of their programs in fundamental ways.
Bjarne Stroustrup: I don't think we could seriously argue from first principles that [argument-dependent lookup should find only function declarations]. In general, C++ name lookup is designed to be independent of type: First we find the name(s), then, we consider its(their) meaning. 6.5 [basic.lookup] states "The name lookup rules apply uniformly to all names ..." That is an important principle.
Thus, I consider text that speaks of "function call" instead of plain "call" or "application of ()" in the context of koenig lookup an accident of history. I find it hard to understand how 7.6.1.3 [expr.call] doesn't either disallow all occurrences of x(y) where x is a class object (that's clearly not intended) or requires koenig lookup for x independently of its type (by reference from 6.5 [basic.lookup]). I suspect that a clarification of 7.6.1.3 [expr.call] to mention function objects is in order. If the left-hand operand of () is a name, it should be looked up using koenig lookup.
John Spicer: This approach causes otherwise well-formed programs to be ill-formed, and it does so by making names visible that might be completely unknown to the author of the program. Using-directives already do this, but argument-dependent lookup is different. You only get names from using-directives if you actually use using-directives. You get names from argument-dependent lookup whether you want them or not.
This basically breaks an important reason for having namespaces. You are not supposed to need any knowledge of the names used by a namespace.
But this example breaks if argument-dependent lookup finds non-functions and if the translation unit includes the <list> header somewhere.
namespace my_ns { struct A {}; void list(std::ostream&, A&); void f() { my_ns::A a; list(cout, a); } }
This really makes namespaces of questionable value if you still need to avoid using the same name as an entity in another namespace to avoid problems like this.
Erwin Unruh: Before we really decide on this topic, we should have more analysis on the impact on programs. I would also like to see a paper on the possibility to overload functions with function surrogates (no, I won't write one). Since such an extension is bound to wait until the next official update, we should not preclude any outcome of the discussion.
I would like to have a change right now, which leaves open several outcomes later. I would like to say that:
Koenig lookup will find non-functions as well. If it finds a variable, the program is ill-formed. If the primary lookup finds a variable, Koenig lookup is done. If the result contains both functions and variables, the program is ill-formed. [Note: A future standard will assign semantics to such a program.]
I myself are not comfortable with this as a long-time result, but it prepares the ground for any of the following long term solutions:
The note is there to prevent compiler vendors to put their own extensions in here.
(See also issues 113 and 143.)
Notes from 04/00 meeting:
Although many agreed that there were valid concerns motivating a desire for Koenig lookup to find non-function declarations, there was also concern that supporting this capability would be more dangerous than helpful in the absence of overload resolution for mixed function and non-function declarations.
A straw poll of the group revealed 8 in favor of Koenig lookup finding functions and function templates only, while 3 supported the broader result.
Notes from the 10/01 meeting:
There was unanimous agreement on one less controversial point: if the normal lookup of the identifier finds a non-function, argument-dependent lookup should not be done.
On the larger issue, the primary point of consensus is that making this change is an extension, and therefore it should wait until the point at which we are considering extensions (which could be very soon). There was also consensus on the fact that the standard as it stands is not clear: some introductory text suggests that argument-dependent lookup finds only functions, but the more detailed text that describes the lookup does not have any such restriction.
It was also noted that some existing implementations (e.g., g++) do find some non-functions in some cases.
The issue at this point is whether we should (1) make a small change to make the standard clear (presumably in the direction of not finding the non-functions in the lookup), and revisit the issue later as an extension, or (2) leave the standard alone for now and make any changes only as part of considering the extension. A straw vote favored option (1) by a strong majority.
Additional Notes (September, 2006):
Recent discussion of this issue has emphasized the following points:
The concept of finding function pointers and function objects as part of argument-dependent lookup is not currently under active discussion in the Evolution Working Group.
The major area of concern with argument-dependent lookup is finding functions in unintended namespaces. There are current proposals to deal with this concern either by changing the definition of “associated namespace” so that fewer namespaces are considered or to provide a mechanism for enabling or disabling ADL altogether. Although this concern is conceptually distinct from the question of whether ADL finds function pointers and function objects, it is related in the sense that the current rules are perceived as finding too many functions (because of searching too many namespaces), and allowing function pointers and function objects would also increase the number of entities found by ADL.
Any expansion of ADL to include function pointers and function objects must necessarily update the overloading rules to specify how they interact with functions and function templates in the overload set. Current implementation experience (g++) is not helpful in making this decision because, although it performs a uniform lookup and finds non-function entities, it diagnoses an error in overload resolution if non-function entities are in the overload set.
There is a possible problem if types are found by ADL: it is not clear that overloading between callable entities (functions, function templates, function pointers, and function objects) and types (where the postfix syntax means a cast or construction of a temporary) is reasonable or useful.
James Widman:
There is a larger debate here about whether ADL should find object names; the proposed wording below is only intended to answer the request for wording to clarify the status quo (option 1 above) and not to suggest the outcome of the larger debate.
Proposed Resolution (October, 2006):
Replace the normative text in 6.5.4 [basic.lookup.argdep] paragraph 3 with the following (leaving the text of the note and example unchanged):
Let X be the lookup set produced by unqualified lookup (6.5.3 [basic.lookup.unqual]) and let Y be the lookup set produced by argument dependent lookup (defined as follows). If X contains
- a declaration of a class member, or
- a block-scope function declaration that is not a using-declaration, or
- a declaration that is neither a function nor a function template
then Y is empty. Otherwise Y is the set of declarations found in the namespaces associated with the argument types as described below. The set of declarations found by the lookup of the name is the union of X and Y.
Change 6.5.3 [basic.lookup.unqual] paragraph 4 as indicated:
When considering an associated namespace, the lookup is the same as the lookup performed when the associated namespace is used as a qualifier (6.5.5.3 [namespace.qual]) except that:
- Any using-directives in the associated namespace are ignored.
- Any namespace-scope friend functions or friend function templates declared in associated classes are visible within their respective namespaces even if they are not visible during an ordinary lookup (11.8.4 [class.friend]).
- All names except those of (possibly overloaded) functions and function templates are ignored.
[Voted into WP at March 2004 meeting.]
Spun off from issue 384.
6.5.4 [basic.lookup.argdep] says:
If T is a template-id, its associated namespaces and classes are the namespace in which the template is defined; for member templates, the member template's class; the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces in which any template template arguments are defined; and the classes in which any member templates used as template template arguments are defined. [Note: non-type template arguments do not contribute to the set of associated namespaces. ]There is a problem with the term "is a template-id". template-id is a syntactic construct and you can't really talk about a type being a template-id. Presumably, this is intended to mean "If T is the type of a class template specialization ...".
Proposed Resolution (October 2003):
In 6.5.4 [basic.lookup.argdep], paragraph 2, bullet 8, replace
If T is a template-id ...with
If T is a class template specialization ...
[Voted into WP at the October, 2006 meeting.]
One might assume from 13.9.2 [temp.inst] paragraph 1 that argument-dependent lookup would require instantiation of any class template specializations used in argument types:
Unless a class template specialization has been explicitly instantiated (13.9.3 [temp.explicit]) or explicitly specialized (13.9.4 [temp.expl.spec]), the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program.
A complete class type is required to determine the associated classes and namespaces for the argument type (to determine the class's bases) and to determine the friend functions declared by the class, so the completeness of the class type certainly “affects the semantics of the program.”
This conclusion is reinforced by the second bullet of 6.5.4 [basic.lookup.argdep] paragraph 2:
If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the namespaces in which its associated classes are defined.
A class template specialization is a class type, so the second bullet would appear to apply, requiring the specialization to be instantiated in order to determine its base classes.
However, bullet 8 of that paragraph deals explicitly with class template specializations:
If T is a class template specialization its associated namespaces and classes are the namespace in which the template is defined; for member templates, the member template's class; the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces in which any template template arguments are defined; and the classes in which any member templates used as template template arguments are defined.
Note that the class template specialization itself is not listed as an associated class, unlike other class types, and there is no mention of base classes. If bullet 8 were intended as a supplement to the treatment of class types in bullet 2, one would expect phrasing along the lines of, “In addition to the associated namespaces and classes for all class types...” or some such; instead, bullet 8 reads like a self-contained and complete specification.
If argument-dependent lookup does not cause implicit instantiation, however, examples like the following fail:
template <typename T> class C { friend void f(C<T>*) { } }; void g(C<int>* p) { f(p); // found by ADL?? }
Implementations differ in whether this example works or not.
Proposed resolution (April, 2006):
Change bullet 2 of 6.5.4 [basic.lookup.argdep] paragraph 2 as indicated:
If T is a class type (including unions), its associated
classes are: the class itself; the class of which it is a member, if
any; and its direct and indirect base classes. Its associated
namespaces are the namespaces in of which its associated
classes are defined members. Furthermore,
if T is a class template specialization, its associated
namespaces and classes also include: the namespaces and classes
associated with the types of the template arguments provided for
template type parameters (excluding template template parameters); the
namespaces of which any template template arguments are members; and
the classes of which any member templates used as template template
arguments are members. [Note: Non-type template arguments do not
contribute to the set of associated namespaces. —end
note]
Delete bullet 8 of 6.5.4 [basic.lookup.argdep] paragraph 2:
If T is a class template specialization its associated
namespaces and classes are the namespace in which the template is
defined; for member templates, the member template's class; the
namespaces and classes associated with the types of the template
arguments provided for template type parameters (excluding template
template parameters); the namespaces in which any template template
arguments are defined; and the classes in which any member templates
used as template template arguments are defined. [Note: non-type
template arguments do not contribute to the set of associated
namespaces. —end note]
[Voted into WP at April 2003 meeting.]
Can a typedef T to a cv-qualified class type be used in a qualified name T::x?
struct A { static int i; }; typedef const A CA; int main () { CA::i = 0; // Okay? }
Suggested answer: Yes. All the compilers I tried accept the test case.
Proposed resolution (10/01):
In 6.5.5.2 [class.qual] paragraph 1 add the indicated text:
If the nested-name-specifier of a qualified-id nominates a class, the name specified after the nested-name-specifier is looked up in the scope of the class (6.5.2 [class.member.lookup]), except for the cases listed below. The name shall represent one or more members of that class or of one of its base classes (11.7 [class.derived]). If the class-or-namespace-name of the nested-name-specifier names a cv-qualified class type, it nominates the underlying class (the cv-qualifiers are ignored).
Notes from 4/02 meeting:
There is a problem in that class-or-namespace-name does not include typedef names for cv-qualified class types. See 9.2.4 [dcl.typedef] paragraph 4:
Argument and text removed from proposed resolution (October 2002):
9.2.4 [dcl.typedef] paragraph 5:
Here's a good question: in this example, should X be used as a name-for-linkage-purposes (FLP name)?
typedef class { } const X;
Because a type-qualifier is parsed as a decl-specifier, it isn't possible to declare cv-qualified and cv-unqualified typedefs for a type in a single declaration. Also, of course, there's no way to declare a typedef for the cv-unqualified version of a type for which only a cv-qualified version has a name. So, in the above example, if X isn't used as the FLP name, then there can be no FLP name. Also note that a FLP name usually represents a parameter type, where top-level cv-qualifiers are usually irrelevant anyway.
Data points: for the above example, Microsoft uses X as the FLP name; GNU and EDG do not.
My recommendation: for consistency with the direction we're going on this issue, for simplicity of description (e.g., "the first class-name declared by the declaration"), and for (very slightly) increased utility, I think Microsoft has this right.
If the typedef declaration defines an unnamed class type (or enum type), the first typedef-name declared by the declaration tobehave thatclasstype(or enum type)or a cv-qualified version thereof is used to denote the class type (or enum type) for linkage purposes only (6.6 [basic.link]). [Example: ...
Proposed resolution (October 2002):
6.5.6 [basic.lookup.elab] paragraphs 2 and 3:
This sentence is deleted twice:
...If this name lookup finds a typedef-name, the elaborated-type-specifier is ill-formed....
Note that the above changes are included in N1376 as part of the resolution of issue 245.
_N4567_.5.1.1 [expr.prim.general] paragraph 7:
This is only a note, and it is at least incomplete (and quite possibly inaccurate), despite (or because of) its complexity. I propose to delete it.
... [Note: a typedef-name that names a class is a class-name (11.3 [class.name]).Except as the identifier in the declarator for a constructor or destructor definition outside of a class member-specification (11.4.5 [class.ctor], 11.4.7 [class.dtor]), a typedef-name that names a class may be used in a qualified-id to refer to a constructor or destructor.]
9.2.4 [dcl.typedef] paragraph 4:
My first choice would have been to make this the primary statement about the equivalence of typedef-name and class-name, since the equivalence comes about as a result of a typedef declaration. Unfortunately, references to class-name point to 11.3 [class.name], so it would seem that the primary statement should be there instead. To avoid the possiblity of conflicts in the future, I propose to make this a note.
[Note: A typedef-name that names a class type, or a cv-qualified version thereof, is also a class-name (11.3 [class.name]). If a typedef-name is usedfollowing the class-key in an elaborated-type-specifier (9.2.9.5 [dcl.type.elab]), or in the class-head of a class declaration (Clause 11 [class]), or is used as the identifier in the declarator for a constructor or destructor declaration (11.4.5 [class.ctor], 11.4.7 [class.dtor]),to identify the subject of an elaborated-type-specifier (9.2.9.5 [dcl.type.elab]), class declaration (Clause 11 [class]), constructor declaration (11.4.5 [class.ctor]), or destructor declaration (11.4.7 [class.dtor]), the program is ill-formed. ] [Example: ...
9.2.9.5 [dcl.type.elab] paragraph 2:
This is the only remaining (normative) statement that a typedef-name can't be used in an elaborated-type-specifier. The reference to template type-parameter is deleted by the resolution of issue 283.
... If the identifier resolves to a typedef-nameor a template type-parameter, the elaborated-type-specifier is ill-formed. [Note: ...
9.3 [dcl.decl] grammar rule declarator-id:
When I looked carefully into the statement of the rule prohibiting a typedef-name in a constructor declaration, it appeared to me that this grammar rule (inadvertently?) allows something that's always forbidden semantically.
declarator-id:
id-expression
::opt nested-name-specifieropttype-nameclass-name
11.3 [class.name] paragraph 5:
Unlike the prohibitions against appearing in an elaborated-type-specifier or constructor or destructor declarator, each of which was expressed more than once, the prohibition against a typedef-name appearing in a class-head was previously stated only in 9.2.4 [dcl.typedef]. It seems to me that that prohibition belongs here instead. Also, it seems to me important to clarify that a typedef-name that is a class-name is still a typedef-name. Otherwise, the various prohibitions can be argued around easily, if perversely ("But that isn't a typedef-name, it's a class-name; it says so right there in 11.3 [class.name].")
A typedef-name (9.2.4 [dcl.typedef]) that names a class type or a cv-qualified version thereof is also a class-name, but shall not be usedin an elaborated-type-specifier; see also 9.2.4 [dcl.typedef].as the identifier in a class-head.
11.4.5 [class.ctor] paragraph 3:
The new nonterminal references are needed to really nail down what we're talking about here. Otherwise, I'm just eliminating redundancy. (A typedef-name that doesn't name a class type is no more valid here than one that does.)
A typedef-name that names a class is a class-name (9.2.4 [dcl.typedef]); however, aA typedef-namethat names a classshall not be used as theidentifierclass-name in thedeclaratordeclarator-id for a constructor declaration.
11.4.7 [class.dtor] paragraph 1:
The same comments apply here as to 11.4.5 [class.ctor].
...A typedef-name that names a class is a class-name (7.1.3); however, aA typedef-namethat names a classshall not be used as theidentifierclass-name following the ~ in the declarator for a destructor declaration.
[Voted into WP at April 2003 meeting.]
A use of an injected-class-name in an elaborated-type-specifier should not name the constructor of the class, but rather the class itself, because in that context we know that we're looking for a type. See issue 147.
Proposed Resolution (revised October 2002):
This clarifies the changes made in the TC for issue 147.
In 6.5.5.2 [class.qual] paragraph 1a replace:
If the nested-name-specifier nominates a class C, and the name specified after the nested-name-specifier, when looked up in C, is the injected class name of C (Clause 11 [class]), the name is instead considered to name the constructor of class C.
with
In a lookup in which the constructor is an acceptable lookup result, if the nested-name-specifier nominates a class C and the name specified after the nested-name-specifier, when looked up in C, is the injected class name of C (Clause 11 [class]), the name is instead considered to name the constructor of class C. [Note: For example, the constructor is not an acceptable lookup result in an elaborated type specifier so the constructor would not be used in place of the injected class name.]
Note that issue 263 updates a part of the same paragraph.
Append to the example:
struct A::A a2; // object of type A
[Voted into WP at March 2004 meeting.]
Consider this code:
struct A { int i; struct i {}; }; struct B { int i; struct i {}; }; struct D : public A, public B { using A::i; void f (); }; void D::f () { struct i x; }
I can't find anything in the standard that says definitively what this means. 9.10 [namespace.udecl] says that a using-declaration shall name "a member of a base class" -- but here we have two members, the data member A::i and the class A::i.
Personally, I'd find it more attractive if this code did not work. I'd like "using A::i" to mean "lookup A::i in the usual way and bind B::i to that", which would mean that while "i = 3" would be valid in D::f, "struct i x" would not be. However, if there were no A::i data member, then "A::i" would find the struct and the code in D::f would be valid.
John Spicer: I agree with you, but unfortunately the standard committee did not.
I remembered that this was discussed by the committee and that a resolution was adopted that was different than what I hoped for, but I had a hard time finding definitive wording in the standard.
I went back though my records and found the paper that proposed a resolution and the associated committee motion that adopted the proposed resolution The paper is N0905, and "option 1" from that paper was adopted at the Stockholm meeting in July of 1996. The resolution is that "using A::i" brings in everything named i from A.
6.5.5.3 [namespace.qual] paragraph 2 was modified to implement this resolution, but interestingly that only covers the namespace case and not the class case. I think the class case was overlooked when the wording was drafted. A core issue should be opened to make sure the class case is handled properly.
Notes from April 2003 meeting:
This is related to issue 11. 9.10 [namespace.udecl] paragraph 10 has an example for namespaces.
Proposed resolution (October 2003):
Add a bullet to the end of 6.5.5.2 [class.qual] paragraph 1:
Change the beginning of 9.10 [namespace.udecl] paragraph 4 from
A using-declaration used as a member-declaration shall refer to a member of a base class of the class being defined, shall refer to a member of an anonymous union that is a member of a base class of the class being defined, or shall refer to an enumerator for an enumeration type that is a member of a base class of the class being defined.
to
In a using-declaration used as a member-declaration, the nested-name-specifier shall name a base class of the class being defined. Such a using-declaration introduces the set of declarations found by member name lookup (6.5.2 [class.member.lookup], 6.5.5.2 [class.qual]).
[Voted into WP at April 2003 meeting.]
I have some concerns with the description of name lookup for elaborated type specifiers in 6.5.6 [basic.lookup.elab]:
Paragraph 2 has some parodoxical statements concerning looking up names that are simple identifers:
If the elaborated-type-specifier refers to an enum-name and this lookup does not find a previously declared enum-name, the elaborated-type-specifier is ill-formed. If the elaborated-type-specifier refers to an [sic] class-name and this lookup does not find a previously declared class-name... the elaborated-type-specifier is a declaration that introduces the class-name as described in 6.4.2 [basic.scope.pdecl]."
It is not clear how an elaborated-type-specifier can refer to an enum-name or class-name given that the lookup does not find such a name and that class-name and enum-name are not part of the syntax of an elaborated-type-specifier.
The second sentence quoted above seems to suggest that the name found will not be used if it is not a class name. typedef-name names are ill-formed due to the sentence preceding the quote. If lookup finds, for instance, an enum-name then a new declaration will be created. This differs from C, and from the enum case, and can have surprising effects:
struct S { enum E { one = 1 }; class E* p; // declares a global class E? };
Was this really the intent? If this is the case then some more work is needed on 6.5.6 [basic.lookup.elab]. Note that the section does not make finding a type template formal ill-formed, as is done in 9.2.9.5 [dcl.type.elab]. I don't see anything that makes a type template formal name a class-name. So the example in 9.2.9.5 [dcl.type.elab] of friend class T; where T is a template type formal would no longer be ill-formed with this interpretation because it would declare a new class T.
(See also issue 254.)
Notes from the 4/02 meeting:
This will be consolidated with the changes for issue 254. See also issue 298.
Proposed resolution (October 2002):
As given in N1376=02-0034. Note that the inserts and strikeouts in that document do not display correctly in all browsers; <del> --> <strike> and <ins> --> <b>, and the similar changes for the closing delimiters, seem to do the trick.
[Voted into WP at April 2003 meeting.]
The text in 6.5.6 [basic.lookup.elab] paragraph 2 twice refers to the possibility that an elaborated-type-specifier might have the form
class-key identifier ;
However, the grammar for elaborated-type-specifier does not include a semicolon.
In both 6.5.6 [basic.lookup.elab] and 9.2.9.5 [dcl.type.elab], the text asserts that an elaborated-type-specifier that refers to a typedef-name is ill-formed. However, it is permissible for the form of elaborated-type-specifier that begins with typename to refer to a typedef-name.
This problem is the result of adding the typename form to the elaborated-type-name grammar without changing the verbiage correspondingly. It could be fixed either by updating the verbiage or by moving the typename syntax into its own production and referring to both nonterminals when needed.
(See also issue 180. If this issue is resolved in favor of a separate nonterminal in the grammar for the typename forms, the wording in that issue's resolution must be changed accordingly.)
Notes from 04/01 meeting:
The consensus was in favor of moving the typename forms out of the elaborated-type-specifier grammar.
Notes from the 4/02 meeting:
This will be consolidated with the changes for issue 245.
Proposed resolution (October 2002):
As given in N1376=02-0034.
[Moved to DR at 10/01 meeting.]
6.6 [basic.link] paragraph 4 says (among other things):A name having namespace scope has external linkage if it is the name ofThat prohibits for example:
- [...]
- a named enumeration (9.8.1 [dcl.enum]), or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes (9.2.4 [dcl.typedef])
typedef enum { e1 } *PE; void f(PE) {} // Cannot declare a function (with linkage) using a // type with no linkage.
However, the same prohibition was not made for class scope types. Indeed, 6.6 [basic.link] paragraph 5 says:
In addition, a member function, static data member, class or enumeration of class scope has external linkage if the name of the class has external linkage.
That allows for:
struct S { typedef enum { e1 } *MPE; void mf(MPE) {} };
My guess is that this is an unintentional consequence of 6.6 [basic.link] paragraph 5, but I would like confirmation on that.
Proposed resolution:
Change text in 6.6 [basic.link] paragraph 5 from:
In addition, a member function, static data member, class or enumeration of class scope has external linkage if the name of the class has external linkage.to:
In addition, a member function, a static data member, a named class or enumeration of class scope, or an unnamed class or enumeration defined in a class-scope typedef declaration such that the class or enumeration has the typedef name for linkage purposes (9.2.4 [dcl.typedef]), has external linkage if the name of the class has external linkage.
[Voted into WP at October 2004 meeting.]
According to 6.6 [basic.link] paragraph 8, "A name with no linkage ... shall not be used to declare an entity with linkage." This would appear to rule out code such as:
typedef struct { int i; } *PT; extern "C" void f(PT);[likewise]
static enum { a } e;which seems rather harmless to me.
See issue 132, which dealt with a closely related issue.
Andrei Iltchenko submitted the same issue via comp.std.c++ on 17 Dec 2001:
Paragraph 8 of Section 6.6 [basic.link] contains the following sentences: "A name with no linkage shall not be used to declare an entity with linkage. If a declaration uses a typedef name, it is the linkage of the type name to which the typedef refers that is considered."
The problem with this wording is that it doesn't cover cases where the type to which a typedef-name refers has no name. As a result it's not clear whether, for example, the following program is well-formed:
#include <vector> int main() { enum { sz = 6u }; typedef int (* aptr_type)[sz]; typedef struct data { int i, j; } * elem_type; std::vector<aptr_type> vec1; std::vector<elem_type> vec2; }
Suggested resolution:
My feeling is that the rules for whether or not a typedef-name used in a declaration shall be treated as having or not having linkage ought to be modelled after those for dependent types, which are explained in 13.8.3.2 [temp.dep.type].
Add the following text at the end of Paragraph 8 of Section 6.6 [basic.link] and replace the following example:
In case of the type referred to by a typedef declaration not having a name, the newly declared typedef-name has linkage if and only if its referred type comprises no names of no linkage excluding local names that are eligible for appearance in an integral constant-expression (7.7 [expr.const]). [Note: if the referred type contains a typedef-name that does not denote an unnamed class, the linkage of that name is established by the recursive application of this rule for the purposes of using typedef names in declarations.] [Example:void f() { struct A { int x; }; // no linkage extern A a; // ill-formed typedef A Bl extern B b; // ill-formed enum { sz = 6u }; typedef int (* C)[sz]; // C has linkage because sz can // appear in a constant expression }--end example.]
Additional issue (13 Jan 2002, from Andrei Iltchenko):
Paragraph 2 of Section 13.4.2 [temp.arg.type] is inaccurate and unnecessarily prohibits a few important cases; it says "A local type, a type with no linkage, an unnamed type or a type compounded from any of these types shall not be used as a template-argument for a template-parameter." The inaccuracy stems from the fact that it is not a type but its name that can have a linkage.
For example based on the current wording of 13.4.2 [temp.arg.type], the following example is ill-formed.
#include <vector> struct data { int i, j; }; int main() { enum { sz = 6u }; std::vector<int(*)[sz]> vec1; // The types 'int(*)[sz]' and 'data*' std::vector<data*> vec2; // have no names and are thus illegal // as template type arguments. }
Suggested resolution:
Replace the whole second paragraph of Section 13.4.2 [temp.arg.type] with the following wording:
A type whose name does not have a linkage or a type compounded from any such type shall not be used as a template-argument for a template-parameter. In case of a type T used as a template type argument not having a name, T constitutes a valid template type argument if and only if the name of an invented typedef declaration referring to T would have linkage; see 3.5. [Example:template <class T> class X { /* ... */ }; void f() { struct S { /* ... */ }; enum { sz = 6u }; X<S> x3; // error: a type name with no linkage // used as template-argument X<S*> x4; // error: pointer to a type name with // no linkage used as template-argument X<int(*)[sz]> x5; // OK: since the name of typedef int // (*pname)[sz] would have linkage }--end example] [Note: a template type argument may be an incomplete type (6.8 [basic.types]).]
Proposed resolution:
This is resolved by the changes for issue 389. The present issue was moved back to Review status in February 2004 because 389 was moved back to Review.
[Voted into WP at October 2004 meeting.]
6.6 [basic.link] paragraph 8 says (among other things):
A name with no linkage (notably, the name of a class or enumeration declared in a local scope (6.4.3 [basic.scope.block])) shall not be used to declare an entity with linkage. If a declaration uses a typedef name, it is the linkage of the type name to which the typedef refers that is considered.
I would expect this to catch situations such as the following:
// File 1: typedef struct {} *UP; void f(UP) {} // File 2: typedef struct {} *UP; // Or: typedef struct {} U, *UP; void f(UP);
The problem here is that most implementations must generate the same mangled name for "f" in two translation units. The quote from the standard above isn't quite clear, unfortunately: There is no type name to which the typedef refers.
A related situation is the following:
enum { no, yes } answer;The variable "answer" is declared as having external linkage, but it is declared with an unnamed type. Section 6.6 [basic.link] talks about the linkage of names, however, and does therefore not prohibit this. There is no implementation issue for most compilers because they do not ordinarily mangle variable names, but I believe the intent was to allow that implementation technique.
Finally, these problems are much less relevant when declaring names with internal linkage. For example, I would expect there to be few problems with:
typedef struct {} *UP; static void g(UP);
I recently tried to interpret 6.6 [basic.link] paragraph 8 with the assumption that types with no names have no linkage. Surprisingly, this resulted in many diagnostics on variable declarations (mostly like "answer" above).
I'm pretty sure the standard needs clarifying words in this matter, but which way should it go?
See also issue 319.
Notes from April 2003 meeting:
There was agreement that this check is not needed for variables and functions with extern "C" linkage, and a change there is desirable to allow use of legacy C headers. The check is also not needed for entities with internal linkage, but there was no strong sentiment for changing that case.
We also considered relaxing this requirement for extern "C++" variables but decided that we did not want to change that case.
We noted that if extern "C" functions are allowed an additional check is needed when such functions are used as arguments in calls of function templates. Deduction will put the type of the extern "C" function into the type of the template instance, i.e., there would be a need to mangle the name of an unnamed type. To plug that hole we need an additional requirement on the template created in such a case.
Proposed resolution (April 2003, revised slightly October 2003 and March 2004):
In 6.6 [basic.link] paragraph 8, change
A name with no linkage (notably, the name of a class or enumeration declared in a local scope (6.4.3 [basic.scope.block])) shall not be used to declare an entity with linkage. If a declaration uses a typedef name, it is the linkage of the type name to which the typedef refers that is considered.
to
A type is said to have linkage if and only ifA type without linkage shall not be used as the type of a variable or function with linkage, unless the variable or function has extern "C" linkage (9.12 [dcl.link]). [Note: in other words, a type without linkage contains a class or enumeration that cannot be named outside of its translation unit. An entity with external linkage declared using such a type could not correspond to any other entity in another translation unit of the program and is thus not permitted. Also note that classes with linkage may contain members whose types do not have linkage, and that typedef names are ignored in the determination of whether a type has linkage.]
- it is a class or enumeration type that is named (or has a name for linkage purposes (9.2.4 [dcl.typedef])) and the name has linkage; or
- it is a specialization of a class template (Clause 13 [temp]) [Footnote: a class template always has external linkage, and the requirements of 13.4.2 [temp.arg.type] and 13.4.3 [temp.arg.nontype] ensure that the template arguments will also have appropriate linkage]; or
- it is a fundamental type (6.8.2 [basic.fundamental]); or
- it is a compound type (6.8.4 [basic.compound]) other than a class or enumeration, compounded exclusively from types that have linkage; or
- it is a cv-qualified (6.8.5 [basic.type.qualifier]) version of a type that has linkage.
Change 13.4.2 [temp.arg.type] paragraph 2 from (note: this is the wording as updated by issue 62)
The following types shall not be used as a template-argument for a template type-parameter:
- a type whose name has no linkage
- an unnamed class or enumeration type that has no name for linkage purposes (9.2.4 [dcl.typedef])
- a cv-qualified version of one of the types in this list
- a type created by application of declarator operators to one of the types in this list
- a function type that uses one of the types in this list
to
A type without linkage (6.6 [basic.link]) shall not be used as a template-argument for a template type-parameter.
Once this issue is ready, issue 319 should be moved back to ready as well.
[Voted into WP at October 2005 meeting.]
Consider the following bit of code:
namespace N { struct S { void f(); }; } using namespace N; void S::f() { extern void g(); // ::g or N::g? }
In 6.6 [basic.link] paragraph 7 the Standard says (among other things),
When a block scope declaration of an entity with linkage is not found to refer to some other declaration, then that entity is a member of the innermost enclosing namespace.
The question then is whether N is an “enclosing namespace” for the local declaration of g()?
Proposed resolution (October 2004):
Add the following text as a new paragraph at the end of 9.9.2 [namespace.def]:
The enclosing namespaces of a declaration are those namespaces in which the declaration lexically appears, except for a redeclaration of a namespace member outside its original namespace (e.g., a definition as specified in _N4868_.9.8.2.3 [namespace.memdef]). Such a redeclaration has the same enclosing namespaces as the original declaration. [Example:namespace Q { namespace V { void f(); // enclosing namespaces are the global namespace, Q, and Q::V class C { void m(); }; } void V::f() { // enclosing namespaces are the global namespace, Q, and Q::V extern void h(); // ... so this declares Q::V::h } void V::C::m() { // enclosing namespaces are the global namespace, Q, and Q::V } }—end example]
[Voted into WP at April, 2006 meeting.]
The standard uses “most derived object” in some places (for example, Clause 3 [intro.defs] “dynamic type,” 7.6.2.9 [expr.delete]) to refer to objects of both class and non-class type. However, 6.7.2 [intro.object] only formally defines it for objects of class type.
Possible fix: Change the wording in 6.7.2 [intro.object] paragraph 4 from
an object of a most derived class type is called a most derived object
to
an object of a most derived class type, or of non-class type, is called a most derived object
Proposed resolution (October, 2005):
Add the indicated words to 6.7.2 [intro.object] paragraph 4:
If a complete object, a data member (11.4 [class.mem]), or an array element is of class type, its type is considered the most derived class, to distinguish it from the class type of any base class subobject; an object of a most derived class type, or of a non-class type, is called a most derived object.
[Voted into the WP at the September, 2008 meeting.]
The requirements on an implementation when presented with an alignment-specifier not supported by that implementation in that context are contradictory: 6.7.3 [basic.align] paragraph 9 says,
If a request for a specific extended alignment in a specific context is not supported by an implementation, the implementation may reject the request as ill-formed. The implementation may also silently ignore the requested alignment.
In contrast, 9.13.2 [dcl.align] paragraph 2, bullet 4 says simply,
- if the constant expression evaluates to an extended alignment and the implementation does not support that alignment in the context of the declaration, the program is ill-formed
with no provision to “silently ignore” the requested alignment. These two passages need to be reconciled.
If the outcome of the reconciliation is to grant implementations the license to accept and ignore extended alignment requests, the specification should be framed in terms of mechanisms that already exist in the Standard, such as undefined behavior and/or conditionally-supported constructs; “ill-formed” is a category that is defined by the Standard, not something that an implementation can decide.
Notes from the February, 2008 meeting:
The consensus was that such requests should be ill-formed and require a diagnostic. However, it was also observed that an implementation need not reject an ill-formed program; the only requirement is that it issue a diagnostic. It would thus be permissible for an implementation to “noisily ignore” (as opposed to “silently ignoring”) an unsupported alignment request.
Proposed resolution (June, 2008):
Change 6.7.3 [basic.align] paragraph 9 as follows:
If a request for a specific extended alignment in a specific context is not supported by an implementation, theimplementation may reject the request asprogram is ill-formed.The implementation may also silently ignore the requested alignment. [Note: aAdditionally, a request for runtime allocation of dynamicmemorystorage for which the requested alignment cannot be honoredmayshall be treated as an allocation failure.—end note]
[Moved to DR at 4/02 meeting.]
Jack Rouse: 6.7.4 [basic.life] paragraph 1 includes:
The lifetime of an object is a runtime property of the object. The lifetime of an object of type T begins when:Consider the code:
- storage with the proper alignment and size for type T is obtained, and
- if T is a class type with a non-trivial constructor (11.4.5 [class.ctor] ), the constructor call has completed.
struct B { B( int = 0 ); ~B(); }; struct S { B b1; }; int main() { S s = { 1 }; return 0; }In the code above, class S does have a non-trivial constructor, the default constructor generated by the compiler. According the text above, the lifetime of the auto s would never begin because a constructor for S is never called. I think the second case in the text needs to include aggregate initialization.
Mike Miller: I see a couple of ways of fixing the problem. One way would be to change "the constructor call has completed" to "the object's initialization is complete."
Another would be to add following "a class type with a non-trivial constructor" the phrase "that is not initialized with the brace notation (9.5.2 [dcl.init.aggr] )."
The first formulation treats aggregate initialization like a constructor call; even POD-type members of an aggregate could not be accessed before the aggregate initialization completed. The second is less restrictive; the POD-type members of the aggregate would be usable before the initialization, and the members with non-trivial constructors (the only way an aggregate can acquire a non-trivial constructor) would be protected by recursive application of the lifetime rule.
Proposed resolution (04/01):
In 6.7.4 [basic.life] paragraph 1, change
If T is a class type with a non-trivial constructor (11.4.5 [class.ctor]), the constructor call has completed.
to
If T is a class type with a non-trivial constructor (11.4.5 [class.ctor]), the initialization is complete. [Note: the initialization can be performed by a constructor call or, in the case of an aggregate with an implicitly-declared non-trivial default constructor, an aggregate initialization (9.5.2 [dcl.init.aggr]).]
[Voted into WP at April 2003 meeting.]
The wording in 6.7.4 [basic.life] paragraph 6 allows an lvalue designating an out-of-lifetime object to be used as the operand of a static_cast only if the conversion is ultimately to "char&" or "unsigned char&". This description excludes the possibility of using a cv-qualified version of these types for no apparent reason.
Notes on 04/01 meeting:
The wording should be changed to allow cv-qualified char types.
Proposed resolution (04/01):
In 6.7.4 [basic.life] paragraph 6 change the third bullet:
[Voted into WP at March 2004 meeting.]
6.7.4 [basic.life] paragraph 1 second bullet says:
if T is a class type with a non-trivial constructor (12.1), the constructor call has completed.
This is confusing; what was intended is probably something like
if T is a class type and the constructor invoked to create the object is non-trivial (12.1), the constructor call has completed.
Proposed Resolution (October 2003):
As given above.
[Voted into the WP at the September, 2008 meeting.]
In ISO/IEC 14882:2003, the second bullet of 6.7.4 [basic.life] paragraph 1 reads,
if T is a class type with a non-trivial constructor (11.4.5 [class.ctor]), the constructor call has completed.
Issue 119 pointed out that aggregate initialization can be used with some classes with a non-trivial implicitly-declared default constructor, and that in such cases there is no call to the object's constructor. The resolution for that issue was to change the previously-cited wording to read,
If T is a class type with a non-trivial constructor (11.4.5 [class.ctor], the initialization is complete.
Later (but before the WP was revised with the wording from the resolution of issue 119), issue 404 changed the 2003 wording to read,
If T is a class type and the constructor invoked to create the object is non-trivial (11.4.5 [class.ctor]), the constructor call has completed.
thus reversing the effect of issue 119, whose whole purpose was to cover objects with non-trivial constructors that are not invoked.
Through an editorial error, the post-Redmond draft (N1905) still contained the original 2003 wording that should have been replaced by the resolution of issue 119, in addition to the new wording from the resolution:
if T is a class type and the constructor invoked to create the object is non-trivial (11.4.5 [class.ctor]), the constructor call has completed. the initialization is complete.
Finally, during the application of the edits for delegating constructors (N1986), this editing error was “fixed” by retaining the original 2003 wording (which was needed for the application of the change specified in N1986), so that the current draft (N2009) reads,
if T is a class type and the constructor invoked to create the object is non-trivial (11.4.5 [class.ctor]), the principal constructor call 11.9.3 [class.base.init]) has completed.
Because the completion of the call to the principal constructor corresponds to the point at which the object is “fully constructed” (14.3 [except.ctor] paragraph 2), i.e., its initialization is complete, I believe that the exact wording of the issue 119 resolution would be correct and should be restored verbatim.
Proposed resolution (June, 2008):
Change 6.7.4 [basic.life] paragraph 1 as follows:
The lifetime of an object is a runtime property of the object. An object is said to have non-trivial initialization if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial default constructor. [Note: Initialization by a trivial copy constructor is non-trivial initialization. —end note] The lifetime of an object
of type Tbegins when:
storage with the proper alignment and size
for type Tis obtained, andif
T is a class type and the constructor invoked to create the object is non-trivial (11.4.5 [class.ctor]), the principal constructor call (11.9.3 [class.base.init]) has completed. [Note: the initialization can be performed by a constructor call or, in the case of an aggregate with an implicitly-declared non-trivial default constructor, an aggregate initialization 9.5.2 [dcl.init.aggr]. —end note]the object has non-trivial initialization, its initialization is complete.The lifetime of an object of type T ends when...
[Voted into WP at the October, 2006 meeting.]
According to 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 3,
Any other allocation function that fails to allocate storage shall only indicate failure by throwing an exception of class std::bad_alloc (17.6.4.1 [bad.alloc]) or a class derived from std::bad_alloc.
Shouldn't this statement have the usual requirements for an unambiguous and accessible base class?
Proposed resolution (April, 2006):
Change the last sentence of 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 3 as indicated:
Any other allocation function that fails to allocate storage shallonlyindicate failure only by throwing an exception ofclass std::bad_alloc (17.6.4.1 [bad.alloc]) or a class derived from std::bad_alloca type that would match a handler (14.4 [except.handle]) of type std::bad_alloc (17.6.4.1 [bad.alloc]).
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
[Picked up by evolution group at October 2002 meeting.]
The default global operators delete are specified to not throw, but there is no requirement that replacement global, or class-specific, operators delete must not throw. That ought to be required.
In particular:
We already require that all versions of an allocator's deallocate() must not throw, so that part is okay.
Rationale (04/00):
Note (March, 2008):
The Evolution Working Group has accepted the intent of this issue and referred it to CWG for action for C++0x (see paper J16/07-0033 = WG21 N2173).
Proposed resolution (March, 2008):
Change 6.7.6.5.3 [basic.stc.dynamic.deallocation] paragraph 3 as follows:
A deallocation function shall not terminate by throwing an exception. The value of the first argument supplied to a deallocation function...
[Voted into WP at October 2005 meeting.]
Standard is clear on behaviour of default allocation/deallocation functions. However, it is surpisingly vague on requirements to the behaviour of user-defined deallocation function and an interaction between delete-expression and deallocation function. This caused a heated argument on fido7.su.c-cpp newsgroup.
Resume:
It is not clear if user-supplied deallocation function is called from delete-expr when the operand of delete-expr is the null pointer (7.6.2.9 [expr.delete]). If it is, standard does not specify what user-supplied deallocation function shall do with the null pointer operand (17.6.3 [new.delete]). Instead, Standard uses the term "has no effect", which meaning is too vague in context given (7.6.2.9 [expr.delete]).
Description:
Consider statements
char* p= 0; //result of failed non-throwing ::new char[] ::delete[] p;Argument passed to delete-expression is valid - it is the result of a call to the non-throwing version of ::new, which has been failed. 7.6.2.9 [expr.delete] paragraph 1 explicitly prohibit us to pass 0 without having the ::new failure.
Standard does NOT specify whether user-defined deallocation function should be called in this case, or not.
Specifically, standard says in 7.6.2.9 [expr.delete] paragraph 2:
...if the value of the operand of delete is the null pointer the operation has no effect.Standard doesn't specify term "has no effect". It is not clear from this context, whether the called deallocation function is required to have no effect, or delete-expression shall not call the deallocation function.
Furthermore, in para 4 standard says on default deallocation function:
If the delete-expression calls the implementation deallocation function (6.7.6.5.3 [basic.stc.dynamic.deallocation]), if the operand of the delete expression is not the null pointer constant, ...Why it is so specific on interaction of default deallocation function and delete-expr?
If "has no effect" is a requirement to the deallocation function, then it should be stated in 6.7.6.5.3 [basic.stc.dynamic.deallocation], or in 17.6.3.2 [new.delete.single] and 17.6.3.3 [new.delete.array], and it should be stated explicitly.
Furthermore, standard does NOT specify what actions shall be performed by user-supplied deallocation function if NULL is given (17.6.3.2 [new.delete.single] paragraph 12):
Required behaviour: accept a value of ptr that is null or that was returned by an earlier call to the default operator new(std::size_t) or operator new(std::size_t, const std::nothrow_t&).
The same corresponds to ::delete[] case.
Expected solution:
Notes from October 2002 meeting:
We believe that study of 17.6.3.2 [new.delete.single] paragraphs 12 and 13, 17.6.3.3 [new.delete.array] paragraphs 11 and 12, and 6.7.6.5.3 [basic.stc.dynamic.deallocation] paragraph 3 shows that the system-provided operator delete functions must accept a null pointer and ignore it. Those sections also show that a user-written replacement for the system-provided operator delete functions must accept a null pointer. There is no requirement that such functions ignore a null pointer, which is okay -- perhaps the reason for replacing the system-provided functions is to do something special with null pointer values (e.g., log such calls and return).
We believe that the standard should not require an implementation to call a delete function with a null pointer, but it must allow that. For the system-provided delete functions or replacements thereof, the standard already makes it clear that the delete function must accept a null pointer. For class-specific delete functions, we believe the standard should require that such functions accept a null pointer, though it should not mandate what they do with null pointers.
7.6.2.9 [expr.delete] needs to be updated to say that it is unspecified whether or not the operator delete function is called with a null pointer, and 6.7.6.5.3 [basic.stc.dynamic.deallocation] needs to be updated to say that any deallocation function must accept a null pointer.
Proposed resolution (October, 2004):
Change 7.6.2.9 [expr.delete] paragraph 2 as indicated:
If the operand has a class type, the operand is converted to a pointer type by calling the above-mentioned conversion function, and the converted operand is used in place of the original operand for the remainder of this section. In either alternative,ifthe value of the operand of deleteis the null pointer the operation has no effectmay be a null pointer value. If it is not a null pointer value, inInthe first alternative (delete object), the value of the operand of delete shall be a pointer to a non-array object or a pointer to a sub-object (6.7.2 [intro.object]) representing a base class of such an object (11.7 [class.derived])...
Change 7.6.2.9 [expr.delete] paragraph 4 as follows (note that the old wording reflects the changes proposed by issue 442:
The cast-expression in a delete-expression shall be evaluated exactly once.
If the delete-expression calls the implementation deallocation function (6.7.6.5.3 [basic.stc.dynamic.deallocation]), and if the value of the operand of the delete expression is not a null pointer, the deallocation function will deallocate the storage referenced by the pointer thus rendering the pointer invalid. [Note: the value of a pointer that refers to deallocated storage is indeterminate. —end note]
Change 7.6.2.9 [expr.delete] paragraphs 6-7 as follows:
TheIf the value of the operand of the delete-expression is not a null pointer value, the delete-expression will invoke the destructor (if any) for the object or the elements of the array being deleted. In the case of an array, the elements will be destroyed in order of decreasing address (that is, in reverse order of the completion of their constructor; see 11.9.3 [class.base.init]).
TheIf the value of the operand of the delete-expression is not a null pointer value, the delete-expression will call a deallocation function (6.7.6.5.3 [basic.stc.dynamic.deallocation]). Otherwise, it is unspecified whether the deallocation function will be called. [Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception. —end note]
Change 6.7.6.5.3 [basic.stc.dynamic.deallocation] paragraph 3 as indicated:
The value of the first argument supplied toone of thea deallocation functions provided in the standard librarymay be a null pointer value; if so, and if the deallocation function is one supplied in the standard library, the callto the deallocation functionhas no effect. Otherwise, the value supplied to operator delete(void*) in the standard library shall be one of the values returned by a previous invocation of either operator new(std::size_t) or operator new(std::size_t, const std::nothrow_t&) in the standard library, and the value supplied to operator delete[](void*) in the standard library shall be one of the values returned by a previous invocation of either operator new[](std::size_t) or operator new[](std::size_t, const std::nothrow_t&) in the standard library.
[Note: this resolution also resolves issue 442.]
[Voted into WP at April, 2006 meeting.]
In 6.7.7 [class.temporary] paragraph 5, should binding a reference to the result of a "?" operation, each of whose branches is a temporary, extend both temporaries?
Here's an example:
const SFileName &C = noDir ? SFileName("abc") : SFileName("bcd");
Do the temporaries created by the SFileName conversions survive the end of the full expression?
Notes from 10/00 meeting:
Other problematic examples include cases where the temporary from one branch is a base class of the temporary from the other (i.e., where the implementation must remember which type of temporary must be destroyed), or where one branch is a temporary and the other is not. Similar questions also apply to the comma operator. The sense of the core language working group was that implementations should be required to support these kinds of code.
Notes from the March 2004 meeting:
We decided that the cleanest model is one in which any "?" operation that returns a class rvalue always copies one of its operands to a temporary and returns the temporary as the result of the operation. (Note that this may involve slicing.) An implementation would be free to optimize this using the rules in 11.4.5.3 [class.copy.ctor] paragraph 15, and in fact we would expect that in many cases compilers would do such optimizations. For example, the compiler could construct both rvalues in the above example into a single temporary, and thus avoid a copy.
See also issue 446.
Proposed resolution (October, 2004):
This issue is resolved by the resolutions of issue 446.
Note (October, 2005):
This issue was overlooked when issue 446 was moved to “ready” status and was thus inadvertently omitted from the list of issues accepted as Defect Reports at the October, 2005 meeting.
[Moved to DR at 4/01 meeting.]
Jack Rouse: 6.7.7 [class.temporary] states that temporary objects will normally be destroyed at the end of the full expression in which they are created. This can create some unique code generation requirements when initializing a class array with a default constructor that uses a default argument. Consider the code:
struct T { int i; T( int ); ~T(); }; struct S { S( int = T(0).i ); ~S(); }; S* f( int n ) { return new S[n]; }The full expression allocating the array in f(int) includes the default constructor for S. Therefore according to 6.9.1 [intro.execution] paragraph 14, it includes the default argument expression for S(int). So evaluation of the full expression should include evaluating the default argument "n" times and creating "n" temporaries of type T. But the destruction of the temporaries must be delayed until the end of the full expression so this requires allocating space at runtime for "n" distinct temporaries. It is unclear how these temporaries are supposed to be allocated and deallocated. They cannot readily be autos because a variable allocation is required.
I believe that many existing implementations will destroy the temporaries needed by the default constructor after each array element is initialized. But I can't find anything in the standard that allows the temporaries to be destroyed early in this case.
I think the standard should allow the early destruction of temporaries used in the default initialization of class array elements. I believe early destruction is the status quo, and I don't think the users of existing C++ compilers have been adversely impacted by it.
Proposed resolution (04/01):
The proposed resolution is contained in the proposal for issue 201.
[Voted into the WP at the April, 2007 meeting as part of paper J16/07-0099 = WG21 N2239.]
6.7.7 [class.temporary] paragraph 3 simply states the requirement that temporaries created during the evaluation of an expression
are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created.There is nothing said about the relative order in which these temporaries are destroyed.
Paragraph 5, dealing with temporaries bound to references, says
the temporaries created during the evaluation of the expression initializing the reference, except the temporary to which the reference is bound, are destroyed at the end of the full-expression in which they are created and in the reverse order of the completion of their construction.Is this difference intentional? May temporaries in expressions other than those initializing references be deleted in non-LIFO order?
Notes from 04/00 meeting:
Steve Adamczyk expressed concern about constraining implementations that are capable of fine-grained parallelism -- they may be unable to determine the order of construction without adding undesirable overhead.
Proposed resolution (April, 2007):
As specified in paper J16/07-0099 = WG21 N2239.
[Moved to DR at 4/01 meeting.]
According to 6.7.7 [class.temporary] paragraph 4, an expression appearing as the initializer in an object definition constitutes a context "in which temporaries are destroyed at a different point than the end of the full-expression." It goes on to say that the temporary containing the value of the expression persists until after the initialization is complete (see also issue 117). This seems to presume that the end of the full-expression is a point earlier than the completion of the initialization.
However, according to 6.9.1 [intro.execution] paragraphs 12-13, the full-expression in such cases is, in fact, the entire initialization. If this is the case, the behavior described for temporaries in an initializer expression is simply the normal behavior of temporaries in any expression, and treating it as an exception to the general rule is both incorrect and confusing.
Proposed resolution (04/01):
[Note: this proposal also addresses issue 124.]
Add to the end of 6.9.1 [intro.execution] paragraph 12:
If the initializer for an object or sub-object is a full-expression, the initialization of the object or sub-object (e.g., by calling a constructor or copying an expression value) is considered to be part of the full-expression.
Replace 6.7.7 [class.temporary] paragraph 4 with:
There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array. If the constructor has one or more default arguments, any temporaries created in the default argument expressions are destroyed immediately after return from the constructor.
[Voted into WP at April 2005 meeting.]
Section 6.7.7 [class.temporary] paragraph 2, abridged:
X f(X); void g() { X a; a = f(a); }a=f(a) requires a temporary for either the argument a or the result of f(a) to avoid undesired aliasing of a.
The note seems to imply that an implementation is allowed to omit copying "a" to f's formal argument, or to omit using a temporary for the return value of f. I don't find that license in normative text.
Function f returns an X by value, and in the expression the value is assigned (not copy-constructed) to "a". I don't see how that temporary can be omitted. (See also 11.4.5.3 [class.copy.ctor] p 15)
Since "a" is an lvalue and not a temporary, I don't see how copying "a" to f's formal parameter can be avoided.
Am I missing something, or is 6.7.7 [class.temporary] p 2 misleading?
Proposed resolution (October, 2004):
In 6.7.7 [class.temporary] paragraph 2, change the last sentence as indicated:
On the other hand, the expression a=f(a) requires a temporary foreither the argument a or the result of f(a) to avoid undesired aliasing of athe result of f(a), which is then assigned to a.
[Voted into WP at March 2004 meeting.]
class C { public: C(); ~C(); int& get() { return p; } // reference return private: int p; }; int main () { if ( C().get() ) // OK? }
Section 6.7.7 [class.temporary] paragraph 3 says a temp is destroyed as the last step in evaluating the full expression. But the expression C().get() has a reference type. Does 6.7.7 [class.temporary] paragraph 3 require that the dereference to get a boolean result occur before the destructor runs, making the code valid? Or does the code have undefined behavior?
Bill Gibbons: It has undefined behavior, though clearly this wasn't intended. The lvalue-to-rvalue conversion that occurs in the "if" statement is not currently part of the full-expression.
From section 6.7.7 [class.temporary] paragraph 3:
Temporary objects are destroyed as the last step in evaluating the full-expression (6.9.1 [intro.execution]) that (lexically) contains the point where they were created.
From section 6.9.1 [intro.execution] paragraph 12:
A full-expression is an expression that is not a subexpression of another expression. If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition.
The note in section 6.9.1 [intro.execution] paragraph 12 goes on to explain that this covers expressions used as initializers, but it does not discuss lvalues within temporaries.
It is a small point but it is probably worth correcting 6.9.1 [intro.execution] paragraph 12. Instead of the "implicit call of a function" wording, it might be better to just say that a full-expression includes any implicit use of the expression value in the enclosing language construct, and include a note giving implicit calls and lvalue-to-rvalue conversions as examples.
Offhand the places where this matters include: initialization (including member initializers), selection statements, iteration statements, return, throw
Proposed resolution (April 2003):
Change 6.9.1 [intro.execution] paragraph 12-13 to read:
A full-expression is an expression that is not a subexpression of another expression. If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition. Conversions applied to the result of an expression in order to satisfy the requirements of the language construct in which the expression appears are also considered to be part of the full-expression.
[Note: certain contexts in C++ cause the evaluation of a full-expression that results from a syntactic construct other than expression (7.6.20 [expr.comma]). For example, in 9.5 [dcl.init] one syntax for initializer is[Example:
( expression-list )but the resulting construct is a function call upon a constructor function with expression-list as an argument list; such a function call is a full-expression. For example, in 9.5 [dcl.init], another syntax for initializer isbut again the resulting construct might be a function call upon a constructor function with one assignment-expression as an argument; again, the function call is a full-expression. ]
= initializer-clausestruct S { S(int i): I(i) { } int& v() { return I; } private: int I; }; S s1(1); // full-expression is call of S::S(int) S s2 = 2; // full-expression is call of S::S(int) void f() { if (S(3).v()) // full-expression includes lvalue-to-rvalue and // int to bool conversions, performed before // temporary is deleted at end of full-expression { } }—end example]
[Voted into WP at April 2005 meeting.]
There seems to be a typo in 6.7.7 [class.temporary]/5, which says "The temporary to which the reference is bound or the temporary that is the complete object TO a subobject OF which the TEMPORARY is bound persists for the lifetime of the reference except as specified below."
I think this should be "The temporary to which the reference is bound or the temporary that is the complete object OF a subobject TO which the REFERENCE is bound persists for the lifetime of the reference except as specified below."
I used upper-case letters for the parts I think need to be changed.
Proposed resolution (October, 2004):
Change 6.7.7 [class.temporary] paragraph 5 as indicated:
The temporary to which the reference is bound or the temporary that is the complete objecttoof a subobjectofto which thetemporaryreference is bound persists for the lifetime of the reference except as specified below.
[Voted into WP at April, 2006 meeting.]
Section 6.7.7 [class.temporary] paragraph 5 ends with this "rule":
For the temporary to be destroyed after obj2 is destroyed, when obj2 has static storage, I would say that the reference to the temporary should also have static storage, but that is IMHO not clear from the paragraph.
Example:
void f () { const T1& ref = T1(); static T2 obj2; ... }
Here the temporary would be destoyed before obj2, contrary to the rule above.
Steve Adamczyk: I agree there's a minor issue here. I think the clause quoted above meant for obj1 and obj2 to have the same storage duration. Replacing "obj2 is an object with static or automatic storage duration" by "obj2 is an object with the same storage duration as obj1" would, I believe, fix the problem.
Notes from October 2004 meeting:
We agreed with Steve Adamczyk's suggestion.
Proposed resolution (October, 2005):
Change 6.7.7 [class.temporary] paragraph 5 as follows:
... In addition, the destruction of temporaries bound to references shall take into account the ordering of destruction of objects with static or automatic storage duration (6.7.6.2 [basic.stc.static], 6.7.6.4 [basic.stc.auto]); that is, if obj1 is an objectwith static or automatic storage durationcreated before the temporary is created with the same storage duration as the temporary, the temporary shall be destroyed before obj1 is destroyed; if obj2 is an objectwith static or automatic storage durationcreated after the temporary is created with the same storage duration as the temporary, the temporary shall be destroyed after obj2 is destroyed...
[Voted into the WP at the June, 2008 meeting.]
The original proposed wording for 6.8 [basic.types] paragraph 11 required a constexpr constructor for a literal class only “if the class has at least one user-declared constructor.” This wording was dropped during the review by CWG out of a desire to ensure that literal types not have any uninitialized members. Thus, a class like
struct pixel { int x, y; };
is not a literal type. However, if an object of that type is aggregate-initialized or value-initialized, there can be no uninitialized members; the missing wording should be restored in order to permit use of expressions like pixel().x as constant expressions.
Proposed resolution (February, 2008):
Change 6.8 [basic.types] paragraph 10 as follows:
A type is a literal type if it is:
- a scalar type; or
- a class type (Clause 11 [class]) with
- a trivial copy constructor,
- a trivial destructor,
- a trivial default constructor or at least one constexpr constructor other than the copy constructor,
- no virtual base classes, and
- all non-static data members and base classes of literal types; or
- an array of literal type.
[Voted into the WP at the September, 2008 meeting.]
In 6.9.1 [intro.execution] paragraph 16, the following expression is still listed as an example of undefined behavior:
i = ++i + 1;
However, it appears that the new sequencing rules make this expression well-defined:
The assignment side-effect is required to be sequenced after the value computations of both its LHS and RHS (7.6.19 [expr.assign] paragraph 1).
The LHS (i) is an lvalue, so its value computation involves computing the address of i.
In order to value-compute the RHS (++i + 1), it is necessary to first value-compute the lvalue expression ++i and then do an lvalue-to-rvalue conversion on the result. This guarantees that the incrementation side-effect is sequenced before the computation of the addition operation, which in turn is sequenced before the assignment side effect. In other words, it yields a well-defined order and final value for this expression.
It should be noted that a similar expression
i = i++ + 1;
is still not well-defined, since the incrementation side-effect remains unsequenced with respect to the assignment side-effect.
It's unclear whether making the expression in the example well-defined was intentional or just a coincidental byproduct of the new sequencing rules. In either case either the example should be fixed, or the rules should be changed.
Clark Nelson: In my opinion, the poster's argument is perfectly correct. The rules adopted reflect the CWG's desired outcome for issue 222. At the Portland meeting, I presented (and still sympathize with) Tom Plum's case that these rules go a little too far in nailing down required behavior; this is a consequence of that.
One way or another, a change needs to be made, and I think we should seriously consider weakening the resolution of issue 222 to keep this example as having undefined behavior. This could be done fairly simply by having the sequencing requirements for an assignment expression depend on whether it appears in an lvalue context.
James Widman: How's this for a possible re-wording?
In all cases, the side effect of the assignment expression is sequenced after the value computations of the right and left operands. Furthermore, if the assignment expression appears in a context where an lvalue is required, the side effect of the assignment expression is sequenced before its value computation.
Notes from the February, 2008 meeting:
There was no real support in the CWG for weakening the resolution of issue 222 and returning the example to having undefined behavior. No one knew of an implementation that doesn't already do the (newly) right thing for such an example, so there was little motivation to go out of our way to increase the domain of undefined behavior. So the proposed resolution is to change the example to one that definitely does have undependable behavior in existing practice, and undefined behavior under the new rules.
Also, the new formulation of the sequencing rules approved in Oxford contained the wording that by and large resolved issue 222, so with the resolution of this issue, we can also close issue 222.
Proposed resolution (March, 2008):
Change the example in 6.9.1 [intro.execution] paragraph 16 as follows:
i = v[i++]; // the behavior is undefined i = 7, i++, i++; // i becomes 9 i =++ii++ + 1; // the behavior is undefined i = i + 1; // the value of i is incremented
This resolution also resolves issue 222.
[Voted into the WP at the September, 2008 meeting.]
Is the behavior undefined in the following example?
void f() { int n = 0; n = --n; }
6.9.1 [intro.execution] paragraph 16 says,
If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.
It's not clear to me whether the two side-effects in n=--n are “different.” As far as I can tell, it seems that both side-effects involve the assignment of -1 to n, which in a sense makes them non-“different.” But I don't know if that's the intent. Would it be better to say “another” instead of “a different?”
On a related note, can we include this example to illustrate?
void f( int, int ); void g( int a ) { f( a = -1, a = -1 ); } // Undefined?
Proposed resolution (March, 2008):
Change 6.9.1 [intro.execution] paragraph 16 as follows:
...If a side effect on a scalar object is unsequenced relative to either
a differentanother side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. [Example:void f(int, int); void g(int i, int* v) { i = v[i++]; // the behavior is undefined i = 7, i++, i++; // i becomes 9 i = ++i + 1; // the behavior is undefined i = i + 1; // the value of i is incremented f(i = -1, i = -1); // the behavior is undefined }—end example] When calling...
[Moved to DR at 4/02 meeting.]
The Standard does not appear to address how the rules for order of initialization apply to static data members of class templates.
Suggested resolution: Add the following verbiage to either 6.9.3.2 [basic.start.static] or 11.4.9.3 [class.static.data]:
Initialization of static data members of class templates shall be performed during the initialization of static data members for the first translation unit to have static initialization performed for which the template member has been instantiated. This requirement shall apply to both the static and dynamic phases of initialization.
Notes from 04/01 meeting:
Enforcing an order of initialization on static data members of class templates will result in substantial overhead on access to such variables. The problem is that the initialization be required as the result of instantiation in a function used in the initialization of a variable in another translation unit. In current systems, the order of initialization of static data data members of class templates is not predictable. The proposed resolution is to state that the order of initialization is undefined.
Proposed resolution (04/01, updated slightly 10/01):
Replace the following sentence in 6.9.3.2 [basic.start.static] paragraph 1:
Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.
with
Dynamic initialization of an object is either ordered or unordered. Explicit specializations and definitions of class template static data members have ordered initialization. Other class template static data member instances have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.
Note that this wording is further updated by issue 362.
Note (07/01):
Brian McNamara argues against the proposed resolution. The following excerpt captures the central point of a long message on comp.std.c++:
I have a class for representing linked lists which looks something liketemplate <class T> class List { ... static List<T>* sentinel; ... }; template <class T> List<T>* List<T>::sentinel( new List<T> ); // static member definitionThe sentinel list node is used to represent "nil" (the null pointer cannot be used with my implementation, for reasons which are immaterial to this discussion). All of the List's non-static member functions and constructors depend upon the value of the sentinel. Under the proposed resolution for issue #270, Lists cannot be safely instantiated before main() begins, as the sentinel's initialization is "unordered".
(Some readers may propose that I should use the "singleton pattern" in the List class. This is undesirable, for reasons I shall describe at the end of this post at the location marked "[*]". For the moment, indulge me by assuming that "singleton" is not an adequate solution.)
Though this is a particular example from my own experience, I believe it is representative of a general class of examples. It is common to use static data members of a class to represent the "distinguished values" which are important to instances of that class. It is imperative that these values be initialized before any instances of the class are created, as the instances depend on the values.
In a comp.std.c++ posting on 28 Jul 2001, Brian McNamara proposes the following alternative resolution:
Replace the following sentence in 6.9.3.2 [basic.start.static] paragraph 1:
Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.with
Objects with static storage duration defined in namespace scope shall be initialized in the order described below.and then after paragraph 1, add this text:
Dynamic initialization is either ordered or quasi-ordered. Explicit specializations of class template static data members have ordered initialization. Other class template static data member instances have quasi-ordered initialization. All other objects defined in namespace scope have ordered initialization. The order of initialization is specified as follows:along with a non-normative note along the lines of
- Objects that are defined within a single translation unit and that have ordered initialization shall be initialized in the order of their definitions in the translation unit.
- Objects that are defined only within a single translation unit and that have quasi-ordered initialization shall also be initialized in the order of their definitions in the translation unit -- that is, as though these objects had ordered initialization.
- Objects that are defined within multiple translation units (which, therefore, must have quasi-ordered initialization) shall be initialized as follows: in exactly one translation unit (which one is unspecified), the object shall be treated as though it has ordered initialization; in the other translation units which define the object, the object will be initialized before all other objects that have ordered initialization in those translation units.
- For any two objects, "X" and "Y", with static storage duration and defined in namespace scope, if the previous bullets do not imply a relationship for the initialization ordering between "X" and "Y", then the relative initialization order of these objects is unspecified.
[ Note: The intention is that translation units can each be compiled separately with no knowledge of what objects may be re-defined in other translation units. Each translation unit can contain a method which initializes all objects (both quasi-ordered and ordered) as though they were ordered. When these translation units are linked together to create an executable program, all of these objects can be initialized by simply calling the initialization methods (one from each translation unit) in any order. Quasi-ordered objects require some kind of guard to ensure that they are not initialized more than once (the first attempt to initialize such an object should succeed; any subsequent attempts should simply be ignored). ]
Erwin Unruh replies: There is a point which is not mentioned with this posting. It is the cost for implementing the scheme. It requires that each static template variable is instantiated in ALL translation units where it is used. There has to be a flag for each of these variables and this flag has to be checked in each TU where the instantiation took place.
I would reject this idea and stand with the proposed resolution of issue 270.
There just is no portable way to ensure the "right" ordering of construction.
Notes from 10/01 meeting:
The Core Working Group reaffirmed its previous decision.
[Voted into WP at April 2005 meeting.]
I have a couple of questions about 6.9.3.2 [basic.start.static], "Initialization of non-local objects." I believe I recall some discussion of related topics, but I can't find anything relevant in the issues list.
The first question arose when I discovered that different implementations treat reference initialization differently. Consider, for example, the following (namespace-scope) code:
int i; int& ir = i; int* ip = &i;Both initializers, "i" and "&i", are constant expressions, per 7.7 [expr.const] paragraph 4-5 (a reference constant expression and an address constant expression, respectively). Thus, both initializations are categorized as static initialization, according to 6.9.3.2 [basic.start.static] paragraph 1:
Zero-initialization and initialization with a constant expression are collectively called static initialization; all other initialization is dynamic initialization.
However, that does not mean that both ir and ip must be initialized at the same time:
Objects of POD types (3.9) with static storage duration initialized with constant expressions (5.19) shall be initialized before any dynamic initialization takes place.
Because "int&" is not a POD type, there is no requirement that it be initialized before dynamic initialization is performed, and implementations differ in this regard. Using a function called during dynamic initialization to print the values of "ip" and "&ir", I found that g++, Sun, HP, and Intel compilers initialize ir before dynamic initialization and the Microsoft compiler does not. All initialize ip before dynamic initialization. I believe this is conforming (albeit inconvenient :-) behavior.
So, my first question is whether it is intentional that a reference of static duration, initialized with a reference constant expression, need not be initialized before dynamic initialization takes place, and if so, why?
The second question is somewhat broader. As 6.9.3.2 [basic.start.static] is currently worded, it appears that there are no requirements on when ir is initialized. In fact, there is a whole category of objects -- non-POD objects initialized with a constant expression -- for which no ordering is specified. Because they are categorized as part of "static initialization," they are not subject to the requirement that they "shall be initialized in the order in which their definition appears in the translation unit." Because they are not POD types, they are not required to be initialized before dynamic initialization occurs. Am I reading this right?
My preference would be to change 6.9.3.2 [basic.start.static] paragraph 1 so that 1) references are treated like POD objects with respect to initialization, and 2) "static initialization" applies only to POD objects and references. Here's some sample wording to illustrate:
Suggested resolution:
Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. Initializing a reference, or an object of POD type, of static storage duration with a constant expression (5.19) is called constant initialization. Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place. [Remainder unchanged.]
Proposed Resolution:
Change 6.9.3.2 [basic.start.static] paragraph 1 as follows:
Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. Initializing a reference, or an object of POD type, of static storage duration with a constant expression (5.19) is called constant initialization. Together, zero-initialization and constant initialization areZero-initialization and initialization with a constant expression are collectivelycalled static initialization; all other initialization is dynamic initialization. Static initialization shall be performedObjects of POD types (3.9) with static storage duration initialized with constant expressions (5.19) shall be initializedbefore any dynamic initialization takes place.
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
Given this literal type,
struct X { constexpr X() { } };
and this definition,
static X x;
the current specification does not require that x be statically initialized because it is not “initialized with a constant expression” (6.9.3.1 [basic.start.main] paragraph 1) .
Lawrence Crowl:
This guarantee is essential for atomics.
Jens Maurer:
Suggestion:
A reference with static storage duration or an object of literal type with static storage duration can be initialized with a constant expression (7.7 [expr.const]) or with a constexpr constructor; this is called constant initialization.
(Not spelling out “default constructor” makes it easier to handle multiple-parameter constexpr constructors, where there isn't “a” constant expression but several.)
Peter Dimov:
In addition, there is a need to enforce static initialization for non-literal types: std::shared_ptr, std::once_flag, and std::atomic_* all have nontrivial copy constructors, making them non-literal types. However, we need a way to ensure that a constexpr constructor called with constant expressions will guarantee static initialization, regardless of the nontriviality of the copy constructor.
Proposed resolution (April, 2008):
Change 6.9.3.2 [basic.start.static] paragraph 1 as follows:
...A reference with static storage duration and an object of trivial or literal type with static storage duration can be initialized with a constant expression (7.7 [expr.const]); thisIf a reference with static storage duration is initialized with a constant expression (7.7 [expr.const]) or if the initialization of an object with static storage duration satisfies the requirements for the object being declared with constexpr (9.2.6 [dcl.constexpr]), that initialization is called constant initialization...
Change 8.9 [stmt.dcl] paragraph 4 as follows:
...A local object of trivial or literal type (6.8 [basic.types]) with static storage duration initialized with constant-expressions is initializedConstant initialization (6.9.3.2 [basic.start.static]) of a local entity with static storage duration is performed before its block is first entered...
Change 9.2.6 [dcl.constexpr] paragraph 7 as follows:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall be initialized, and every expression that appears in its initializer (9.5 [dcl.init]) shall be a constant expression. Every implicit conversion used in converting the initializer expressions and every constructor call used for the initialization shall be one of those allowed in a constant expression (7.7 [expr.const])...
Replace 9.5.2 [dcl.init.aggr] paragraph 14 as follows:
When an aggregate with static storage duration is initialized with a brace-enclosed initializer-list, if all the member initializer expressions are constant expressions, and the aggregate is a trivial type, the initialization shall be done during the static phase of initialization (6.9.3.2 [basic.start.static]); otherwise, it is unspecified whether the initialization of members with constant expressions takes place during the static phase or during the dynamic phase of initialization.[Note: The order of initialization for aggregates with static storage duration is specified in 6.9.3.2 [basic.start.static] and 8.9 [stmt.dcl]. —end note]
(Note: the change to 6.9.3.2 [basic.start.static] paragraph 1 needs to be reconciled with the conflicting change in issue 684.)
[Voted into the WP at the June, 2008 meeting.]
The C++ standard has inherited the definition of the 'exit' function more or less unchanged from ISO C.
However, when the 'exit' function is called, objects of static extent which have been initialised, will be destructed if their types posses a destructor.
In addition, the C++ standard has inherited the definition of the 'signal' function and its handlers from ISO C, also pretty much unchanged.
The C standard says that the only standard library functions that may be called while a signal handler is executing, are the functions 'abort', 'signal' and 'exit'.
This introduces a bit of a nasty turn, as it is not at all unusual for the destruction of static objects to have fairly complex destruction semantics, often associated with resource release. These quite commonly involve apparently simple actions such as calling 'fclose' for a FILE handle.
Having observed some very strange behaviour in a program recently which in handling a SIGTERM signal, called the 'exit' function as indicated by the C standard.
But unknown to the programmer, a library static object performed some complicated resource deallocation activities, and the program crashed.
The C++ standard says nothing about the interaction between signals, exit and static objects. My observations, was that in effect, because the destructor called a standard library function other than 'abort', 'exit' or 'signal', while transitively in the execution context of the signal handler, it was in fact non-compliant, and the behaviour was undefined anyway.
This is I believe a plausible judgement, but given the prevalence of this common programming technique, it seems to me that we need to say something a lot more positive about this interaction.
Curiously enough, the C standard fails to say anything about the analogous interaction with functions registered with 'atexit' ;-)
Proposed Resolution (10/98):
The current Committee Draft of the next version of the ISO C standard specifies that the only standard library function that may be called while a signal handler is executing is 'abort'. This would solve the above problem.
[This issue should remain open until it has been decided that the next version of the C++ standard will use the next version of the C standard as the basis for the behavior of 'signal'.]
Notes (November, 2006):
C89 is slightly contradictory here: It allows any signal handler to terminate by calling abort, exit, longjmp, but (for asynchronous signals, i.e. not those produced by abort or raise) then makes calling any library function other than signal with the current signal undefined behavior (C89 7.7.1.1). For synchronous signals, C99 forbids calls to raise, but imposes no other restrictions. For asynchronous signals, C99 allows only calls to abort, _Exit, and signal with the current signal (C99 7.14.1.1). The current C++ WP refers to “plain old functions” and “conforming C programs” (17.14 [support.runtime] paragraph 6).
Proposed Resolution (November, 2006):
Change the footnote in 17.14 [support.runtime] paragraph 6 as follows:
In particular, a signal handler using exception handling is very likely to have problems. Also, invoking std::exit may cause destruction of objects, including those of the standard library implementation, which, in general, yields undefined behavior in a signal handler (see 6.9.1 [intro.execution]).
[Voted into the WP at the September, 2008 meeting.]
I believe that the committee has neglected to take into account one of the differences between C and C++ when defining sequence points. As an example, consider
(a += b) += c;
where a, b, and c all have type int. I believe that this expression has undefined behavior, even though it is well-formed. It is not well-formed in C, because += returns an rvalue there. The reason for the undefined behavior is that it modifies the value of `a' twice between sequence points.
Expressions such as this one are sometimes genuinely useful. Of course, we could write this particular example as
a += b; a += c;
but what about
void scale(double* p, int n, double x, double y) { for (int i = 0; i < n; ++i) { (p[i] *= x) += y; } }
All of the potential rewrites involve multiply-evaluating p[i] or unobvious circumlocations like creating references to the array element.
One way to deal with this issue would be to include built-in operators in the rule that puts a sequence point between evaluating a function's arguments and evaluating the function itself. However, that might be overkill: I see no reason to require that in
x[i++] = y;
the contents of `i' must be incremented before the assignment.
A less stringent alternative might be to say that when a built-in operator yields an lvalue, the implementation shall not subsequently change the value of that object as a consequence of that operator.
I find it hard to imagine an implementation that does not do this already. Am I wrong? Is there any implementation out there that does not `do the right thing' already for (a += b) += c?
7.6.19 [expr.assign] paragraph 1 says,
The result of the assignment operation is the value stored in the left operand after the assignment has taken place; the result is an lvalue.
What is the normative effect of the words "after the assignment has taken place"? I think that phrase ought to mean that in addition to whatever constraints the rules about sequence points might impose on the implementation, assignment operators on built-in types have the additional constraint that they must store the left-hand side's new value before returning a reference to that object as their result.
One could argue that as the C++ standard currently stands, the effect of x = y = 0; is undefined. The reason is that it both fetches and stores the value of y, and does not fetch the value of y in order to compute its new value.
I'm suggesting that the phrase "after the assignment has taken place" should be read as constraining the implementation to set y to 0 before yielding the value of y as the result of the subexpression y = 0.
Francis Glassborow:
My understanding is that for a single variable:
It is the 3) that is often ignored because in practice the compiler hardly ever codes for the read because it already has that value but in complicated evaluations with a shortage of registers, that is not always the case. Without getting too close to the hardware, I think we both know that a read too close to a write can be problematical on some hardware.
So, in x = y = 0;, the implementation must NOT fetch a value from y, instead it has to "know" what that value will be (easy because it has just computed that in order to know what it must, at some time, store in y). From this I deduce that computing the lvalue (to know where to store) and the rvalue to know what is stored are two entirely independent actions that can occur in any order commensurate with the overall requirements that both operands for an operator be evaluated before the operator is.
Erwin Unruh:
C distinguishes between the resulting value of an assignment and putting the value in store. So in C a compiler might implement the statement x=y=0; either as x=0;y=0; or as y=0;x=0; In C the statement (x += 5) += 7; is not allowed because the first += yields an rvalue which is not allowed as left operand to +=. So in C an assignment is not a sequence of write/read because the result is not really "read".
In C++ we decided to make the result of assignment an lvalue. In this case we do not have the option to specify the "value" of the result. That is just the variable itself (or its address in a different view). So in C++, strictly speaking, the statement x=y=0; must be implemented as y=0;x=y; which makes a big difference if y is declared volatile.
Furthermore, I think undefined behaviour should not be the result of a single mentioning of a variable within an expression. So the statement (x +=5) += 7; should NOT have undefined behaviour.
In my view the semantics could be:
Jerry Schwarz:
My recollection is different from Erwin's. I am confident that the intention when we decided to make assignments lvalues was not to change the semantics of evaluation of assignments. The semantics was supposed to remain the same as C's.
Ervin seems to assume that because assignments are lvalues, an assignment's value must be determined by a read of the location. But that was definitely not our intention. As he notes this has a significant impact on the semantics of assignment to a volatile variable. If Erwin's interpretation were correct we would have no way to write a volatile variable without also reading it.
Lawrence Crowl:
For x=y=0, lvalue semantics implies an lvalue to rvalue conversion on the result of y=0, which in turn implies a read. If y is volatile, lvalue semantics implies both a read and a write on y.
The standard apparently doesn't state whether there is a value dependence of the lvalue result on the completion of the assignment. Such a statement in the standard would solve the non-volatile C compatibility issue, and would be consistent with a user-implemented operator=.
Another possible approach is to state that primitive assignment operators have two results, an lvalue and a corresponding "after-store" rvalue. The rvalue result would be used when an rvalue is required, while the lvalue result would be used when an lvalue is required. However, this semantics is unsupportable for user-defined assignment operators, or at least inconsistent with all implementations that I know of. I would not enjoy trying to write such two-faced semantics.
Erwin Unruh:
The intent was for assignments to behave the same as in C. Unfortunately the change of the result to lvalue did not keep that. An "lvalue of type int" has no "int" value! So there is a difference between intent and the standard's wording.
So we have one of several choices:
I think the last one has the least impact on existing programs, but it is an ugly solution.
Andrew Koenig:
Whatever we may have intended, I do not think that there is any clean way of making
volatile int v; int i; i = v = 42;have the same semantics in C++ as it does in C. Like it or not, the subexpression v = 42 has the type ``reference to volatile int,'' so if this statement has any meaning at all, the meaning must be to store 42 in v and then fetch the value of v to assign it to i.
Indeed, if v is volatile, I cannot imagine a conscientious programmer writing a statement such as this one. Instead, I would expect to see
v = 42; i = v;if the intent is to store 42 in v and then fetch the (possibly changed) value of v, or
v = 42; i = 42;if the intent is to store 42 in both v and i.
What I do want is to ensure that expressions such as ``i = v = 42'' have well-defined semantics, as well as expressions such as (i = v) = 42 or, more realistically, (i += v) += 42 .
I wonder if the following resolution is sufficient:
Append to 7.6.19 [expr.assign] paragraph 1:
There is a sequence point between assigning the new value to the left operand and yielding the result of the assignment expression.
I believe that this proposal achieves my desired effect of not constraining when j is incremented in x[j++] = y, because I don't think there is a constraint on the relative order of incrementing j and executing the assignment. However, I do think it allows expressions such as (i += v) += 42, although with different semantics from C if v is volatile.
Notes on 10/01 meeting:
There was agreement that adding a sequence point is probably the right solution.
Notes from the 4/02 meeting:
The working group reaffirmed the sequence-point solution, but we will look for any counter-examples where efficiency would be harmed.
For drafting, we note that ++x is defined in 7.6.2.3 [expr.pre.incr] as equivalent to x+=1 and is therefore affected by this change. x++ is not affected. Also, we should update any list of all sequence points.
Notes from October 2004 meeting:
Discussion centered around whether a sequence point “between assigning the new value to the left operand and yielding the result of the expression” would require completion of all side effects of the operand expressions before the value of the assignment expression was used in another expression. The consensus opinion was that it would, that this is the definition of a sequence point. Jason Merrill pointed out that adding a sequence point after the assignment is essentially the same as rewriting
b += a
as
b += a, b
Clark Nelson expressed a desire for something like a “weak” sequence point that would force the assignment to occur but that would leave the side effects of the operands unconstrained. In support of this position, he cited the following expression:
j = (i = j++)
With the proposed addition of a full sequence point after the assignment to i, the net effect is no change to j. However, both g++ and MSVC++ behave differently: if the previous value of j is 5, the value of the expression is 5 but j gets the value 6.
Clark Nelson will investigate alternative approaches and report back to the working group.
Proposed resolution (March, 2008):
This issue is resolved by the adoption of the sequencing rules and the resolution of issue 637.
[Voted into WP at March 2004 meeting.]
I have found what looks like a bug in Clause 7 [expr], paragraph 4:
Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined. Example:i = v[i++]; // the behavior is unspecified i = 7, i++, i++; // i becomes 9 i = ++i + 1; // the behavior is unspecified i = i + 1; // the value of i is incremented--end example]
So which is it, unspecified or undefined?
Notes from October 2002 meeting:
We should find out what C99 says and do the same thing.
Proposed resolution (April 2003):
Change the example in Clause 7 [expr], paragraph 4 from
[Example:i = v[i++]; // the behavior is unspecified i = 7, i++, i++; // i becomes 9 i = ++i + 1; // the behavior is unspecified i = i + 1; // the value of i is incremented--- end example]
to (changing "unspecified" to "undefined" twice)
[Example:i = v[i++]; // the behavior is undefined i = 7, i++, i++; // i becomes 9 i = ++i + 1; // the behavior is undefined i = i + 1; // the value of i is incremented--- end example]
[Voted into WP at October 2005 meeting.]
Clause 7 [expr] par. 5 of the standard says:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expression is a constant expression (5.19), in which case the program is ill-formed.
Well, we do know that except in some contexts (e.g. controlling expression of a #if, array bounds), a compiler is not required to evaluate constant-expressions in compile time, right?
Now, let us consider, the following simple snippet:
if (a && 1/0) ...with a, to fix our attention, being *not* a constant expression. The quote above seems to say that since 1/0 is a constant (sub-)expression, the program is ill-formed. So, is it the intent that such ill-formedness is diagnosable at run-time? Or is it the intent that the above gives undefined behavior (if 1/0 is evaluated) and is not ill-formed?
I think the intent is actually the latter, so I propose the following rewording of the quoted section:
If an expression is evaluated but its result is not mathematically defined or not in the range of representable values for its type the behavior is undefined, unless such an expression is a constant expression (5.19) that shall be evaluated during program translation, in which case the program is ill-formed.
Rationale (March, 2004):
We feel the standard is clear enough. The quoted sentence does begin "If during the evaluation of an expression, ..." so the rest of the sentence does not apply to an expression that is not evaluated.
Note (September, 2004):
Gennaro Prota feels that the CWG missed the point of his original comment: unless a constant expression appears in a context that requires a constant expression, an implementation is permitted to defer its evaluation to runtime. An evaluation that fails at runtime cannot affect the well-formedness of the program; only expressions that are evaluated at compile time can make a program ill-formed.
The status has been reset to “open” to allow further discussion.
Proposed resolution (October, 2004):
Change paragraph 5 of Clause 7 [expr] as indicated:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expressionis a constant expressionappears where an integral constant expression is required (7.7 [expr.const]), in which case the program is ill-formed.
[Moved to DR at 4/02 meeting.]
7.2.1 [basic.lval] paragraph 15 lists the types via which an lvalue can be used to access the stored value of an object; using an lvalue type that is not listed results in undefined behavior. It is permitted to add cv-qualification to the actual type of the object in this access, but only at the top level of the type ("a cv-qualified version of the dynamic type of the object").
However, 7.3.6 [conv.qual] paragraph 4 permits a "conversion [to] add cv-qualifiers at levels other than the first in multi-level pointers." The combination of these two rules allows creation of pointers that cannot be dereferenced without causing undefined behavior. For instance:
int* jp; const int * const * p1 = &jp; *p1; // undefined behavior!
The reason that *p1 results in undefined behavior is that the type of the lvalue is const int * const", which is not "a cv-qualified version of" int*.
Since the conversion is permitted, we must give it defined semantics, hence we need to fix the wording in 7.2.1 [basic.lval] to include all possible conversions of the type via 7.3.6 [conv.qual].
Proposed resolution (04/01):
Add a new bullet to 7.2.1 [basic.lval] paragraph 15, following "a cv-qualified version of the dynamic type of the object:"
[Voted into WP at April, 2006 meeting.]
The C standard says in 6.3.2.3, paragraph 4:
Conversion of a null pointer to another pointer type yields a null pointer of that type. Any two null pointers shall compare equal.
C++ appears to be incompatible with the first sentence in only two areas:
A *a = 0; void *v = a;
C++ (7.3.12 [conv.ptr] paragraph 2) says nothing about the value of v.
void *v = 0; A *b = (A*)v; // aka static_cast<A*>(v)
C++ (7.6.1.9 [expr.static.cast] paragraph 10) says nothing about the value of b.
Suggested changes:
Add the following sentence to 7.3.12 [conv.ptr] paragraph 2:
The null pointer value is converted to the null pointer value of the destination type.
Add the following sentence to 7.6.1.9 [expr.static.cast] paragraph 10:
The null pointer value (7.3.12 [conv.ptr]) is converted to the null pointer value of the destination type.
Proposed resolution (October, 2005):
Add the indicated words to 7.3.12 [conv.ptr] paragraph 2:
An rvalue of type “pointer to cv T,” where T is an object type, can be converted to an rvalue of type “pointer to cv void”. The result of converting a “pointer to cv T” to a “pointer to cv void” points to the start of the storage location where the object of type T resides, as if the object is a most derived object (6.7.2 [intro.object]) of type T (that is, not a base class subobject). The null pointer value is converted to the null pointer value of the destination type.
Add the indicated words to 7.6.1.9 [expr.static.cast] paragraph 11:
An rvalue of type “pointer to cv1 void” can be converted to an rvalue of type “pointer to cv2 T,” where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. The null pointer value is converted to the null pointer value of the destination type. A value of type pointer to object converted to “pointer to cv void” and back, possibly with different cv-qualification, shall have its original value...
[Voted into the WP at the June, 2008 meeting as paper N2656.]
In the interest of promoting use of nullptr instead of the integer literal 0 as the null pointer constant, the proposal accepted by the Committee does not provide for converting a zero-valued integral constant to type std::nullptr_t. However, this omission reduces the utility of the feature for use in the library for smart pointers. In particular, the addition of that conversion (along with a converting constructor accepting a std::nullptr_t) would allow smart pointers to be used just like ordinary pointers in expressions like:
if (p == 0) { } if (0 == p) { } if (p != 0) { } if (0 != p) { } p = 0;
The existing use of the “unspecified bool type” idiom supports this usage, but being able to use std::nullptr_t instead would be simpler and more elegant.
Jason Merrill: I have another reason to support the conversion as well: it seems to me very odd for nullptr_t to be more restrictive than void*. Anything we can do with an arbitrary pointer, we ought to be able to do with nullptr_t as well. Specifically, since there is a standard conversion from literal 0 to void*, and there is a standard conversion from void* to bool, nullptr_t should support the same conversions.
This changes two of the example lines in the proposal as adopted:
if (nullptr) ; // error, no conversion to bool if (nullptr == 0) ; // error
become
if (nullptr) ; // evaluates to false if( nullptr == 0 ); // evaluates to true
And later,
char* ch3 = expr ? nullptr : nullptr; // ch3 is the null pointer value char* ch4 = expr ? 0 : nullptr; // ch4 is the null pointer value int n3 = expr ? nullptr : nullptr; // error, nullptr_t can't be converted to int int n4 = expr ? 0 : nullptr; // error, nullptr_t can't be converted to int
I would also allow reinterpret_cast from nullptr_t to integral type, with the same semantics as a reinterpret_cast from the null pointer value to integral type.
Basically, I would like nullptr_t to act like a void* which is constrained to always be (void*)0.
[Voted into WP at the October, 2006 meeting.]
When the Standard refers to a virtual base class, it should be understood to include base classes of virtual bases. However, the Standard doesn't actually say this anywhere, so when 7.3.13 [conv.mem] (for example) forbids casting to a derived class member pointer from a virtual base class member pointer, it could be read as meaning:
struct B {}; struct D : public B {}; struct D2 : virtual public D {}; int B::*p; int D::*q; void f() { static_cast<int D2::*>(p); // permitted static_cast<int D2::*>(q); // forbidden }
Proposed resolution (October, 2005):
Change 7.3.13 [conv.mem] paragraph 2 as indicated:
...If B is an inaccessible (11.8 [class.access]), ambiguous (6.5.2 [class.member.lookup]) or virtual (11.7.2 [class.mi]) base class of D, or a base class of a virtual base class of D, a program that necessitates this conversion is ill-formed...
Change 7.6.1.9 [expr.static.cast] paragraph 2 as indicated:
...and B isnotneither a virtual base class of D nor a base class of a virtual base class of D...
Change 7.6.1.9 [expr.static.cast] paragraph 9 as indicated:
...and B isnotneither a virtual base class of D nor a base class of a virtual base class of D...
[Moved to DR at 10/01 meeting.]
Christophe de Dinechin: In 7.6.1.3 [expr.call] , paragraph 2 reads:
If no declaration of the called function is visible from the scope of the call the program is ill-formed.I think nothing there or in the previous paragraph indicates that this does not apply to calls through pointer or virtual calls.
Mike Miller: "The called function" is unfortunate phraseology; it makes it sound as if it's referring to the function actually called, as opposed to the identifier in the postfix expression. It's wrong with respect to Koenig lookup, too (the declaration need not be visible if it can be found in a class or namespace associated with one or more of the arguments).
In fact, this paragraph should be a note. There's a general rule that says you have to find an unambiguous declaration of any name that is used (6.5 [basic.lookup] paragraph 1) ; the only reason this paragraph is here is to contrast with C's implicit declaration of called functions.
Proposed resolution:
Change section 7.6.1.3 [expr.call] paragraph 2 from:If no declaration of the called function is visible from the scope of the call the program is ill-formed.to:
[Note: if a function or member function name is used, and name lookup (6.5 [basic.lookup]) does not find a declaration of that name, the program is ill-formed. No function is implicitly declared by such a call. ]
(See also issue 218.)
[Voted into the WP at the June, 2008 meeting.]
Martin O'Riordan: Having gone through all the relevant references in the IS, it is not conclusive that a call via a pointer to a virtual member function is polymorphic at all, and could legitimately be interpreted as being static.
Consider 7.6.1.3 [expr.call] paragraph 1:
The function called in a member function call is normally selected according to the static type of the object expression ( 11.7 [class.derived] ), but if that function is virtual and is not specified using a qualified-id then the function actually called will be the final overrider (11.7.3 [class.virtual] ) of the selected function in the dynamic type of the object expression.Here it is quite specific that you get the polymorphic call only if you use the unqualified syntax. But, the address of a member function is "always" taken using the qualified syntax, which by inference would indicate that call with a PMF is static and not polymorphic! Not what was intended.
Yet other references such as 7.6.4 [expr.mptr.oper] paragraph 4:
If the dynamic type of the object does not contain the member to which the pointer refers, the behavior is undefined.indicate that the opposite may have been intended, by stating that it is the dynamic type and not the static type that matters. Also, 7.6.4 [expr.mptr.oper] paragraph 6:
If the result of .* or ->* is a function, then that result can be used only as the operand for the function call operator (). [Example:which also implies that it is the object pointed to that determines both the validity of the expression (the static type of 'ptr_to_obj' may not have a compatible function) and the implicit (polymorphic) meaning. Note too, that this is stated in the non-normative example text.(ptr_to_obj->*ptr_to_mfct)(10);calls the member function denoted by ptr_to_mfct for the object pointed to by ptr_to_obj. ]
Andy Sawyer: Assuming the resolution is what I've assumed it is for the last umpteen years (i.e. it does the polymorphic thing), then the follow on to that is "Should there also be a way of selecting the non-polymorphic behaviour"?
Mike Miller: It might be argued that the current wording of 7.6.1.3 [expr.call] paragraph 1 does give polymorphic behavior to simple calls via pointers to members. (There is no qualified-id in obj.*pmf, and the IS says that if the function is not specified using a qualified-id, the final overrider will be called.) However, it clearly says the wrong thing when the pointer-to-member itself is specified using a qualified-id (obj.*X::pmf).
Bill Gibbons: The phrase qualified-id in 7.6.1.3 [expr.call] paragraph 1 refers to the id-expression and not to the "pointer-to-member expression" earlier in the paragraph:
For a member function call, the postfix expression shall be an implicit (11.4.3 [class.mfct.non.static] , 11.4.9 [class.static] ) or explicit class member access (7.6.1.5 [expr.ref] ) whose id-expression is a function member name, or a pointer-to-member expression (7.6.4 [expr.mptr.oper] ) selecting a function member.
Mike Miller: To be clear, here's an example:
struct S { virtual void f(); }; void (S::*pmf)(); void g(S* sp) { sp->f(); // 1: polymorphic sp->S::f(); // 2: non-polymorphic (sp->S::f)(); // 3: non-polymorphic (sp->*pmf)(); // 4: polymorphic (sp->*&S::f)(); // 5: polymorphic }
Notes from October 2002 meeting:
This was moved back to open for lack of a champion. Martin O'Riordan is not expected to be attending meetings.
Proposed resolution (February, 2008):
Change 7.6.1.3 [expr.call] paragraph 1 as follows:
... For a member function call, the postfix expression shall be an implicit (11.4.3 [class.mfct.non.static], 11.4.9 [class.static]) or explicit class member access (7.6.1.5 [expr.ref]) whose id-expression is a function member name, or a pointer-to-member expression (7.6.4 [expr.mptr.oper]) selecting a function member. The first expression in the postfix expression is then called the object expression, and; the call is as a member of the object pointed to or referred to by the object expression (7.6.1.5 [expr.ref], 7.6.4 [expr.mptr.oper]). In the case of an implicit class member access, the implied object is the one pointed to by this. [Note: a member function call of the form f() is interpreted as (*this).f() (see 11.4.3 [class.mfct.non.static]). —end note] If a function or member function name is used, the name can be overloaded ( Clause 12 [over]), in which case the appropriate function shall be selected according to the rules in 12.2 [over.match].The function called in a member function call is normally selected according to the static type of the object expression (11.7 [class.derived]), but if that function is virtual and is not specified using a qualified-id then the function actually called will be the final overrider (11.7.3 [class.virtual]) of the selected function in the dynamic type of the object expressionIf the selected function is non-virtual, or if the id-expression in the class member access expression is a qualified-id, that function is called. Otherwise, its final overrider (11.7.3 [class.virtual]) in the dynamic type of the object expression is called. ...
Change 7.6.4 [expr.mptr.oper] paragraph 4 as follows:
The first operand is called the object expression. If the dynamic type of the object expression does not contain the member to which the pointer refers, the behavior is undefined.
[Voted into WP at the October, 2006 meeting.]
The current wording of 7.6.1.3 [expr.call] paragraph 7 states:
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 (17.14 [support.runtime]). The lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the argument expression. After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed. If the argument has a non-POD class type ( Clause 11 [class]), the behavior is undefined.
Paper J16/04-0167=WG21 N1727 suggests that passing a non-POD object to ellipsis be ill-formed. In discussions at the Lillehammer meeting, however, the CWG felt that the newly-approved category of conditionally-supported behavior would be more appropriate.
Proposed resolution (October, 2005):
Change 7.6.1.3 [expr.call] paragraph 7 as indicated:
...After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed.If the argument has a non-POD class type (clause 9), the behavior is undefined.Passing an argument of non-POD class type (clause 9) with no corresponding parameter is conditionally-supported, with implementation-defined semantics.
[Voted into the WP at the September, 2008 meeting.]
Issue 506 changed passing a non-POD class type to an ellipsis from undefined behavior to conditionally-supported behavior. As a result, an implementation could conceivably reject code like the following:
struct two {char _[2];}; template <class From, class To> struct is_convertible { private: static From f; template <class U> static char test(const U&); template <class U> static two test(...); public: static const bool value = sizeof(test<To>(f)) == 1; }; struct A { A(); }; int main() { const bool b = is_convertible<A,int>::value; // b == false }
This technique has become popular in template metaprogramming, and no non-POD object is actually passed at runtime. Concepts will eliminate much (perhaps not all) of the need for this kind of programming, but legacy code will persist.
Perhaps this technique should be officially supported by allowing implementations to reject passing a non-POD type to ellipsis only if it appears in a potentially-evaluated expression?
Notes from the July, 2007 meeting:
The CWG agreed with the suggestion to allow such calls in unevaluated contexts.
Proposed resolution (September, 2007):
Change 7.6.1.3 [expr.call] paragraph 7 as follows:
...Passingana potentially-evaluated argument of non-trivial class type (Clause 11 [class]) with no corresponding parameter is conditionally-supported, with implementation-defined semantics...
[Voted into WP at March 2004 meeting.]
Consider
typedef struct { int a; } A; A f(void) { A a; return a; } int main(void) { int* p = &f().a; // #1 }
Should #1 be rejected? The standard is currently silent.
Mike Miller: I don't believe the Standard is silent on this. I will agree that the wording of 7.6.1.5 [expr.ref] bullet 4.2 is unfortunate, as it is subject to misinterpretation. It reads,
If E1 is an lvalue, then E1.E2 is an lvalue.The intent is, "and not otherwise."
Notes from October 2003 meeting:
We agree the reference should be an rvalue, and a change along the lines of that recommended by Mike Miller is reasonable.
Proposed Resolution (October 2003):
Change the second bullet of 7.6.1.5 [expr.ref] paragraph 4 to read:
If E1 is an lvalue, then E1.E2 is an lvalue; otherwise, it is an rvalue.
[Voted into WP at April, 2006 meeting.]
There is an inconsistency between the normative text in section 7.6.1.8 [expr.typeid] and the example that follows.
Here is the relevant passage (starting with paragraph 4):
When typeid is applied to a type-id, the result refers to a std::type_info object representing the type of the type-id. If the type of the type-id is a reference type, the result of the typeid expression refers to a std::type_info object representing the referenced type.
The top-level cv-qualifiers of the lvalue expression or the type-id that is the operand of typeid are always ignored.
and the example:
typeid(D) == typeid(const D&); // yields true
The second paragraph above says the “type-id that is the operand”. This would be const D&. In this case, the const is not at the top-level (i.e., applied to the operand itself).
By a strict reading, the above should yield false.
My proposal is that the strict reading of the normative test is correct. The example is wrong. Different compilers here give different answers.
Proposed resolution (April, 2005):
Change the second sentence of 7.6.1.8 [expr.typeid] paragraph 4 as follows:
If the type of the type-id is a reference to a possibly cv-qualified type, the result of the typeid expression refers to a std::type_info object representing the cv-unqualified referenced type.
[Voted into WP at October 2004 meeting.]
Is it okay to use a static_cast to cast from a private base class to a derived class? That depends on what the words "valid standard conversion" in paragraph 8 mean — do they mean the conversion exists, or that it would not get an error if it were done? I think the former was intended — and therefore a static_cast from a private base to a derived class would be allowed.
Rationale (04/99): A static_cast from a private base to a derived class is not allowed outside a member from the derived class, because 7.3.12 [conv.ptr] paragraph 3 implies that the conversion is not valid. (Classic style casts work.)
Reopened September 2003:
Steve Adamczyk: It makes some sense to disallow the inverse conversion that is pointer-to-member of derived to pointer-to-member of private base. There's less justification for the pointer-to-private-base to pointer-to-derived case. EDG, g++ 3.2, and MSVC++ 7.1 allow the pointer case and disallow the pointer-to-member case. Sun disallows the pointer case as well.
struct B {}; struct D : private B {}; int main() { B *p = 0; static_cast<D *>(p); // Pointer case: should be allowed int D::*pm = 0; static_cast<int B::*>(pm); // Pointer-to-member case: should get error }
There's a tricky case with old-style casts: because the static_cast interpretation is tried first, you want a case like the above to be considered a static_cast, but then issue an error, not be rejected as not a static cast; if you did the latter, you would then try the cast as a reinterpret_cast.
Ambiguity and casting to a virtual base should likewise be errors after the static_cast interpretation is selected.
Notes from the October 2003 meeting:
There was lots of sentiment for making things symmetrical: the pointer case should be the same as the pointer-to-member case. g++ 3.3 now issues errors on both cases.
We decided an error should be issued on both cases. The access part of the check should be done later; by some definition of the word the static_cast is valid, and then later an access error is issued. This is similar to the way standard conversions work.
Proposed Resolution (October 2003):
Replace paragraph 7.6.1.9 [expr.static.cast]/6:
The inverse of any standard conversion sequence ( 7.3 [conv]), other than the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), function-to-pointer (7.3.4 [conv.func]), and boolean (7.3.14 [conv.fctptr]) conversions, can be performed explicitly using static_cast. The lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) conversions are applied to the operand. Such a static_cast is subject to the restriction that the explicit conversion does not cast away constness (7.6.1.11 [expr.const.cast]), and the following additional rules for specific cases:
with two paragraphs:
The inverse of any standard conversion sequence ( 7.3 [conv]), other than the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), function-to-pointer (7.3.4 [conv.func]), and boolean (7.3.14 [conv.fctptr]) conversions, can be performed explicitly using static_cast. A program is ill-formed if it uses static_cast to perform the inverse of an ill-formed standard conversion sequence.[Example:--- end example]struct B {}; struct D : private B {}; void f() { static_cast<D*>((B*)0); // Error: B is a private base of D. static_cast<int B::*>((int D::*)0); // Error: B is a private base of D. }The lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) conversions are applied to the operand. Such a static_cast is subject to the restriction that the explicit conversion does not cast away constness (7.6.1.11 [expr.const.cast]), and the following additional rules for specific cases:
In addition, modify the second sentence of 7.6.3 [expr.cast]/5. The first two sentences of 7.6.3 [expr.cast]/5 presently read:
The conversions performed bycan be performed using the cast notation of explicit type conversion. The same semantic restrictions and behaviors apply.
- a const_cast (5.2.11),
- a static_cast (5.2.9),
- a static_cast followed by a const_cast,
- a reinterpret_cast (5.2.10), or
- a reinterpret_cast followed by a const_cast,
Change the second sentence to read:
The same semantic restrictions and behaviors apply, with the exception that in performing a static_cast in the following situations the conversion is valid even if the base class is inaccessible:
- a pointer to an object of derived class type or an lvalue of derived class type may be explicitly converted to a pointer or reference to an unambiguous base class type, respectively;
- a pointer to member of derived class type may be explicitly converted to a pointer to member of an unambiguous non-virtual base class type;
- a pointer to an object of an unambiguous non-virtual base class type, an lvalue of an unambiguous non-virtual base class type, or a pointer to member of an unambiguous non-virtual base class type may be explicitly converted to a pointer, a reference, or a pointer to member of a derived class type, respectively.
Remove paragraph 7.6.3 [expr.cast]/7, which presently reads:
In addition to those conversions, the following static_cast and reinterpret_cast operations (optionally followed by a const_cast operation) may be performed using the cast notation of explicit type conversion, even if the base class type is not accessible:
- a pointer to an object of derived class type or an lvalue of derived class type may be explicitly converted to a pointer or reference to an unambiguous base class type, respectively;
- a pointer to member of derived class type may be explicitly converted to a pointer to member of an unambiguous non-virtual base class type;
- a pointer to an object of non-virtual base class type, an lvalue of non-virtual base class type, or a pointer to member of non-virtual base class type may be explicitly converted to a pointer, a reference, or a pointer to member of a derived class type, respectively.
[Voted into WP at October 2004 meeting.]
Consider this code:
struct B {}; struct D : public B { D(const B&); }; extern B& b; void f() { static_cast<const D&>(b); }
The rules for static_cast permit the conversion to "const D&" in two ways:
The first alternative is 7.6.1.9 [expr.static.cast]/5; the second is 7.6.1.9 [expr.static.cast]/2.
Presumably the first alternative is better -- it's the "simpler" conversion. The standard does not seem to make that clear.
Steve Adamczyk: I take the "Otherwise" at the beginning of 7.6.1.9 [expr.static.cast]/3 as meaning that the paragraph 2 interpretation is used if available, which means in your example above interpretation 2 would be used. However, that's not what EDG's compiler does, and I agree that it's not the "simpler" conversion.
Proposed Resolution (October 2003):
Move paragraph 5.2.9/5:
An lvalue of type ``cv1 B'', where B is a class type, can be cast to type ``reference to cv2 D'', where D is a class derived ( 11.7 [class.derived]) from B, if a valid standard conversion from ``pointer to D'' to ``pointer to B'' exists (7.3.12 [conv.ptr]), cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is not a virtual base class of D. The result is an lvalue of type ``cv2 D.'' If the lvalue of type ``cv1 B'' is actually a sub-object of an object of type D, the lvalue refers to the enclosing object of type D. Otherwise, the result of the cast is undefined. [Example:
struct B {}; struct D : public B {}; D d; B &br = d; static_cast<D&>(br); // produces lvalue to the original d object--- end example]
before paragraph 7.6.1.9 [expr.static.cast]/2.
Insert Otherwise, before the text of paragraph 7.6.1.9 [expr.static.cast]/2 (which will become 7.6.1.9 [expr.static.cast]/3 after the above insertion), so that it reads:
Otherwise, an expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration "T t(e);" is well-formed, for some invented temporary variable t (9.5 [dcl.init]). The effect of such an explicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. The result is an lvalue if T is a reference type (9.3.4.3 [dcl.ref]), and an rvalue otherwise. The expression e is used as an lvalue if and only if the initialization uses it as an lvalue.
[Voted into WP at April 2005 meeting.]
Paragraph 7.6.1.9 [expr.static.cast] paragraph 10 says that:
A value of type pointer to object converted to "pointer to cv void" and back to the original pointer type will have its original value.
That guarantee should be stronger. In particular, given:
T* p1 = new T; const T* p2 = static_cast<const T*>(static_cast<void *>(p1)); if (p1 != p2) abort ();there should be no call to "abort". The last sentence of Paragraph 7.6.1.9 [expr.static.cast] paragraph 10 should be changed to read:
A value of type pointer to object converted to "pointer to cv void" and back to the original pointer type (or a variant of the original pointer type that differs only in the cv-qualifiers applied to the object type) will have its original value. [Example:---end example.]T* p1 = new T; const T* p2 = static_cast<const T*>(static_cast<void *>(p1)); bool b = p1 == p2; // b will have the value true.
Proposed resolution:
Change 7.6.1.9 [expr.static.cast] paragraph 10 as indicated:
A value of type pointer to object converted to "pointer to cv void" and back to the original pointer type, possibly with different cv-qualification, will have its original value. [Example:T* p1 = new T; const T* p2 = static_cast<const T*>(static_cast<void *>(p1)); bool b = p1 == p2; // b will have the value true.---end example]
Rationale: The wording "possibly with different cv-qualification" was chosen over the suggested wording to allow for changes in cv-qualification at different levels in a multi-level pointer, rather than only at the object type level.
[Voted into the WP at the September, 2008 meeting.]
There appears to be no provision in the Standard for explicit conversion of a value of a scoped enumeration type to an integral type, even though the inverse conversion is permitted. That is,
enum class E { e }; static_cast<E>(0); // #1: OK static_cast<int>(E::e); // #2: error
This is because values of scope enumeration types (intentionally) cannot be implicitly converted to integral types (7.3.7 [conv.prom] and 7.3.9 [conv.integral]) and 7.6.1.9 [expr.static.cast] was not updated to permit #2, although #1 is covered by paragraph 8.
Proposed resolution (June, 2008):
Add the following as a new paragraph following 7.6.1.9 [expr.static.cast] paragraph 8:
A value of a scoped enumeration type (9.8.1 [dcl.enum]) can be explicitly converted to an integral type. The value is unchanged if the original value can be represented by the specified type. Otherwise, the resulting value is unspecified.
[Voted into WP at April 2005 meeting.]
It is currently not permitted to cast directly between a pointer to function type and a pointer to object type. This conversion is not listed in 7.6.1.9 [expr.static.cast] and 7.6.1.10 [expr.reinterpret.cast] and thus requires a diagnostic to be issued. However, if a sufficiently long integral type exists (as is the case in many implementations), it is permitted to cast between pointer to function types and pointer to object types using that integral type as an intermediary.
In C the cast results in undefined behavior and thus does not require a diagnostic, and Unix C compilers generally do not issue one. This fact is used in the definition of the standard Unix function dlsym, which is declared to return void* but in fact may return either a pointer to a function or a pointer to an object. The fact that C++ compilers are required to issue a diagnostic is viewed as a "competitive disadvantage" for the language.
Suggested resolution: Add wording to 7.6.1.10 [expr.reinterpret.cast] allowing conversions between pointer to function and pointer to object types, if the implementation has an integral data type that can be used as an intermediary.
Several points were raised in opposition to this suggestion:
Martin O'Riordan suggested an alternative approach:
The advantage of this approach is that it would permit writing portable, well-defined programs involving such conversions. However, it breaks the current degree of compatibility between old and new casts, and it adds functionality to dynamic_cast which is not obviously related to its current meaning.
Notes from 04/00 meeting:
Andrew Koenig suggested yet another approach: specify that "no diagnostic is required" if the implementation supports the conversion.
Later note:
It was observed that conversion between function and data pointers is listed as a "common extension" in C99.
Notes on the 10/01 meeting:
It was decided that we want the conversion defined in such a way that it always exists but is always undefined (as opposed to existing only when the size relationship is appropriate, and being implementation-defined in that case). This would allow an implementation to issue an error at compile time if the conversion does not make sense.
Bill Gibbons notes that the definitions of the other similar casts are inconsistent in this regard. Perhaps they should be updated as well.
Proposed resolution (April 2003):
After 7.6.1.10 [expr.reinterpret.cast] paragraph 6, insert:
A pointer to a function can be explicitly converted to a pointer to a function of a different type. The effect of calling a function through a pointer to a function type (9.3.4.6 [dcl.fct]) that is not the same as the type used in the definition of the function is undefined. Except that converting an rvalue of type ``pointer to T1'' to the type ``pointer to T2'' (where T1 and T2 are function types) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified. [Note: see also 7.3.12 [conv.ptr] for more details of pointer conversions. ] It is implementation defined whether a conversion from pointer to object to pointer to function and/or a conversion from pointer to function to pointer to object exist.and in paragraph 10:
An lvalue expression of type T1 can be cast to the type ``reference to T2'' if T1 and T2 are object types and an expression of type ``pointer to T1'' can be explicitly converted to the type ``pointer to T2'' using a reinterpret_cast. That is, a reference cast reinterpret_cast< T& >(x) has the same effect as the conversion *reinterpret_cast< T* >(&x) with the built-in & and * operators. The result is an lvalue that refers to the same object as the source lvalue, but with a different type. No temporary is created, no copy is made, and constructors (11.4.5 [class.ctor]) or conversion functions (11.4.8 [class.conv]) are not called.
Drafting Note:
If either conversion exists, the implementation already has to define the behavior (paragraph 3).
Notes from April 2003 meeting:
The new consensus is that if the implementation allows this cast, pointer-to-function converted to pointer-to-object converted back to the original pointer-to-function should work; anything else is undefined behavior. If the implementation does not allow the cast, it should be ill-formed.
Tom Plum is investigating a new concept, that of a "conditionally-defined" feature, which may be applicable here.
Proposed Resolution (October, 2004):
(See paper J16/04-0067 = WG21 N1627 for background material and rationale for this resolution. The resolution proposed here differs only editorially from the one in the paper.)
Insert the following into Clause 3 [intro.defs] and renumber all following definitions accordingly:
1.3.2 conditionally-supported behavior
behavior evoked by a program construct that is not a mandatory requirement of this International Standard. If a given implementation supports the construct, the behavior shall be as described herein; otherwise, the implementation shall document that the construct is not supported and shall treat a program containing an occurrence of the construct as ill-formed (Clause 3 [intro.defs]).
Add the indicated words to 4.1 [intro.compliance] paragraph 2, bullet 2:
If a program contains a violation of any diagnosable rule, or an occurrence of a construct described herein as “conditionally-supported” or as resulting in “conditionally-supported behavior” when the implementation does not in fact support that construct, a conforming implementation shall issue at least one diagnostic message, except that
Insert the following as a new paragraph following 7.6.1.10 [expr.reinterpret.cast] paragraph 7:
Converting a pointer to a function to a pointer to an object type or vice versa evokes conditionally-supported behavior. In any such conversion supported by an implementation, converting from an rvalue of one type to the other and back (possibly with different cv-qualification) shall yield the original pointer value; mappings between pointers to functions and pointers to objects are otherwise implementation-defined.
Change 9.11 [dcl.asm] paragraph 1 as indicated:
The meaning of anAn asm declaration evokes conditionally-supported behavior. If supported, its meaning is implementation-defined.
Change 9.12 [dcl.link] paragraph 2 as indicated:
The string-literal indicates the required language linkage.The meaning of the string-literal is implementation-defined. A linkage-specification with a string that is unknown to the implementation is ill-formed.This International Standard specifies the semantics of C and C++ language linkage. Other values of the string-literal evoke conditionally-supported behavior, with implementation-defined semantics. [Note: Therefore, a linkage-specification with a string-literal that is unknown to the implementation requires a diagnostic.When the string-literal in a linkage-specification names a programming language, the spelling of the programming language's name is implementation-defined. [Note:It is recommended that the spelling be taken from the document defining that language, for example Ada (not ADA) and Fortran or FORTRAN (depending on the vintage).The semantics of a language linkage other than C++ or C are implementation-defined.]
Change Clause 13 [temp] paragraph 4 as indicated:
A template, a template explicit specialization (13.9.4 [temp.expl.spec]), or a class template partial specialization shall not have C linkage. If the linkage of one of these is something other than C or C++, thebehavior is implementation-definedresult is conditionally-supported behavior, with implementation-defined semantics.
[Voted into WP at April, 2006 meeting.]
Is reinterpret_cast<T*>(null_pointer_constant) guaranteed to yield the null pointer value of type T*?
I think a committee clarification is needed. Here's why: 7.6.1.10 [expr.reinterpret.cast] par. 8 talks of "null pointer value", not "null pointer constant", so it would seem that
reinterpret_cast<T*>(0)is a normal int->T* conversion, with an implementation-defined result.
However a little note to 7.6.1.10 [expr.reinterpret.cast] par. 5 says:
Converting an integral constant expression (5.19) with value zero always yields a null pointer (4.10), but converting other expressions that happen to have value zero need not yield a null pointer.Where is this supported in normative text? It seems that either the footnote or paragraph 8 doesn't reflect the intent.
SUGGESTED RESOLUTION: I think it would be better to drop the footnote #64 (and thus the special case for ICEs), for two reasons:
a) it's not normative anyway; so I doubt anyone is relying on the guarantee it hints at, unless that guarantee is given elsewhere in a normative part
b) users expect reinterpret_casts to be almost always implementation dependent, so this special case is a surprise. After all, if one wants a null pointer there's static_cast. And if one wants reinterpret_cast semantics the special case requires doing some explicit cheat, such as using a non-const variable as intermediary:
int v = 0; reinterpret_cast<T*>(v); // implementation defined reinterpret_cast<T*>(0); // null pointer value of type T* const int w = 0; reinterpret_cast<T*>(w); // null pointer value of type T*
It seems that not only that's providing a duplicate functionality, but also at the cost to hide what seems the more natural one.
Notes from October 2004 meeting:
This footnote was added in 1996, after the invention of reinterpret_cast, so the presumption must be that it was intentional. At this time, however, the CWG feels that there is no reason to require that reinterpret_cast<T*>(0) produce a null pointer value as its result.
Proposed resolution (April, 2005):
Delete the footnote in 7.6.1.10 [expr.reinterpret.cast] paragraph 5 reading,
Converting an integral constant expression (7.7 [expr.const]) with value zero always yields a null pointer (7.3.12 [conv.ptr]), but converting other expressions that happen to have value zero need not yield a null pointer.
Add the indicated note to 7.6.1.10 [expr.reinterpret.cast] paragraph 8:
The null pointer value (7.3.12 [conv.ptr]) is converted to the null pointer value of the destination type. [Note: A null pointer constant, which has integral type, is not necessarily converted to a null pointer value. —end note]
[Voted into WP at October 2003 meeting.]
An assignment returns an lvalue for its left operand. If that operand refers to a bit field, can the "&" operator be applied to the assignment? Can a reference be bound to it?
struct S { int a:3; int b:3; int c:3; }; void f() { struct S s; const int *p = &(s.b = 0); // (a) const int &r = (s.b = 0); // (b) int &r2 = (s.b = 0); // (c) }
Notes from the 4/02 meeting:
The working group agreed that this should be an error.
Proposed resolution (October 2002):
In 7.6.2.3 [expr.pre.incr] paragraph 1 (prefix "++" and "--" operators), change
The value is the new value of the operand; it is an lvalue.to
The result is the updated operand; it is an lvalue, and it is a bit-field if the operand is a bit-field.
In 7.6.16 [expr.cond] paragraph 4 ("?" operator), add the indicated text:
If the second and third operands are lvalues and have the same type, the result is of that type and is an lvalue and it is a bit-field if the second or the third operand is a bit-field, or if both are bit-fields.
In 7.6.19 [expr.assign] paragraph 1 (assignment operators) add the indicated text (the original text is as updated by issue 221, which is DR but not in TC1):
The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue with the type and value of the left operand after the assignment has taken place. The result in all cases is a bit-field if the left operand is a bit-field.
Note that issue 222 adds (non-conflicting) text at the end of this same paragraph (7.6.19 [expr.assign] paragraph 1).
In 7.6.20 [expr.comma] paragraph 1 (comma operator), change:
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.to
The type and value of the result are the type and value of the right operand; the result is an lvalue if the right operand is an lvalue, and is a bit-field if the right operand is an lvalue and a bit-field.
Relevant related text (no changes required):
7.6.2.2 [expr.unary.op] paragraph 4:
The operand of & shall not be a bit-field.
9.5.4 [dcl.init.ref] paragraph 5, bullet 1, sub-bullet 1 (regarding binding a reference to an lvalue):
... is an lvalue (but is not a bit-field) ...
[Voted into the WP at the September, 2008 meeting.]
The specification for the alignof operator (7.6.2.6 [expr.alignof]) does not forbid function types as operands, although it probably should.
Proposed resolution (March, 2008):
The issue, as described, is incorrect. The requirement in 7.6.2.6 [expr.alignof] is for “a complete object type,” so a function type is already forbidden. However, the existing text does have a problem in this requirement in that it does not allow a reference type, as anticipated by paragraph 3. Consequently, the proposal is to change 7.6.2.6 [expr.alignof] paragraph 1 as indicated:
An alignof expression yields the alignment requirement of its operand type. The operand shall be a type-id representing a complete object type or a reference to a complete object type.
[Voted into the WP at the September, 2008 meeting.]
[Picked up by evolution group at October 2002 meeting.]
(See also issue 476.)
The size requested by an array allocation is computed by multiplying the number of elements requested by the size of each element and adding an implementation-specific amount for overhead. It is possible for this calculation to overflow. Is an implementation required to detect this situation and, for instance, throw std::bad_alloc?
On one hand, the maximum allocation size is one of the implementation limits specifically mentioned in Annex Clause Annex B [implimits], and, according to 4.1 [intro.compliance] paragraph 2, an implementation is only required to "accept and correctly execute" programs that do not violate its resource limits.
On the other hand, it is difficult or impossible for user code to detect such overflows in a portable fashion, especially given that the array allocation overhead is not fixed, and it would be a service to the user to handle this situation gracefully.
Rationale (04/01):
Each implementation is required to document the maximum size of an object (Annex Clause Annex B [implimits]). It is not difficult for a program to check array allocations to ensure that they are smaller than this quantity. Implementations can provide a mechanism in which users concerned with this problem can request extra checking before array allocations, just as some implementations provide checking for array index and pointer validity. However, it would not be appropriate to require this overhead for every array allocation in every program.
(See issue 624 for a request to reconsider this resolution.)
Note (March, 2008):
The Evolution Working Group has accepted the intent of this issue and referred it to CWG for action for C++0x (see paper J16/07-0033 = WG21 N2173).
Proposed resolution (September, 2008):
This issue is resolved by the resolution of issue 624, given in paper N2757.
[Voted into WP at October 2005 meeting.]
In 7.6.2.8 [expr.new], the standard says that the expression in an array-new has to have integral type. There's already a DR (issue 74) that says it should also be allowed to have enumeration type. But, it should probably also say that it can have a class type with a single conversion to integral type; in other words the same thing as in 8.5.3 [stmt.switch] paragraph 2.
Suggested resolution:
In 7.6.2.8 [expr.new] paragraph 6, replace "integral or enumeration type (6.8.2 [basic.fundamental])" with "integral or enumeration type (6.8.2 [basic.fundamental]), or a class type for which a single conversion function to integral or enumeration type exists".
Proposed resolution (October, 2004):
Change 7.6.2.8 [expr.new] paragraph 6 as follows:
Every constant-expression in a direct-new-declarator shall be an integral constant expression (7.7 [expr.const]) and evaluate to a strictly positive value. The expression in a direct-new-declarator shallhavebe of integral type,orenumeration type(3.9.1), or a class type for which a single conversion function to integral or enumeration type exists (11.4.8 [class.conv]). If the expression is of class type, the expression is converted by calling the conversion function, and the result of the conversion is used in place of the original expression. The value of the expression shall bewith anon-negativevalue. [Example: ...
Proposed resolution (April, 2005):
Change 7.6.2.8 [expr.new] paragraph 6 as follows:
Every constant-expression in a direct-new-declarator shall be an integral constant expression (7.7 [expr.const]) and evaluate to a strictly positive value. The expression in a direct-new-declarator shallhave integral or enumeration type (6.8.2 [basic.fundamental]) with a non-negative valuebe of integral type, enumeration type, or a class type for which a single conversion function to integral or enumeration type exists (11.4.8 [class.conv]). If the expression is of class type, the expression is converted by calling that conversion function, and the result of the conversion is used in place of the original expression. If the value of the expression is negative, the behavior is undefined. [Example: ...
[Voted into WP at October 2004 meeting.]
What does this example do?
#include <stdio.h> #include <stdlib.h> struct A { void* operator new(size_t alloc_size, size_t dummy=0) { printf("A::operator new()\n"); return malloc(alloc_size); }; void operator delete(void* p, size_t s) { printf("A::delete %d\n", s); }; A() {printf("A constructing\n"); throw 2;}; }; int main() { try { A* ap = new A; delete ap; } catch(int) {printf("caught\n"); return 1;} }
The fundamental issue here is whether the deletion-on-throw is driven by the syntax of the new (placement or non-placement) or by signature matching. If the former, the operator delete would be called with the second argument equal to the size of the class. If the latter, it would be called with the second argument 0.
Core issue 127 (in TC1) dealt with this topic. It removed some wording in 14.3 [except.ctor] paragraph 2 that implied a syntax-based interpretation, leaving wording in 7.6.2.8 [expr.new] paragraph 19 that is signature-based. But there is no accompanying rationale to confirm an explicit choice of the signature-based approach.
EDG and g++ get 0 for the second argument, matching the presumed core issue 127 resolution. But maybe this should be revisited.
Notes from October 2003 meeting:
There was widespread agreement that the compiler shouldn't just silently call the delete with either of the possible values. In the end, we decided it's smarter to issue an error on this case and force the programmer to say what he means.
Mike Miller's analysis of the status quo: 6.7.6.5.3 [basic.stc.dynamic.deallocation] paragraph 2 says that "operator delete(void*, std::size_t)" is a "usual (non-placement) deallocation function" if the class does not declare "operator delete(void*)." 6.7.6.5.2 [basic.stc.dynamic.allocation] does not use the same terminology for allocation functions, but the most reasonable way to understand the uses of the term "placement allocation function" in the Standard is as an allocation function that has more than one parameter and thus can (but need not) be called using the "new-placement" syntax described in 7.6.2.8 [expr.new]. (In considering issue 127, the core group discussed and endorsed the position that, "If a placement allocation function has default arguments for all its parameters except the first, it can be called using non-placement syntax.")
7.6.2.8 [expr.new] paragraph 19 says that any non-placement deallocation function matches a non-placement allocation function, and that a placement deallocation function matches a placement allocation function with the same parameter types after the first -- i.e., a non-placement deallocation function cannot match a placement allocation function. This makes sense, because non-placement ("usual") deallocation functions expect to free memory obtained from the system heap, which might not be the case for storage resulting from calling a placement allocation function.
According to this analysis, the example shows a placement allocation function and a non-placement deallocation function, so the deallocation function should not be invoked at all, and the memory will just leak.
Proposed Resolution (October 2003):
Add the following text at the end of 7.6.2.8 [expr.new] paragraph 19:
If the lookup finds the two-parameter form of a usual deallocation function (6.7.6.5.3 [basic.stc.dynamic.deallocation]), and that function, considered as a placement deallocation function, would have been selected as a match for the allocation function, the program is ill-formed. [Example:--- end example]struct S { // Placement allocation function: static void* operator new(std::size_t, std::size_t); // Usual (non-placement) deallocation function: static void operator delete(void*, std::size_t); }; S* p = new (0) S; // ill-formed: non-placement deallocation function matches // placement allocation function
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
Issue 256 was closed without action, principally on the the grounds that an implementation could provide a means (command-line option, #pragma, etc.) for requesting that the allocation size be checked for validity, but that “it would not be appropriate to require this overhead for every array allocation in every program.”
This rationale may be giving too much weight to the overhead such a check would add, especially when compared to the likely cost of actually doing the storage allocation. In particular, the test essentially amounts to something like
if (max_allocation_size / sizeof(T) < num_elements) throw std::bad_alloc();
(noting that max_allocation_size/sizeof(T) is a compile-time constant). It might make more sense to turn the rationale around and require the check, assuming that implementations could provide a mechanism for suppressing it if needed.
Suggested resolution:
In 7.6.2.8 [expr.new] paragraph 7, add the following words before the example:
If the value of the expression is such that the size of the allocated object would exceed the implementation-defined limit, an exception of type std::bad_alloc is thrown and no storage is obtained.
Note (March, 2008):
The Evolution Working Group has accepted the intent of issue 256 and referred it to CWG for action for C++0x (see paper J16/07-0033 = WG21 N2173).
Proposed resolution (March, 2008):
As suggested.
Notes from the June, 2008 meeting:
The CWG felt that this situation should not be treated like an out-of-memory situation and thus an exception of type std::bad_alloc (or, alternatively, returning a null pointer for a throw() allocator) would not be appropriate.
Proposed resolution (June, 2008):
Change 7.6.2.8 [expr.new] paragraph 8 as follows:
If the value of the expression in a direct-new-declarator is such that the size of the allocated object would exceed the implementation-defined limit, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (14.4 [except.handle]) of type std::length_error (19.2.6 [length.error]). Otherwise, ifWhenthe value ofthethat expressionin a direct-new-declaratoris zero, the allocation function is called to allocate an array with no elements.
[Drafting note: std::length_error is thrown by std::string and std::vector and thus appears to be the right choice for the exception to be thrown here.]
[Voted into the WP at the June, 2008 meeting.]
For delete expressions, 7.6.2.9 [expr.delete] paragraph 1 says
The operand shall have a pointer type, or a class type having a single conversion function to a pointer type.
However, paragraph 3 of that same section says:
if the static type of the operand is different from its dynamic type, the static type shall be a base class of the operand's dynamic type and the static type shall have a virtual destructor or the behavior is undefined.
Since the operand must be of pointer type, its static type is necessarily the same as its dynamic type. That clause is clearly referring to the object being pointed at, and not to the pointer operand itself.
Correcting the wording gets a little complicated, because dynamic and static types are attributes of expressions, not objects, and there's no sub-expression of a delete-expression which has the relevant types.
Suggested resolution:
then there is a static type and a dynamic type that the hypothetical expression (* const-expression) would have. If that static type is different from that dynamic type, then that static type shall be a base class of that dynamic type, and that static type shall have a virtual destructor, or the behavior is undefined.
There's precedent for such use of hypothetical constructs: see 7.6.10 [expr.eq] paragraph 2, and 9.3.2 [dcl.name] paragraph 1.
11.7.3 [class.virtual] paragraph 3 has a similar problem. It refers to
the type of the pointer or reference denoting the object (the static type).
The type of the pointer is different from the type of the reference, both of which are different from the static type of '*pointer', which is what I think was actually intended. Paragraph 6 contains the exact same wording, in need of the same correction. In this case, perhaps replacing "pointer or reference" with "expression" would be the best fix. In order for this fix to be sufficient, pointer->member must be considered equivalent to (*pointer).member, in which case the "expression" referred to would be (*pointer).
11.4.11 [class.free] paragraph 4 says thatif a delete-expression is used to deallocate a class object whose static type has...
This should be changed to
if a delete-expression is used to deallocate a class object through a pointer expression whose dereferenced static type would have...
The same problem occurs later, when it says that the
static and dynamic types of the object shall be identical
In this case you could replace "object" with "dereferenced pointer expression".
Footnote 104 says that
7.6.2.9 [expr.delete] requires that ... the static type of the delete-expression's operand be the same as its dynamic type.
This would need to be changed to
the delete-expression's dereferenced operand
Proposed resolution (December, 2006):
Change 7.6.2.9 [expr.delete] paragraph 3 as follows:
In the first alternative (delete object), if the static type of theoperandobject to be deleted is different from its dynamic type, the static type shall be a base class of theoperand'sdynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined. In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.
Change the footnote in 11.4.11 [class.free] paragraph 4 as follows:
A similar provision is not needed for the array version of operator delete because 7.6.2.9 [expr.delete] requires that in this situation, the static type of thedelete-expression's operandobject to be deleted be the same as its dynamic type.
Change the footnote in 11.4.11 [class.free] paragraph 5 as follows:
If the static typein the delete-expressionof the object to be deleted is different from the dynamic type and the destructor is not virtual the size might be incorrect, but that case is already undefined; see 7.6.2.9 [expr.delete].
[Drafting notes: No change is required for 11.7.3 [class.virtual] paragraph 7 because “the type of the pointer” includes the pointed-to type. No change is required for 11.4.11 [class.free] paragraph 4 because “...used to deallocate a class object whose static type...” already refers to the object (and not the operand expression).]
[Voted into WP at April 2003 meeting.]
In a couple of comp.std.c++ threads, people have asked whether the Standard guarantees that the deallocation function will be called in a delete-expression if the destructor throws an exception. Most/all people have expressed the opinion that the deallocation function must be called in this case, although no one has been able to cite wording in the Standard supporting that view.
#include <new.h> #include <stdio.h> #include <stdlib.h> static int flag = 0; inline void operator delete(void* p) throw() { if (flag) printf("in deallocation function\n"); free(p); } struct S { ~S() { throw 0; } }; void f() { try { delete new S; } catch(...) { } } int main() { flag=1; f(); flag=0; return 0; }
Proposed resolution (October 2002):
Add to 7.6.2.9 [expr.delete] paragraph 7 the highlighted text:
The delete-expression will call a deallocation function (6.7.6.5.3 [basic.stc.dynamic.deallocation]) [Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception. ]
[Voted into WP at October 2005 meeting.]
After some discussion in comp.lang.c++.moderated we came to the conclusion that there seems to be a defect in 7.6.2.9 [expr.delete]/4, which says:
The cast-expression in a delete-expression shall be evaluated exactly once. If the delete-expression calls the implementation deallocation function (3.7.3.2), and if the operand of the delete expression is not the null pointer constant, the deallocation function will deallocate the storage referenced by the pointer thus rendering the pointer invalid. [Note: the value of a pointer that refers to deallocated storage is indeterminate. ]
In the second sentence, the term "null pointer constant" should be changed to "null pointer". In its present form, the passage claims that the deallocation function will deallocate the storage refered to by a null pointer that did not come from a null pointer constant in the delete expression. Besides, how can the null pointer constant be the operand of a delete expression, as "delete 0" is an error because delete requires a pointer type or a class type having a single conversion function to a pointer type?
See also issue 348.
Proposed resolution:
Change the indicated sentence of 7.6.2.9 [expr.delete] paragraph 4 as follows:
If the delete-expression calls the implementation deallocation function (6.7.6.5.3 [basic.stc.dynamic.deallocation]), and if the value of the operand of the delete expression is notthea null pointerconstant, the deallocation function will deallocate the storage referenced by the pointer thus rendering the pointer invalid.
Notes from October 2004 meeting:
This wording is superseded by, and this issue will be resolved by, the resolution of issue 348.
Proposed resolution (April, 2005):
This issue is resolved by the resolution of issue 348.
[Voted into WP at April, 2007 meeting.]
7.6.3 [expr.cast] paragraph 6 says,
The operand of a cast using the cast notation can be an rvalue of type “pointer to incomplete class type”. The destination type of a cast using the cast notation can be “pointer to incomplete class type”. In such cases, even if there is a inheritance relationship between the source and destination classes, whether the static_cast or reinterpret_cast interpretation is used is unspecified.
The wording seems to allow the following:
casting from void pointer to incomplete type
struct A; struct B; void *v; A *a = (A*)v; // allowed to choose reinterpret_cast
variant application of static or reinterpret casting
B *b = (B*)a; // compiler can choose static_cast here A *aa = (A*)b; // compiler can choose reinterpret_cast here assert(aa == a); // might not hold
ability to somehow choose static_cast
It's not entirely clear how a compiler can choose static_cast as 7.6.3 [expr.cast] paragraph 6 seems to allow. I believe the intent of 7.6.3 [expr.cast] paragraph 6 is to force the use of reinterpret_cast when either are incomplete class types and static_cast iff the compiler knows both types and there is a non-ambiguous hierarchy-traversal between that cast (or maybe not, core issue 242 talks about this). I cannot see any other interpretation because it isn't intuitive, every compiler I've tried agrees with me, and neither standard pointer conversions (7.3.12 [conv.ptr] paragraph 3) nor static_cast (7.6.1.9 [expr.static.cast] paragraph 5) talk about incomplete class types. If the committee agrees with me, I would like to see 7.3.12 [conv.ptr] paragraph 3 and 7.6.1.9 [expr.static.cast] paragraph 5 explicitly disallow incomplete class types and the wording of 7.6.3 [expr.cast] paragraph 6 changed to not allow any other interpretation.
Proposed resolution (April, 2006):
Change 7.6.3 [expr.cast] paragraph 6 as indicated:
The operand of a cast using the cast notation can be an rvalue of type “pointer to incomplete class type.” The destination type of a cast using the cast notation can be “pointer to incomplete class type.”In such cases, even if there is a inheritance relationship between the source and destination classes, whether the static_cast or reinterpret_cast interpretation is used is unspecified.If both the operand and destination types are class types and one or both are incomplete, it is unspecified whether the static_cast or the reinterpret_cast interpretation is used, even if there is an inheritance relationship between the two classes. [Note: For example, if the classes were defined later in the translation unit, a multi-pass compiler would be permitted to interpret a cast between pointers to the classes as if the class types were complete at that point. —end note]
[Voted into WP at October 2005 meeting.]
7.6.4 [expr.mptr.oper] paragraph 5 contains the following example:
struct S { mutable int i; }; const S cs; int S::* pm = &S::i; // pm refers to mutable member S::i cs.*pm = 88; // ill-formed: cs is a const object
The const object cs is not explicitly initialized, and class S does not have a user-declared default constructor. This makes the code ill-formed as per 9.5 [dcl.init] paragraph 9.
Proposed resolution (April, 2005):
Change the example in 7.6.4 [expr.mptr.oper] paragraph 5 to read as follows:
struct S { S() : i(0) { } mutable int i; }; void f() { const S cs; int S::* pm = &S::i; // pm refers to mutable member S::i cs.*pm = 88; // ill-formed: cs is a const object }
[Voted into the WP at the September, 2008 meeting as part of paper N2757.]
The current Standard leaves it implementation-defined whether integer division rounds the result toward 0 or toward negative infinity and thus whether the result of % may be negative. C99, apparently reflecting (nearly?) unanimous hardware practice, has adopted the rule that integer division rounds toward 0, thus requiring that the result of -1 % 5 be -1. Should the C++ Standard follow suit?
On a related note, does INT_MIN % -1 invoke undefined behavior? The % operator is defined in terms of the / operator, and INT_MIN / -1 overflows, which by Clause 7 [expr] paragraph 5 causes undefined behavior; however, that is not the “result” of the % operation, so it's not clear. The wording of 7.6.5 [expr.mul] paragraph 4 appears to allow % to cause undefined behavior only when the second operand is 0.
Proposed resolution (August, 2008):
Change 7.6.5 [expr.mul] paragraph 4 as follows:
The binary / operator yields the quotient, and the binary % operator yields the remainder from the division of the first expression by the second. If the second operand of / or % is zero the behavior is undefined; otherwise (a/b)*b + a%b is equal to a. If both operands are nonnegative then the remainder is nonnegative; if not, the sign of the remainder is implementation-defined. [Footnote: According to work underway toward the revision of ISO C, the preferred algorithm for integer division follows the rules defined in the ISO Fortran standard, ISO/IEC 1539:1991, in which the quotient is always rounded toward zero. —end footnote]. For integral operands, the / operator yields the algebraic quotient with any fractional part discarded; [Footnote: This is often called “truncation towards zero.” —end footnote] if the quotient a/b is representable in the type of the result, (a/b)*b + a%b is equal to a.
[Drafting note: see C99 6.5.5 paragraph 6.]
[Voted into the WP at the June, 2008 meeting.]
The actual semantics of arithmetic comparison — e.g., whether 1 < 2 yields true or false — appear not to be specified anywhere in the Standard. The C Standard has a general statement that
Each of the operators < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to) shall yield 1 if the specified relation is true and 0 if it is false.
There is no corresponding statement in the C++ Standard.
Proposed resolution (February, 2008):
Append the following paragraph to the end of 7.6.9 [expr.rel]:
If both operands (after conversions) are of arithmetic type, each of the operators shall yield true if the specified relation is true and false if it is false.
Append the following paragraph to the end of 7.6.10 [expr.eq]:
Each of the operators shall yield true if the specified relation is true and false if it is false.
[Voted into WP at October 2005 meeting.]
The problem occurs when the value of the operator is determined to be an rvalue, the selected argument is an lvalue, the type is a class type and a non-const member is invoked on the modifiable rvalue result.
struct B { int v; B (int v) : v(v) { } void inc () { ++ v; } }; struct D : B { D (int v) : B(v) { } }; B b1(42); (0 ? B(13) : b1).inc(); assert(b1.v == 42);
The types of the second and third operands are the same and one is an rvalue. Nothing changes until p6 where an lvalue to rvalue conversion is performed on the third operand. 6.7.7 [class.temporary] states that an lvalue to rvalue conversion produces a temporary and there is nothing to remove it. It seems clear that the assertion must pass, yet most implementations fail.
There seems to be a defect in p3 b2 b1. First, the conditions to get here and pass the test.
If E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1.
If both E1 and E2 are lvalues, passing the conditions here also passes the conditions for p3 b1. Thus, at least one is an rvalue. The case of two rvalues is not interesting and the action is covered by the case when E1 is an rvalue.
(0 ? D(13) : b1).inc(); assert(b1.v == 42);
E1 is changed to an rvalue of type T2 that still refers to the original source class object (or the appropriate subobject thereof). [Note: that is, no copy is made. ]
Having changed the rvalue to base type, we are back to the above case where an lvalue to rvalue conversion is required on the third operand at p6. Again, most implementations fail.
The remaining case, E1 an lvalue and E2 an rvalue, is the defect.
D d1(42); (0 ? B(13) : d1).inc(); assert(d1.v == 42);
The above quote states that an lvalue of type T1 is changed to an rvalue of type T2 without creating a temporary. This is in contradiction to everything else in the standard about lvalue to rvalue conversions. Most implementations pass in spite of the defect.
The usual accessible and unambiguous is missing from the base class.
There seems to be two possible solutions. Following other temporary creations would produce a temporary rvalue of type T1 and change it to an rvalue of type T2. Keeping the no copy aspect of this bullet intact would change the lvalue of type T1 to an lvalue of type T2. In this case the lvalue to rvalue conversion would happen in p6 as usual.
Suggested wording for p3 b2 b1
The base part:
If E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or an accessible and unambiguous base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied:
The same type temporary version:
If E1 is an lvalue, an lvalue to rvalue conversion is applied. The resulting or original rvalue is changed to an rvalue of type T2 that refers to the same class object (or the appropriate subobject thereof). [Note: that is, no copy is made in changing the type of the rvalue. ]
The never copy version:
The lvalue(rvalue) E1 is changed to an lvalue(rvalue) of type T2 that refers to the original class object (or the appropriate subobject thereof). [Note: that is, no copy is made. ]
The test case was posted to clc++m and results for implementations were reported.
#include <cassert> struct B { int v; B (int v) : v(v) { } void inc () { ++ v; } }; struct D : B { D (int v) : B(v) { } }; int main () { B b1(42); D d1(42); (0 ? B(13) : b1).inc(); assert(b1.v == 42); (0 ? D(13) : b1).inc(); assert(b1.v == 42); (0 ? B(13) : d1).inc(); assert(d1.v == 42); } // CbuilderX(EDG301) FFF Rob Williscroft // ICC-8.0 FFF Alexander Stippler // COMO-4.301 FFF Alexander Stippler // BCC-5.4 FFP Rob Williscroft // BCC32-5.5 FFP John Potter // BCC32-5.65 FFP Rob Williscroft // VC-6.0 FFP Stephen Howe // VC-7.0 FFP Ben Hutchings // VC-7.1 FFP Stephen Howe // OpenWatcom-1.1 FFP Stephen Howe // Sun C++-6.2 PFF Ron Natalie // GCC-3.2 PFP John Potter // GCC-3.3 PFP Alexander Stippler // GCC-2.95 PPP Ben Hutchings // GCC-3.4 PPP Florian Weimer
I see no defect with regards to lvalue to rvalue conversions; however, there seems to be disagreement about what it means by implementers. It may not be surprising because 5.16 and passing a POD struct to an ellipsis are the only places where an lvalue to rvalue conversion applies to a class type. Most lvalue to rvalue conversions are on basic types as operands of builtin operators.
Notes from the March 2004 meeting:
We decided all "?" operators that return a class rvalue should copy the second or third operand to a temporary. See issue 86.
Proposed resolution (October 2004):
Change 7.6.16 [expr.cond] bullet 3.2 sub-bullet 1 as follows:
if E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied, E1 is changed to an rvalue of type T2that still refers to the original source class object (or the appropriate subobject thereof). [Note: that is, no copy is made. —end note]by copy-initializing a temporary of type T2 from E1 and using that temporary as the converted operand.
Change 7.6.16 [expr.cond] bullet 6.1 as follows:
The second and third operands have the same type; the result is of that type. If the operands have class type, the result is an rvalue temporary of the result type, which is copy-initialized from either the second operand or the third operand depending on the value of the first operand.
Change 7.3.2 [conv.lval] paragraph 2 as follows:
The value contained in the object indicated by the lvalue is the rvalue result.When an lvalue-to-rvalue conversion occurs within the operand of sizeof (7.6.2.5 [expr.sizeof]) the value contained in the referenced object is not accessed, since that operator does not evaluate its operand. Otherwise, if the lvalue has a class type, the conversion copy-initializes a temporary of type T from the lvalue and the result of the conversion is an rvalue for the temporary. Otherwise, the value contained in the object indicated by the lvalue is the rvalue result.
[Note: this wording partially resolves issue 86. See also issue 462.]
[Voted into the WP at the June, 2008 meeting as paper N2634.]
I've seen some pieces of code recently that put complex expressions involving overload resolution inside sizeof operations in constant expressions in templates.
7.7 [expr.const] paragraph 1 implies that some kinds of nonconstant expressions are allowed inside a sizeof in a constant expression, but it's not clear that this was intended to extend all the way to things like overload resolution. Allowing such things has some hidden costs. For example, name mangling has to be able to represent all operators, including calls, and not just the operators that can appear in constant expressions.
template <int I> struct A {}; char xxx(int); char xxx(float); template <class T> A<sizeof(xxx((T)0))> f(T){} int main() { f(1); }
If complex expressions are indeed allowed, it should be because of an explicit committee decision rather than because of some looseness in this section of the standard.
Notes from the 4/02 meeting:
Any argument for restricting such expressions must involve a cost/benefit ratio: a restriction would be palatable only if it causes minimum hardship for users and allows a substantial reduction in implementation cost. If we propose a restriction, it must be one that library writers can live with.
Lots of these cases fail with current compilers, so there can't be a lot of existing code using them. We plan to find out what cases there are in libraries like Loki and Boost.
We noted that in many cases one can move the code into a class to get the same result. The implementation problem comes up when the expression-in-sizeof is in a template deduction context or part of a template signature. The problem cases are ones where an error causes deduction to fail, as opposed to contexts where an error causes a diagnostic. The latter contexts are easier to handle; however, there are situations where "fail deduction" may be the desired behavior.
Notes from the April 2003 meeting:
Here is a better example:
extern "C" int printf(const char *, ...); char f(int); int f(...); // Approach 1 -- overload resolution in template class // No problem template <class T> struct conv_int { static const bool value = (sizeof(f(T())) == 1); }; // Approach 2 -- overload resolution in type deduction // Difficult template <int I> struct A { static const int value = I; }; template <class T> bool conv_int2(A<sizeof(f(T()))> p) { return p.value == 1; } template<typename T> A<sizeof(f(T()))> make_A() { return A<sizeof(f(T()))>(); } int main() { printf("short: %d\n", conv_int<short>::value); printf("int *: %d\n", conv_int<int *>::value); printf("short: %d\n", conv_int2<short>(make_A<short>())); printf("int *: %d\n", conv_int2<int *>(make_A<int*>())); }
The core working group liked the idea of a restriction that says that expressions inside sizeof in template signature contexts must be otherwise valid as nontype template argument expressions (i.e., integer operations only, limited casts). This of course is subject to whether users can live with that restriction. This topic was brought up in full committee, but there was limited feedback from other groups.
It was also noted that if typeof (whatever it is called) is added, there may be a similar issue there.
Note (March, 2005):
Dave Abrahams (quoting a Usenet posting by Vladimir Marko): The decltype and auto proposal (revision 3: N1607) presents
template <class T,class U> decltype((*(T*)0)+(*(U*)0)) add(const T& t,const U& u);
as a valid declaration (if the proposal is accepted). If [the restrictions in the April, 2003 note] really applied to decltype, the declaration above would be invalid. AFAICT every non-trivial use of decltype in a template function declaration would be invalid. And for me this would render my favorite proposal useless.
I would propose to allow any kind of expression inside sizeof (and decltype) and explicitly add sizeof (and decltype) expressions involving template-parameters to non-deduced contexts (add a bullet to 13.10.3.5 [temp.deduct.partial] paragraph 4).
Jaakko Jarvi: Just reinforcing that this is important and hope for insights. The topic is discussed a bit on page 10 of the latest revision of the proposal (N1705). Here's a quote from the proposal:
However, it is crucial that no restrictions are placed on what kinds of expressions are allowed inside decltype, and therefore also inside sizeof. We suggest that issue 339 is resolved to require the compiler to fail deduction (apply the SFINAE principle), and not produce an error, for as large set of invalid expressions in operands of sizeof or decltype as is possible to comfortably implement. We wish that implementors aid in classifying the kinds of expressions that should produce errors, and the kinds that should lead to failure of deduction.
Notes from the April, 2007 meeting:
The CWG is pursuing a compromise proposal, to which the EWG has tentatively agreed, which would allow arbitrary expressions in the return types of function templates but which would restrict the expressions that participate in the function signature (and thus in overload resolution) to those that can be used as non-type template arguments. During deduction and overload resolution, these complex return types would be ignored; that is, there would be no substitution of the deduced template arguments into the return type at this point. If such a function were selected by overload resolution, however, a substitution failure in the return type would produce a diagnostic rather than a deduction failure.
This approach works when doing overload resolution in the context of a function call, but additional tricks (still being defined) are needed in other contexts such as friend function declaration matching and taking the address of a function, in which the return type does play a part.
Notes from the July, 2007 meeting:
The problem is whether arbitrary expressions (for example, ones that include overload resolution) are allowed in template deduction contexts, and, if so, which expression errors are SFINAE failures and which are hard errors.
This issue deals with arbitrary expressions inside sizeof in deduction contexts. That's a fringe case right now (most compilers don't accept them). decltype makes the problem worse, because the standard use case is one that involves overload resolution. Generalized constant expressions make it worse yet, because they allow overload resolution and class types to show up in any constant expression in a deduction context.
Why is this an issue? Why don't we just say everything is allowed and be done with it?
At the April, 2007 meeting, we were headed toward a solution that imposed a restriction on expressions in deduction contexts, but such a restriction seems to really hamper uses of constexpr functions. So we're now proposing that fully general expressions be allowed, and that most errors in such expressions be treated as SFINAE failures rather than errors.
One issue with writing Standard wording for that is how to define “most.” There's a continuum of errors, some errors being clearly SFINAE failures, and some clearly “real” errors, with lots of unclear cases in between. We decided it's easier to write the definition by listing the errors that are not treated as SFINAE failures, and the list we came up with is as follows:
Everything else produces a SFINAE failure rather than a hard error.
There was broad consensus that this felt like a good solution, but that feeling was mixed with trepidation on several fronts:
We will be producing wording for the Working Draft for the October, 2007 meeting.
(See also issue 657.)
[Voted into WP at October 2003 meeting.]
According to 15.2 [cpp.cond] paragraph 1, the if-group
#if "Hello, world"
is well-formed, since it is an integral constant expression. Since that may not be obvious, here is why:
7.7 [expr.const] paragraph 1 says that an integral constant expression may involve literals (5.13 [lex.literal]); "Hello, world" is a literal. It restricts operators to not use certain type conversions; this expression does not use type conversions. It further disallows functions, class objects, pointers, ... - this expression is none of those, since it is an array.
However, 15.2 [cpp.cond] paragraph 6 does not explain what to do with this if-group, since the expression evaluates neither to false(zero) nor true(non-zero).
Proposed resolution (October 2002):
Change the beginning of the second sentence of 7.7 [expr.const] paragraph 1 which currently reads
An integral constant-expression can involve only literals (5.13 [lex.literal]), ...to say
An integral constant-expression can involve only literals of arithmetic types (5.13 [lex.literal], 6.8.2 [basic.fundamental]), ...
[Voted into WP at the October, 2006 meeting.]
The following translation unit appears to be well-formed.
int x[true?throw 4:5];
According to 7.7 [expr.const], this appears to be an integral constant expression: it is a conditional expression, involves only literals, and no assignment, increment, decrement, function-call, or comma operators. However, if this is well-formed, the standard gives no meaning to this declaration, since the array bound (9.3.4.5 [dcl.array] paragraph 1) cannot be computed.
I believe the defect is that throw expressions should also be banned from constant expressions.
Notes from October 2002 meeting:
We should also check on new and delete.
Notes from the April, 2005 meeting:
Although it could be argued that all three of these operators potentially involve function calls — throw to std::terminate, new and delete to the corresponding allocation and deallocation functions — and thus would already be excluded from constant expressions, this reasoning was considered to be too subtle to allow closing the issue with no change. A modification that explicitly clarifies the status of these operators will be drafted.
Proposed resolution (October, 2005):
Change the last sentence of 7.7 [expr.const] as indicated:
In particular, except in sizeof expressions, functions, class objects, pointers, or references shall not be used, and assignment, increment, decrement,function-callfunction call (including new-expressions and delete-expressions),orcomma operators, or throw-expressions shall not be used.
Note: this sentence is also changed by the resolution of issue 530.
[Voted into WP at April 2005 meeting.]
I'm looking at 7.7 [expr.const]. I see:
An integral constant-expression can involve only ... const variables or static data members of integral or enumeration types initialized with constant expressions ...
Shouldn't that be "const non-volatile"?
It seems weird to say that:
const volatile int i = 3; int j[i];is valid.
Steve Adamczyk: See issue 76, which made the similar change to 9.2.9.2 [dcl.type.cv] paragraph 2, and probably should have changed this one as well.
Proposed resolution (October, 2004):
Change the first sentence in the second part of 7.7 [expr.const] paragraph 1 as follows:
An integral constant-expression can involve only literals of arithmetic types (5.13 [lex.literal], 6.8.2 [basic.fundamental]), enumerators, non-volatile const variables or static data members of integral or enumeration types initialized with constant expressions (9.5 [dcl.init]), non-type template parameters of integral or enumeration types, and sizeof expressions.
[Voted into the WP at the April, 2007 meeting as part of paper J16/07-0095 = WG21 N2235.]
Consider:
template <int* p> struct S { static const int I = 3; }; int i; int a[S<&i>::I];
Clearly this should be valid, but a pedantic reading of 7.7 [expr.const] would suggest that this is invalid because “&i” is not permitted in integral constant expressions.
Proposed resolution (October, 2005):
Change the last sentence of 7.7 [expr.const] paragraph 1 as indicated:
In particular, except in non-type template-arguments or sizeof expressions, functions, class objects, pointers, or references shall not be used, and assignment, increment, decrement, function-call, or comma operators shall not be used.
(Note: the same text is changed by the resolution of issue 367.)
Notes from April, 2006 meeting:
The proposed resolution could potentially be read as saying that the prohibited operations and operators would be permitted in integral constant expressions that are non-type template-arguments. John Spicer is investigating an alternate approach, to say that expressions in non-type template arguments are not part of the expression in which the template-id appears (in contrast to the operand of sizeof, which is part of the containing expression).
Additional note (May, 2008):
This issue is resolved by the rewrite of 7.7 [expr.const] that was done in conjunction with the constexpr proposal, paper N2235.
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
The expressions that are excluded from being constant expressions in 7.7 [expr.const] paragraph 2 does not address an example like the following:
void f() {
int a;
constexpr int* p = &a; // should be ill-formed, currently isn't
}
Suggested resolution:
Add the following bullet to the list in 7.7 [expr.const] paragraph 2:
an id-expression that refers to a variable with automatic storage duration unless a permitted lvalue-to-rvalue conversion is applied (see above)
Proposed resolution (June, 2008):
Change 6.9.3.2 [basic.start.static] paragraph 1 as follows:
Objects with static storage duration (6.7.6.2 [basic.stc.static]) or thread storage duration (3.7.2) shall be zero-initialized (9.5 [dcl.init]) before any other initialization takes place.A reference with static or thread storage duration and an object of trivial or literal type with static or thread storage duration can be initialized with a constant expression (7.7 [expr.const]); this is called constant initialization.Constant initialization is performed:Together, zero-initialization and constant initialization...
if an object of trivial or literal type with static or thread storage duration is initialized with a constant expression (7.7 [expr.const]), or
if a reference with static or thread storage duration is initialized with a constant expression that is not an lvalue designating an object with thread or automatic storage duration.
Add the following in 7.7 [expr.const] paragraph 2:
an lvalue-to-rvalue conversion (4.1) unless it is applied to...
an array-to-pointer conversion (7.3.3 [conv.array]) that is applied to an lvalue that designates an object with thread or automatic storage duration
a unary operator & (7.6.2.2 [expr.unary.op]) that is applied to an lvalue that designates an object with thread or automatic storage duration
an id-expression that refers to a variable or data member of reference type;
...
(Note: the change to 6.9.3.2 [basic.start.static] paragraph 1 needs to be reconciled with the conflicting change in issue 688.)
[Voted into the WP at the June, 2008 meeting.]
According to 8.7 [stmt.jump] paragraph 2,
On exit from a scope (however accomplished), destructors (11.4.7 [class.dtor]) are called for all constructed objects with automatic storage duration (6.7.6.4 [basic.stc.auto]) (named objects or temporaries) that are declared in that scope, in the reverse order of their declaration.
This wording is problematic for temporaries and for parameters. First, temporaries are not "declared," so this requirement does not apply to them, in spite of the assertion in the quoted text that it does.
Second, although the parameters of a function are declared in the called function, they are constructed and destroyed in the calling context, and the order of evaluation of the arguments is unspecified (cf 7.6.1.3 [expr.call] paragraphs 4 and 8). The order of destruction of the parameters might, therefore, be different from the reverse order of their declaration.
Notes from 04/01 meeting:
Any resolution of this issue should be careful not to introduce requirements that are redundant or in conflict with those of other parts of the IS. This is especially true in light of the pending issues with respect to the destruction of temporaries (see issues 86, 124, 199, and 201). If possible, the wording of a resolution should simply reference the relevant sections.
It was also noted that the temporary for a return value is also destroyed "out of order."
Note that issue 378 picks a nit with the wording of this same paragraph.
Proposed Resolution (November, 2006):
Change 8.7 [stmt.jump] paragraph 2 as follows:
On exit from a scope (however accomplished),destructors (11.4.7 [class.dtor]) are called for all constructed objects with automatic storage duration (6.7.6.4 [basic.stc.auto]) (named objects or temporaries) that are declared in that scope, in the reverse order of their declaration.variables with automatic storage duration (6.7.6.4 [basic.stc.auto]) that have been constructed in that scope are destroyed in the reverse order of their construction. [Note: For temporaries, see 6.7.7 [class.temporary]. —end note] Transfer out of a loop...
Paragraph 8.7 [stmt.jump] paragraph 2 of the standard says:
On exit from a scope (however accomplished), destructors (11.4.7 [class.dtor]) are called for all constructed objects with automatic storage duration (6.7.6.4 [basic.stc.auto]) (named objects or temporaries) that are declared in that scope.
It refers to objects "that are declared" but the text in parenthesis also mentions temporaries, which cannot be declared. I think that text should be removed.
This is related to issue 276.
Proposed Resolution (November, 2006):
This issue is resolved by the resolution of issue 276.
[Moved to DR at October 2002 meeting.]
There is currently no restriction on the use of the inline specifier in friend declarations. That would mean that the following usage is permitted:
struct A { void f(); }; struct B { friend inline void A::f(); }; void A::f(){}
I believe this should be disallowed because a friend declaration in one class should not be able to change attributes of a member function of another class.
More generally, I think that the inline attribute should only be permitted in friend declarations that are definitions.
Notes from the 04/01 meeting:
The consensus agreed with the suggested resolution. This outcome would be similar to the resolution of issue 136.
Proposed resolution (10/01):
Add to the end of 9.2.3 [dcl.fct.spec] paragraph 3:
If the inline specifier is used in a friend declaration, that declaration shall be a definition or the function shall have previously been declared inline.
[Voted into WP at October 2005 meeting.]
Steve Clamage: Consider this sequence of declarations:
void foo() { ... } inline void foo();The non-inline definition of foo precedes the inline declaration. It seems to me this code should be ill-formed, but I could not find anything in the standard to cover the situation.
Bjarne Stroustrup: Neither could I, so I looked in the ARM, which addressed this case (apparently for member function only) in some detail in 7.1.2 (pp103-104).
The ARM allows declaring a function inline after its initial declaration, as long as it has not been called.
Steve Clamage: If the above code is valid, how about this:
void foo() { ... } // define foo void bar() { foo(); } // use foo inline void foo(); // declare foo inline
Bjarne Stroustrup: ... and [the ARM] disallows declaring a function inline after it has been called.
This may still be a good resolution.
Steve Clamage: But the situation in the ARM is the reverse: Declare a function inline, and define it later (with no intervening call). That's a long-standing rule in C++, and allows you to write member function definitions outside the class.
In my example, the compiler could reasonably process the entire function as out-of-line, and not discover the inline declaration until it was too late to save the information necessary for inline generation. The equivalent of another compiler pass would be needed to handle this situation.
Bjarne Stroustrup: I see, and I think your argument it conclusive.
Steve Clamage: I'd like to open a core issue on this point, and I recommend wording along the lines of: "A function defined without an inline specifier shall not be followed by a declaration having an inline specifier."
I'd still like to allow the common idiom
class T { int f(); }; inline int T::f() { ... }
Martin Sebor: Since the inline keyword is just a hint to the compiler, I don't see any harm in allowing the construct. Your hypothetical compiler can simply ignore the inline on the second declaration. On the other hand, I feel that adding another special rule will unnecessarily complicate the language.
Steve Clamage: The inline specifier is more than a hint. You can have multiple definitions of inline functions, but only one definition of a function not declared inline. In particular, suppose the above example were in a header file, and included multiple times in a program.
Proposed resolution (October, 2004):
Add the indicated words to 9.2.3 [dcl.fct.spec] paragraph 4:
An inline function shall be defined in every translation unit in which it is used and shall have exactly the same definition in every case (6.3 [basic.def.odr]). [Note: a call to the inline function may be encountered before its definition appears in the translation unit. —end note] If the definition of a function appears in a translation unit before its first declaration as inline, the program is ill-formed. If a function with external linkage is declared inline in one translation unit...
[Voted into WP at March 2004 meeting.]
BTW, I noticed that the following note in 9.2.2 [dcl.stc] paragraph 2 doesn't seem to have made it onto the issues list or into the TR:
[Note: hence, the auto specifier is almost always redundant and not often used; one use of auto is to distinguish a declaration-statement from an expression-statement (stmt.ambig) explicitly. --- end note]
I thought that this was well known to be incorrect, because using auto does not disambiguate this. Writing:
auto int f();is still a declaration of a function f, just now with an error since the function's return type may not use an auto storage class specifier. I suppose an error is an improvement over a silent ambiguity going the wrong way, but it's still not a solution for the user who wants to express the other in a compilable way.
Proposed resolution: Replace that note with the following note:
[Note: hence, the auto specifier is always redundant and not often used. --- end note]
John Spicer: I support the proposed change, but I think the disambiguation case is not the one that you describe. An example of the supposed disambiguation is:
int i; int j; int main() { int(i); // declares i, not reference to ::i auto int(j); // declares j, not reference to ::j }
cfront would take "int(i)" as a cast of ::i, so the auto would force what it would otherwise treat as a statement to be considered a declaration (cfront 3.0 warned that this would change in the future).
In a conforming compiler the auto is always redundant (as you say) because anything that could be considered a valid declaration should be treated as one.
Proposed resolution (April 2003):
Replace 9.2.2 [dcl.stc] paragraph 2
[Note: hence, the auto specifier is almost always redundant and not often used; one use of auto is to distinguish a declaration-statement from an expression-statement (8.10 [stmt.ambig]) explicitly. --- end note]with
[Note: hence, the auto specifier is always redundant and not often used. One use of auto is to distinguish a declaration-statement from an expression-statement explicitly rather than relying on the disambiguation rules (8.10 [stmt.ambig]), which may aid readers. --- end note]
[Voted into WP at April, 2007 meeting.]
Are string literals from default arguments used in extern inlines supposed to have the same addresses across all translation units?
void f(const char* = "s") inline g() { f(); }
Must the "s" strings be the same in all copies of the inline function?
Steve Adamczyk: The totality of the standard's wisdom on this topic is (9.2.3 [dcl.fct.spec] paragraph 4):
A string literal in an extern inline function is the same object in different translation units.
I'd hazard a guess that a literal in a default argument expression is not "in" the extern inline function (it doesn't appear in the tokens of the function), and therefore it need not be the same in different translation units.
I don't know that users would expect such strings to have the same address, and an equally valid (and incompatible) expectation would be that the same string literal would be used for every expansion of a given default argument in a single translation unit.
Notes from April 2003 meeting:
The core working group feels that the address of a string literal should be guaranteed to be the same only if it actually appears textually within the body of the inline function. So a string in a default argument expression in a block extern declaration inside the body of a function would be the same in all instances of the function. On the other hand, a string in a default argument expression in the header of the function (i.e., outside of the body) would not be the same.
Proposed resolution (April 2003):
Change the last sentence and add the note to the end of 9.2.3 [dcl.fct.spec] paragraph 4:
A string literal in the body of an extern inline function is the same object in different translation units. [Note: A string literal that is encountered only in the context of a function call (in the default argument expression of the called function), is not “in” the extern inline function.]
Notes from October 2003 meeting:
We discussed ctor-initializer lists and decided that they are also part of the body. We've asked Clark Nelson to work on syntax changes to give us a syntax term for the body of a function so we can refer to it here. See also issue 452, which could use this term.
(October, 2005: moved to “review” status in concert with issue 452. With that resolution, the wording above needs no further changes.)
Proposed resolution (April, 2006):
Change the last sentence and add the note to the end of 9.2.3 [dcl.fct.spec] paragraph 4:
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 considered to be “in the body” of an inline function merely by virtue of the expression’s use in a function call from that inline function. —end note]
[Voted into WP at the October, 2006 meeting.]
I couldn't find wording that makes it invalid to say friend virtual... The closest seems to be 9.2.3 [dcl.fct.spec] paragraph 5, which says:
The virtual specifier shall only be used in declarations of nonstatic class member functions that appear within a member-specification of a class definition; see 11.7.3 [class.virtual].
I don't think that excludes a friend declaration (which is a valid member-specification by 11.4 [class.mem]).
John Spicer: I agree that virtual should not be allowed on friend declarations. I think the wording in 9.2.3 [dcl.fct.spec] is intended to be the declaration of a function within its class, although I think the wording should be improved to make it clearer.
Proposed resolution (October, 2005):
Change 9.2.3 [dcl.fct.spec] paragraphs 5-6 as indicated:
The virtual specifier shall
onlybe used only indeclarationsthe initial declaration of a non-static class memberfunctions that appear within a member-specification of a class definitionfunction; see 11.7.3 [class.virtual].The explicit specifier shall be used only in
declarationsthe declaration ofconstructorsa constructor withinaits class definition; see 11.4.8.2 [class.conv.ctor].
[Voted into WP at March 2004 meeting.]
I wonder if perhaps the core issue 56 change in 9.2.4 [dcl.typedef] paragraph 2 wasn't quite careful enough. The intent was to remove the allowance for:
struct S { typedef int I; typedef int I; };
but I think it also disallows the following:
class B { typedef struct A {} A; void f(struct B::A*p); };
See also issue 407.
Proposed resolution (October 2003):
At the end of 9.2.4 [dcl.typedef] paragraph 2, add the following:
In a given class scope, a typedef specifier can be used to redefine any class-name declared in that scope that is not also a typedef-name to refer to the type to which it already refers. [Example:struct S { typedef struct A {} A; // OK typedef struct B B; // OK typedef A A; // error };]
[Voted into the WP at the September, 2008 meeting.]
According to 9.2.6 [dcl.constexpr] paragraph 5,
If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function, the constexpr specifier is ignored and the specialization is not a constexpr function.
One would expect to see a similar provision for an instantiated constructor template (because the requirements for a constexpr function [paragraph 3] are different from the requirements for a constexpr constructor [paragraph 4]), but there is none; constexpr constructor templates are not mentioned.
Suggested resolution:
Change the wording of 9.2.6 [dcl.constexpr] paragraph 5 as indicated:
If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function or constexpr constructor, as appropriate to the function template, the constexpr specifier is ignored and the specialization is not a constexpr function or constexpr constructor.
Proposed resolution (June, 2008):
[Drafting note: This resolution goes beyond the problem described in the issue discussion, which is one aspect of the general failure of the existing wording to deal consistently with the distinctions between constexpr functions and constexpr constructors. The wording below attempts to rectify that problem systematically.]
Change 9.2.6 [dcl.constexpr] paragraph 2 as follows:
A constexpr specifier used ina function declarationthe declaration of a function that is not a constructor declares that function to be a constexpr function. Similarly, a constexpr specifier used in a constructor declaration declares that constructor to be a constexpr constructor. Constexpr functions and constexpr constructors are implicitly inline (9.2.3 [dcl.fct.spec]).A constexpr function shall not be virtual (10.3).
Change 9.2.6 [dcl.constexpr] paragraph 3 as follows:
The definition of a constexpr function shall satisfy the following constraints:
it shall not be virtual (11.7.3 [class.virtual])
its return type shall be a literal type
each of its parameter types shall be a literal type
its function-body shall be a compound-statement of the form
{ return expression ; }
where expression is a potential constant expression (7.7 [expr.const])
every implicit conversion used in converting expression to the function return type (9.5 [dcl.init]) shall be one of those allowed in a constant expression (7.7 [expr.const]).
[Example:...
Change 9.2.6 [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 (11.9.3 [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
invoked with potential constant expression arguments, if any.every constructor argument and full-expression in a mem-initializer shall be a potential 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.
A trivial copy constructor is also a constexpr constructor. [Example: ...
Change 9.2.6 [dcl.constexpr] paragraph 5 as follows:
If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function or constexpr constructor, the constexpr specifier is ignoredand the specialization is not a constexpr function.
Change 9.2.6 [dcl.constexpr] paragraph 6 as follows:
A constexpr specifierused infor a non-static member functiondefinitionthat is not a constructor declares that member function to be const (11.4.3 [class.mfct.non.static]). [Note: ...
[Voted into the WP at the September, 2008 meeting.]
The current wording of 9.2.6 [dcl.constexpr] paragraph 7 seems not quite correct. It reads,
A constexpr specifier used in an object declaration declares the object as const. Such an object shall be initialized, and every expression that appears in its initializer (9.5 [dcl.init]) shall be a constant expression.
The phrase “every expression” is intended to cover multiple arguments to a constexpr constructor and multiple expressions in an aggregate initializer. However, it could be read (incorrectly) as saying that non-constant expressions cannot appear as subexpressions in such initializers, even in places where they do not render the full-expression non-constant (i.e., as unevaluated operands and in the unselected branches of &&, ||, and ?:). Perhaps this problem could be remedied by replacing “every expression” with “every full-expression?”
Proposed resolution (June, 2008):
Change 9.2.6 [dcl.constexpr] paragraph 7 as follows:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall beinitialized, and every expression that appears in its initializer (8.5)initialized. If it is initialized by a constructor call, the constructor shall be a constexpr constructor and every argument to the constructor shall be a constant expression. Otherwise, every full-expression that appears in its initializer shall be a constant expression. Every implicit conversion used...
[Voted into WP at April 2003 meeting.]
Although 13.2 [temp.param] paragraph 3 contains an assertion that
A type-parameter defines its identifier to be a type-name (if declared with class or typename)
the grammar in 9.2.9.3 [dcl.type.simple] paragraph 1 says that a type-name is either a class-name, an enum-name, or a typedef-name. The identifier in a template type-parameter is none of those. One possibility might be to equate the identifier with a typedef-name instead of directly with a type-name, which would have the advantage of not requiring parallel treatment of the two in situations where they are treated the same (e.g., in elaborated-type-specifiers, see issue 245). See also issue 215.
Proposed resolution (Clark Nelson, March 2002):
In 13.2 [temp.param] paragraph 3, change "A type-parameter defines its identifier to be a type-name" to "A type-parameter defines its identifier to be a typedef-name"
In 9.2.9.5 [dcl.type.elab] paragraph 2, change "If the identifier resolves to a typedef-name or a template type-parameter" to "If the identifier resolves to a typedef-name".
This has been consolidated with the edits for some other issues. See N1376=02-0034.
[Voted into WP at the October, 2006 meeting.]
9.2.9.3 [dcl.type.simple] paragraph 3 reads,
It is implementation-defined whether bit-fields and objects of char type are represented as signed or unsigned quantities. The signed specifier forces char objects and bit-fields to be signed; it is redundant with other integral types.
The last sentence in that quote is misleading w.r.t. bit-fields. The first sentence in that quote is correct but incomplete.
Proposed fix: change the two sentences to read:
It is implementation-defined whether objects of char type are represented as signed or unsigned quantities. The signed specifier forces char objects signed; it is redundant with other integral types except when declaring bit-fields (11.4.10 [class.bit]).
Proposed resolution (October, 2005):
Change 9.2.9.3 [dcl.type.simple] paragraph 3 as indicated:
When multiple simple-type-specifiers are allowed, they can be freely intermixed with other decl-specifiers in any order. [Note: It is implementation-defined whetherbit-fields andobjects of char type and certain bit-fields (11.4.10 [class.bit]) are represented as signed or unsigned quantities. The signed specifier forces bit-fields and char objectsand bit-fieldsto be signed; it is redundantwith other integral typesin other contexts. —end note]
[Voted into the WP at the September, 2008 meeting.]
The second bullet of 9.2.9.3 [dcl.type.simple] paragraph 4 reads,
- otherwise, if e is a function call (7.6.1.3 [expr.call]) or an invocation of an overloaded operator (parentheses around e are ignored), decltype(e) is the return type of that function;
The reference to “that function” is imprecise; it is not the actual function called at runtime but the statically chosen function (ignoring covariant return types in virtual functions).
Also, the examples in this paragraph have errors:
The declaration of struct A should end with a semicolon.
The lines of the form decltype(...); are ill-formed; they need a declarator.
Proposed Resolution (October, 2007):
Change 9.2.9.3 [dcl.type.simple] paragraph 4 as follows:
The type denoted by decltype(e) is defined as follows:
if e is an id-expression or a class member access (7.6.1.5 [expr.ref]), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;
otherwise, if e is a function call (7.6.1.3 [expr.call]) or an invocation of an overloaded operator (parentheses around e are ignored), decltype(e) is the return type of
thatthe statically chosen function;otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
otherwise, decltype(e) is the type of e.
The operand of the decltype specifier is an unevaluated operand (Clause 7 [expr]).
[Example:
const int&& foo(); int i; struct A { double x; }; const A* a = new A(); decltype(foo()) x1; // type is const int&& decltype(i) x2; // type is int decltype(a->x) x3; // type is double decltype((a->x)) x4; // type is const double&—end example]
[Voted into the WP at the February, 2008 meeting as paper J16/08-0056 = WG21 N2546.]
We've found an interesting parsing ambiguity with the new meaning of auto. Consider:
typedef int T; void f() { auto T = 42; // Valid or not? }
The question here is whether T should be a type specifier or a storage class? 9.2.9.7 [dcl.spec.auto] paragraph 1 says,
The auto type-specifier has two meanings depending on the context of its use. In a decl-specifier-seq that contains at least one type-specifier (in addition to auto) that is not a cv-qualifier, the auto type-specifier specifies that the object named in the declaration has automatic storage duration.
In this case, T is a type-specifier, so the declaration is ill-formed: there is no declarator-id. Many, however, would like to see auto work “just like int,” i.e., forcing T to be redeclared in the inner scope. Concerns cited included hijacking of the name in templates and inline function bodies over the course of time if a program revision introduces a type with that name in the surrounding context. Although it was pointed out that enclosing the name in parentheses in the inner declaration would prevent any such problems, this was viewed as unacceptably ugly.
Notes from the April, 2007 meeting:
The CWG wanted to avoid a rule like, “if auto can be a type-specifier, it is” (similar to the existing “if it can be a declaration, it is” rule) because of the lookahead and backtracking difficulties such an approach would pose for certain kinds of parsing techniques. It was noted that the difficult lookahead cases all involve parentheses, which would not be a problem if only the “=” form of initializer were permitted in auto declarations; only very limited lookahead is required in that case. It was also pointed out that the “if it can be a type-specifier, it is” approach results in a quiet change of meaning for cases like
typedef int T; int n = 3; void f() { auto T(n); }
This currently declares n to be an int variable in the inner scope but would, under the full lookahead approach, declare T to be a variable, quitely changing uses of n inside f() to refer to the outer variable.
The consensus of the CWG was to pursue the change to require the “=” form of initializer for auto.
Notes from the July, 2007 meeting:
See paper J16/07-0197 = WG21 N2337. There was no consensus among the CWG for either of the approaches recommended in the paper; additional input and direction is required.
[Voted into the WP at the September, 2008 meeting.]
The restrictions on declaring and/or defining classes inside type-specifier-seqs and type-ids are inconsistent throughout the Standard. This is probably due to the fact that nearly all of the sections that deal with them attempt to state the restriction afresh. There are three cases:
7.6.2.8 [expr.new], 8.5 [stmt.select], and 11.4.8.3 [class.conv.fct] prohibit “declarations” of classes and enumerations. That means that
while (struct C* p = 0) ;
is ill-formed unless a prior declaration of C has been seen. These appear to be cases that should have been fixed by issue 379, changing “class declaration” to “class definition,” but were overlooked.
7.5.6 [expr.prim.lambda], 9.1 [dcl.pre], and 9.3.4.6 [dcl.fct] (late-specified return types) do not contain any restriction at all.
All the remaining cases prohibit “type definitions,” apparently referring to classes and enumerations.
Suggested resolution:
Add something like, “A class or enumeration shall not be defined in a type-specifier-seq or in a type-id,” to a single place in the Standard and remove all other mentions of that restriction (allowing declarations via elaborated-type-specifier).
Mike Miller:
An alias-declaration is just a different syntax for a typedef declaration, which allows definitions of a class in the type; I would expect the same to be true of an alias-declaration. I don't have any particularly strong attachment to allowing a class definition in an alias-declaration. My only concern is introducing an irregularity into what are currently exact-match semantics with typedefs.
There's a parallel restriction in many (but not all?) of these places on typedef declarations.
Jens Maurer:
Those are redundant, as typedef is not a type-specifier, and should be removed as well.
Proposed resolution (March, 2008):
Delete the indicated words from 7.6.1.7 [expr.dynamic.cast] paragraph 1:
...Types shall not be defined in a dynamic_cast....
Delete the indicated words from 7.6.1.8 [expr.typeid] paragraph 4:
...Types shall not be defined in the type-id....
Delete the indicated words from 7.6.1.9 [expr.static.cast] paragraph 1:
...Types shall not be defined in a static_cast....
Delete the indicated words from 7.6.1.10 [expr.reinterpret.cast] paragraph 1:
...Types shall not be defined in a reinterpret_cast....
Delete the indicated words from 7.6.1.11 [expr.const.cast] paragraph 1:
...Types shall not be defined in a const_cast....
Delete paragraph 5 of 7.6.2.5 [expr.sizeof]:
Types shall not be defined in a sizeof expression.
Delete paragraph 5 of 7.6.2.8 [expr.new]:
The type-specifier-seq shall not contain class declarations, or enumeration declarations.
Delete paragraph 4 of 7.6.2.6 [expr.alignof]:
A type shall not be defined in an alignof expression.
Delete paragraph 3 of 7.6.3 [expr.cast]:
Types shall not be defined in casts.
Delete the indicated words from 8.5 [stmt.select] paragraph 2:
...The type-specifier-seq shall not contain typedef and shall not declare a new class or enumeration....
Add the indicated words to 9.2.9 [dcl.type] paragraph 3:
At least one type-specifier that is not a cv-qualifier is required in a declaration unless it declares a constructor, destructor or conversion function. [Footnote: ... ] A type-specifier-seq shall not define a class or enumeration unless it appears in the type-id of an alias-declaration (9.2.4 [dcl.typedef]).
Delete the indicated words from 11.4.8.3 [class.conv.fct] paragraph 1:
...Classes, enumerations, and typedef-names shall not be declared in the type-specifier-seq....
Delete the indicated words from 14.4 [except.handle] paragraph 1:
...Types shall not be defined in an exception-declaration.
Delete paragraph 6 of 14.5 [except.spec]:
Types shall not be defined in exception-specifications.
[Drafting note: no changes are required to 7.5.6 [expr.prim.lambda], 9.2.4 [dcl.typedef], 9.13.2 [dcl.align], 9.8.1 [dcl.enum], 9.3.4.6 [dcl.fct], 13.2 [temp.param], or 13.3 [temp.names].]
[Moved to DR at 10/01 meeting.]
9.3.3 [dcl.ambig.res] paragraph 3 shows an example that includes <cstddef> with no using declarations or directives and refers to size_t without the std:: qualification.
Many references to size_t throughout the document omit the std:: namespace qualification.
This is a typical case. The use of std:: is inconsistent throughout the document.
In addition, the use of exception specifications should be examined for consistency.
(See also issue 282.)
Proposed resolution:
In 6.9.1 [intro.execution] paragraph 9, replace all two instances of "sig_atomic_t" by "std::sig_atomic_t".
In 6.2 [basic.def] paragraph 4, replace all three instances of "string" by "std::string" in the example and insert "#include <string>" at the beginning of the example code.
In 6.9.3.1 [basic.start.main] paragraph 4, replace
Calling the functionvoid exit(int);declared in <cstdlib>...
by
Calling the function std::exit(int) declared in <cstdlib>...
and also replace "exit" by "std::exit" in the last sentence of that paragraph.
In 6.9.3.1 [basic.start.main] first sentence of paragraph 5, replace "exit" by "std::exit".
In 6.9.3.2 [basic.start.static] paragraph 4, replace "terminate" by "std::terminate".
In 6.9.3.3 [basic.start.dynamic] paragraph 1, replace "exit" by "std::exit" (see also issue 28).
In 6.9.3.3 [basic.start.dynamic] paragraph 3, replace all three instances of "atexit" by "std::atexit" and both instances of "exit" by "std::exit" (see also issue 28).
In 6.9.3.3 [basic.start.dynamic] paragraph 4, replace
Calling the functionvoid abort();declared in <cstdlib>...
by
Calling the function std::abort() declared in <cstdlib>...and "atexit" by "std::atexit" (see also issue 28).
In 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 1 third sentence, replace "size_t" by "std::size_t".
In 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 3, replace "new_handler" by "std::new_handler". Furthermore, replace "set_new_handler" by "std::set_new_handler" in the note.
In 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 4, replace "type_info" by "std::type_info" in the note.
In 6.7.6.5.3 [basic.stc.dynamic.deallocation] paragraph 3, replace all four instances of "size_t" by "std::size_t".
In 6.7.4 [basic.life] paragraph 5, replace "malloc" by "std::malloc" in the example code and insert "#include <cstdlib>" at the beginning of the example code.
In 6.8 [basic.types] paragraph 2, replace "memcpy" by "std::memcpy" (the only instance in the footnote and both instances in the example) and replace "memmove" by "std::memmove" in the footnote (see also issue 43).
In 6.8 [basic.types] paragraph 3, replace "memcpy" by "std::memcpy", once in the normative text and once in the example (see also issue 43).
In 6.8.2 [basic.fundamental] paragraph 8 last sentence, replace "numeric_limits" by "std::numeric_limits".
In 7.6.1.7 [expr.dynamic.cast] paragraph 9 second sentence, replace "bad_cast" by "std::bad_cast".
In 7.6.1.8 [expr.typeid] paragraph 2, replace "type_info" by "std::type_info" and "bad_typeid" by "std::bad_typeid".
In 7.6.1.8 [expr.typeid] paragraph 3, replace "type_info" by "std::type_info".
In 7.6.1.8 [expr.typeid] paragraph 4, replace both instances of "type_info" by "std::type_info".
In 7.6.2.5 [expr.sizeof] paragraph 6, replace both instances of "size_t" by "std::size_t".
In 7.6.2.8 [expr.new] paragraph 11 last sentence, replace "size_t" by "std::size_t".
In 7.6.6 [expr.add] paragraph 6, replace both instances of "ptrdiff_t" by "std::ptrdiff_t".
In 7.6.6 [expr.add] paragraph 8, replace "ptrdiff_t" by "std::ptrdiff_t".
In 8.7 [stmt.jump] paragraph 2, replace "exit" by "std::exit" and "abort" by "std::abort" in the note.
In 9.3.3 [dcl.ambig.res] paragraph 3, replace "size_t" by "std::size_t" in the example.
In 9.6 [dcl.fct.def] paragraph 5, replace "printf" by "std::printf" in the note.
In 11.4.7 [class.dtor] paragraph 13, replace "size_t" by "std::size_t" in the example.
In 11.4.11 [class.free] paragraph 2, replace all four instances of "size_t" by "std::size_t" in the example.
In 11.4.11 [class.free] paragraph 6, replace both instances of "size_t" by "std::size_t" in the example.
In 11.4.11 [class.free] paragraph 7, replace all four instances of "size_t" by "std::size_t" in the two examples.
In 11.9.5 [class.cdtor] paragraph 4, replace "type_info" by "std::type_info".
In 12.5 [over.built] paragraph 13, replace all five instances of "ptrdiff_t" by "std::ptrdiff_t".
In 12.5 [over.built] paragraph 14, replace "ptrdiff_t" by "std::ptrdiff_t".
In 12.5 [over.built] paragraph 21, replace both instances of "ptrdiff_t" by "std::ptrdiff_t".
In 13.3 [temp.names] paragraph 4, replace both instances of "size_t" by "std::size_t" in the example. (The example is quoted in issue 96.)
In 13.4 [temp.arg] paragraph 1, replace "complex" by "std::complex", once in the example code and once in the comment.
In 13.9.4 [temp.expl.spec] paragraph 8, issue 24 has already corrected the example.
In 14.2 [except.throw] paragraph 6, replace "uncaught_exception" by "std::uncaught_exception".
In 14.2 [except.throw] paragraph 7, replace "terminate" by "std::terminate" and both instances of "unexpected" by "std::unexpected".
In 14.2 [except.throw] paragraph 8, replace "terminate" by "std::terminate".
In 14.3 [except.ctor] paragraph 3, replace "terminate" by "std::terminate".
In 14.4 [except.handle] paragraph 9, replace "terminate" by "std::terminate".
In 14.5 [except.spec] paragraph 8, replace "unexpected" by "std::unexpected".
In 14.5 [except.spec] paragraph 9, replace "unexpected" by "std::unexpected" and "terminate" by "std::terminate".
In 14.6 [except.special] paragraph 1, replace "terminate" by "std::terminate" and "unexpected" by "std::unexpected".
In the heading of 14.6.2 [except.terminate], replace "terminate" by "std::terminate".
In 14.6.2 [except.terminate] paragraph 1, footnote in the first bullet, replace "terminate" by "std::terminate". In the same paragraph, fifth bullet, replace "atexit" by "std::atexit". In the same paragraph, last bullet, replace "unexpected_handler" by "std::unexpected_handler".
In 14.6.2 [except.terminate] paragraph 2, replace
In such cases,void terminate();is called...
by
In such cases, std::terminate() is called...
and replace all three instances of "terminate" by "std::terminate".
In the heading of _N4606_.15.5.2 [except.unexpected], replace "unexpected" by "std::unexpected".
In _N4606_.15.5.2 [except.unexpected] paragraph 1, replace
...the functionvoid unexpected();is called...
by
...the function std::unexpected() is called....
In _N4606_.15.5.2 [except.unexpected] paragraph 2, replace "unexpected" by "std::unexpected" and "terminate" by "std::terminate".
In _N4606_.15.5.2 [except.unexpected] paragraph 3, replace "unexpected" by "std::unexpected".
In the heading of _N5001_.14.6.3 [except.uncaught], replace "uncaught_exception" by "std::uncaught_exception".
In _N5001_.14.6.3 [except.uncaught] paragraph 1, replace
The functionbool uncaught_exception()returns true...
by
The function std::uncaught_exception() returns true....
In the last sentence of the same paragraph, replace "uncaught_exception" by "std::uncaught_exception".
[Moved to DR at 10/01 meeting.]
Steve Clamage: Section 9.3.4.5 [dcl.array] paragraph 1 reads in part as follows:
Any type of the form "cv-qualifier-seq array of N T" is adjusted to "array of N cv-qualifier-seq T," and similarly for "array of unknown bound of T." [Example:The Note appears to contradict the sentence that precedes it.typedef int A[5], AA[2][3]; typedef const A CA; // type is "array of 5 const int" typedef const AA CAA; // type is "array of 2 array of 3 const int"—end example] [Note: an "array of N cv-qualifier-seq T" has cv-qualified type; such an array has internal linkage unless explicitly declared extern (9.2.9.2 [dcl.type.cv] ) and must be initialized as specified in 9.5 [dcl.init] . ]
Mike Miller: I disagree; all it says is that whether the qualification on the element type is direct ("const int x[5]") or indirect ("const A CA"), the array itself is qualified in the same way the elements are.
Steve Clamage: In addition, section 6.8.5 [basic.type.qualifier] paragraph 2 says:
A compound type (6.8.4 [basic.compound] ) is not cv-qualified by the cv-qualifiers (if any) of the types from which it is compounded. Any cv-qualifiers applied to an array type affect the array element type, not the array type (9.3.4.5 [dcl.array] )."The Note appears to contradict that section as well.
Mike Miller: Yes, but consider the last two sentences of 6.8.5 [basic.type.qualifier] paragraph 5:
Cv-qualifiers applied to an array type attach to the underlying element type, so the notation "cv T," where T is an array type, refers to an array whose elements are so-qualified. Such array types can be said to be more (or less) cv-qualified than other types based on the cv-qualification of the underlying element types.I think this says essentially the same thing as 9.3.4.5 [dcl.array] paragraph 1 and its note: the qualification of an array is (bidirectionally) equivalent to the qualification of its members.
Mike Ball: I find this a very far reach. The text in 9.3.4.5 [dcl.array] is essentially that which is in the C standard (and is a change from early versions of C++). I don't see any justification at all for the bidirectional equivalence. It seems to me that the note is left over from the earlier version of the language.
Steve Clamage: Finally, the Note seems to say that the declaration
volatile char greet[6] = "Hello";gives "greet" internal linkage, which makes no sense.
Have I missed something, or should that Note be entirely removed?
Mike Miller: At least the wording in the note should be repaired not to indicate that volatile-qualification gives an array internal linkage. Also, depending on how the discussion goes, either the wording in 6.8.5 [basic.type.qualifier] paragraph 2 or in paragraph 5 needs to be amended to be consistent regarding whether an array type is considered qualified by the qualification of its element type.
Steve Adamczyk pointed out that the current state of affairs resulted from the need to handle reference binding consistently. The wording is intended to define the question, "Is an array type cv-qualified?" as being equivalent to the question, "Is the element type of the array cv-qualified?"
Proposed resolution (10/00):
Replace the portion of the note in 9.3.4.5 [dcl.array] paragraph 1 reading
such an array has internal linkage unless explicitly declared extern (9.2.9.2 [dcl.type.cv]) and must be initialized as specified in 9.5 [dcl.init].
with
see 6.8.5 [basic.type.qualifier].
[Moved to DR at 10/01 meeting.]
9.3.4.6 [dcl.fct] paragraph 3 says,
All declarations for a function with a given parameter list shall agree exactly both in the type of the value returned and in the number and type of parameters.It is not clear what this requirement means with respect to a pair of declarations like the following:
int f(const int); int f(int x) { ... }Do they violate this requirement? Is x const in the body of the function declaration?
Tom Plum: I think the FDIS quotation means that the pair of decls are valid. But it doesn't clearly answer whether x is const inside the function definition. As to intent, I know the intent was that if the function definition wants to specify that x is const, the const must appear specifically in the defining decl, not just on some decl elsewhere. But I can't prove that intent from the drafted words.
Mike Miller: I think the intent was something along the following lines:
Two function declarations denote the same entity if the names are the same and the function signatures are the same. (Two function declarations with C language linkage denote the same entity if the names are the same.) All declarations of a given function shall agree exactly both in the type of the value returned and in the number and type of parameters; the presence or absence of the ellipsis is considered part of the signature.(See 6.6 [basic.link] paragraph 9. That paragraph talks about names in different scopes and says that function references are the same if the "types are identical for purposes of overloading," i.e., the signatures are the same. See also 9.12 [dcl.link] paragraph 6 regarding C language linkage, where only the name is required to be the same for declarations in different namespaces to denote the same function.)
According to this paragraph, the type of a parameter is determined by considering its decl-specifier-seq and declarator and then applying the array-to-pointer and function-to-pointer adjustments. The cv-qualifier and storage class adjustments are performed for the function type but not for the parameter types.
If my interpretation of the intent of the second sentence of the paragraph is correct, the two declarations in the example violate that restriction — the parameter types are not the same, even though the function types are. Since there's no dispensation mentioned for "no diagnostic required," an implementation presumably must issue a diagnostic in this case. (I think "no diagnostic required" should be stated if the declarations occur in different translation units — unless there's a blanket statement to that effect that I have forgotten?)
(I'd also note in passing that, if my interpretation is correct,
void f(int); void f(register int) { }is also an invalid pair of declarations.)
Proposed resolution (10/00):
In Clause 3 [intro.defs] “signature,” change "the types of its parameters" to "its parameter-type-list (9.3.4.6 [dcl.fct])".
In the third bullet of 6.6 [basic.link] paragraph 9 change "the function types are identical for the purposes of overloading" to "the parameter-type-lists of the functions (9.3.4.6 [dcl.fct]) are identical."
In the sub-bullets of the third bullet of 7.6.1.5 [expr.ref] paragraph 4, change all four occurrences of "function of (parameter type list)" to "function of parameter-type-list."
In 9.3.4.6 [dcl.fct] paragraph 3, change
All declarations for a function with a given parameter list shall agree exactly both in the type of the value returned and in the number and type of parameters; the presence or absence of the ellipsis is considered part of the function type.to
All declarations for a function shall agree exactly in both the return type and the parameter-type-list.
In 9.3.4.6 [dcl.fct] paragraph 3, change
The resulting list of transformed parameter types is the function's parameter type list.to
The resulting list of transformed parameter types and the presence or absence of the ellipsis is the function's parameter-type-list.
In 9.3.4.6 [dcl.fct] paragraph 4, change "the parameter type list" to "the parameter-type-list."
In the second bullet of _N4868_.12.2 [over.load] paragraph 2, change all occurrences of "parameter types" to "parameter-type-list."
In 12.2 [over.match] paragraph 1, change "the types of the parameters" to "the parameter-type-list."
In the last sub-bullet of the third bullet of 12.2.2.3 [over.match.oper] paragraph 3, change "parameter type list" to "parameter-type-list."
Note, 7 Sep 2001:
Editorial changes while putting in issue 147 brought up the fact that injected-class-name is not a syntax term and therefore perhaps shouldn't be written with hyphens. The same can be said of parameter-type-list.
[Voted into WP at April 2003 meeting.]
The interaction of default arguments and ellipsis is not clearly spelled out in the current wording of the Standard. 9.3.4.7 [dcl.fct.default] paragraph 4 says,
In a given function declaration, all parameters subsequent to a parameter with a default argument shall have default arguments supplied in this or previous declarations.
Strictly speaking, ellipsis isn't a parameter, but this could be clearer. Also, in 9.3.4.6 [dcl.fct] paragraph 2,
If the parameter-declaration-clause terminates with an ellipsis, the number of arguments shall be equal to or greater than the number of parameters specified.
This could be interpreted to refer to the number of arguments after the addition of default arguments to the argument list given in the call expression, but again it could be clearer.
Notes from 04/01 meeting:
The consensus opinion was that an ellipsis is not a parameter and that default arguments should be permitted preceding an ellipsis.
Proposed Resolution (4/02):
Change the following sentence in 9.3.4.6 [dcl.fct] paragraph 2 from
If the parameter-declaration-clause terminates with an ellipsis, the number of arguments shall be equal to or greater than the number of parameters specified.
to
If the parameter-declaration-clause terminates with an ellipsis, the number of arguments shall be equal to or greater than the number of parameters that do not have a default argument.
As noted in the defect, section 9.3.4.7 [dcl.fct.default] is correct but could be clearer.
In 9.3.4.7 [dcl.fct.default], add the following as the first line of the example in paragraph 4.
void g(int = 0, ...); // okay, ellipsis is not a parameter so it can follow // a parameter with a default argument
[Moved to DR at October 2002 meeting.]
This concerns the inconsistent treatment of cv qualifiers on reference types and function types. The problem originated with GCC bug report c++/2810. The bug report is available at http://gcc.gnu.org/cgi-bin/gnatsweb.pl?cmd=view&pr=2810&database=gcc
9.3.4.3 [dcl.ref] describes references. Of interest is the statement (my emphasis)
Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of a typedef or of a template type argument, in which case the cv-qualifiers are ignored.
Though it is strange to ignore 'volatile' here, that is not the point of this defect report. 9.3.4.6 [dcl.fct] describes function types. Paragraph 4 states,
In fact, if at any time in the determination of a type a cv-qualified function type is formed, the program is ill-formed.
No allowance for typedefs or template type parameters is made here, which is inconsistent with the equivalent reference case.
The GCC bug report was template code which attempted to do,
template <typename T> void foo (T const &); void baz (); ... foo (baz);
in the instantiation of foo, T is `void ()' and an attempt is made to const qualify that, which is ill-formed. This is a surprise.
Suggested resolution:
Replace the quoted sentence from paragraph 4 in 9.3.4.6 [dcl.fct] with
cv-qualified functions are ill-formed, except when the cv-qualifiers are introduced through the use of a typedef or of a template type argument, in which case the cv-qualifiers are ignored.
Adjust the example following to reflect this.
Proposed resolution (10/01):
In 9.3.4.6 [dcl.fct] paragraph 4, replace
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, i.e., it does not create a cv-qualified function type. In fact, if at any time in the determination of a type a cv-qualified function type is formed, the program is ill-formed. [Example:bytypedef void F(); struct S { const F f; // ill-formed };-- end example]
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. [Example:typedef void F(); struct S { const F f; // ok; equivalent to void f(); };-- end example]
Strike the last bulleted item in 13.10.3 [temp.deduct] paragraph 2, which reads
Attempting to create a cv-qualified function type.
Nathan Sidwell comments (18 Dec 2001 ): The proposed resolution simply states attempts to add cv qualification on top of a function type are ignored. There is no mention of whether the function type was introduced via a typedef or template type parameter. This would appear to allow
void (const *fptr) ();but, that is not permitted by the grammar. This is inconsistent with the wording of adding cv qualifiers to a reference type, which does mention typedefs and template parameters, even though
int &const ref;is also not allowed by the grammar.
Is this difference intentional? It seems needlessly confusing.
Notes from 4/02 meeting:
Yes, the difference is intentional. There is no way to add cv-qualifiers other than those cases.
Notes from April 2003 meeting:
Nathan Sidwell pointed out that some libraries use the inability to add const to a type T as a way of testing that T is a function type. He will get back to us if he has a proposal for a change.
[Voted into the WP at the September, 2008 meeting as part of paper N2757.]
The wording added to 9.3.4.6 [dcl.fct] for declarators with late-specified return types says,
In a declaration T D where D has the form
D1 ( parameter-declaration-clause ) cv-qualifier-seqopt ref-qualifieropt exception-specificationopt -> type-id
and the type of the contained declarator-id in the declaration T D1 is “derived-declarator-type-list T,” T shall be the single type-specifier auto and the derived-declarator-type-list shall be empty.
These restrictions were intended to ensure that the return type of the function is exactly the specified type-id following the ->, not modified by declarator operators and cv-qualification.
Unfortunately, the requirement for an empty derived-declarator-type-list does not achieve this goal but instead forbids declarations like
auto (*fp)() -> int; // pointer to function returning int
while allowing declarations like
auto *f() -> int; // function returning pointer to int
The reason for this is that, according to the grammar in 9.3 [dcl.decl] paragraph 4, the declarator *f() -> int is parsed as a ptr-operator applied to the direct-declarator f() -> int; that is, the declarator D1 seen in 9.3.4.6 [dcl.fct] is just f, and the derived-declarator-type-list is thus empty.
By contrast, the declarator (*fp)() -> int is parsed as the direct-declarator (*fp) followed by the parameter-declaration-clause, etc. In this case, D1 in 9.3.4.6 [dcl.fct] is (*fp) and the derived-declarator-type-list is “pointer to,” i.e., not empty.
My personal view is that there is no reason to forbid the (*fp)() -> int form, and that doing so is problematic. For example, this restriction would require users desiring the late-specified return type syntax to write function parameters as function types and rely on parameter type transformations rather than writing them as pointer-to-function types, as they will actually turn out to be:
void f(auto (*fp)() -> int); // ill-formed void f(auto fp() -> int); // OK (but icky)
It may be helpful in deciding whether to allow this form to consider the example of a function returning a pointer to a function. With the current restriction, only one of the three plausible forms is allowed:
auto (*f())() -> int; // Disallowed auto f() -> int (*)(); // Allowed auto f() -> auto (*)() -> int; // DisallowedSuggested resolution:
Delete the words “and the derived-declarator-type-list shall be empty” from 9.3.4.6 [dcl.fct] paragraph 2.
Add a new paragraph following 9.3 [dcl.decl] paragraph 4:
A ptr-operator shall not be applied, directly or indirectly, to a function declarator with a late-specified return type (9.3.4.6 [dcl.fct]).
Proposed resolution (June, 2008):
Change the grammar in 9.3 [dcl.decl] paragraph 4 as follows:
Change the grammar in 9.3.2 [dcl.name] paragraph 1 as follows:
Change 9.3.4.6 [dcl.fct] paragraph 2 as follows:
... T shall be the single type-specifier autoand the derived-declarator-type-list shall be empty. Then the type...
Change all occurrences of direct-new-declarator in 7.6.2.8 [expr.new] to noptr-new-declarator. These changes appear in the grammar in paragraph 1 and in the text of paragraphs 6-8, as follows:
...
new-declarator:
ptr-operator new-declaratoropt
direct-noptr-new-declarator
direct-noptr-new-declarator:
[ expression ]
...
direct-noptr-new-declarator [ constant-expression ]
When the allocated object is an array (that is, the
direct-noptr-new-declarator syntax is used or the new-type-id or type-id denotes an array type), the new-expression yields a pointer to the initial element (if any) of the array. [Note: both new int and new int[10] have type int* and the type of new int[i][10] is int (*)[10] —end note]Every constant-expression in a
direct-noptr-new-declarator shall be an integral constant expression (7.7 [expr.const]) and evaluate to a strictly positive value. The expression in adirect-noptr-new-declarator shall be of integral type, enumeration type, or a class type for which a single non-explicit conversion function to integral or enumeration type exists (11.4.8 [class.conv]). If the expression is of class type, the expression is converted by calling that conversion function, and the result of the conversion is used in place of the original expression. If the value of the expression is negative, the behavior is undefined. [Example: given the definition int n = 42, new float[n][5] is well-formed (because n is the expression of adirect-noptr-new-declarator), but new float[5][n] is ill-formed (because n is not a constant expression). If n is negative, the effect of new float[n][5] is undefined. —end example]When the value of the expression in a
direct-noptr-new-declarator is zero, the allocation function is called to allocate an array with no elements.
[Moved to DR at 10/01 meeting.]
9.3.4.7 [dcl.fct.default] paragraph 4 says,
For non-template functions, default arguments can be added in later declarations of a function in the same scope. Declarations in different scopes have completely distinct sets of default arguments. That is, declarations in inner scopes do not acquire default arguments from declarations in outer scopes, and vice versa.It is unclear how this wording applies to friend function declarations. For example,
void f(int, int, int=0); // #1 class C { friend void f(int, int=0, int); // #2 }; void f(int=0, int, int); // #3Does the declaration at #2 acquire the default argument from #1, and does the one at #3 acquire the default arguments from #2?
There are several related questions involved with this issue:
Mike Miller: 9.3.4.7 [dcl.fct.default] paragraph 4 is speaking about the lexical location of the declaration... The friend declaration occurs in a different declarative region from the declaration at #1, so I would read [this paragraph] as saying that it starts out with a clean slate of default arguments.
Bill Gibbons: Yes. It occurs in a different region, although it declares a name in the same region (i.e. a redeclaration). This is the same as with local externs and is intended to work the same way. We decided that local extern declarations cannot add (beyond the enclosing block) new default arguments, and the same should apply to friend declarations.
John Spicer: The question is whether [this paragraph] does (or should) mean declarations that appear in the same lexical scope or declarations that declare names in the same scope. In my opinion, it really needs to be the latter. It seems somewhat paradoxical to say that a friend declaration declares a function in namespace scope yet the declaration in the class still has its own attributes. To make that work I think you'd have to make friends more like block externs that really do introduce a name into the scope in which the declaration is contained.
Bill Gibbons: In the absence of a declaration visible in class scope to which they could be attached, default arguments on friend declarations do not make sense. [They should be] ill-formed, to prevent surprises.
John Spicer: It is important that the following case work correctly:
class X { friend void f(X x, int i = 1){} }; int main() { X x; f(x); }
In other words, a function first declared in a friend declaration must be permitted to have default arguments and those default arguments must be usable when the function is found by argument dependent lookup. The reason that this is important is that it is common practice to define functions in friend declarations in templates, and that definition is the only place where the default arguments can be specified.
John Spicer: We want to avoid instantiation side effects. IMO, the way to do this would be to prohibit a friend declaration from providing default arguments if a declaration of that function is already visible. Once a function has had a default specified in a friend declaration it should not be possible to add defaults in another declaration be it a friend or normal declaration.
Mike Miller: The position that seems most reasonable to me is to allow default arguments in friend declarations to be used in Koenig lookup, but to say that they are completely unrelated to default arguments in declarations in the surrounding scope; and to forbid use of a default argument in a call if more than one declaration in the overload set has such a default, as in the proposed resolution for issue 1.
Notes from 10/99 meeting:
Four possible outcomes were identified:
The core group eliminated the first and fourth options from consideration, but split fairly evenly between the remaining two.
A straw poll of the full committee yielded the following results (given as number favoring/could live with/"over my dead body"):
Additional discussion is recorded in the "Record of Discussion" for the meeting, J16/99-0036 = WG21 N1212. See also paper J16/00-0040 = WG21 N1263.
Proposed resolution (10/00):
In 9.3.4.7 [dcl.fct.default], add following paragraph 4:
If a friend declaration specifies a default argument expression, that declaration must be a definition and shall be the only declaration of the function or function template in the translation unit.
[Moved to DR at 4/01 meeting.]
The description of copy-initialization in 9.5 [dcl.init] paragraph 14 says:
struct A { A(A&); }; struct B : A { }; struct C { operator B&(); }; C c; const A a = c; // allowed?
The temporary created with the conversion function is an lvalue of type B. If the temporary must have the cv-qualifiers of the destination type (i.e. const) then the copy-constructor for A cannot be called to create the object of type A from the lvalue of type const B. If the temporary has the cv-qualifiers of the result type of the conversion function, then the copy-constructor for A can be called to create the object of type A from the lvalue of type const B. This last outcome seems more appropriate.
Steve Adamczyk:
Because of late changes to this area, the relevant text is now the third sub-bullet of the fourth bullet of 9.5 [dcl.init] paragraph 14:
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated... The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.
The issue still remains whether the wording should refer to "the cv-unqualified version of the destination type." I think it should.
Notes from 10/00 meeting:
The original example does not illustrate the remaining problem. The following example does:
struct C { }; C c; struct A { A(const A&); A(const C&); }; const volatile A a = c; // Okay
Proposed Resolution (04/01):
In 9.5 [dcl.init], paragraph 14, bullet 4, sub-bullet 3, change
if the function is a constructor, the call initializes a temporary of the destination type.
to
if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type.
Paragraph 9 of 9.5 [dcl.init] says:
If no initializer is specified for an object, and the object is of (possibly cv-qualified) non-POD class type (or array thereof), the object shall be default-initialized; if the object is of const-qualified type, the underlying class type shall have a user-declared default constructor. Otherwise, if no initializer is specified for an object, the object and its subobjects, if any, have an indeterminate initial value; if the object or any of its subobjects are of const-qualified type, the program is ill-formed.It should be made clear that this paragraph does not apply to static objects.
Proposed resolution (10/00): In 9.5 [dcl.init] paragraph 9, replace
Otherwise, if no initializer is specified for an object..."with
Otherwise, if no initializer is specified for a non-static object...
[Moved to DR at 4/02 meeting.]
Is the temporary created during copy-initialization of a class object treated as an lvalue or an rvalue? That is, is the following example well-formed or not?
struct B { }; struct A { A(A&); // not const A(const B&); }; B b; A a = b;
According to 9.5 [dcl.init] paragraph 14, the initialization of a is performed in two steps. First, a temporary of type A is created using A::A(const B&). Second, the resulting temporary is used to direct-initialize a using A::A(A&).
The second step requires binding a reference to non-const to the temporary resulting from the first step. However, 9.5.4 [dcl.init.ref] paragraph 5 requires that such a reference be bound only to lvalues.
It is not clear from 7.2.1 [basic.lval] whether the temporary created in the process of copy-initialization should be treated as an lvalue or an rvalue. If it is an lvalue, the example is well-formed, otherwise it is ill-formed.
Proposed resolution (04/01):
In 9.5 [dcl.init] paragraph 14, insert the following after "the call initializes a temporary of the destination type":
The temporary is an rvalue.
In 14.2 [except.throw] paragraph 3, replace
The temporary is used to initialize the variable...
with
The temporary is an lvalue and is used to initialize the variable...
(See also issue 84.)
[Moved to DR at 10/01 meeting.]
The intent of 9.5 [dcl.init] paragraph 5 is that pointers that are zero-initialized will contain a null pointer value. Unfortunately, the wording used,
...set to the value of 0 (zero) converted to T
does not match the requirements for creating a null pointer value given in 7.3.12 [conv.ptr] paragraph 1:
A null pointer constant is an integral constant expression (7.7 [expr.const]) rvalue of integer type that evaluates to zero. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type...
The problem is that the "value of 0" in the description of zero-initialization is not specified to be an integral constant expression. Nonconstant expressions can also have the value 0, and converting a nonconst 0 to pointer type need not result in a null pointer value.
Proposed resolution (04/01):
In 9.5 [dcl.init] paragraph 5, change
...set to the value 0 (zero) converted to T;
to
...set to the value 0 (zero), taken as an integral constant expression, converted to T; [footnote: as specified in 7.3.12 [conv.ptr], converting an integral constant expression whose value is 0 to a pointer type results in a null pointer value.]
[Moved to DR at October 2002 meeting.]
We've been looking at implementing value-initialization. At one point, some years back, I remember Bjarne saying that something like X() in an expression should produce an X object with the same value one would get if one created a static X object, i.e., the uninitialized members would be zero-initialized because the whole object is initialized at program startup, before the constructor is called.
The formulation for default-initialization that made it into TC1 (in 9.5 [dcl.init]) is written a little differently (see issue 178), but I had always assumed that it would still be a valid implementation to zero the whole object and then call the default constructor for the troublesome "non-POD but no user-written constructor" cases.
That almost works correctly, but I found a problem case:
struct A { A(); ~A(); }; struct B { // B is a non-POD with no user-written constructor. // It has a nontrivial generated constructor. const int i; A a; }; int main () { // Value-initializing a "B" doesn't call the default constructor for // "B"; it value-initializes the members of B. Therefore it shouldn't // cause an error on generation of the default constructor for the // following: new B(); }
If the definition of the B::B() constructor is generated, an error is issued because the const member "i" is not initialized. But the definition of value-initialization doesn't require calling the constructor, and therefore it doesn't require generating it, and therefore the error shouldn't be detected.
So this is a case where zero-initializing and then calling the constructor is not equivalent to value-initializing, because one case generates an error and the other doesn't.
This is sort of unfortunate, because one doesn't want to generate all the required initializations at the point where the "()" initialization occurs. One would like those initializations to be packaged in a function, and the default constructor is pretty much the function one wants.
I see several implementation choices:
Personally, I find option 1 the least objectionable.
Proposed resolution (10/01):
Add the indicated wording to the third-to-last sentence of 6.3 [basic.def.odr] pararaph 2:
A default constructor for a class is used by default initialization or value initialization as specified in 9.5 [dcl.init].
Add a footnote to the indicated bullet in 9.5 [dcl.init] paragraph 5:
Add the indicated wording to the first sentence of 11.4.5 [class.ctor] paragraph 7:
An implicitly-declared default constructor for a class is implicitly defined when it is used (6.3 [basic.def.odr]) to create an object of its class type (6.7.2 [intro.object]).
[Voted into the WP at the September, 2008 meeting (resolution in paper N2762).]
The definition of default initialization (9.5 [dcl.init] paragraph 5) is:
if T is a non-POD class type (Clause 11 [class]), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
if T is an array type, each element is default-initialized;
otherwise, the object is zero-initialized.
However, default initialization is invoked only for non-POD class types and arrays thereof (7.6.2.8 [expr.new] paragraph 15 for new-expressions, 9.5 [dcl.init] paragraph 10 for top-level objects, and 11.9.3 [class.base.init] paragraph 4 for member and base class subobjects — but see issue 510). Consequently, all cases that invoke default initialization are handled by the first two bullets; the third bullet can never be reached. Its presence is misleading, so it should be removed.
Notes from the September, 2008 meeting:
The approach adopted in the resolution in paper N2762 was different from the suggestion above: it changes the definition of default initialization to include POD types and changes the third bullet to specify that “no initialization is performed.”
[Voted into the WP at the September, 2008 meeting (resolution in paper N2762).]
The wording resulting from the resolution of issue 302 does not quite implement the intent of the issue. The revised wording of 6.3 [basic.def.odr] paragraph 2 is:
A default constructor for a class is used by default initialization or value initialization as specified in 9.5 [dcl.init].
This sounds as if 9.5 [dcl.init] specifies how and under what circumstances value initialization uses a default constructor (which was, in fact, the case for default initialization in the original wording). However, the normative text there makes it plain that value initialization does not call the default constructor (the permission granted to implementations to call the default constructor for value initialization is in a non-normative footnote).
The example that occasioned this observation raises an additional question. Consider:
struct POD { const int x; }; POD data = POD();
According to the (revised) resolution of issue 302, this code is ill-formed because the implicitly-declared default constructor will be implicitly defined as a result of being used by value initialization (11.4.5 [class.ctor] paragraph 7), and the implicitly-defined constructor fails to initialize a const-qualified member (11.9.3 [class.base.init] paragraph 4). This seems unfortunate, because the (trivial) default constructor of a POD class is otherwise not used — default initialization applies only to non-PODs — and it is not actually needed in value initialization. Perhaps value initialization should be defined to “use” the default constructor only for non-POD classes? If so, both of these problems would be resolved by rewording the above-referenced sentence of 6.3 [basic.def.odr] paragraph 2 as:
A default constructor for a non-POD class is used by default initialization or value initializationas specified in(9.5 [dcl.init]).
Notes from the April, 2006 meeting:
The approach favored by the CWG was to leave 6.3 [basic.def.odr] unchanged and to add normative wording to 9.5 [dcl.init] indicating that it is unspecified whether the default constructor is called.
Notes from the October, 2006 meeting:
The CWG now prefers that it should not be left unspecified whether programs of this sort are well- or ill-formed; instead, the Standard should require that the default constructor be defined in such cases. Three possibilities of implementing this decision were discussed:
Change 6.3 [basic.def.odr] to state flatly that the default constructor is used by value initialization (removing the implication that 9.5 [dcl.init] determines the conditions under which it is used).
Change 9.5 [dcl.init] to specify that non-union class objects with no user-declared constructor are value-initialized by first zero-initializing the object and then calling the (implicitly-defined) default constructor, replacing the current specification of value-initializing each of its sub-objects.
Add a normative statement to 9.5 [dcl.init] that value-initialization causes the implicitly-declared default constructor to be implicitly defined, even if it is not called.
Proposed resolution (June, 2008):
Change the second bullet of the value-initialization definition in 9.5 [dcl.init] paragraph 5 as follows:
if T is a non-union class type without a
user-provided constructor, then every non-static data member and
base-class component of T is value-initialized;
[Footnote: Value-initialization for such a class object may be
implemented by zero-initializing the object and then calling the
default constructor. —end footnote] the object is
zero-initialized and the implicitly-defined default constructor is
called;
Notes from the September, 2008 meeting:
The resolution supplied in paper N2762 differs from the June, 2008 proposed resolution in that the implicitly-declared default constructor is only called (and thus defined) if it is non-trivial, making the struct POD example above well-formed.
[Voted into the WP at the April, 2007 meeting as part of paper J16/07-0099 = WG21 N2239.]
A recent GCC bug report ( http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11633) asks about the validity of
int count = 23; int foo[] = { count++, count++, count++ };is this undefined or unspecified or something else? I can find nothing in 9.5.2 [dcl.init.aggr] that indicates whether the components of an initializer-list are evaluated in order or not, or whether they have sequence points between them.
6.7.8/23 of the C99 std has this to say
The order in which any side effects occur among the initialization list expressions is unspecified.I think similar wording is needed in 9.5.2 [dcl.init.aggr]
Steve Adamczyk: I believe the standard is clear that each initializer expression in the above is a full-expression (6.9.1 [intro.execution]/12-13; see also issue 392) and therefore there is a sequence point after each expression (6.9.1 [intro.execution]/16). I agree that the standard does not seem to dictate the order in which the expressions are evaluated, and perhaps it should. Does anyone know of a compiler that would not evaluate the expressions left to right?
Mike Simons: Actually there is one, that does not do left to right: gcc/C++. None of the post increment operations take effect until after the statement finishes. So in the sample code gcc stores 23 into all positions in the array. The commercial vendor C++ compilers for AIX, Solaris, Tru64, HPUX (parisc and ia64), and Windows, all do sequence points at each ',' in the initializer list.
[Voted into WP at April, 2007 meeting.]
The current wording of 9.5.2 [dcl.init.aggr] paragraph 8 requires that
An initializer for an aggregate member that is an empty class shall have the form of an empty initializer-list {}.
This is overly constraining. There is no reason that the following should be ill-formed:
struct S { }; S s; S arr[1] = { s };
Mike Miller: The wording of 9.5.2 [dcl.init.aggr] paragraph 8 is unclear. “An aggregate member” would most naturally mean “a member of an aggregate.” In context, however, I think it must mean “a member [of an aggregate] that is an aggregate”, that is, a subaggregate. Members of aggregates need not themselves be aggregates (cf paragraph 13 and 11.9.2 [class.expl.init]); it cannot be the case that an object of an empty class with a user-declared constructor must be initialized with {} when it is a member of an aggregate. This wording should be clarified, regardless of the decision on Nathan's point.
Proposed resolution (October, 2005):
This issue is resolved by the resolution of issue 413.
[Voted into the WP at the June, 2008 meeting as part of paper N2672.]
C (both C90 and C99) appear to allow a declaration of the form
struct S { int i; } s = { { 5 } };
in which the initializer of a scalar member of an aggregate can itself be brace-enclosed. The relevant wording from the C99 Standard is found in 6.7.8 paragraph 11:
The initializer for a scalar shall be a single expression, optionally enclosed in braces.
and paragraph 16:
Otherwise, the initializer for an object that has aggregate or union type shall be a brace-enclosed list of initializers for the elements or named members.
The “list of initializers” in paragraph 16 must be a recursive reference to paragraph 11 (that's the only place that describes how an initialized item gets its value from the initializer expression), which would thus make the “brace-enclosed” part of paragraph 11 apply to each of the initializers in the list in paragraph 16 as well.
This appears to be an incompatibility between C and C++: 9.5.2 [dcl.init.aggr] paragraph 11 says,
If the initializer-list begins with a left brace, then the succeeding comma-separated list of initializer-clauses initializes the members of a subaggregate....
which clearly leaves the impression that only a subaggregate may be initialized by a brace-enclosed initializer-clause.
Either the specification in 9.5.2 [dcl.init.aggr] should be changed to allow a brace-enclosed initializer of a scalar member of an aggregate, as in C, or this incompatibility should be listed in Appendix Clause Annex C [diff].
Notes from the July, 2007 meeting:
It was noted that implementations differ in their handling of this construct; however, the issue is long-standing and fairly obscure.
Notes from the October, 2007 meeting:
The initializer-list proposal will resolve this issue when it is adopted.
[Voted into WP at October 2005 meeting.]
There is a place in the Standard where overload resolution is implied but the way that a set of candidate functions is to be formed is omitted. See below.
According to the Standard, when initializing a reference to a non-volatile const class type (cv1 T1) with an rvalue expression (cv2 T2) where cv1 T1 is reference compatible with cv2 T2, the implementation shall proceed in one of the following ways (except when initializing the implicit object parameter of a copy constructor) 9.5.4 [dcl.init.ref] bullet 5.2 sub-bullet 1:
While the first case is quite obvious, the second one is a bit unclear as it says "a constructor is called to copy the entire rvalue object into the temporary" without specifying how the temporary is created -- by direct-initialization or by copy-initialization? As stated in DR 152, this can make a difference when the copy constructor is declared as explicit. How should the set of candidate functions be formed? The most appropriate guess is that it shall proceed as per 12.2.2.4 [over.match.ctor].
Another detail worth of note is that in the draft version of the Standard as of 2 December 1996 the second bullet read:
J. Stephen Adamczyk replied that the reason for changing "a copy constructor" to "a constructor" was to allow for member template converting constructors.
However, the new wording is somewhat in conflict with the footnote #93 that says that when initializing the implicit object parameter of a copy constructor an implementation must eventually choose the first alternative (binding without copying) to avoid infinite recursion. This seems to suggest that a copy constructor is always used for initializing the temporary of type "cv1 T2".
Furthermore, now that the set of candidate functions is not limited to only the copy constructors of T2, there might be some unpleasant consequences. Consider a rather contrived sample below:
int * pi = ::new(std::nothrow) int; const std::auto_ptr<int> & ri = std::auto_ptr<int>(pi);
In this example the initialization of the temporary of type '<TT>const std::auto_ptr<int>' (to which 'ri' is meant to be subsequently bound) doesn't fail, as it would had the approach with copy constructors been retained, instead, a yet another temporary gets created as the well-known sequence:
std::auto_ptr<int>::operator std::auto_ptr_ref<int>() std::auto_ptr<int>(std::auto_ptr_ref<int>)
is called (assuming, of course, that the set of candidate functions is formed as per 12.2.2.4 [over.match.ctor]). The second temporary is transient and gets destroyed at the end of the initialization. I doubt that this is the way that the committee wanted this kind of reference binding to go.
Besides, even if the approach restricting the set of candidates to copy constructors is restored, it is still not clear how the initialization of the temporary (to which the reference is intended to be bound) is to be performed -- using direct-initialization or copy-initialization.
Another place in the Standard that would benefit from a similar clarification is the creation of an exception object, which is delineated in 14.2 [except.throw].
David Abrahams (February 2004): It appears, looking at core 291, that there may be a need to tighten up 9.5.4 [dcl.init.ref]/5.
Please see the attached example file, which demonstrates "move semantics" in C++98. Many compilers fail to compile test 10 because of the way 8.5.3/5 is interpreted. My problem with that interpretation is that test 20:
typedef X const XC; sink2(XC(X()));does compile. In other words, it *is* possible to construct the const temporary from the rvalue. IMO, that is the proper test.
8.5.3/5 doesn't demand that a "copy constructor" is used to copy the temporary, only that a constructor is used "to copy the temporary". I hope that when the language is tightened up to specify direct (or copy initialization), that it also unambiguously allows the enclosed test to compile. Not only is it, I believe, within the scope of reasonable interpretation of the current standard, but it's an incredibly important piece of functionality for library writers and users alike.
#include <iostream> #include <cassert> template <class T, class X> struct enable_if_same { }; template <class X> struct enable_if_same<X, X> { typedef char type; }; struct X { static int cnt; // count the number of Xs X() : id(++cnt) , owner(true) { std::cout << "X() #" << id << std::endl; } // non-const lvalue - copy ctor X(X& rhs) : id(++cnt) , owner(true) { std::cout << "copy #" << id << " <- #" << rhs.id << std::endl; } // const lvalue - T will be deduced as X const template <class T> X(T& rhs, typename enable_if_same<X const,T>::type = 0) : id(++cnt) , owner(true) { std::cout << "copy #" << id << " <- #" << rhs.id << " (const)" << std::endl; } ~X() { std::cout << "destroy #" << id << (owner?"":" (EMPTY)") << std::endl; } private: // Move stuff struct ref { ref(X*p) : p(p) {} X* p; }; public: // Move stuff operator ref() { return ref(this); } // non-const rvalue X(ref rhs) : id(++cnt) , owner(rhs.p->owner) { std::cout << "MOVE #" << id << " <== #" << rhs.p->id << std::endl; rhs.p->owner = false; assert(owner); } private: // Data members int id; bool owner; }; int X::cnt; X source() { return X(); } X const csource() { return X(); } void sink(X) { std::cout << "in rvalue sink" << std::endl; } void sink2(X&) { std::cout << "in non-const lvalue sink2" << std::endl; } void sink2(X const&) { std::cout << "in const lvalue sink2" << std::endl; } void sink3(X&) { std::cout << "in non-const lvalue sink3" << std::endl; } template <class T> void tsink(T) { std::cout << "in templated rvalue sink" << std::endl; } int main() { std::cout << " ------ test 1, direct init from rvalue ------- " << std::endl; #ifdef __GNUC__ // GCC having trouble parsing the extra parens X z2((0, X() )); #else X z2((X())); #endif std::cout << " ------ test 2, copy init from rvalue ------- " << std::endl; X z4 = X(); std::cout << " ------ test 3, copy init from lvalue ------- " << std::endl; X z5 = z4; std::cout << " ------ test 4, direct init from lvalue ------- " << std::endl; X z6(z4); std::cout << " ------ test 5, construct const ------- " << std::endl; X const z7; std::cout << " ------ test 6, copy init from lvalue ------- " << std::endl; X z8 = z7; std::cout << " ------ test 7, direct init from lvalue ------- " << std::endl; X z9(z7); std::cout << " ------ test 8, pass rvalue by-value ------- " << std::endl; sink(source()); std::cout << " ------ test 9, pass const rvalue by-value ------- " << std::endl; sink(csource()); std::cout << " ------ test 10, pass rvalue by overloaded reference ------- " << std::endl; // This one fails in Comeau's strict mode due to 8.5.3/5. GCC 3.3.1 passes it. sink2(source()); std::cout << " ------ test 11, pass const rvalue by overloaded reference ------- " << std::endl; sink2(csource()); #if 0 // These two correctly fail to compile, just as desired std::cout << " ------ test 12, pass rvalue by non-const reference ------- " << std::endl; sink3(source()); std::cout << " ------ test 13, pass const rvalue by non-const reference ------- " << std::endl; sink3(csource()); #endif std::cout << " ------ test 14, pass lvalue by-value ------- " << std::endl; sink(z5); std::cout << " ------ test 15, pass const lvalue by-value ------- " << std::endl; sink(z7); std::cout << " ------ test 16, pass lvalue by-reference ------- " << std::endl; sink2(z4); std::cout << " ------ test 17, pass const lvalue by const reference ------- " << std::endl; sink2(z7); std::cout << " ------ test 18, pass const lvalue by-reference ------- " << std::endl; #if 0 // correctly fails to compile, just as desired sink3(z7); #endif std::cout << " ------ test 19, pass rvalue by value to template param ------- " << std::endl; tsink(source()); std::cout << " ------ test 20, direct initialize a const A with an A ------- " << std::endl; typedef X const XC; sink2(XC(X())); }
Proposed Resolution:
(As proposed by N1610 section 5, with editing.)
Change 8.5.3 [stmt.switch] paragraph 5, second bullet, first sub-bullet, second sub-sub-bullet as follows:
A temporary of type "cv1 T2" [sic] is created, and a constructor is called to copy the entire rvalue object into the temporaryvia copy-initialization from the entire rvalue object. The reference is bound to the temporary or to a sub-object within the temporary.
The text immediately following that is changed as follows:
The constructor that would be used to make the copy shall be callable whether or not the copy is actually done.The constructor and any conversion function that would be used in the initialization shall be callable whether or not the temporary is actually created.
Note, however, that the way the core working group is leaning on issue 391 (i.e., requiring direct binding) would make this change unnecessary.
Proposed resolution (April, 2005):
This issue is resolved by the resolution of issue 391.
[Voted into WP at October 2005 meeting.]
After some email exchanges with Rani Sharoni, I've come up with the following proposal to allow reference binding to non-copyable rvalues in some cases. Rationale and some background appear afterwards.
---- proposal ----
Replace the section of 9.5.4 [dcl.init.ref] paragraph 5 that begins "If the initializer expression is an rvalue" with the following:
---- rationale ----
class nc { nc (nc const &); // private, nowhere defined public: nc (); nc const &by_ref () const { return *this; } }; void f () { void g (nc const &); g (nc()); // Ill-formed g (nc().by_ref()); // Ok - binds directly to rvalue }Forcing a direct binding in this way is possible wherever the lifetime of the reference does not extend beyond the containing full expression, since the reference returned by the member function remains valid for this long.
---- background ----
The proposal is based on a recent discussion in this group. I originally wanted to leave the implementation free to copy the rvalue if there was a callable copy constructor, and only have to bind directly if none was callable. Unfortunately, a traditional compiler can't always tell whether a function is callable or not, e.g. if the copy constructor is declared but not defined. Rani pointed this out in an example, and suggested that maybe trivial copy constructors should still be allowed (by extension, maybe wherever the compiler can determine callability). I've gone with this version because it's simpler, and I also figure the "as if" rule gives the compiler some freedom with POD types anyway.
Notes from April 2003 meeting:
We agreed generally with the proposal. We were unsure about the need for the restriction regarding long-lived references. We will check with the proposer about that.
Jason Merrill points out that the test case in issue 86 may be a case where we do not want to require direct binding.
Further information from Rani Sharoni (April 2003):
I wasn't aware about the latest suggestion of Raoul as it appears in core issue 391. In our discussions we tried to formulate a different proposal.
The rational, as we understood, behind the implementation freedom to make an extra copying (8.5.3/5/2/12) of the rvalue is to allow return values in registers which on some architectures are not addressable. The example that Raoul and I presented shows that this implementation freedom is not always possible since we can "force" the rvalue to be addressable using additional member function (by_ref). The example only works for short lived rvalues and this is probably why Raoul narrow the suggestion.
I had different rational which was related to the implementation of conditional operator in VC. It seems that when conditional operator is involved VC does use an extra copying when the lifetime of the temporary is extended:
struct A { /* ctor with side effect */}; void f(A& x) { A const& r = cond ? A(1) : x; // VC actually make an extra copy of // the rvalue A(1) }
I don't know what the consideration behind the VC implementation was (I saw open bug on this issue) but it convinced me to narrow the suggestion.
IMHO such limitation seems to be too strict because it might limit the optimizer since returning class rvalues in registers might be useful (although I'm not aware about any implementation that actually does it). My suggestion was to forbid the extra copying if the ctor is not viable (e.g. A::A(A&) ). In this case the implementation "freedom" doesn't exist (since the code might not compile) and only limits the programmer freedom (e.g. Move Constructors - http://www.cuj.com/experts/2102/alexandr.htm [Note: URL is now defunct; observed March,2019.]).
Core issue 291 is strongly related to the above issue and I personally prefer to see it resolved first. It seems that VC already supports the resolution I prefer.
Notes from October 2003 meeting:
We ended up feeling that this is just one of a number of cases of optimizations that are widely done by compilers and allowed but not required by the standard. We don't see any strong reason to require compilers to do this particular optimization.
Notes from the March 2004 meeting:
After discussing issue 450, we found ourselves reconsidering this, and we are now inclined to make a change to require the direct binding in all cases, with no restriction on long-lived references. Note that such a change would eliminate the need for a change for issue 291.
Proposed resolution (October, 2004):
Change 9.5.4 [dcl.init.ref] bullet 5.2 sub-bullet 1 as follows:
If the initializer expression is an rvalue, with T2 a class type, and "cv1 T1" is reference-compatible with "cv2 T2", the reference is bound to the object represented by the rvalue (see 7.2.1 [basic.lval]) or to a sub-object within that object.in one of the following ways (the choice is implementation-defined):[Example:The constructor that would be used to make the copy shall be callable whether or not the copy is actually done.
- The reference is bound to the object represented by the rvalue (see 7.2.1 [basic.lval]) or to a sub-object within that object.
- A temporary of type "cv1 T2" [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.
struct A { }; struct B : public A { } b; extern B f(); const A& rca = f (); // Bound—end example]Either boundto the A sub-object of the B rvalue, // or the entire B object is copied and the reference // is bound to the A sub-object of the copy
[This resolution also resolves issue 291.]
[Voted into WP at October 2005 meeting.]
It's unclear whether the following is valid:
const int N = 10; const int M = 20; typedef int T; void f(T const (&x)[N][M]){} struct X { int i[10][20]; }; X g(); int main() { f(g().i); }
When you run this through 9.5.4 [dcl.init.ref], you sort of end up falling off the end of the standard's description of reference binding. The standard says in the final bullet of paragraph 5 that an array temporary should be created and copy-initialized from the rvalue array, which seems implausible.
I'm not sure what the right answer is. I think I'd be happy with allowing the binding in this case. We would have to introduce a special case like the one for class rvalues.
Notes from the March 2004 meeting:
g++ and EDG give an error. Microsoft (8.0 beta) and Sun accept the example. Our preference is to allow the direct binding (no copy). See the similar issue with class rvalues in issue 391.
Proposed resolution (October, 2004):
Insert a new bullet in 9.5.4 [dcl.init.ref] bullet 5.2 before sub-bullet 2 (which begins, “Otherwise, a temporary of type ‘cv1 T1’ is created...”):
If the initializer expression is an rvalue, with T2 an array type, and “cv1 T1” is reference-compatible with “cv2 T2”, the reference is bound to the object represented by the rvalue (see 7.2.1 [basic.lval]).
Change 7.2.1 [basic.lval] paragraph 2 as follows:
An lvalue refers to an object or function. Some rvalue expressions — those of (possibly cv-qualified) class or array typeor cv-qualified class type— also refer to objects.
[Moved to DR at October 2002 meeting.]
According to 9.8.1 [dcl.enum] paragraph 5, the underlying type of an enum is an unspecified integral type, which could potentially be unsigned int. The promotion rules in 7.3.7 [conv.prom] paragraph 2 say that such an enumeration value used in an expression will be promoted to unsigned int. This means that a conforming implementation could give the value false for the following code:
enum { zero }; -1 < zero; // might be falseThis is counterintuitive. Perhaps the description of the underlying type of an enumeration should say that an unsigned underlying type can be used only if the values of the enumerators cannot be represented in the corresponding signed type. This approach would be consistent with the treatment of integral promotion of bitfields (7.3.7 [conv.prom] paragraph 3) .
On a related note, 9.8.1 [dcl.enum] paragraph 5 says,
the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int.
This specification does not allow for an enumeration like
enum { a = -1, b = UINT_MAX };
Since each enumerator can fit in an int or unsigned int, the underlying type is required to be no larger than int, even though there is no such type that can represent all the enumerators.
Proposed resolution (04/01; obsolete, see below):
Change 9.8.1 [dcl.enum] paragraph 5 as follows:
It is implementation-defined which integral type is used as the underlying type for an enumeration except that the underlying type shall not be larger than int unlessthe value of an enumerator cannot fit in an int or unsigned intneither int nor unsigned int can represent all the enumerator values. Furthermore, the underlying type shall not be an unsigned type if the corresponding signed type can represent all the enumerator values.
See also issue 58.
Notes from 04/01 meeting:
It was noted that 7.3.7 [conv.prom] promotes unsigned types smaller than int to (signed) int. The signedness chosen by an implementation for small underlying types is therefore unobservable, so the last sentence of the proposed resolution above should apply only to int and larger types. This observation also prompted discussion of an alternative approach to resolving the issue, in which the bmin and bmax of the enumeration would determine the promoted type rather than the underlying type.
Proposed resolution (10/01):
Change 7.3.7 [conv.prom] paragraph 2 from
An rvalue of type wchar_t (6.8.2 [basic.fundamental]) or an enumeration type (9.8.1 [dcl.enum]) can be converted to an rvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long, or unsigned long.to
An rvalue of type wchar_t (6.8.2 [basic.fundamental]) can be converted to an rvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long, or unsigned long. An rvalue of an enumeration type (9.8.1 [dcl.enum]) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration (i.e., the values in the range bmin to bmax as described in 9.8.1 [dcl.enum]): int, unsigned int, long, or unsigned long.
[Voted into WP at April 2003 meeting.]
9.8.1 [dcl.enum] defines the underlying type of an enumeration as an integral type "that can represent all the enumerator values defined in the enumeration".
What does the standard say about this code:
enum E { a = LONG_MIN, b = ULONG_MAX };
?
I think this should be ill-formed.
Proposed resolution:
In 9.8.1 [dcl.enum] paragraph 5 after
The underlying type of an enumeration is an integral type that can represent all the enumerator values defined in the enumeration.insert
If no integral type can represent all the enumerator values, the enumeration is ill-formed.
[Voted into WP at April, 2006 meeting.]
The C language (since C99), and some C++ compilers, accept:
enum { FOO, };
as syntactically valid. It would be useful
for machine generated code
for minimising changes when editing
to allow a distinction between the final item being intended as an ordinary item or as a limit:
enum { red, green, blue, num_colours }; // note no comma enum { fred, jim, sheila, }; // last is not special
This proposed change is to permit a trailing comma in enum by adding:
enum identifieropt { enumerator-list , }
as an alternative definition for the enum-specifier nonterminal in 9.8.1 [dcl.enum] paragraph 1.
Proposed resolution (October, 2005):
Change the grammar in 9.8.1 [dcl.enum] paragraph 1 as indicated:
enum-specifier:enum identifieropt { enumerator-listopt }
enum identifieropt { enumerator-list , }
[Voted into the WP at the September, 2008 meeting.]
The current specification of scoped enumerations does not appear to forbid an example like the following, even though the enumerator e cannot be used:
enum class { e };
This might be covered by 9.1 [dcl.pre] paragraph 3,
In a simple-declaration, the optional init-declarator-list can be omitted only when declaring a class (Clause 11 [class]) or enumeration (9.8.1 [dcl.enum]), that is, when the decl-specifier-seq contains either a class-specifier, an elaborated-type-specifier with a class-key (11.3 [class.name]), or an enum-specifier. In these cases and whenever a class-specifier or enum-specifier is present in the decl-specifier-seq, the identifiers in these specifiers are among the names being declared by the declaration (as class-names, enum-names, or enumerators, depending on the syntax). In such cases, and except for the declaration of an unnamed bit-field (11.4.10 [class.bit]), the decl-specifier-seq shall introduce one or more names into the program, or shall redeclare a name introduced by a previous declaration.
which, when combined with paragraph 2,
A declaration occurs in a scope (6.4 [basic.scope]); the scope rules are summarized in 6.5 [basic.lookup]. A declaration that declares a function or defines a class, namespace, template, or function also has one or more scopes nested within it. These nested scopes, in turn, can have declarations nested within them. Unless otherwise stated, utterances in Clause 9 [dcl] about components in, of, or contained by a declaration or subcomponent thereof refer only to those components of the declaration that are not nested within scopes nested within the declaration.
appears to rule out the similar class definition,
struct { int m; };
However, a scoped enumeration is not listed in paragraph 2 among the constructs containing a nested scope (although 6.4.8 [basic.scope.enum] does describe “enumeration scope”); furthermore, an enumerator-definition is not formally a “nested declaration.” If unusable scoped enumeration definitions are to be banned, these shortcomings in 9.1 [dcl.pre] paragraph 2 must be addressed. (A note in 9.8.1 [dcl.enum] mentioning that unnamed scoped enumerations are not allowed would also be helpful.)
Notes from the February, 2008 meeting:
The consensus was to require that the identifier be present in an enum-specifier unless the enum-key is enum.
Proposed resolution (June, 2008):
Change 9.8.1 [dcl.enum] paragraph 2 as follows:
...The enum-keys enum class and enum struct are semantically equivalent; an enumeration type declared with one of these is a scoped enumeration, and its enumerators are scoped enumerators. The optional identifier shall not be omitted in the declaration of a scoped enumeration. The type-specifier-seq of an enum-base...
[Voted into the WP at the October, 2006 meeting as part of paper J16/06-0188 = WG21 N2118.]
The resolution of issue 106 specifies that an attempt to create a type “reference to cv1 T,” where T is a typedef or template parameter of the type “reference to cv2 S,” actually creates the type “reference to cv12 S,” where cv12 is the union of the two sets of cv-qualifiers.
One objection that has been raised to this resolution is that it is inconsistent with the treatment of cv-qualification and references specified in 9.3.4.3 [dcl.ref] paragraph 1, which says that cv-qualifiers applied to a typedef or template argument that is a reference type are ignored. For example:
typedef int& intref; const intref r1; // reference to int const intref& r2; // reference to const int
In fact, however, these two declarations are quite different. In the declaration of r1, const applies to a “top-level” reference, while in the declaration of t2, it occurs under a reference. In general, cv-qualifiers that appear under a reference are preserved, even if the type appears in a context in which top-level cv-qualification is removed, for example, in determining the type of a function from parameter types (9.3.4.6 [dcl.fct] paragraph 3) and in template argument deduction (13.10.3.2 [temp.deduct.call] paragraph 2).
Another objection to the resolution is that type composition gives different results in a single declaration than it does when separated into two declarations. For example:
template <class T> struct X { typedef T const T_const; typedef T_const& type1; typedef T const& type2; }; X<int&>::type1 t1; // int& X<int&>::type2 t2; // int const&
The initial motivation for the propagation of cv-qualification during reference-to-reference collapse was to prevent inadvertent loss of cv-qualifiers in contexts in which it could make a difference. For example, if the resolution were changed to discard, rather than propagate, embedded cv-qualification, overload resolution could surprisingly select a non-const version of a member function:
struct X { void g(); void g() const; }; template <typename T> struct S { static void f(const T& t) { t.g(); // const or non-const??? } }; X x; void q() { S<X>::f(x); // calls X::g() const S<X&>::f(x); // calls X::g() }
Another potentially-surprising outcome of dropping embedded cv-qualifiers would be:
template <typename T> struct A { void f(T&); // mutating version void f(const T&); // non-mutating version }; A<int&> ai; // Ill-formed: A<int&> declares f(int&) twice
On the other hand, those who would like to see the resolution changed to discard embedded cv-qualifiers observe that these examples are too simple to be representative of real-world code. In general, it is unrealistic to expect that a template written with non-reference type parameters in mind will automatically work correctly with reference type parameters as a result of applying the issue 106 resolution. Instead, template metaprogramming allows the template author to choose explicitly whether cv-qualifiers are propagated or dropped, according to the intended use of the template, and it is more important to respect the reasonable intuition that a declaration involving a template parameter will not change the type that the parameter represents.
As a sample of real-world code, tr1::tuple was examined. In both cases — the current resolution of issue 106 and one in which embedded cv-qualifiers were dropped — some metaprogramming was required to implement the intended interface, although the version reflecting the revised resolution was somewhat simpler.
Notes from the October, 2005 meeting:
The consensus of the CWG was that the resolution of issue 106 should be revised not to propagate embedded cv-qualification.
Note (February, 2006):
The wording included in the rvalue-reference paper, J16/06-0022 = WG21 N1952, incorporates changes intended to implement the October, 2005 consensus of the CWG.
[Voted into WP at March 2004 meeting.]
Issue 1:
The working paper is not clear about how the typename/template keywords interact with using-declarations:
template<class T> struct A { typedef int X; }; template<class T> void f() { typename A<T>::X a; // OK using typename A<T>::X; // OK typename X b; // ill-formed; X must be qualified X c; // is this OK? }When the rules for typename and the similar use of template were decided, we chose to require that they be used at every reference. The way to avoid typename at every use is to declare a typedef; then the typedef name itself is known to be a type. For using-declarations, we decided that they do not introduce new declarations but rather are aliases for existing declarations, like symbolic links. This makes it unclear whether the declaration "X c;" above should be well-formed, because there is no new name declared so there is no declaration with a "this is a type" attribute. (The same problem would occur with the template keyword when a member template of a dependent class is used). I think these are the main options:
The core WG already resolved this issue according to (1), but the wording does not seem to have been added to the standard. New wording needs to be drafted.
Issue 2:
Either way, one more point needs clarification. If the first option is adopted:
template<class T> struct A { struct X { }; }; template<class T> void g() { using typename A<T>::X; X c; // if this is OK, then X by itself is a type int X; // is this OK? }When "g" is instantiated, the two declarations of X are compatible (9.10 [namespace.udecl] paragraph 10) . But there is no way to know this when the definition of "g" is compiled. I think this case should be ill-formed under the first option. (It cannot happen under the second option.) If the second option is adopted:
template<class T> struct A { struct X { }; }; template<class T> void g() { using A<T>::X; int X; // is this OK? }Again, the instantiation would work but there is no way to know that in the template definition. I think this case should be ill-formed under the second option. (It would already be ill-formed under the first option.)
From John Spicer:
The "not a new declaration" decision is more of a guiding principle than a hard and fast rule. For example, a name introduced in a using-declaration can have different access than the original declaration.Tentative Resolution:Like symbolic links, a using-declaration can be viewed as a declaration that declares an alias to another name, much like a typedef.
In my opinion, "X c;" is already well-formed. Why would we permit typename to be used in a using-declaration if not to permit this precise usage?
In my opinion, all that needs to be done is to clarify that the "typeness" or "templateness" attribute of the name referenced in the using-declaration is attached to the alias created by the using-declaration. This is solution #1.
The rules for multiple declarations with the same name in the same scope should treat a using-declaration which names a type as a typedef, just as a typedef of a class name is treated as a class declaration. This needs drafting work. Also see Core issue 36.
Rationale (04/99): Any semantics associated with the typename keyword in using-declarations should be considered an extension.
Notes from the April 2003 meeting:
This was reopened because we are now considering extensions again. We agreed that it is desirable for the typename to be "sticky" on a using-declaration, i.e., references to the name introduced by the using-declaration are known to be type names without the use of the typename keyword (which can't be specified on an unqualified name anyway, as of now). The related issue with the template keyword already has a separate issue 109.
Issue 2 deals with the "struct hack." There is an example in 9.10 [namespace.udecl] paragraph 10 that shows a use of using-declarations to import two names that coexist because of the "struct hack." After some deliberation, we decided that the template-dependent using-declaration case is different enough that we did not have to support the "struct hack" in that case. A name introduced in such a case is like a typedef, and no other hidden type can be accessed through an elaborated type specifier.
Proposed resolution (April 2003, revised October 2003):
Add a new paragraph to the bottom of 9.10 [namespace.udecl]:
If a using-declaration uses the keyword typename and specifies a dependent name (13.8.3 [temp.dep]), the name introduced by the using-declaration is treated as a typedef-name (9.2.4 [dcl.typedef]).
[Voted into WP at April 2003 meeting.]
According to 9.10 [namespace.udecl] paragraph 12,
When a using-declaration brings names from a base class into a derived class scope, member functions in the derived class override and/or hide member functions with the same name and parameter types in a base class (rather than conflicting).
Note that this description says nothing about the cv-qualification of the hiding and hidden member functions. This means, for instance, that a non-const member function in the derived class hides a const member function with the same name and parameter types instead of overloading it in the derived class scope. For example,
struct A { virtual int f() const; virtual int f(); }; struct B: A { B(); int f(); using A::f; }; const B cb; int i = cb.f(); // ill-formed: A::f() const hidden in B
The same terminology is used in 11.7.3 [class.virtual] paragraph 2:
If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name and same parameter list as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides Base::vf.
Notes on the 04/01 meeting:
The hiding and overriding should be on the basis of the function signature, which includes any cv-qualification on the function.
Proposed resolution (04/02):
In 9.10 [namespace.udecl] paragraph 12 change:
When a using-declaration brings names from a base class into a derived class scope, member functions in the derived class override and/or hide member functions with the same name and parameter types in a base class (rather than conflicting).to read:
When a using-declaration brings names from a base class into a derived class scope, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list (9.3.4.6 [dcl.fct]), and cv-qualification in a base class (rather than conflicting).
In 11.7.3 [class.virtual] paragraph 2 change:
If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name and same parameter list as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides Base::vf.to read:
If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name, parameter-type-list (9.3.4.6 [dcl.fct]), and cv-qualification as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides Base::vf.
See issue 140 for the definition of parameter-type-list.
[Voted into WP at April 2005 meeting.]
Can a using-declaration be used to import a namespace?
namespace my_namespace{ namespace my_namespace2 { int function_of_my_name_space(){ return 2;} } } int main (){ using ::my_namespace::my_namespace2; return my_namespace2::function_of_my_name_space(); }
Several popular compilers give an error on this, but there doesn't seem to be anything in 9.10 [namespace.udecl] that prohibits it. It should be noted that the user can get the same effect by using a namespace alias:
namespace my_namespace2 = ::my_namespace::my_namespace2;
Notes from the March 2004 meeting:
We agree that it should be an error.
Proposed resolution (October, 2004):
Add the following as a new paragraph after 9.10 [namespace.udecl] paragraph 5:
A using-declaration shall not name a namespace;
[Moved to DR at 4/01 meeting.]
9.12 [dcl.link] paragraph 6 says the following:
extern "C" { static void f(int) {} static void f(float) {} };Can a function with internal linkage "have C linkage" at all (assuming that phrase means "has extern "C" linkage"), for how can a function be extern "C" if it's not extern? The function type can have extern "C" linkage — but I think that's independent of the linkage of the function name. It should be perfectly reasonable to say, in the example above, that extern "C" applies only to the types of f(int) and f(float), not to the function names, and that the rule in 9.12 [dcl.link] paragraph 6 doesn't apply.
Suggested resolution: The extern "C" linkage specification applies only to the type of functions with internal linkage, and therefore some of the rules that have to do with name overloading don't apply.
Proposed Resolution:
The intent is to distingush implicit linkage from explicit linkage for both name linkage and language (function type) linkage. (It might be more clear to use the terms name linkage and type linkage to distinguish these concepts. A function can have a name with one kind of linkage and a type with a different kind of linkage. The function itself has no linkage: it has no name, only the declaration has a name. This becomes more obvious when you consider function pointers.)
The tentatively agreed proposal is to apply implicit linkage to names declared in brace-enclosed linkage specifications and to non-top-level names declared in simple linkage specifications; and to apply explicit linkage to top-level names declared in simple linkage specifications.
The language linkage of any function type formed through a function declarator is that of the nearest enclosing linkage-specification. For purposes of determining whether the declaration of a namespace-scope name matches a previous declaration, the language linkage portion of the type of a function declaration (that is, the language linkage of the function itself, not its parameters, return type or exception specification) is ignored.
For a linkage-specification using braces, i.e.
extern string-literal { declaration-seqopt }the linkage of any declaration of a namespace-scope name (including local externs) which is not contained in a nested linkage-specification, is not declared to have no linkage (static), and does not match a previous declaration is given the linkage specified in the string-literal. The language linkage of the type of any function declaration of a namespace-scope name (including local externs) which is not contained in a nested linkage-specification and which is declared with function declarator syntax is the same as that of a matching previous declaration, if any, else is specified by string-literal.
For a linkage-specification without braces, i.e.
extern string-literal declaration
the linkage of the names declared in the top-level declarators of declaration is specified by string-literal; if this conflicts with the linkage of any matching previous declarations, the program is ill-formed. The language linkage of the type of any top-level function declarator is specified by string-literal; if this conflicts with the language linkage of the type of any matching previous function declarations, the program is ill-formed. The effect of the linkage-specification on other (non top-level) names declared in declaration is the same as that of the brace-enclosed form.
Bill Gibbons: In particular, these should be well-formed:
extern "C" void f(void (*fp)()); // parameter type is pointer to // function with C language linkage extern "C++" void g(void (*fp)()); // parameter type is pointer to // function with C++ language linkage extern "C++" { // well-formed: the linkage of "f" void f(void(*fp)()); // and the function type used in the } // parameter still "C" extern "C" { // well-formed: the linkage of "g" void g(void(*fp)()); // and the function type used in the } // parameter still "C++"
but these should not:
extern "C++" void f(void(*fp)()); // error - linkage of "f" does not // match previous declaration // (linkage of function type used in // parameter is still "C" and is not // by itself ill-formed) extern "C" void g(void(*fp)()); // error - linkage of "g" does not // match previous declaration // (linkage of function type used in // parameter is still "C++" and is not // by itself ill-formed)
That is, non-top-level declarators get their linkage from matching declarations, if any, else from the nearest enclosing linkage specification. (As already described, top-level declarators in a brace-enclosed linkage specification get the linkage from matching declarations, if any, else from the linkage specifcation; while top-level declarators in direct linkage specifications get their linkage from that specification.)
Mike Miller: This is a pretty significant change from the current specification, which treats the two forms of language linkage similarly for most purposes. I don't understand why it's desirable to expand the differences.
It seems very unintuitive to me that you could have a top-level declaration in an extern "C" block that would not receive "C" linkage.
In the current standard, the statement in 9.12 [dcl.link] paragraph 4 that
the specified language linkage applies to the function types of all function declarators, function names, and variable names introduced by the declaration(s)
applies to both forms. I would thus expect that in
extern "C" void f(void(*)()); extern "C++" { void f(void(*)()); } extern "C++" f(void(*)());
both "C++" declarations would be well-formed, declaring an overloaded version of f that takes a pointer to a "C++" function as a parameter. I wouldn't expect that either declaration would be a redeclaration (valid or invalid) of the "C" version of f.
Bill Gibbons: The potential difficulty is the matching process and the handling of deliberate overloading based on language linkage. In the above examples, how are these two declarations matched:
extern "C" void f(void (*fp1)()); extern "C++" { void f(void(*fp2)()); }
given that the linkage that is part of fp1 is "C" while the linkage (prior to the matching process) that is part of fp2 is "C++"?
The proposal is that the linkage which is part of the parameter type is not determined until after the match is attempted. This almost always correct because you can't overload "C" and "C++" functions; so if the function names match, it is likely that the declarations are supposed to be the same.
Mike Miller: This seems like more trouble than it's worth. This comparison of function types ignoring linkage specifications is, as far as I know, not found anywhere in the current standard. Why do we need to invent it?
Bill Gibbons: It is possible to construct pathological cases where this fails, e.g.
extern "C" typedef void (*PFC)(); // pointer to "C" linkage function void f(PFC); // parameter is pointer to "C" function void f(void (*)()); // matching declaration or overload based on // difference in linkage type?
It is reasonable to require explicit typedefs in this case so that in the above example the second function declaration gets its parameter type function linkage from the first function declaration.
(In fact, I think you can't get into this situation without having already used typedefs to declare different language linkage for the top-level and parameter linkages.)
For example, if the intent is to overload based on linkage a typedef is needed:
extern "C" typedef void (*PFC)(); // pointer to "C" linkage function void f(PFC); // parameter is pointer to "C" function typedef void (*PFCPP)(); // pointer to "C++" linkage function void f(PFCPP); // parameter is pointer to "C++" function
In this case the two function declarations refer to different functions.
Mike Miller: This seems pretty strange to me. I think it would be simpler to determine the type of the parameter based on the containing linkage specification (implicitly "C++") and require a typedef if the user wants to override the default behavior. For example:
extern "C" { typedef void (*PFC)(); // pointer to "C" function void f(void(*)()); // takes pointer to "C" function } void f(void(*)()); // new overload of "f", taking // pointer to "C++" function void f(PFC); // redeclare extern "C" version
Notes from 04/00 meeting:
The following changes were tentatively approved, but because they do not completely implement the proposal above the issue is being kept for the moment in "drafting" status.
Notes from 10/00 meeting:
After further discussion, the core language working group determined that the more extensive proposal described above is not needed and that the following changes are sufficient.
Proposed resolution (04/01):
Change the first sentence of 9.12 [dcl.link] paragraph 1 from
All function types, function names, and variable names have a language linkage.
to
All function types, function names with external linkage, and variable names with external linkage have a language linkage.
In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names, and variable names introduced by the declaration(s).
to
In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names with external linkage, and variable names with external linkage declared within the linkage-specification.
Add at the end of the final example on 9.12 [dcl.link] paragraph 4:
extern "C" { static void f4(); // the name of the function f4 has // internal linkage (not C language // linkage) and the function's type // has C language linkage } extern "C" void f5() { extern void f4(); // Okay -- name linkage (internal) // and function type linkage (C // language linkage) gotten from // previous declaration. } extern void f4(); // Okay -- name linkage (internal) // and function type linkage (C // language linkage) gotten from // previous declaration. void f6() { extern void f4(); // Okay -- name linkage (internal) // and function type linkage (C // language linkage) gotten from // previous declaration. }
Change 9.12 [dcl.link] paragraph 7 from
Except for functions with internal linkage, a function first declared in a linkage-specification behaves as a function with external linkage. [Example:
extern "C" double f(); static double f(); // erroris ill-formed (9.2.2 [dcl.stc]). ] The form of linkage-specification that contains a braced-enclosed declaration-seq does not affect whether the contained declarations are definitions or not (6.2 [basic.def]); the form of linkage-specification directly containing a single declaration is treated as an extern specifier (9.2.2 [dcl.stc]) for the purpose of determining whether the contained declaration is a definition. [Example:
extern "C" int i; // declaration extern "C" { int i; // definition }—end example] A linkage-specification directly containing a single declaration shall not specify a storage class. [Example:
extern "C" static void f(); // error—end example]
to
A declaration directly contained in a linkage-specification is treated as if it contains the extern specifier (9.2.2 [dcl.stc]) for the purpose of determining the linkage of the declared name and whether it is a definition. Such a declaration shall not specify a storage class. [Example:extern "C" double f(); static double f(); // error extern "C" int i; // declaration extern "C" { int i; // definition } extern "C" static void g(); // error—end example]
[Moved to DR at October 2002 meeting. This was incorrectly marked as having DR status between 4/01 and 4/02. It was overlooked when issue 4 was moved to DR at the 4/01 meeting; this one should have been moved as well, because it's resolved by the changes there.]
Consider the following:
extern "C" void foo() { extern void bar(); bar(); }Does "bar()" have "C" language linkage?
The ARM is explicit and says
A linkage-specification for a function also applies to functions and objects declared within it.The DIS says
In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names, and variable names introduced by the declaration(s).Is the body of a function definition part of the declaration?
From Mike Miller:
Yes: from 9.1 [dcl.pre] paragraph 1,
From Dag Brück:
Consider the following where extern "C" has been moved to a separate declaration:
extern "C" void foo(); void foo() { extern void bar(); bar(); }I think the ARM wording could possibly be interpreted such that bar() has "C" linkage in my example, but not the DIS wording.
As a side note, I have always wanted to think that placing extern "C" on a function definition or a separate declaration would produce identical programs.
Proposed Resolution (04/01):
See the proposed resolution for Core issue 4, which covers this case.
The ODR should also be checked to see whether it addresses name and type linkage.
[Moved to DR at 10/01 meeting.]
With class name injection, when a base class name is used in a derived class, the name found is the injected name in the base class, not the name of the class in the scope containing the base class. Consequently, if the base class name is not accessible (e.g., because is is in a private base class), the base class name cannot be used unless a qualified name is used to name the class in the class or namespace of which it is a member.
Without class name injection the following example is valid. With class name injection, A is inaccessible in class C.
class A { }; class B: private A { }; class C: public B { A* p; // error: A inaccessible };
At the least, the standard should be more explicit that this is, in fact, ill-formed.
(See paper J16/99-0010 = WG21 N1187.)
Proposed resolution (04/01):
Add to the end of 11.8.2 [class.access.spec] paragraph 3:
[Note: In a derived class, the lookup of a base class name will find the injected-class-name instead of the name of the base class in the scope in which it was declared. The injected-class-name might be less accessible than the name of the base class in the scope in which it was declared.] [Example:
class A { }; class B : private A { }; class C : public B { A* p; // error: injected-class-name A is inaccessible ::A* q; // OK };—end example]
[Moved to DR at October 2002 meeting.]
I think that the definition of a POD class in the current version of the Standard is overly permissive in that it allows for POD classes for which a user-defined operator function operator& may be defined. Given that the idea behind POD classes was to achieve compatibility with C structs and unions, this makes 'Plain old' structs and unions behave not quite as one would expect them to.
In the C language, if x and y are variables of struct or union type S that has a member m, the following expression are allowed: &x, x.m, x = y. While the C++ standard guarantees that if x and y are objects of a POD class type S, the expressions x.m, x = y will have the same effect as they would in C, it is still possible for the expression &x to be interpreted differently, subject to the programmer supplying an appropriate version of a user-defined operator function operator& either as a member function or as a non-member function.
This may result in surprising effects. Consider:
// POD_bomb is a POD-struct. It has no non-static non-public data members, // no virtual functions, no base classes, no constructors, no user-defined // destructor, no user-defined copy assignment operator, no non-static data // members of type pointer to member, reference, non-POD-struct, or // non-POD-union. struct POD_bomb { int m_value1; int m_value2; int operator&() { return m_value1++; } int operator&() const { return m_value1 + m_value2; } };
6.8 [basic.types] paragraph 2 states:
For any complete POD object type T, whether or not the object holds a valid value of type T, the underlying bytes (6.7.1 [intro.memory]) making up the object can be copied into an array of char or unsigned char [footnote: By using, for example, the library functions (16.4.2.3 [headers]) memcpy or memmove]. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value. [Example:#define N sizeof(T) char buf[N]; T obj; // obj initialized to its original value memcpy(buf, &obj, N); // between these two calls to memcpy, // obj might be modified memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type // holds its original value—end example]
Now, supposing that the complete POD object type T in the example above is POD_bomb, and we cannot any more count on the assertions made in the comments to the example. Given a standard conforming implementation, the code will not even compile. And I see no legal way of copying the contents of an object of a complete object type POD_bomb into an array of char or unsigned char with memcpy or memmove without making use of the unary & operator. Except, of course, by means of an ugly construct like:
struct POD_without_ampersand { POD_bomb a_bomb; } obj; #define N sizeof(POD_bomb) char buf[N]; memcpy(buf, &obj, N); memcpy(&obj, buf, N);
The fact that the definition of a POD class allows for POD classes for which a user-defined operator& is defined, may also present major obstacles to implementers of the offsetof macro from <cstddef>
17.2 [support.types] paragraph 5 says:
The macro offsetof accepts a restricted set of type arguments in this International Standard. type shall be a POD structure or a POD union (Clause 11 [class]). The result of applying the offsetof macro to a field that is a static data member or a function is undefined."
Consider a well-formed C++ program below:
#include <cstddef> #include <iostream> struct POD_bomb { int m_value1; int m_value2; int operator&() { return m_value1++; } int operator&() const { return m_value1 + m_value2; } }; // POD_struct is a yet another example of a POD-struct. struct POD_struct { POD_bomb m_nonstatic_bomb1; POD_bomb m_nonstatic_bomb2; }; int main() { std::cout << "offset of m_nonstatic_bomb2: " << offsetof(POD_struct, m_nonstatic_bomb2) << '\n'; return 0; }
See Jens Maurer's paper 01-0038=N1324 for an analysis of this issue.
Notes from 10/01 meeting:
A consensus was forming around the idea of disallowing operator& in POD classes when it was noticed that it is permitted to declare global-scope operator& functions, which cause the same problems. After more discussion, it was decided that such functions should not be prohibited in POD classes, and implementors should simply be required to "get the right answer" in constructs such as offsetof and va_start that are conventionally implemented using macros that use the "&" operator. It was noted that one can cast the original operand to char & to de-type it, after which one can use the built-in "&" safely.
Proposed resolution:
[Footnote: Note that offsetof is required to work as specified even if unary operator& is overloaded for any of the types involved.]
[Footnote: Note that va_start is required to work as specified even if unary operator& is overloaded for the type of parmN.]
[Moved to DR at October 2002 meeting.]
Although 9.3.4 [dcl.meaning] requires that a declaration of a qualified-id refer to a member of the specified namespace or class and that the member not have been introduced by a using-declaration, it applies only to names declared in a declarator. It is not clear whether there is existing wording enforcing the same restriction for qualified-ids in class-specifiers and elaborated-type-specifiers or whether additional wording is required. Once such wording is found/created, the proposed resolution of issue 275 must be modified accordingly.
Notes from 10/01 meeting:
The sentiment was that this should be required on class definitions, but not on elaborated type specifiers in general (which are references, not declarations). We should also make sure we consider explicit instantiations, explicit specializations, and friend declarations.
Proposed resolution (10/01):
Add after the end of 11.3 [class.name] paragraph 3:
When a nested-name-specifier is specified in a class-head or in an elaborated-type-specifier, the resulting qualified name shall refer to a previously declared member of the class or namespace to which the nested-name-specifier 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.
[Voted into WP at April, 2007 meeting.]
In Clause 11 [class] paragraph 4, the first sentence says "A structure is a class definition defined with the class-key struct". As far as I know, there is no such thing as a structure in C++; it certainly isn't listed as one of the possible compound types in 6.8.4 [basic.compound]. And defining structures opens the question of whether a forward declaration is a structure or not. The parallel here with union (which follows immediately) suggests that structures and classes are really different things, since the same wording is used, and classes and unions do have some real differences, which manifest themselves outside of the definition. It also suggests that since one can't forward declare union with class and vice versa, the same should hold for struct and class -- I believe that the intent was that one could use struct and class interchangeably in forward declaration.
Suggested resolution:
I suggest something like the following:
If a class is defined with the class-key class, its members and base classes are private by default. If a class is defined with the class-key struct, its members and base classes are public by default. If a class is defined with the class-key union, its members are public by default, and it holds only one data member at a time. Such classes are called unions, and obey a number of additional restrictions, see 11.5 [class.union].
Proposed resolution (April, 2006):
This issue is resolved by the resolution of issue 538.
[Voted into WP at March 2004 meeting.]
The ARM used the term "class declaration" to refer to what would usually be termed the definition of the class. The standard now often uses "class definition", but there are some surviving uses of "class declaration" with the old meaning. They should be found and changed.
Proposed resolution (April 2003):
Replace in 6.2 [basic.def] paragraph 2
A declaration is a definition unless it declares a function without specifying the function's body (9.6 [dcl.fct.def]), it contains the extern specifier (9.2.2 [dcl.stc]) or a linkage-specification [Footnote: Appearing inside the braced-enclosed declaration-seq in a linkage-specification does not affect whether a declaration is a definition. --- end footnote] (9.12 [dcl.link]) and neither an initializer nor a function-body, it declares a static data member in a classdeclarationdefinition (11.4.9 [class.static]), it is a class name declaration (11.3 [class.name]), or it is a typedef declaration (9.2.4 [dcl.typedef]), a using-declaration (9.10 [namespace.udecl]), or a using-directive (9.9.4 [namespace.udir]).
Replace in 9.2.3 [dcl.fct.spec] paragraphs 5 and 6
The virtual specifier shall only be used in declarations of nonstatic class member functions that appear within a member-specification of a class
declarationdefinition; see 11.7.3 [class.virtual].The explicit specifier shall be used only in declarations of constructors within a class
declarationdefinition; see 11.4.8.2 [class.conv.ctor].
Replace in 9.2.4 [dcl.typedef] paragraph 4
A typedef-name that names a class is a class-name (11.3 [class.name]). If a typedef-name is used following the class-key in an elaborated-type-specifier (9.2.9.5 [dcl.type.elab]) or in the class-head of a classdeclarationdefinition (Clause 11 [class]), or is used as the identifier in the declarator for a constructor or destructor declaration (11.4.5 [class.ctor], 11.4.7 [class.dtor]), the program is ill-formed.
Replace in _N4868_.9.8.2.3 [namespace.memdef] paragraph 3
The name of the friend is not found by simple name lookup until a matching declaration is provided in that namespace scope (either before or after the classdeclarationdefinition granting friendship).
Replace in 9.3.4.3 [dcl.ref] paragraph 4
There shall be no references to references, no arrays of references, and no pointers to references. The declaration of a reference shall contain an initializer (9.5.4 [dcl.init.ref]) except when the declaration contains an explicit extern specifier (9.2.2 [dcl.stc]), is a class member (11.4 [class.mem]) declaration within a classdeclarationdefinition, or is the declaration of a parameter or a return type (9.3.4.6 [dcl.fct]); see 6.2 [basic.def].
Replace in 9.5.4 [dcl.init.ref] paragraph 3
The initializer can be omitted for a reference only in a parameter declaration (9.3.4.6 [dcl.fct]), in the declaration of a function return type, in the declaration of a class member within its classdeclarationdefinition (11.4 [class.mem]), and where the extern specifier is explicitly used.
Replace in 11.3 [class.name] paragraph 2
A classdefinitiondeclaration introduces the class name into the scope where it isdefineddeclared and hides any class, object, function, or other declaration of that name in an enclosing scope (6.4 [basic.scope]). If a class name is declared in a scope where an object, function, or enumerator of the same name is also declared, then when both declarations are in scope, the class can be referred to only using an elaborated-type-specifier (6.5.6 [basic.lookup.elab]).
Replace in 11.4.9 [class.static] paragraph 4
Static members obey the usual class member access rules ( 11.8 [class.access]). When used in the declaration of a class member, the static specifier shall only be used in the member declarations that appear within the member-specification of the classdeclarationdefinition.
Replace in 11.4.12 [class.nest] paragraph 1
A class can bedefineddeclared within another class. A classdefineddeclared within another is called a nested class. The name of a nested class is local to its enclosing class. The nested class is in the scope of its enclosing class. Except by using explicit pointers, references, and object names, declarations in a nested class can use only type names, static members, and enumerators from the enclosing class.
Replace in 11.6 [class.local] paragraph 1
A class can bedefineddeclared within a function definition; such a class is called a local class. The name of a local class is local to its enclosing scope. The local class is in the scope of the enclosing scope, and has the same access to names outside the function as does the enclosing function. Declarations in a local class can use only type names, static variables, extern variables and functions, and enumerators from the enclosing scope.
Replace in 11.7 [class.derived] paragraph 1
... The class-name in a base-specifier shall not be an incompletely defined class (Clause 11 [class]); this class is called a direct base class for the class beingdeclareddefined. During the lookup for a base class name, non-type names are ignored (_N4868_.6.4.10 [basic.scope.hiding]). If the name found is not a class-name, the program is ill-formed. A class B is a base class of a class D if it is a direct base class of D or a direct base class of one of D's base classes. A class is an indirect base class of another if it is a base class but not a direct base class. A class is said to be (directly or indirectly) derived from its (direct or indirect) base classes. [Note: See 11.8 [class.access] for the meaning of access-specifier.] Unlessredefinedredeclared in the derived class, members of a base class are also considered to be members of the derived class. The base class members are said to be inherited by the derived class. Inherited members can be referred to in expressions in the same manner as other members of the derived class, unless their names are hidden or ambiguous (6.5.2 [class.member.lookup]). [Note: the scope resolution operator :: (_N4567_.5.1.1 [expr.prim.general]) can be used to refer to a direct or indirect base member explicitly. This allows access to a name that has beenredefinedredeclared in the derived class. A derived class can itself serve as a base class subject to access control; see 11.8.3 [class.access.base]. A pointer to a derived class can be implicitly converted to a pointer to an accessible unambiguous base class (7.3.12 [conv.ptr]). An lvalue of a derived class type can be bound to a reference to an accessible unambiguous base class (9.5.4 [dcl.init.ref]).]
Replace in 11.7.2 [class.mi] paragraph 5
For another example,for an object c of class type C, a single subobject of type V is shared by every base subobject of c thatclass V { /* ... */ }; class A : virtual public V { /* ... */ }; class B : virtual public V { /* ... */ }; class C : public A, public B { /* ... */ };is declared to havehas a virtual base class of type V.
Replace in the example in 6.5.2 [class.member.lookup] paragraph 6 (the whole paragraph was turned into a note by the resolution of core issue 39)
The namesdefineddeclared in V and the left hand instance of W are hidden by those in B, but the namesdefineddeclared in the right hand instance of W are not hidden at all.
Replace in 11.7.4 [class.abstract] paragraph 2
... A virtual function is specified pure by using a pure-specifier (11.4 [class.mem]) in the function declaration in the classdeclarationdefinition. ...
Replace in the footnote at the end of 11.8.3 [class.access.base] paragraph 1
[Footnote: As specified previously in 11.8 [class.access], private members of a base class remain inaccessible even to derived classes unless friend declarations within the base classdeclarationdefinition are used to grant access explicitly.]
Replace in _N3225_.11.3 [class.access.dcl] paragraph 1
The access of a member of a base class can be changed in the derived class by mentioning its qualified-id in the derived classdeclarationdefinition. Such mention is called an access declaration. ...
Replace in the example in 12.3 [over.over] paragraph 5
The initialization of pfe is ill-formed because no f() with type int(...) has beendefineddeclared, and not because of any ambiguity.
Replace in C.7.6 [diff.dcl] paragraph 1
Rationale: Storage class specifiers don't have any meaning when associated with a type. In C++, class members can bedefineddeclared with the static storage class specifier. Allowing storage class specifiers on type declarations could render the code confusing for users.
Replace in C.7.7 [diff.class] paragraph 3
In C++, a typedef name may not beDrafting notes:redefinedredeclared in a classdeclarationdefinition after being used inthe declarationthat definition
The resolution of core issue 45 (DR) deletes 11.8.8 [class.access.nest] paragraph 2.
The following occurrences of "class declaration" are not changed:
[Voted into WP at March 2004 meeting.]
The standard (Clause 11 [class] par. 4) says that "A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined copy assignment operator and no user-defined destructor."
Note that it says 'user-defined', not 'user-declared'. Is it the intent that if e.g. a copy assignment operator is declared but not defined, this does not (per se) prevent the class to be a POD-struct?
Proposed resolution (April 2003):
Replace in Clause 11 [class] paragraph 4
A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defineddeclared copy assignment operator and no user-defineddeclared destructor. Similarly, a POD-union is an aggregate union that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defineddeclared copy assignment operator and no user-defineddeclared destructor.
Drafting note: The changes are shown relative to TC1, incorporating the changes from the resolution of core issue 148.
[Voted into WP at April, 2007 meeting.]
The proposal says that value is true if "T is an empty class (10)". Clause 10 doesn't define an empty class, although it has a note that says a base class may "be of zero size (clause 9)" 9/3 says "Complete objects and member subobjects of class type shall have nonzero size." This has a footnote, which says "Base class subobjects are not so constrained."
The standard uses the term "empty class" in two places (9.5.2 [dcl.init.aggr]), but neither of those places defines it. It's also listed in the index, which refers to the page that opens clause 9, i.e. the nonzero size stuff cited above.
So, what's the definition of "empty class" that determines whether the predicate is_empty is true?
The one place where it's used is 9.5.2 [dcl.init.aggr] paragraph 8, which says (roughly paraphrased) that an aggregate initializer for an empty class must be "{}", and when such an initializer is used for an aggregate that is not an empty class the members are default-initialized. In this context it's pretty clear what's meant. In the type traits proposal it's not as clear, and it was probably intended to have a different meaning. The boost implementation, after it eliminates non-class types, determines whether the trait is true by comparing the size of a class derived from T to the size of an otherwise-identical class that is not derived from T.
Howard Hinnant: is_empty was created to find out whether a type could be derived from and have the empty base class optimization successfully applied. It was created in part to support compressed_pair which attempts to optimize away the space for one of its members in an attempt to reduce spatial overhead. An example use is:
template <class T, class Compare = std::less<T> > class SortedVec { public: ... private: T* data_; compressed_pair<Compare, size_type> comp_; Compare& comp() {return comp_.first();} const Compare& comp() const {return comp_.first();} size_type& sz() {return comp_.second();} size_type sz() const {return comp_.second();} };
Here the compare function is optimized away via the empty base optimization if Compare turns out to be an "empty" class. If Compare turns out to be a non-empty class, or a function pointer, the space is not optimized away. is_empty is key to making this work.
This work built on Nathan's article: http://www.cantrip.org/emptyopt.html.
Clark Nelson: I've been looking at issue 413, and I've reached the conclusion that there are two different kinds of empty class. A class containing only one or more anonymous bit-field members is empty for purposes of aggregate initialization, but not (necessarily) empty for purposes of empty base-class optimization.
Of course we need to add a definition of emptiness for purposes of aggregate initialization. Beyond that, there are a couple of questions:
Notes from the October, 2005 meeting:
There are only two places in the Standard where the phrase “empty class” appears, both in 9.5.2 [dcl.init.aggr] paragraph 8. Because it is not clear whether the definition of “empty for initialization purposes” is suitable for use in defining the is_empty predicate, it would be better just to avoid using the phrase in the language clauses. The requirements of 9.5.2 [dcl.init.aggr] paragraph 8 appear to be redundant; paragraph 6 says that an initializer-list must have no more initializers than the number of elements to initialize, so an empty class already requires an empty initializer-list, and using an empty initializer-list with a non-empty class is covered adequately by paragraph 7's description of the handling of an initializer-list with fewer initializers than the number of members to initialize.
Proposed resolution (October, 2005):
Change 9.5.2 [dcl.init.aggr] paragraph 5 by inserting the indicated text:
Static data members and anonymous bit fields are not considered members of the class for purposes of aggregate initialization. [Example:
struct A { int i; static int s; int j; int :17; int k; } a = { 1 , 2 , 3 };Here, the second initializer 2 initializes a.j and not the static data member A::s, and the third initializer 3 initializes a.k and not the padding before it. —end example]
Delete 9.5.2 [dcl.init.aggr] paragraph 8:
An initializer for an aggregate member that is an empty class shall have the form of an empty initializer-list {}. [Example:
struct S { }; struct A { S s; int i; } a = { { } , 3 };—end example] An empty initializer-list can be used to initialize any aggregate. If the aggregate is not an empty class, then each member of the aggregate shall be initialized with a value of the form T() (7.6.1.4 [expr.type.conv]), where T represents the type of the uninitialized member.
This resolution also resolves issue 491.
Additional note (October, 2005):
Deleting 9.5.2 [dcl.init.aggr] paragraph 8 altogether may not be a good idea. It would appear that, in its absence, the initializer elision rules of paragraph 11 would allow the initializer for a in the preceding example to be written { 3 } (because the empty-class member s would consume no initializers from the list).
Proposed resolution (October, 2006):
(Drafting note: this resolution also cleans up incorrect references to syntactic non-terminals in the nearby paragraphs.)
Change 9.5.2 [dcl.init.aggr] paragraph 4 as indicated:
An array of unknown size initialized with a brace-enclosed initializer-list containing ninitializersinitializer-clauses, where n shall be greater than zero... An empty initializer list {} shall not be used as theinitializerinitializer-clause for an array of unknown bound.
Change 9.5.2 [dcl.init.aggr] paragraph 5 by inserting the indicated text:
Static data members and anonymous bit fields are not considered members of the class for purposes of aggregate initialization. [Example:
struct A { int i; static int s; int j; int :17; int k; } a = { 1 , 2 , 3 };Here, the second initializer 2 initializes a.j and not the static data member A::s, and the third initializer 3 initializes a.k and not the anonymous bit field before it. —end example]
Change 9.5.2 [dcl.init.aggr] paragraph 6 as indicated:
An initializer-list is ill-formed if the number ofinitializersinitializer-clauses exceeds the number of members...
Change 9.5.2 [dcl.init.aggr] paragraph 7 as indicated:
If there are fewerinitializersinitializer-clauses in the list than there are members...
Replace 9.5.2 [dcl.init.aggr] paragraph 8:
An initializer for an aggregate member that is an empty class shall have the form of an empty initializer-list {}. [Example:
struct S { }; struct A { S s; int i; } a = { { } , 3 };—end example] An empty initializer-list can be used to initialize any aggregate. If the aggregate is not an empty class, then each member of the aggregate shall be initialized with a value of the form T() (7.6.1.4 [expr.type.conv]), where T represents the type of the uninitialized member.
with:
If an aggregate class C contains a subaggregate member m that has no members for purposes of aggregate initialization, the initializer-clause for m shall not be omitted from an initializer-list for an object of type C unless the initializer-clauses for all members of C following m are also omitted. [Example:
struct S { } s; struct A { S s1; int i1; S s2; int i2; S s3; int i3; } a = { { }, // Required initialization 0, s, // Required initialization 0 }; // Initialization not required for A::s3 because A::i3 is also not initialized—end example]
Change 9.5.2 [dcl.init.aggr] paragraph 10 as indicated:
When initializing a multi-dimensional array, theinitializersinitializer-clauses initialize the elements...
Change 9.5.2 [dcl.init.aggr] paragraph 11 as indicated:
Braces can be elided in an initializer-list as follows. If the initializer-list begins with a left brace, then the succeeding comma-separated list ofinitializersinitializer-clauses initializes the members of a subaggregate; it is erroneous for there to be moreinitializersinitializer-clauses than members. If, however, the initializer-list for a subaggregate does not begin with a left brace, then only enoughinitializersinitializer-clauses from the list are taken to initialize the members of the subaggregate; any remaininginitializersinitializer-clauses are left to initialize the next member of the aggregate of which the current subaggregate is a member. [Example:...
Change 9.5.2 [dcl.init.aggr] paragraph 12 as indicated:
All implicit type conversions (7.3 [conv]) are considered when initializing the aggregate member with aninitializer from an initializer-listassignment-expression. If theinitializerassignment-expression can initialize a member, the member is initialized. Otherwise, if the member is itself anon-emptysubaggregate, brace elision is assumed and theinitializerassignment-expression is considered for the initialization of the first member of the subaggregate. [Note: As specified above, brace elision cannot apply to subaggregates with no members for purposes of aggregate initialization; an initializer-clause for the entire subobject is required. —end note] [Example:... Braces are elided around theinitializerinitializer-clause for b.a1.i...
Change 9.5.2 [dcl.init.aggr] paragraph 15 as indicated:
When a union is initialized with a brace-enclosed initializer, the braces shall only contain aninitializerinitializer-clause for the first member of the union...
Change 9.5.2 [dcl.init.aggr] paragraph 16 as indicated:
[Note:asAs described above, the braces around theinitializerinitializer-clause for a union member can be omitted if the union is a member of another aggregate. —end note]
(Note: this resolution also resolves issue 491.)
[Voted into WP at April, 2007 meeting.]
There are several problems with the terms defined in Clause 11 [class] paragraph 4:
A structure is a class defined with the class-key struct; its members and base classes (11.7 [class.derived]) are public by default ( 11.8 [class.access]). A union is a class defined with the class-key union; its members are public by default and it holds only one data member at a time (11.5 [class.union]). [Note: aggregates of class type are described in 9.5.2 [dcl.init.aggr]. —end note] A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-declared copy assignment operator and no user-declared destructor. Similarly, a POD-union is an aggregate union that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-declared copy assignment operator and no user-declared destructor. A POD class is a class that is either a POD-struct or a POD-union.
Although the term structure is defined here, it is used only infrequently throughout the Standard, often apparently inadvertently and consequently incorrectly:
7.6.1.5 [expr.ref] paragraph 4: the use is in a note and is arguably correct and helpful.
11.4 [class.mem] paragraph 11: the term is used (three times) in an example. There appears to be no reason to use it instead of “class,” but its use is not problematic.
_N4567_.17.3 [definitions] “iostream class templates:” the traits argument to the iostream class templates is (presumably unintentionally) constrained to be a structure, i.e., to use the struct keyword and not the class keyword in its definition.
Clause Annex B [implimits] paragraph 2: the minimum number of declarator operators is given for structures and unions but not for classes defined using the class keyword.
Clause Annex B [implimits] paragraph 2: class, structure, and union are used as disjoint terms in describing nesting levels. (The nonexistent nonterminal struct-declaration-list is used, as well.)
There does not appear to be a reason for defining the term structure. The one reference where it is arguably useful, in the note in 7.6.1.5 [expr.ref], could be rewritten as something like, “'class objects' may be defined using the class, struct, or union class-keys; see Clause 11 [class].”
Based on its usage later in the paragraph and elsewhere, “POD-struct” appears to be intended to exclude unions. However, the definition of “aggregate class” in 9.5.2 [dcl.init.aggr] paragraph 1 includes unions. Furthermore, the name itself is confusing, leading to the question of whether it was intended that only classes defined using struct could be POD-structs or if class-classes are included. The definition should probably be rewritten as, “A POD-struct is an aggregate class defined with the class-key struct or the class-key class that has no...
In most references outside Clause 11 [class], POD-struct and POD-union are mentioned together and treated identically. These references should be changed to refer to the unified term, “POD class.”
Noted in passing: 17.2 [support.types] paragraph 4 refers to the undefined terms “POD structure” and (unhyphenated) “POD union;” the pair should be replaced by a single reference to “POD class.”
Proposed resolution (April, 2006):
Change Clause 11 [class] paragraph 4 as indicated:
A structure is a class defined with the class-key struct; its members and base classes (11.7 [class.derived]) are public by default ( 11.8 [class.access]).A union is a class defined with the class-key union;its members are public by default andit holds only one data member at a time (11.5 [class.union]). [Note: aggregates of class type are described in 9.5.2 [dcl.init.aggr]. —end note]A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-declared copy assignment operator and no user-declared destructor. Similarly, a POD-union is an aggregate union that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-declared copy assignment operator and no user-declared destructor. A POD class is a class that is either a POD-struct or a POD-union.A POD class is an aggregate class that has no non-static data members of non-POD type (or array of such a type) or reference, and has no user-declared copy assignment operator and no user-declared destructor. A POD-struct is a POD class defined with the class-key struct or the class-key class. A POD-union is a POD class defined with the class-key union.
Change 11.8.3 [class.access.base] paragraph 2 as indicated:
In the absence of an access-specifier for a base class, public is assumed when the derived class isdeclareddefined with the class-key struct and private is assumed when the class isdeclareddefined with the class-key class. [Example:...
Delete the note in 7.6.1.5 [expr.ref] paragraph 4:
[Note: “class objects” can be structures (11.4 [class.mem]) and unions (11.5 [class.union]). Classes are discussed in Clause 11 [class]. —end note]
Change the commentary in the example in 11.4 [class.mem] paragraph 11 as indicated:
...an integer, and two pointers to
similar structuresobjects of the same type. Once this definition......the count member of the
structureobject to which sp points; s.left refers to the left subtree pointer of thestructureobject s; and...
Change _N4567_.17.3 [definitions] “iostream class templates” as indicated:
...the argument traits is astructureclass which defines additional characteristics...
Change 17.6 [support.dynamic] paragraph 4 as indicated:
If type is not aPOD structure or a POD unionPOD class (clause 9), the results are undefined.
Change the third bullet of Clause Annex B [implimits] paragraph 2 as indicated:
Pointer, array, and function declarators (in any combination)
modifying an a class, arithmetic, structure,
union, or incomplete type in a declaration [256].
Change the nineteenth bullet of Clause Annex B [implimits] paragraph 2 as indicated:
Data members in a single class, structure, or union [16 384].
Change the twenty-first bullet of Clause Annex B [implimits] paragraph 2 as indicated:
Levels of nested class, structure, or union definitions in a
single struct-declaration-list
member-specification [256].
Change C.8 [diff.library] paragraph 6 as indicated:
The C++ Standard library provides 2 standardstructuresstructs from the C library, as shown in Table 126.
Change the last sentence of 6.8 [basic.types] paragraph 10 as indicated:
Scalar types,POD-struct types, POD-union typesPOD classes (Clause 11 [class]), arrays of such types and cv-qualified versions of these types (6.8.5 [basic.type.qualifier]) are collectively called POD types.
Drafting note: Do not change 6.8 [basic.types] paragraph 11, because it's a note and the definition of “layout-compatible” is separate for POD-struct and POD-union in 11.4 [class.mem].
(This resolution also resolves issue 327.)
[Voted into the WP at the July, 2007 meeting as part of paper J16/07-0202 = WG21 N2342.]
A POD struct (Clause 11 [class] paragraph 4) is “an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types), or reference, and that has no user-defined copy assignment operator and no user-defined destructor.” Meanwhile, an aggregate class (9.5.2 [dcl.init.aggr] paragraph 1) must have “no user-declared constructors, no private or protecte non-static data members, no base classes, and no virtual functions.”
This is too strict. The whole reason we define the notion of POD is for the layout compatibility guarantees in 11.4 [class.mem] paragraphs 14-17 and the byte-for-byte copying guarantees of 6.8 [basic.types] paragraph 2. None of those guarantees should be affected by the presence of ordinary constructors, any more than they're affected by the presence of any other member function. It’s silly for the standard to make layout and memcpy guarantees for this class:
struct A { int n; };
but not for this one:
struct B { int n; B(n_) : n(n_) { } };
With either A or B, it ought to be possible to save an array of those objects to disk with a single call to Unix’s write(2) system call or the equivalent. At present the standard says that it’s legal for A but not B, and there isn’t any good reason for that distinction.
Suggested resolution:
The following doesn’t fix all problems (in particular it still doesn’t let us treat pair<int, int> as a POD), but at least it goes a long way toward fixing the problem: in 9.5.2 [dcl.init.aggr] paragraph 1, change “no user-declared constructors” to “no nontrivial default constructor and no user-declared copy constructor.”
(Yes, I’m aware that this proposed change would also allow brace initialization for some types that don't currently allow it. I consider this to be a feature, not a bug.)
Mike Miller: I agree that something needs to be done about “POD,” but I’m not sure that this is it. My own take is that “POD” is used for too many different things — things that are related but not identical — and the concept should be split. The current definition is useful, as is, for issues regarding initialization and lifetime. For example, I wouldn’t want to relax the prohibition of jumping over a constructor call in 8.9 [stmt.dcl] (which is currently phrased in terms of POD types). On the other hand, I agree that the presence of a user-declared constructor says nothing about layout and bitwise copying. This needs (IMHO) a non-trivial amount of further study to determine how many categories we need (instead of just POD versus non-POD), which guarantees and prohibitions go with which category, the interaction of “memcpy initialization” (for want of a better term) with object lifetime, etc.
(See paper J16/06-0172 = WG21 N2102.)
Proposed resolution (April, 2007):
Adoption of the POD proposal (currently J16/07-0090 = WG21 N2230) will resolve this issue.
[Voted into WP at October 2004 meeting.]
We had a user complain that our compiler was allowing the following code:
struct B { struct S; }; struct D : B { }; struct D::S { };
We took one look at the code and made the reasonable (I would claim) assumption that this was indeed a bug in our compiler. Especially as we had just fixed a very similar issue with the definition of static data members.
Imagine our surprise when code like this showed up in Boost and that every other compiler we tested accepts this code. So is this indeed legal (it seems like it must be) and if so is there any justification for this beyond 6.5.5.2 [class.qual]?
John Spicer: The equivalent case for a member function is covered by the declarator rules in 9.3.4 [dcl.meaning] paragraph 1. The committee has previously run into cases where a restriction should apply to both classes and non-classes, but fails to do so because there is no equivalent of 9.3.4 [dcl.meaning] paragraph 1 for classes.
Given that, by the letter of the standard, I would say that this case is allowed.
Notes from October 2003 meeting:
We feel this case should get an error.
Proposed Resolution (October 2003):
Note that the change here interacts with issue 432.
Add the following as a new paragraph immediately following 6.4.2 [basic.scope.pdecl] paragraph 2:
The point of declaration for a class first declared by a class-specifier is immediately after the identifier or template-id (if any) in its class-head (Clause 11 [class]). The point of declaration for an enumeration is immediately after the identifier (if any) in its enum-specifier (9.8.1 [dcl.enum]).
Change point 1 of 6.4.7 [basic.scope.class] paragraph 1 to read:
The potential scope of a name declared in a class consists not only of the declarative region following the name'sdeclaratorpoint of declaration, but also of all function bodies, default arguments, and constructor ctor-initializers in that class (including such things in nested classes).
[Note that the preceding change duplicates one of the changes in the proposed resolution of issue 432.]
Change 13.9.3 [temp.explicit] paragraph 2 to read:
If the explicit instantiation is for a member function, a member class or a static data member of a class template specialization, the name of the class template specialization in the qualified-id for the memberdeclaratorname shall be a template-id.
Add the following as paragraph 5 of Clause 11 [class]:
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 (i.e., neither inherited nor introduced by a using-declaration), and the class-specifier shall appear in a namespace enclosing the previous declaration.
Delete 11.3 [class.name] paragraph 4 (this was added by issue 284):
When a nested-name-specifier is specified in a class-head or in an elaborated-type-specifier, the resulting qualified name shall refer to a previously declared member of the class or namespace to which the nested-name-specifier 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.
[Voted into WP at March 2004 meeting.]
Is it legal to use an incomplete type (6.8 [basic.types] paragraph 6) as a class member, if no object of such class is ever created ?
And as a class template member, even if the template is instantiated, but no object of the instantiated class is created?
The consensus seems to be NO, but no wording was found in the standard which explicitly disallows it.
The problem seems to be that most of the restrictions on incomplete types are on their use in objects, but class members are not objects.
A possible resolution, if this is considered a defect, is to add to 6.3 [basic.def.odr] paragraph 4, (situations when T must be complete), the use of T as a member of a class or instantiated class template.
The thread on comp.std.c++ which brought up the issue was "Compiler differences: which is correct?", started 2001 11 30. <3c07c8fb$0$8507$ed9e5944@reading.news.pipex.net>
Proposed Resolution (April 2002, revised April 2003):
Change the first bullet of the note in 6.3 [basic.def.odr] paragraph 4 and add two new bullets following it, as follows:
Replace 11.4 [class.mem] paragraph 8 by:
Non-static (11.4.9 [class.static]) data members shall not have incomplete types. In particular, a class C shall not contain a non-static member of class C, but it can contain a pointer or reference to an object of class C.
See also 6.8 [basic.types] paragraph 6, which is relevant but not changed by the Proposed Resolution.
[Voted into WP at April 2005 meeting.]
I've encountered a C++ program in which a member function wants to declare that it may throw an object of its own class, e.g.:
class Foo { private: int val; public: Foo( int &initval ) { val = initval; }; void throwit() throw(Foo) { throw (*this); }; };
The compiler is complaining that Foo is an incomplete type, and can't be used in the exception specification.
My reading of the standard [basic.types] is inconclusive. Although it does state that the class declaration is considered complete when the closing brace is read, I believe it also intends that the member function declarations should not be semantically validated until the class has been completely declared.
If this isn't allowed, I don't know how else a member function could be declared to throw an object of its own class.
John Spicer: The type is considered complete within function bodies, but not in their declaration (see 11.4 [class.mem] paragraph 2).
Proposed Resolution:
Change 11.4 [class.mem] paragraph 2 as follows:
Within the class member-specification, the class is regarded as complete within function bodies, default arguments, exception-specifications, and constructor ctor-initializers (including such things in nested classes).
Rationale: Taken with 9.3.4.6 [dcl.fct] paragraph 6, the exception-specification is the only part of a function declaration/definition in which the class name cannot be used because of its putative incompleteness. There is no justification for singling out exception specifications this way; both in the function body and in a catch clause, the class type will be complete, so there is no harm in allowing the class name to be used in the exception-specification.
[Voted into WP at April, 2007 meeting.]
According to 11.4 [class.mem] paragraph 9, the name of a non-static data member can only be used with an object reference (explicit or implied by the this pointer of a non-static member function) or to form a pointer to member. This restriction applies even in the operand of sizeof, although the operand is not evaluated and thus no object is needed to perform the operation. Consequently, determining the size of a non-static class member often requires a circumlocution like
sizeof ((C*) 0)->m
instead of the simpler and more obvious (but incorrect)
sizeof (C::m)
The CWG considered this question as part of issue 198 and decided at that time to retain the restriction on consistency grounds: the rule was viewed as applying uniformly to expressions, and making an exception for sizeof would require introducing a special-purpose “wart.”
The issue has recently resurfaced, in part due to the fact that the restriction would also apply to the decltype operator. Like the unary & operator to form a pointer to member, sizeof and decltype need neither an lvalue nor an rvalue, requiring solely the declarative information of the named operand. One possible approach would be to define the concept of “unevaluated operand” or the like, exempt unevaluated operands from the requirement for an object reference in 11.4 [class.mem] paragraph 9, and then define the operands of these operators as “unevaluated.”
Proposed resolution (April, 2007):
The wording is given in paper J16/07-0113 = WG21 N2253.
[Voted into the WP at the July, 2007 meeting as part of paper J16/07-0202 = WG21 N2342.]
It should be made clear in 11.4 [class.mem] paragraph 15,
Two POD-struct (Clause 11 [class]) types are layout-compatible if they have the same number of non-static data members, and corresponding non-static data members (in order) have layout-compatible types (6.8 [basic.types]).
that “corresponding... (in order)” refers to declaration order and not the order in which the members are laid out in memory.
However, this raises the point that, in cases where an access-specifier is involved, the declaration and layout order can be different (see paragraph 12). Thus, for two POD-struct classes A and B,
struct A { char c; int i; } struct B { char c; public: int i; };
a compiler could move B::i before B::c, but A::c must precede A::i. It does not seem reasonable that these two POD-structs would be considered layout-compatible, even though they satisfy the requirement that corresponding members in declaration order are layout-compatible.
One possibility would be to require that neither POD-struct have an access-specifier in order to be considered layout-compatible. (It's not sufficient to require that they have the same access-specifiers, because the compiler is not required to lay out the storage the same way for different classes.)
9.5.2 [dcl.init.aggr] paragraph 2 should also be clarified to make explicit that “increasing... member order” refers to declaration order.
Proposed resolution (April, 2007):
This issue will be resolved by the adoption of the POD proposal (currently J16/07-0090 = WG21 N2230). That paper does not propose a change to the wording of 9.5.2 [dcl.init.aggr] paragraph 2, but the CWG feels that the intent of that paragraph (that the initializers are used in declaration order) is clear enough not to require revision.
[Voted into WP at April 2003 meeting.]
According to 11.4.5 [class.ctor] paragraph 1, a declaration of a constructor has a special limited syntax, in which only function-specifiers are allowed. A friend specifier is not a function-specifier, so one interpretation is that a constructor cannot be declared in a friend declaration.
(It should also be noted, however, that neither friend nor function-specifier is part of the declarator syntax, so it's not clear that anything conclusive can be derived from the wording of 11.4.5 [class.ctor].)
Notes from 04/01 meeting:
The consensus of the core language working group was that it should be permitted to declare constructors as friends.
Proposed Resolution (revised October 2002):
Change paragraph 1a in 6.5.5.2 [class.qual] (added by the resolution of issue 147) as follows:
If the nested-name-specifier nominates a class C, and the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C ( Clause 11 [class]), the name is instead considered to name the constructor of class C. Such a constructor name shall be used only in the declarator-id of aconstructor definitiondeclaration thatappears outside of the class definitionnames a constructor....
Note: the above does not allow qualified names to be used for in-class declarations; see 9.3.4 [dcl.meaning] paragraph 1. Also note that issue 318 updates the same paragraph.
Change the example in 11.8.4 [class.friend], paragraph 4 as follows:
class Y { friend char* X::foo(int); friend X::X(char); // constructors can be friends friend X::~X(); // destructors can be friends //... };
[Voted into WP at October 2003 meeting.]
In 11.4.5 [class.ctor] paragraph 5, the standard says "A constructor is trivial if [...]", and goes on to define a trivial default constructor. Taken literally, this would mean that a copy constructor can't be trivial (contrary to 11.4.5.3 [class.copy.ctor] paragraph 6). I suggest changing this to "A default constructor is trivial if [...]". (I think the change is purely editorial.)
Proposed Resolution (revised October 2002):
Change 11.4.5 [class.ctor] paragraph 5-6 as follows:
A default constructor for a class X is a constructor of class X that can be called without an argument. If there is no
user-declareduser-declared constructor for class X, a default constructor is implicitly declared. Animplicitly-declaredimplicitly-declared default constructor is an inline public member of its class. A default constructor is trivial if it isanimplicitly-declareddefault constructorand if:
- its class has no virtual functions (11.7.3 [class.virtual]) and no virtual base classes (11.7.2 [class.mi]), and
- all the direct base classes of its class have trivial default constructors, and
- for all the nonstatic data members of its class that are of class type (or array thereof), each such class has a trivial default constructor.
Otherwise, the default constructor is non-trivial.
Change 11.4.7 [class.dtor] paragraphs 3-4 as follows (the main changes are removing italics):
If a class has no
user-declareduser-declared destructor, a destructor is declared implicitly. Animplicitly-declaredimplicitly-declared destructor is an inline public member of its class. A destructor is trivial if it isanimplicitly-declareddestructorand if:
- all of the direct base classes of its class have trivial destructors and
- for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.
Otherwise, the destructor is
non-trivialnon-trivial.
In 11.5 [class.union] paragraph 1, change "trivial constructor" to "trivial default constructor".
In 6.7.7 [class.temporary] paragraph 3, add to the reference to 11.4.5 [class.ctor] a second reference, to 11.4.5.3 [class.copy.ctor].
[Voted into WP at October 2003 meeting.]
11.4.5 [class.ctor] paragraph 10 states
A copy constructor for a class X is a constructor with a first parameter of type X & or of type const X &. [Note: see 11.4.5.3 [class.copy.ctor] for more information on copy constructors.]
No mention is made of constructors with first parameters of types volatile X & or const volatile X &. This statement seems to be in contradiction with 11.4.5.3 [class.copy.ctor] paragraph 2 which states
A non-template constructor for class X is a copy constructor if its first parameter is of type X &, const X &, volatile X & or const volatile X &, ...
11.4.5.3 [class.copy.ctor] paragraph 5 also mentions the volatile versions of the copy constructor, and the comparable paragraphs for copy assignment (11.4.5.3 [class.copy.ctor] paragraphs 9 and 10) all allow volatile versions, so it seems that 11.4.5 [class.ctor] is at fault.
Proposed resolution (October 2002):
Change 11.4.5 [class.ctor] paragraph 10 from
A copy constructor for a class X is a constructor with a first parameter of type X& or of type const X&. [Note: see 11.4.5.3 [class.copy.ctor] for more information on copy constructors. ]to (note that the dropping of italics is intentional):
A copy constructor (11.4.5.3 [class.copy.ctor]) is used to copy objects of class type.
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
Part of the decision regarding whether a class has a trivial special function (copy constructor, copy assignment operator, default constructor) is whether its base and member subobjects have corresponding trivial member functions. However, with the advent of defaulted functions, it is now possible for a single class to have both trivial and nontrivial overloads for those functions. For example,
struct B { B(B&) = default; // trivial B(const B&); // non-trivial, because user-provided }; struct D : B { };
Although B has a trivial copy constructor and thus satisfies the requirements in 11.4.5.3 [class.copy.ctor] paragraph 6, the copy constructor in B that would be called by the implicitly-declared copy constructor in D is not trivial. This could be fixed either by requiring that all the subobject's copy constructors (or copy assignment operators, or default constructors) be trivial or that the one that would be selected by overload resolution be trivial.
Proposed resolution (July, 2008):
Change 9.6 [dcl.fct.def] paragraph 9 as follows:
... A special member function that would be implicitly defined as deleted shall not be explicitly defaulted. If a special member function for a class X is defaulted on its first declaration, no other special member function of the same kind (default constructor, copy constructor, or copy assignment operator) shall be declared in class X. A special member function is user-provided...
Notes from the September, 2008 meeting:
The resolution adopted as part of paper N2757 differs from the July, 2008 proposed resolution by allowing defaulted and user-provided special member functions to coexist. Instead, a trivial class is defined as having no non-trivial copy constructors or copy assignment operators, and a trivial copy constructor or assignment operator is defined as invoking only trivial copy operations for base and member subobjects.
[Moved to DR at October 2002 meeting.]
11.4.7 [class.dtor] contains this example:
struct B { virtual ~B() { } }; struct D : B { ~D() { } }; D D_object; typedef B B_alias; B* B_ptr = &D_object; void f() { D_object.B::~B(); // calls B's destructor B_ptr->~B(); // calls D's destructor B_ptr->~B_alias(); // calls D's destructor B_ptr->B_alias::~B(); // calls B's destructor B_ptr->B_alias::~B_alias(); // error, no B_alias in class B }
On the other hand, 6.5.5 [basic.lookup.qual] contains this example:
struct C { typedef int I; }; typedef int I1, I2; extern int* p; extern int* q; p->C::I::~I(); // I is looked up in the scope of C q->I1::~I2(); // I2 is looked up in the scope of // the postfix-expression struct A { ~A(); }; typedef A AB; int main() { AB *p; p->AB::~AB(); // explicitly calls the destructor for A }
Note that
B_ptr->B_alias::~B_alias();
is claimed to be an error, while the equivalent
p->AB::~AB();
is claimed to be well-formed.
I believe that clause 3 is correct and that clause 12 is in error. We worked hard to get the destructor lookup rules in clause 3 to be right, and I think we failed to notice that a change was also needed in clause 12.
Mike Miller:
Unfortunately, I don't believe 6.5.5 [basic.lookup.qual] covers the case of p->AB::~AB(). It's clearly intended to do so, as evidenced by 6.5.5.2 [class.qual] paragraph 1 ("a destructor name is looked up as specified in 6.5.5 [basic.lookup.qual]"), but I don't think the language there does so.
The relevant paragraph is 6.5.5 [basic.lookup.qual] paragraph 5. (None of the other paragraphs in that section deal with this topic at all.) It has two parts. The first is
If a pseudo-destructor-name (_N4778_.7.6.1.4 [expr.pseudo]) contains a nested-name-specifier, the type-names are looked up as types in the scope designated by the nested-name-specifier.
This sentence doesn't apply, because ~AB isn't a pseudo-destructor-name. _N4778_.7.6.1.4 [expr.pseudo] makes clear that this syntactic production (7.6.1 [expr.post] paragraph 1) only applies to cases where the type-name is not a class-name. p->AB::~AB is covered by the production using id-expression.
The second part of 6.5.5 [basic.lookup.qual] paragraph 5 says
In a qualified-id of the form:
::opt nested-name-specifier ~ class-name
where the nested-name-specifier designates a namespace name, and in a qualified-id of the form:
::opt nested-name-specifier class-name :: ~ class-name
the class-names are looked up as types in the scope designated by the nested-name-specifier.
This wording doesn't apply, either. The first one doesn't because the nested-name-specifier is a class-name, not a namespace name. The second doesn't because there's only one layer of qualification.
As far as I can tell, there's no normative text that specifies how the ~AB is looked up in p->AB::~AB(). 6.5.5.2 [class.qual], where all the other class member qualified lookups are handled, defers to 6.5.5 [basic.lookup.qual], and 6.5.5 [basic.lookup.qual] doesn't cover the case.
See also issue 305.
Jason Merrill: My thoughts on the subject were that the name we use in a destructor call is really meaningless; as soon as we see the ~ we know what the user means, all we're doing from that point is testing their ability to name the destructor in a conformant way. I think that everyone will agree that
anything::B::~B()should be well-formed, regardless of the origins of the name "B". I believe that the rule about looking up the second "B" in the same context as the first was intended to provide this behavior, but to me this seems much more heavyweight than necessary. We don't need a whole new type of lookup to be able to use the same name before and after the ~; we can just say that if the two names match, the call is well-formed. This is significantly simpler to express, both in the standard and in an implementation.
Anyone writing two different names here is either deliberately writing obfuscated code, trying to call the destructor of a nested class, or fighting an ornery compiler (i.e. one that still wants to see B_alias::~B()). I think we can ignore the first case. The third would be handled by reverting to the old rule (look up the name after ~ in the normal way) with the lexical matching exception described above -- or we could decide to break such code, do no lookup at all, and only accept a matching name. In a good implementation, the second should probably get an error message telling them to write Outer::Inner::~Inner instead.
We discussed this at the meetings, but I don't remember if we came to any sort of consensus on a direction. I see three options:
My order of preference is 2, 3, 1.
Incidentally, it seems to me oddly inconsistent to allow Namespace::~Class, but not Outer::~Inner. Prohibiting the latter makes sense from the standpoint of avoiding ambiguity, but what was the rationale for allowing the former?
John Spicer: I agree that allowing Namespace::~Class is odd. I'm not sure where this came from. If we eliminated that special case, then I believe the #1 rule would just be that in A::B1::~B2 you look up B1 and B2 in the same place in all cases.
I don't like #2. I don't think the "old" rules represent a deliberate design choice, just an error in the way the lookup was described. The usage that rule permits p->X::~Y (where Y is a typedef to X defined in X), but I doubt people really do that. In other words, I think that #1 a more useful special case than #2 does, not that I think either special case is very important.
One problem with the name matching rule is handling cases like:
A<int> *aip; aip->A<int>::~A<int>(); // should work aip->A<int>::~A<char>(); // should notI would favor #1, while eliminating the special case of Namespace::~Class.
Proposed resolution (10/01):
Replace the normative text of 6.5.5 [basic.lookup.qual] paragraph 5 after the first sentence with:
Similarly, in a qualified-id of the form:
::opt nested-name-specifieropt class-name :: ~ class-namethe second class-name is looked up in the same scope as the first.
In 11.4.7 [class.dtor] paragraph 12, change the example to
D D_object; typedef B B_alias; B* B_ptr = &D_object; void f() { D_object.B::~B(); // calls B's destructor B_ptr->~B(); // calls D's destructor B_ptr->~B_alias(); // calls D's destructor B_ptr->B_alias::~B(); // calls B's destructor B_ptr->B_alias::~B_alias(); // calls B's destructor }
April 2003: See issue 399.
[Moved to DR at 10/01 meeting.]
There is a mismatch between 11.4.7 [class.dtor] paragraph 11 and 11.4.11 [class.free] paragraph 4 regarding the lookup of deallocation functions in virtual destructors. 11.4.7 [class.dtor] says,
At the point of definition of a virtual destructor (including an implicit definition (11.4.5.3 [class.copy.ctor])), non-placement operator delete shall be looked up in the scope of the destructor's class (6.5.3 [basic.lookup.unqual]) and if found shall be accessible and unambiguous. [Note: this assures that an operator delete corresponding to the dynamic type of an object is available for the delete-expression (11.4.11 [class.free]). ]
The salient features to note from this description are:
On the other hand, 11.4.11 [class.free] says,
If a delete-expression begins with a unary :: operator, the deallocation function's name is looked up in global scope. Otherwise, if the delete-expression is used to deallocate a class object whose static type has a virtual destructor, the deallocation function is the one found by the lookup in the definition of the dynamic type's virtual destructor (11.4.7 [class.dtor]). Otherwise, if the delete-expression is used to deallocate an object of class T or array thereof, the static and dynamic types of the object shall be identical and the deallocation function's name is looked up in the scope of T. If this lookup fails to find the name, the name is looked up in the global scope. If the result of the lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function, the program is ill-formed.
Points of interest in this description include:
Suggested resolution: Change the description of the lookup in 11.4.7 [class.dtor] paragraph 11 to match the one in 11.4.11 [class.free] paragraph 4.
Proposed resolution (10/00):
Replace 11.4.7 [class.dtor] paragraph 11 with the following:
At the point of definition of a virtual destructor (including an implicit definition), the non-array deallocation function is looked up in the scope of the destructor's class (6.5.2 [class.member.lookup]), and, if no declaration is found, the function is looked up in the global scope. If the result of this lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function, the program is ill-formed. [Note: this assures that a deallocation function corresponding to the dynamic type of an object is available for the delete-expression (11.4.11 [class.free]).]
In 11.4.11 [class.free] paragraph 4, change
...the deallocation function is the one found by the lookup in the definition of the dynamic type's virtual destructor (11.4.7 [class.dtor]).
to
...the deallocation function is the one selected at the point of definition of the dynamic type's virtual destructor (11.4.7 [class.dtor]).
[Moved to DR at 10/01 meeting.]
11.4.7 [class.dtor] paragraph 12 contains the following note:an explicit destructor call must always be written using a member access operator (7.6.1.5 [expr.ref]); in particular, the unary-expression ~X() in a member function is not an explicit destructor call (7.6.2.2 [expr.unary.op]).
This note is incorrect, as an explicit destructor call can be written as a qualified-id, e.g., X::~X(), which does not use a member access operator.
Proposed resolution (04/01):
Change 11.4.7 [class.dtor] paragraph 12 as follows:
[Note: an explicit destructor call must always be written using a member access operator (7.6.1.5 [expr.ref]) or a qualified-id (_N4567_.5.1.1 [expr.prim.general]); in particular, the unary-expression ~X() in a member function is not an explicit destructor call (7.6.2.2 [expr.unary.op]).]
[Voted into the WP at the September, 2008 meeting.]
Deallocation functions can't be virtual because they are static member functions; however, according to 11.4.11 [class.free] paragraph 7, they behave like virtual functions when the class's destructor is virtual:
Since member allocation and deallocation functions are static they cannot be virtual. [Note: however, when the cast-expression of a delete-expression refers to an object of class type, because the deallocation function actually called is looked up in the scope of the class that is the dynamic type of the object, if the destructor is virtual, the effect is the same.
Because the intent is to make any use of a deleted function diagnosable at compile time, a virtual deleted function can neither override nor be overridden by a non-deleted function, as described in 11.7.3 [class.virtual] paragraph 14:
A function with a deleted definition (9.6 [dcl.fct.def]) shall not override a function that does not have a deleted definition. Likewise, a function that does not have a deleted definition shall not override a function with a deleted definition.
One would assume that a similar kind of prohibition is needed for deallocation functions in a class hierarchy with virtual destructors, but it's not clear that the current specification says that. 9.6 [dcl.fct.def] paragraph 10 says,
A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.
Furthermore, the deallocation function is looked up at the point of definition of a virtual destructor (11.4.7 [class.dtor] paragraph 11) , and the function found by this lookup is considered to be “used” (6.3 [basic.def.odr] paragraph 2). However, it's not completely clear that this “use” constitutes a “reference” in the sense of 9.6 [dcl.fct.def] paragraph 10, especially in a program in which an object of a type that would call that deallocation function is never deleted.
Suggested resolution:Augment the list of lookup results from a virtual destructor that render a program ill-formed in 11.4.7 [class.dtor] paragraph 10 to include a deleted function:
If the result of this lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function or a function with a deleted definition (9.6 [dcl.fct.def]), the program is ill-formed.
Proposed resolution (June, 2008):
Change 11.4.7 [class.dtor] paragraph 10 as follows:
If the result of this lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function or a function with a deleted definition (9.6 [dcl.fct.def]), the program is ill-formed.
[Moved to DR at October 2002 meeting.]
May user-defined conversion functions be static? That is, should this compile?
class Widget { public: static operator bool() { return true; } };
All my compilers hate it. I hate it, too. However, I don't see anything in 11.4.8.3 [class.conv.fct] that makes it illegal. Is this a prohibition that arises from the grammar, i.e., the grammar doesn't allow "static" to be followed by a conversion-function-id in a member function declaration? Or am I just overlooking something obvious that forbids static conversion functions?
Proposed Resolution (4/02):
Add to 11.4.8.3 [class.conv.fct] as a new paragraph 7:
Conversion functions cannot be declared static.
[Voted into WP at March 2004 meeting.]
The following test program is claimed to be a negative C++ test case for "Unnamed classes shall not contain static data members", c.f. ISO/IEC 14882 section 11.4.9.3 [class.static.data] paragraph 5.
struct B { typedef struct { static int i; // Is this legal C++ ? } A; }; int B::A::i = 47; // Is this legal C++ ?
We are not quite sure about what an "unnamed class" is. There is no exact definition in ISO/IEC 14882; the closest we can come to a hint is the wording of section 9.2.4 [dcl.typedef] paragraph 5, where it seems to be understood that a class-specifier with no identifier between "class" and "{" is unnamed. The identifier provided after "}" ( "A" in the test case above) is there for "linkage purposes" only.
To us, class B::A in the test program above seems "named" enough, and there is certainly a mechanism to provide the definition for B::A::i (in contrast to the note in section 11.4.9.3 [class.static.data] paragraph 5) .
Our position is therefore that the above test program is indeed legal C++. Can you confirm or reject this claim?
Herb Sutter replied to the submitter as follows: Here are my notes based on a grep for "unnamed class" in the standard:
a named class (clause class), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes (9.2.4 [dcl.typedef]);Likewise in your example, you have an unnamed class defined in a typedef declaration.
So yes, an unnamed class is one where there is no identifier (class name) between the class-key and the {. This is also in harmony with the production for class-name in Clause 11 [class] paragraph 1:
Notes from the October 2003 meeting:
We agree that the example is not valid; this is an unnamed class. We will add wording to define an unnamed class. The note in 11.4.9.3 [class.static.data] paragraph 5 should be corrected or deleted.
Proposed Resolution (October 2003):
At the end of Clause 11 [class], paragraph 1, add the following:
A class-specifier where the class-head omits the optional identifier defines an unnamed class.
Delete the following from 11.4.9.3 [class.static.data] paragraph 5:
[ Note: this is because there is no mechanism to provide the definitions for such static data members. ]
[Voted into WP at the October, 2006 meeting.]
As a result of the resolution of core issue 48, the current C++ standard is not in sync with existing practice and with user expectations as far as definitions of static data members having const integral or const enumeration type are concerned. Basically what current implementations do is to require a definition only if the address of the constant is taken. Example:
void f() { std::string s; ... // current implementations don't require a definition if (s.find('a', 3) == std::string::npos) { ... }
To the letter of the standard, though, the above requires a definition of npos, since the expression std::string::npos is potentially evaluated. I think this problem would be easily solved with simple changes to 11.4.9.3 [class.static.data] paragraph 4, 11.4.9.3 [class.static.data] paragraph 5 and 6.3 [basic.def.odr] paragraph 3.
Suggested resolution:
Replace 11.4.9.3 [class.static.data] paragraph 4 with:
If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be [note1] an integral constant expression (5.19). In that case, the member can appear in integral constant expressions. No definition of the member is required, unless an lvalue expression that designates it is potentially evaluated and either used as operand to the built-in unary & operator [note 2] or directly bound to a reference.
If a definition exists, it shall be at namespace scope and shall not contain an initializer.
In 11.4.9.3 [class.static.data] paragraph 5 change
There shall be exactly one definition of a static data member that is used in a program; no diagnostic is required; see 3.2.
to
Except as allowed by 9.4.2 par. 4, there shall be exactly one definition of a static data member that is potentially evaluated (3.2) in a program; no diagnostic is required.
In 6.3 [basic.def.odr] paragraph 3 add, at the beginning:
Except for the omission allowed by 9.4.2, par. 4, ...
[note 1] Actually it shall be a "= followed by a constant-expression". This could probably be an editorial fix, rather than a separate DR.
[note 2] Note that this is the case when reinterpret_cast-ing to a reference, like in
struct X { static const int value = 0; }; const char & c = reinterpret_cast<const char&>(X::value);See 7.6.1.10 [expr.reinterpret.cast]/10
More information, in response to a question about why issue 48 does not resolve the problem:
The problem is that the issue was settled in a way that solves much less than it was supposed to solve; that's why I decided to file, so to speak, a DR on a DR.
I understand this may seem a little 'audacious' on my part, but please keep reading. Quoting from the text of DR 48 (emphasis mine):
Originally, all static data members still had to be defined outside the class whether they were used or not.
But that restriction was supposed to be lifted [...]
In particular, if an integral/enum const static data member is initialized within the class, and its address is never taken, we agreed that no namespace-scope definition was required.
The corresponding resolution doesn't reflect this intent, with the definition being still required in most situations anyway: it's enough that the constant appears outside a place where constants are required (ignoring the obvious cases of sizeof and typeid) and you have to provide a definition. For instance:
struct X { static const int c = 1; }; void f(int n) { if (n == X::c) // <-- potentially evaluated ... }
<start digression>
Most usages of non-enum BOOST_STATIC_COSTANTs, for instance, are (or were, last time I checked) non-conforming. If you recall, Paul Mensonides pointed out that the following template
// map_integral template<class T, T V> struct map_integral : identity<T> { static const T value = V; }; template<class T, T V> const T map_integral<T, V>::value;
whose main goal is to map the same couples (type, value) to the same storage, also solves the definition problem. In this usage it is an excellent hack (if your compiler is good enough), but IMHO still a hack on a language defect.
<end digression>
What I propose is to solve the issue according to the original intent, which is also what users expect and all compilers that I know of already do. Or, in practice, we would have a rule that exists only as words in a standard document.
PS: I've sent a copy of this to Mr. Adamczyk to clarify an important doubt that occurred to me while writing this reply:
if no definition is provided for an integral static const data member is that member an object? Paragraph 1.8/1 seems to say no, and in fact it's difficult to think it is an object without assuming/pretending that a region of storage exists for it (an object *is* a region of storage according to the standard).
I would think that when no definition is required we have to assume that it could be a non-object. In that case there's nothing in 3.2 which says what 'used' means for such an entity and the current wording would thus be defective. Also, since the name of the member is an lvalue and 3.10/2 says an lvalue refers to an object we would have another problem.
OTOH the standard could pretend it is always an object (though the compiler can optimize it away) and in this case it should probably make a special case for it in 3.2/2.
Notes from the March 2004 meeting:
We sort of like this proposal, but we don't feel it has very high priority. We're not going to spend time discussing it, but if we get drafting for wording we'll review it.
Proposed resolution (October, 2005):
Change the first two sentences of 6.3 [basic.def.odr] paragraph 2 from:
An expression is potentially evaluated unless it appears where an integral constant expression is required (see 7.7 [expr.const]), is the operand of the sizeof operator (7.6.2.5 [expr.sizeof]), or is the operand of the typeid operator and the expression does not designate an lvalue of polymorphic class type (7.6.1.8 [expr.typeid]). An object or non-overloaded function is used if its name appears in a potentially-evaluated expression.
to
An expression that is the operand of the sizeof operator (7.6.2.5 [expr.sizeof]) is unevaluated, as is an expression that is the operand of the typeid operator if it is not an lvalue of a polymorphic class type (7.6.1.8 [expr.typeid]); all other expressions are potentially evaluated. 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 an integral constant expression (7.7 [expr.const]) and the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is immediately applied.
Change the first sentence of 11.4.9.3 [class.static.data] paragraph 2 as indicated:
If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializerwhichwhose constant-expression shall be an integral constant expression (7.7 [expr.const]).
[Voted into WP at the October, 2006 meeting.]
Section 11.4.10 [class.bit] paragraph 4 needs to be more specific about the signedness of bit fields of enum type. How much leeway does an implementation have in choosing the signedness of a bit field? In particular, does the phrase "large enough to hold all the values of the enumeration" mean "the implementation decides on the signedness, and then we see whether all the values will fit in the bit field", or does it require the implementation to make the bit field signed or unsigned if that's what it takes to make it "large enough"?
(See also issue 172.)
Note (March, 2005): Clark Nelson observed that there is variation among implementations on this point.
Notes from April, 2005 meeting:
Although implementations enjoy a great deal of latitude in handling bit-fields, it was deemed more user-friendly to ensure that the example in paragraph 4 will work by requiring implementations to use an unsigned underlying type if the enumeration type has no negative values. (If the implementation is allowed to choose a signed representation for such bit-fields, the comparison against TRUE will be false.)
In addition, it was observed that there is an apparent circularity between 9.8.1 [dcl.enum] paragraph 7 and 11.4.10 [class.bit] paragraph 4 that should be resolved.
Proposed resolution (April, 2006):
Replace 9.8.1 [dcl.enum] paragraph 7, deleting the embedded footnote 85, with the following:
For an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values in the range bmin to bmax, defined as follows: Let K be 1 for a two's complement representation and 0 for a one's complement or sign-magnitude representation. bmax is the smallest value greater than or equal to max(|emin|-K,|emax|) and equal to 2M-1, where M is a non-negative integer. bmin is zero if emin is non-negative and -(bmax+K) otherwise. The size of the smallest bit-field large enough to hold all the values of the enumeration type is max(M,1) if bmin is zero and M+1 otherwise. It is possible to define an enumeration that has values not defined by any of its enumerators.
Add the indicated text to the second sentence of 11.4.10 [class.bit] paragraph 4:
If the value of an enumerator is stored into a bit-field of the same enumeration type and the number of bits in the bit-field is large enough to hold all the values of that enumeration type (9.8.1 [dcl.enum]), the original enumerator value and the value of the bit-field shall compare equal.
[Voted into WP at October 2004 meeting.]
It looks like the example on 11.4.10 [class.bit] paragraph 4 has both the enum and function contributing the identifier "f" for the same scope.
enum BOOL { f=0, t=1 }; struct A { BOOL b:1; }; A a; void f() { a.b = t; if (a.b == t) // shall yield true { /* ... */ } }
Proposed resolution:
Change the example at the end of 11.4.10 [class.bit]/4 from:
enum BOOL { f=0, t=1 }; struct A { BOOL b:1; }; A a; void f() { a.b = t; if (a.b == t) // shall yield true { /* ... */ } }
To:
enum BOOL { FALSE=0, TRUE=1 }; struct A { BOOL b:1; }; A a; void f() { a.b = TRUE; if (a.b == TRUE) // shall yield true { /* ... */ } }
[Voted into WP at April 2003 meeting.]
11.6 [class.local] paragraph 1 says,
Declarations in a local class can use only type names, static variables, extern variables and functions, and enumerators from the enclosing scope.The definition of when an object or function is "used" is found in 6.3 [basic.def.odr] paragraph 2 and essentially says that the operands of sizeof and non-polymorphic typeid operators are not used. (The resolution for issue 48 will add contexts in which integral constant expressions are required to the list of non-uses.)
This definition of "use" would presumably allow code like
void foo() { int i; struct S { int a[sizeof(i)]; }; };which is required for C compatibility.
However, the restrictions on nested classes in 11.4.12 [class.nest] paragraph 1 are very similar to those for local classes, and the example there explicitly states that a reference in a sizeof expression is a forbidden use (abbreviated for exposition):
class enclose { public: int x; class inner { void f(int i) { int a = sizeof(x); // error: refers to enclose::x } }; };
[As a personal note, I have seen real-world code that was exactly like this; it was hard to persuade the author that the required writearound, sizeof(((enclose*) 0)->x), was an improvement over sizeof(x). —wmm]
Similarly, 11.4 [class.mem] paragraph 9 would appear to prohibit examples like the following:
struct B { char x[10]; }; struct D: B { char y[sizeof(x)]; };
Suggested resolution: Add cross-references to 6.3 [basic.def.odr] following the word "use" in both 11.4.12 [class.nest] and 11.6 [class.local] , and change the example in 11.4.12 [class.nest] to indicate that a reference in a sizeof expression is permitted. In 11.4 [class.mem] paragraph 9, "referred to" should be changed to "used" with a cross_reference to 6.3 [basic.def.odr].
Notes from 10/01 meeting:
It was noted that the suggested resolution did not make the sizeof() example in 11.4.12 [class.nest] valid. Although the reference to the argument of sizeof() is not regarded as a use, the right syntax must be used nonetheless to reference a non-static member from the enclosing class. The use of the member name by itself is not valid. The consensus within the core working group was that nothing should be done about this case. It was later discovered that 11.4.9 [class.static] paragraph 3 states that
The definition of a static member shall not use directly the names of the nonstatic members of its class or of a base class of its class (including as operands of the sizeof operator). The definition of a static member may only refer to these members to form pointer to members (7.6.2.2 [expr.unary.op]) or with the class member access syntax (7.6.1.5 [expr.ref]).
This seems to reinforce the decision of the working group.
The use of "use" should still be cross-referenced. The statements in 11.4.12 [class.nest] and 11.6 [class.local] should also be rewritten to state the requirement positively rather than negatively as the list of "can't"s is already missing some cases such as template parameters.
Notes from the 4/02 meeting:
We backed away from "use" in the technical sense, because the requirements on the form of reference are the same whether or not the reference occurs inside a sizeof.
Proposed Resolution (revised October 2002):
In 11.4 [class.mem] paragraph 9, replace
Except when used to form a pointer to member (7.6.2.2 [expr.unary.op]), when used in the body of a nonstatic member function of its class or of a class derived from its class (11.4.3 [class.mfct.non.static]), or when used in a mem-initializer for a constructor for its class or for a class derived from its class (11.9.3 [class.base.init]), a nonstatic data or function member of a class shall only be referred to with the class member access syntax (7.6.1.5 [expr.ref]).
with the following paragraph
Each occurrence in an expression of the name of a nonstatic data member or nonstatic member function of a class shall be expressed as a class member access (7.6.1.5 [expr.ref]), except when it appears in the formation of a pointer to member (7.6.2.2 [expr.unary.op]), when it appears in the body of a nonstatic member function of its class or of a class derived from its class (11.4.3 [class.mfct.non.static]), or when it appears in a mem-initializer for a constructor for its class or for a class derived from its class (11.9.3 [class.base.init]).
In 11.4.12 [class.nest] paragraph 1, replace the last sentence,
Except by using explicit pointers, references, and object names, declarations in a nested class can use only type names, static members, and enumerators from the enclosing class.
with the following
[Note: In accordance with 11.4 [class.mem], except by using explicit pointers, references, and object names, declarations in a nested class shall not use nonstatic data members or nonstatic member functions from the enclosing class. This restriction applies in all constructs including the operands of the sizeof operator.]
In the example following 11.4.12 [class.nest] paragraph 1, change the comment on the first statement of function f to emphasize that sizeof(x) is an error. The example reads in full:
int x; int y; class enclose { public: int x; static int s; class inner { void f(int i) { int a = sizeof(x); // error: direct use of enclose::x even in sizeof x = i; // error: assign to enclose::x s = i; // OK: assign to enclose::s ::x = i; // OK: assign to global x y = i; // OK: assign to global y } void g(enclose* p, int i) { p->x = i; // OK: assign to enclose::x } }; }; inner* p = 0; // error: inner not in scope
[Voted into WP at the October, 2006 meeting.]
Issue 298, recently approved, affirms that cv-qualified class types can be used as nested-name-specifiers. Should the same be true for base-specifiers?
Rationale (April, 2005):
The resolution of issue 298 added new text to 11.3 [class.name] paragraph 5 making it clear that a typedef that names a cv-qualified class type is a class-name. Because the definition of base-specifier simply refers to class-name, it is already the case that cv-qualified class types are permitted as base-specifiers.
Additional notes (June, 2005):
It's not completely clear what it means to have a cv-qualified type as a base-specifier. The original proposed resolution for issue 298 said that “the cv-qualifiers are ignored,” but that wording is not in the resolution that was ultimately approved.
If the cv-qualifiers are not ignored, does that mean that the base-class subobject should be treated as always similarly cv-qualified, regardless of the cv-qualification of the derived-class lvalue used to access the base-class subobject? For instance:
typedef struct B { void f(); void f() const; int i; } const CB; struct D: CB { }; void g(D* dp) { dp->f(); // which B::f? dp->i = 3; // permitted? }
Proposed resolution (October, 2005):
Change 11.3 [class.name] paragraph 5 as indicated:
A typedef-name (9.2.4 [dcl.typedef]) that names a class type, or a cv-qualified version thereof, is also aclass-name, butclass-name. If a typedef-name that names a cv-qualified class type is used where a class-name is required, the cv-qualifiers are ignored. A typedef-name shall not be used as the identifier in a class-head.
Delete 9.2.4 [dcl.typedef] paragraph 8:
[Note: if the typedef-name is used where a class-name (or enum-name) is required, the program is ill-formed. For example,
typedef struct { S(); // error: requires a return type because S is // an ordinary member function, not a constructor } S;—end note]
[Voted into WP at March 2004 meeting.]
In 11.7.4 [class.abstract] paragraph 2, it reads:
A pure virtual function need be defined only if explicitly called with the qualified-id syntax (_N4567_.5.1.1 [expr.prim.general]).
This is IMHO incomplete. A dtor is a function (well, a "special member function", but this also makes it a function, right?) but it is called implicitly and thus without a qualified-id syntax. Another alternative is that the pure virtual function is called directly or indirectly from the ctor. Thus the above sentence which specifies when a pure virtual function need be defined ("...only if...") needs to be extended:
A pure virtual function need be defined only if explicitly called with the qualified-id syntax (_N4567_.5.1.1 [expr.prim.general]) or if implicitly called (11.4.7 [class.dtor] or 11.9.5 [class.cdtor]).
Proposed resolution:
Change 11.7.4 [class.abstract] paragraph 2 from
A pure virtual function need be defined only if explicitly called with the qualified-id syntax (_N4567_.5.1.1 [expr.prim.general]).
to
A pure virtual function need be defined only ifexplicitlycalled with, or as if with (11.4.7 [class.dtor]), the qualified-id syntax (_N4567_.5.1.1 [expr.prim.general]).
Note: 11.4.7 [class.dtor] paragraph 6 defines the "as if" cited.
[Moved to DR at 4/01 meeting.]
Consider the following example:
class A { class A1{}; static void func(A1, int); static void func(float, int); static const int garbconst = 3; public: template < class T, int i, void (*f)(T, int) > class int_temp {}; template<> class int_temp<A1, 5, func> { void func1() }; friend int_temp<A1, 5, func>::func1(); int_temp<A1, 5, func>* func2(); }; A::int_temp<A::A1, A::garbconst + 2, &A::func>* A::func2() {...}ISSUE 1:
In 11.8 [class.access] paragraph 5 we have:
A::int_temp A::A1 A::garbconst (part of an expression) A::func (after overloading is done)I suspect that member templates were not really considered when this was written, and that it might have been written rather differently if they had been. Note that access to the template arguments is only legal because the class has been declared a friend, which is probably not what most programmers would expect.
Rationale:
Not a defect. This behavior is as intended.
ISSUE 2:
Now consider void A::int_temp<A::A1, A::garbconst + 2, &A::func>::func1() {...} By my reading of 11.8.8 [class.access.nest] , the references to A::A1, A::garbconst and A::func are now illegal, and there is no way to define this function outside of the class. Is there any need to do anything about either of these Issues?
Proposed resolution (04/01):
The resolution for this issue is contained in the resolution for issue 45.
[Voted into WP at the October, 2006 meeting.]
The proposed resolution for issue 45 inserts the following sentence after 11.8 [class.access] paragraph 1:
A member of a class can also access all names as the class of which it is a member.
I don't think that this is correctly constructed English. I see two possibilities:
This is a typo, and the correct change is:
A member of a class can also access all names of the class of which it is a member.
The intent is something more like:
A member of a nested class can also access all names accessible by any other member of the class of which it is a member.
[Note: this was editorially corrected at the time defect resolutions were being incorporated into the Working Paper to read, “...can also access all the names declared in the class of which it is a member,” which is essentially the same as the preceding option 1.]
I would prefer to use the language proposed for 11.8.8 [class.access.nest]:
A nested class is a member and as such has the same access rights as any other member.
A second problem is with the text in 11.8.4 [class.friend] paragraph 2:
[Note: this means that access to private and protected names is also granted to member functions of the friend class (as if the functions were each friends) and to the static data member definitions of the friend class. This also means that private and protected type names from the class granting friendship can be used in the base-clause of a nested class of the friend class. However, the declarations of members of classes nested within the friend class cannot access the names of private and protected members from the class granting friendship. Also, because the base-clause of the friend class is not part of its member declarations, the base-clause of the friend class cannot access the names of the private and protected members from the class granting friendship. For example,class A { class B { }; friend class X; }; class X : A::B { // ill-formed: A::B cannot be accessed // in the base-clause for X A::B mx; // OK: A::B used to declare member of X class Y: A::B { // OK: A::B used to declare member of X A::B my; // ill-formed: A::B cannot be accessed // to declare members of nested class of X }; };—end note]
This seems to be an oversight. The proposed change to 11.8.8 [class.access.nest] paragraph 1 would appear to have eliminated the restrictions on nested class access. However, at least one compiler (gcc 3.4.3) doesn't appear to take my view, and continues with the restrictions on access by classes within a friend class, while implementing the rest of the resolution of issue 45.
Note (March, 2005):
Andreas Hommel: I think issue 45 requires an additional change in 11.4.12 [class.nest] paragraph 4:
Like a member function, a friend function (11.8.4 [class.friend]) defined within a nested class is in the lexical scope of that class; it obeys the same rules for name binding as a static member function of that class (11.4.9 [class.static]) and has no special access rights to members of an enclosing class.
I believe the “no special access rights” language should be removed.
Proposed resolution (October, 2005):
This issue is resolved by the resolution of issue 372.
[Moved to DR at 4/01 meeting.]
11.8.3 [class.access.base] paragraph 4 says:
A base class is said to be accessible if an invented public member of the base class is accessible. If a base class is accessible, one can implicitly convert a pointer to a derived class to a pointer to that base class.Given the above, is the following well-formed?
class D; class B { protected: int b1; friend void foo( D* pd ); }; class D : protected B { }; void foo( D* pd ) { if ( pd->b1 > 0 ); // Is 'b1' accessible? }Can you access the protected member b1 of B in foo? Can you convert a D* to a B* in foo?
1st interpretation:
A public member of B is accessible within foo (since foo is a friend), therefore foo can refer to b1 and convert a D* to a B*.
2nd interpretation:
B is a protected base class of D, and a public member of B is a protected member of D and can only be accessed within members of D and friends of D. Therefore foo cannot refer to b1 and cannot convert a D* to a B*.
(See J16/99-0042 = WG21 N1218.)
Proposed Resolution (04/01):
A base class B of N is accessible at R, if
- an invented public member of B would be a public member of N, or
- R occurs in a member or friend of class N, and an invented public member of B would be a private or protected member of N, or
- R occurs in a member or friend of a class P derived from N, and an invented public member of B would be a private or protected member of P, or
- there exists a class S such that B is a base class of S accessible at R and S is a base class of N accessible at R. [Example:
class B { public: int m; }; class S: private B { friend class N; }; class N: private S { void f() { B* p = this; // OK because class S satisfies the // fourth condition above: B is a base // class of N accessible in f() because // B is an accessible base class of S // and S is an accessible base class of N. } };—end example]
A base class is said to be accessible if an invented public member of the base class is accessible.
A member m is accessible at the point R when named in class N if
- m as a member of N is public, or
- m as a member of N is private, and R occurs in a member or friend of class N, or
- m as a member of N is protected, and R occurs in a member or friend of class N, or in a member or friend of a class P derived from N, where m as a member of P is private or protected, or
- there exists a base class B of N that is accessible at R, and m is accessible at R when named in class B. [Example:...
The resolution for issue 207 modifies this wording slightly.
[Moved to DR at 4/01 meeting.]
The text in 11.8.3 [class.access.base] paragraph 4 does not seem to handle the following cases:
class D; class B { private: int i; friend class D; }; class C : private B { }; class D : private C { void f() { B::i; //1: well-formed? i; //2: well-formed? } };The member i is not a member of D and cannot be accessed in the scope of D. What is the naming class of the member i on line //1 and line //2?
Proposed Resolution (04/01): The resolution for this issue is contained in the resolution for issue 9..
[Moved to DR at 10/01 meeting.]
Consider the following example:
class A { protected: static void f() {}; }; class B : A { public: using A::f; void g() { A::f(); } };
The standard says in 11.8.3 [class.access.base] paragraph 4 that the call to A::f is ill-formed:
A member m is accessible when named in class N if
- m as a member of N is public, or
- m as a member of N is private, and the reference occurs in a member or friend of class N, or
- m as a member of N is protected, and the reference occurs in a member or friend of class N, or in a member or friend of a class P derived from N, where m as a member of P is private or protected, or
- there exists a base class B of N that is accessible at the point of reference, and m is accessible when named in class B.
Here, m is A::f and N is A.
It seems clear to me that the third bullet should say "public, private or protected".
Steve Adamczyk:The words were written before using-declarations existed, and therefore didn't anticipate this case.
Proposed resolution (04/01):
Modify the third bullet of the third change ("A member m is accessible...") in the resolution of issue 9 to read "public, private, or protected" instead of "private or protected."
[Moved to DR at 4/02 meeting.]
The definition of "friend" in 11.8.4 [class.friend] says:
A friend of a class is a function or class that is not a member of the class but is permitted to use the private and protected member names from the class. ...A nested class, i.e. INNER in the example below, is a member of class OUTER. The sentence above states that it cannot be a friend. I think this is a mistake.
class OUTER { class INNER; friend class INNER; class INNER {}; };
Proposed resolution (04/01):
Change the first sentence of 11.8.4 [class.friend] as follows:
A friend of a class is a function or class that isnot a member of the class but is allowedgiven permission to use the private and protected member names from the class.The name of a friend is not in the scope of the class, and the friend is not called with the member access operators (7.6.1.5 [expr.ref]) unless it is a member of another class.A class specifies its friends, if any, by way of friend declarations. Such declarations give special access rights to the friends, but they do not make the nominated friends members of the befriending class.
[Voted into WP at the October, 2006 meeting.]
I don't know the reason for this distinction, but it seems to be surprising that Base::A is legal and D is illegal in this example:
class D; class Base { class A; class B; friend class D; }; class Base::B { }; class Base::A : public Base::B // OK because of issue 45 { }; class D : public Base::B // illegal because of 11.4p4 { };
Shouldn't this be consistent (either way)?
Notes from the April, 2005 meeting:
In discussing issue 372, the CWG decided that access in the base-specifiers of a class should be the same as for its members, and that resolution will apply to friend declarations, as well.
Proposed resolution (October, 2005):
This issue is resolved by the resolution of issue 372.
[Voted into WP at October 2004 meeting.]
We consider it not unreasonable to do the following
class A { protected: void g(); }; class B : public A { public: using A::g; // B::g is a public synonym for A::g }; class C: public A { void foo(); }; void C::foo() { B b; b.g(); }
However the EDG front-end does not like and gives the error
#410-D: protected function "A::g" is not accessible through a "B" pointer or object b.g(); ^
Steve Adamczyk: The error in this case is due to 11.8.5 [class.protected] of the standard, which is an additional check on top of the other access checking. When that section says "a protected nonstatic member function ... of a base class" it doesn't indicate whether the fact that there is a using-declaration is relevant. I'd say the current wording taken at face value would suggest that the error is correct -- the function is protected, even if the using-declaration for it makes it accessible as a public function. But I'm quite sure the wording in 11.8.5 [class.protected] was written before using-declarations were invented and has not been reviewed since for consistency with that addition.
Notes from April 2003 meeting:
We agreed that the example should be allowed.
Proposed resolution (April 2003, revised October 2003):
Change 11.8.5 [class.protected] paragraph 1 from
When a friend or a member function of a derived class references a protected nonstatic member function or protected nonstatic data member of a base class, an access check applies in addition to those described earlier in 11.8 [class.access]. [Footnote: This additional check does not apply to other members, e.g. static data members or enumerator member constants.] Except when forming a pointer to member (7.6.2.2 [expr.unary.op]), the access must be through a pointer to, reference to, or object of the derived class itself (or any class derived from that class (7.6.1.5 [expr.ref]). If the access is to form a pointer to member, the nested-name-specifier shall name the derived class (or any class derived from that class).
to
An additional access check beyond those described earlier in 11.8 [class.access] is applied when a nonstatic data member or nonstatic member function is a protected member of its naming class (11.8.3 [class.access.base]). [Footnote: This additional check does not apply to other members, e.g., static data members or enumerator member constants.] As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C. If the access is to form a pointer to member (7.6.2.2 [expr.unary.op]), the nested-name-specifier shall name C or a class derived from C. All other accesses involve a (possibly implicit) object expression (7.6.1.5 [expr.ref]). In this case, the class of the object expression shall be C or a class derived from C.
Additional discussion (September, 2004):
Steve Adamczyk: I wonder if this wording is incorrect. Consider:
class A { public: int p; }; class B : protected A { // p is a protected member of B }; class C : public B { friend void fr(); }; void fr() { B *pb = new B; pb->p = 1; // Access okay? Naming class is B, p is a protected member of B, // the "C" of the issue 385 wording is C, but access is not via // an object of type C or a derived class thereof. }
I think the formulation that the member is a protected member of its naming class is not what we want. I think we intended that the member is protected in the declaration that is found, where the declaration found might be a using-declaration.
Mike Miller: I think the proposed wording makes the access pb->p ill-formed, and I think that's the right thing to do.
First, protected inheritance of A by B means that B intends the public and protected members of A to be part of B's implementation, available to B's descendants only. (That's why there's a restriction on converting from B* to A*, to enforce B's intention on the use of members of A.) Consequently, I see no difference in access policy between your example and
class B { protected: int p; };
Second, the reason we have this rule is that C's use of inherited protected members might be different from their use in a sibling class, say D. Thus members and friends of C can only use B::p in a manner consistent with C's usage, i.e., in C or derived-from-C objects. If we rewrote your example slightly,
class D: public B { }; void fr(B* pb) { pb->p = 1; } void g() { fr(new D); }
it's clear that the intent of this rule is broken — fr would be accessing B::p assuming C's policies when the object in question actually required D's policies.
(See also issues 471 and 472.)
[Moved to DR at 4/01 meeting.]
Paragraph 1 says: "The members of a nested class have no special access to members of an enclosing class..."
This prevents a member of a nested class from being defined outside of its class definition. i.e. Should the following be well-formed?
class D { class E { static E* m; }; }; D::E* D::E::m = 1; // ill-formedThis is because the nested class does not have access to the member E in D. 11.8 [class.access] paragraph 5 says that access to D::E is checked with member access to class E, but unfortunately that doesn't give access to D::E. 11.8 [class.access] paragraph 6 covers the access for D::E::m, but it doesn't affect the D::E access. Are there any implementations that are standard compliant that support this?
Here is another example:
class C { class B { C::B *t; //2 error, C::B is inaccessible }; };This causes trouble for member functions declared outside of the class member list. For example:
class C { class B { B& operator= (const B&); }; }; C::B& C::B::operator= (const B&) { } //3If the return type (i.e. C::B) is access checked in the scope of class B (as implied by 11.8 [class.access] paragraph 5) as a qualified name, then the return type is an error just like referring to C::B in the member list of class B above (i.e. //2) is ill-formed.
Proposed resolution (04/01):
The resolution for this issue is incorporated into the resolution for issue 45.
[Moved to DR at 4/01 meeting.]
Example:
#include <iostream.h> class C { // entire body is private struct Parent { Parent() { cout << "C::Parent::Parent()\n"; } }; struct Derived : Parent { Derived() { cout << "C::Derived::Derived()\n"; } }; Derived d; }; int main() { C c; // Prints message from both nested classes return 0; }How legal/illegal is this? Paragraphs that seem to apply here are:
11.8 [class.access] paragraph 1:
A member of a class can beand 11.8.8 [class.access.nest] paragraph 1:
- private; that is, its name can be used only by members and friends of the class in which it is declared. [...]
The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (11.8 [class.access] ) shall be obeyed. [...]This makes me think that the ': Parent' part is OK by itself, but that the implicit call of 'Parent::Parent()' by 'Derived::Derived()' is not.
From Mike Miller:
I think it is completely legal, by the reasoning given in the (non-normative) 11.8.8 [class.access.nest] paragraph 2. The use of a private nested class as a base of another nested class is explicitly declared to be acceptable there. I think the rationale in the comments in the example ("// OK because of injection of name A in A") presupposes that public members of the base class will be public members in a (publicly-derived) derived class, regardless of the access of the base class, so the constructor invocation should be okay as well.
I can't find anything normative that explicitly says that, though.
(See also papers J16/99-0009 = WG21 N1186, J16/00-0031 = WG21 N1254, and J16/00-0045 = WG21 N1268.)
Proposed Resolution (04/01):
Insert the following as a new paragraph following 11.8 [class.access] paragraph 1:
A member of a class can also access all names as the class of which it is a member. A local class of a member function may access the same names that the member function itself may access. [Footnote: Access permissions are thus transitive and cumulative to nested and local classes.]
Delete 11.8 [class.access] paragraph 6.
In 11.8.8 [class.access.nest] paragraph 1, change
The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (11.8 [class.access]) shall be obeyed.
to
A nested class is a member and as such has the same access rights as any other member.
Change
B b; // error: E::B is private
to
B b; // Okay, E::I can access E::B
Change
p->x = i; // error: E::x is private
to
p->x = i; // Okay, E::I can access E::x
Delete 11.8.8 [class.access.nest] paragraph 2.
(This resolution also resolves issues 8 and 10.
[Voted into WP at April, 2006 meeting.]
9.5 [dcl.init] paragraph 10 makes it clear that non-static POD class objects with no initializer are left uninitialized and have an indeterminate initial value:
If no initializer is specified for an object, and the object is of (possibly cv-qualified) non-POD class type (or array thereof), the object shall be default-initialized; if the object is of const-qualified type, the underlying class type shall have a user-declared default constructor. Otherwise, if no initializer is specified for a non-static object, the object and its subobjects, if any, have an indeterminate initial value; if the object or any of its subobjects are of const-qualified type, the program is ill-formed.
11.9 [class.init] paragraph 1, however, implies that all class objects without initializers, whether POD or not, are default-initialized:
When no initializer is specified for an object of (possibly cv-qualified) class type (or array thereof), or the initializer has the form (), the object is initialized as specified in 9.5 [dcl.init]. The object is default-initialized if there is no initializer, or value-initialized if the initializer is ().
Proposed resolution (October, 2005):
Remove the indicated words from 11.9 [class.init] paragraph 1:
When no initializer is specified for an object of (possibly cv-qualified) class type (or array thereof), or the initializer has the form (), the object is initialized as specified in 9.5 [dcl.init].The object is default-initialized if there is no initializer, or value-initialized if the initializer is ().
[Moved to DR at October 2002 meeting.]
12.2.2.2 [over.match.call] paragraph 3 says that when a call of the form
(&C::f)()is written, the set of overloaded functions named by C::f must not contain any nonstatic member functions. A footnote gives the rationale: if a member of C::f is a nonstatic member function, &C::f is a pointer to member constant, and therefore the call is invalid.
This is clear, it's implementable, and it doesn't directly contradict anything else in the standard. However, I'm not sure it's consistent with some similar cases.
In 12.3 [over.over] paragraph 5, second example, it is made amply clear that when &C::f is used as the address of a function, e.g.,
int (*pf)(int) = &C::f;the overload set can contain both static and nonstatic member functions. The function with the matching signature is selected, and if it is nonstatic &C::f is a pointer to member function, and otherwise &C::f is a normal pointer to function.
Similarly, 12.2.2.2.2 [over.call.func] paragraph 3 makes it clear that
C::f();is a valid call even if the overload set contains both static and nonstatic member functions. Overload resolution is done, and if a nonstatic member function is selected, an implicit this-> is added, if that is possible.
Those paragraphs seem to suggest the general rule that you do overload resolution first and then you interpret the construct you have according to the function selected. The fact that there are static and nonstatic functions in the overload set is irrelevant; it's only necessary that the chosen function be static or nonstatic to match the context.
Given that, I think it would be more consistent if the (&C::f)() case would also do overload resolution first. If a nonstatic member is chosen, the program would be ill-formed.
Proposed resolution (04/01):
Change the indicated text in 12.2.2.2 [over.match.call] paragraph 3:
The fourth case arises from a postfix-expression of the form &F, where F names a set of overloaded functions. In the context of a function call,the set of functions named by F shall contain only non-member functions and static member functions. [Footnote: If F names a non-static member function, &F is a pointer-to-member, which cannot be used with the function call syntax.] And in this context using &F behaves the same as using&F is treated the same as the name F by itself. Thus, (&F)(expression-listopt) is simply (F)(expression-listopt), which is discussed in 12.2.2.2.2 [over.call.func]. If the function selected by overload resolution according to 12.2.2.2.2 [over.call.func] is a nonstatic member function, the program is ill-formed. [Footnote: When F is a nonstatic member function, a reference of the form &A::F is a pointer-to-member, which cannot be used with the function-call syntax, and a reference of the form &F is an invalid use of the "&" operator on a nonstatic member function.] (The resolution of &F in other contexts is described in 12.3 [over.over].)
[Moved to DR at 4/01 meeting.]
In describing non-member functions in an overload set, footnote 116 (12.2.2.2.2 [over.call.func]) says,Because of the usual name hiding rules, these will be introduced by declarations or by using-directives all found in the same block or all found at namespace scope.
At least in terms of the current state of the Standard, this is not correct: a block extern declaration does not prevent Koenig lookup from occurring. For example,
enum E { zero }; void f(E); void g() { void f(int); f(zero); }
In this example, the overload set will include declarations from both namespace and block scope.
(See also issue 12.)
Proposed resolution (04/01):
In 6.5.4 [basic.lookup.argdep] paragraph 2, change
If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered.
to
If the ordinary unqualified lookup of the name finds the declaration of a class member function, or a block-scope function declaration that is not a using-declaration, the associated namespaces and classes are not considered.
and change the example to:
namespace NS { class T { }; void f(T); void g(T, int); } NS::T parm; void g(NS::T, float); int main() { f(parm); // OK: calls NS::f extern void g(NS::T, float); g(parm, 1); // OK: calls g(NS::T, float) }
In 12.2.2.2.2 [over.call.func] paragraph 3 from:
If the name resolves to a non-member function declaration, that function and its overloaded declarations constitute the set of candidate functions.
to
If the name resolves to a set of non-member function declarations, that set of functions constitutes the set of candidate functions.
Note that this text is also edited by issue 364. Also, remove the associated footnote 116.
[Voted into WP at October 2003 meeting.]
Consider this program:
struct S { static void f (int); void f (char); }; void g () { S::f ('a'); }
G++ 3.1 rejects it, saying:
test.C:7: cannot call member function `void S::f(char)' without object
Mark Mitchell: It looks to me like G++ is correct, given 12.2.2.2.2 [over.call.func]. This case is the "unqualified function call" case described in paragraph 3 of that section. ("Unqualified" here means that there is no "x->" or "x." in front of the call, not that the name is unqualified.)
That paragraph says that you first do name lookup. It then asks you to look at what declaration is returned. (That's a bit confusing; you presumably get a set of declarations. Or maybe not; the name lookup section says that if name lookup finds a non-static member in a context like this the program is in error. But surely this program is not erroneous. Hmm.)
Anyhow, you have -- at least -- "S::f(char)" as the result of the lookup.
The keyword "this" is not in scope, so "all overloaded declarations of the function name in T become candidate functions and a contrived object of type T becomes the implied object argument." That means we get both versions of "f" at this point. Then, "the call is ill-formed, however, if overload resolution selects one of the non-static members of T in this case." Since, in this case, "S::f(char)" is the winner, the program is ill-formed.
Steve Adamczyk: This result is surprising, because we've selected a function that we cannot call, when there is another function that can be called. This should either be ambiguous, or it should select the static member function. See also 12.2.2 [over.match.funcs] paragraph 2: "Similarly, when appropriate, the context can construct an argument list that contains an implied object argument..."
Notes from October 2002 meeting:
We agreed that g++ has it right, but the standard needs to be clearer.
Proposed resolution (October 2002, revised April 2003):
Change 12.2.2.2.2 [over.call.func] paragraphs 2 and 3 as follows:
In qualified function calls, the name to be resolved is an id-expression and is preceded by an -> or . operator. Since the construct A->B is generally equivalent to (*A).B, the rest of Clause 12 [over] assumes, without loss of generality, that all member function calls have been normalized to the form that uses an object and the . operator. Furthermore, Clause 12 [over] assumes that the postfix-expression that is the left operand of the . operator has type ``cv T'' where T denotes a class. [Footnote: Note that cv-qualifiers on the type of objects are significant in overload resolution for both lvalue and class rvalue objects. --- end footnote] Under this assumption, the id-expression in the call is looked up as a member function of T following the rules for looking up names in classes (6.5.2 [class.member.lookup]).
If a member function is found, that function and its overloaded declarationsThe function declarations found by that lookup constitute the set of candidate functions. The argument list is the expression-list in the call augmented by the addition of the left operand of the . operator in the normalized member function call as the implied object argument (12.2.2 [over.match.funcs]).In unqualified function calls, the name is not qualified by an -> or . operator and has the more general form of a primary-expression. The name is looked up in the context of the function call following the normal rules for name lookup in function calls (
6.5.4 [basic.lookup.argdep]6.5 [basic.lookup]).If the name resolves to a non-member function declaration, that function and its overloaded declarationsThe function declarations found by that lookup constitute the set of candidate functions.[Footnote: Because of the usual name hiding rules, these will be introduced by declarations or by using-directives all found in the same block or all found at namespace scope. --- end footnote]Because of the rules for name lookup, the set of candidate functions consists (1) entirely of non-member functions or (2) entirely of member functions of some class T. In case (1), tThe argument list is the same as the expression-list in the call.If the name resolves to a nonstatic member function, then the function call is actually a member function call.In case (2), the argument list is the expression-list in the call augmented by the addition of an implied object argument as in a qualified function call. If the keyword this (_N4868_.11.4.3.2 [class.this]) is in scope and refers totheclass Tof that member function, or a derived classthereofof T, then thefunction call is transformed into a normalized qualified function call usingimplied object argument is(*this)as the postfix-expression to the left of the . operator.The candidate functions and argument list are as described for qualified function calls above.If the keyword this is not in scope or refers to another class, thenname resolution found a static member of some classT. In this case,all overloaded declarations of the function name in T become candidate functions anda contrived object of type T becomes the implied object argument. [Footnote: An implied object argument must be contrived to correspond to the implicit object parameter attributed to member functions during overload resolution. It is not used in the call to the selected function. Since the member functions all have the same implicit object parameter, the contrived object will not be the cause to select or reject a function. --- end footnote] If the argument list is augmented by a contrived object andThe call is ill-formed, however, ifoverload resolution selects one of the non-static member functions of T, the call is ill-formedin this case.
Note that issue 239 also edits paragraph 3.
[Voted into WP at October 2003 meeting.]
According to 12.2.2.2.3 [over.call.object] paragraph 2, when the primary-expression E in the function call syntax evaluates to a class object of type "cv T", a surrogate call function corresponding to an appropriate conversion function declared in a direct or indirect base class B of T is included or not included in the set of candidate functions based on class B being accessible.
For instance in the following code sample, as per the paragraph in question, the expression c(3) calls f2, instead of the construct being ill-formed due to the conversion function A::operator fp1 being inaccessible and its corresponding surrogate call function providing a better match than the surrogate call function corresponding to C::operator fp2:
void f1(int) { } void f2(float) { } typedef void (* fp1)(int); typedef void (* fp2)(float); struct A { operator fp1() { return f1; } }; struct B : private A { }; struct C : B { operator fp2() { return f2; } }; int main() { C c; c(3); // f2 is called, instead of the construct being ill-formed. return 0; }
The fact that the accessibility of a base class influences the overload resolution process contradicts the fundamental language rule (6.5 [basic.lookup] paragraph 1, and 12.2 [over.match] paragraph 2) that access checks are applied only once name lookup and function overload resolution (if applicable) have succeeded.
Notes from 4/02 meeting:
There was some concern about whether 6.5.2 [class.member.lookup] (or anything else, for that matter) actually defines "ambiguous base class". See issue 39. See also issue 156.
Notes from October 2002 meeting:
It was suggested that the ambiguity check is done as part of the call of the conversion function.
Proposed resolution (revised October 2002):
In 12.2.2.2.3 [over.call.object] paragraph 2, replace the last sentence
Similarly, surrogate call functions are added to the set of candidate functions for each conversion function declared in an accessible base class provided the function is not hidden within T by another intervening declaration.
with
Similarly, surrogate call functions are added to the set of candidate functions for each conversion function declared in a base class of T provided the function is not hidden within T by another intervening declaration.
Replace 12.2.2.2.3 [over.call.object] paragraph 3
If such a surrogate call function is selected by overload resolution, its body, as defined above, will be executed to convert E to the appropriate function and then to invoke that function with the arguments of the call.by
If such a surrogate call function is selected by overload resolution, the corresponding conversion function will be called to convert E to the appropriate function pointer or reference, and the function will then be invoked with the arguments of the call. If the conversion function cannot be called (e.g., because of an ambiguity), the program is ill-formed.
[Voted into WP at October 2004 meeting.]
Normally reference semantics allow incomplete types in certain contexts, but isn't this:
class A; A& operator<<(A& a, const char* msg); void foo(A& a) { a << "Hello"; }
required to be diagnosed because of the op<<? The reason being that the class may actually have an op<<(const char *) in it.
What is it? un- or ill-something? Diagnosable? No problem at all?
Steve Adamczyk: I don't know of any requirement in the standard that the class be complete. There is a rule that will instantiate a class template in order to be able to see whether it has any operators. But I wouldn't think one wants to outlaw the above example merely because the user might have an operator<< in the class; if he doesn't, he would not be pleased that the above is considered invalid.
Mike Miller: Hmm, interesting question. My initial reaction is that it just uses ::operator<<; any A::operator<< simply won't be considered in overload resolution. I can't find anything in the Standard that would say any different.
The closest analogy to this situation, I'd guess, would be deleting a pointer to an incomplete class; 7.6.2.9 [expr.delete] paragraph 5 says that that's undefined behavior if the complete type has a non-trivial destructor or an operator delete. However, I tend to think that that's because it deals with storage and resource management, not just because it might have called a different function. Generally, overload resolution that goes one way when it might have gone another with more declarations in scope is considered to be not an error, cf 9.10 [namespace.udecl] paragraph 9, _N4868_.13.8.4 [temp.nondep] paragraph 1, etc.
So my bottom line take on it would be that it's okay, it's up to the programmer to ensure that all necessary declarations are in scope for overload resolution. Worst case, it would be like the operator delete in an incomplete class -- undefined behavior, and thus not required to be diagnosed.
12.2.2.3 [over.match.oper] paragraph 3, bullet 1, says, "If T1 is a class type, the set of member candidates is the result of the qualified lookup of T1::operator@ (12.2.2.2.2 [over.call.func])." Obviously, that lookup is not possible if T1 is incomplete. Should 12.2.2.3 [over.match.oper] paragraph 3, bullet 1, say "complete class type"? Or does the inability to perform the lookup mean that the program is ill-formed? 6.3 [basic.def.odr] paragraph 4 doesn't apply, I don't think, because you don't know whether you'll be applying a class member access operator until you know whether the operator involved is a member or not.
Notes from October 2003 meeting:
We noticed that the title of this issue did not match the body. We checked the original source and then corrected the title (so it no longer mentions templates).
We decided that this is similar to other cases like deleting a pointer to an incomplete class, and it should not be necessary to have a complete class. There is no undefined behavior.
Proposed Resolution (October 2003):
Change the first bullet of 12.2.2.3 [over.match.oper] paragraph 3 to read:
If T1 is a complete class type, the set of member candidates is the result of the qualified lookup of T1::operator@ (12.2.2.2.2 [over.call.func]); otherwise, the set of member candidates is empty.
[Moved to DR at October 2002 meeting.]
Does dropping a cv-qualifier on a reference binding prevent the binding as far as overload resolution is concerned? Paragraph 4 says "Other restrictions on binding a reference to a particular argument do not affect the formation of a conversion sequence." This was intended to refer to things like access checking, but some readers have taken that to mean that any aspects of reference binding not mentioned in this section do not preclude the binding.
Proposed resolution (10/01):
In 12.2.4.2.5 [over.ics.ref] paragraph 4 add the indicated text:
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.
[Voted into WP at October 2003 meeting.]
template <class T> void f(T); template <class T> void g(T); template <class T> void g(T,T); int main() { (&f<int>); (&g<int>); }The question is whether &f<int> identifies a unique function. &g<int> is clearly ambiguous.
12.3 [over.over] paragraph 1 says that a function template name is considered to name a set of overloaded functions. I believe it should be expanded to say that a function template name with an explicit template argument list is also considered to name a set of overloaded functions.
In the general case, you need to have a destination type in order to identify a unique function. While it is possible to permit this, I don't think it is a good idea because such code depends on there only being one template of that name that is visible.
The EDG front end issues an error on this use of "f". egcs 1.1.1 allows it, but the most current snapshot of egcs that I have also issues an error on it.
It has been pointed out that when dealing with nontemplates, the rules for taking the address of a single function differ from the rules for an overload set, but this asymmetry is needed for C compatibility. This need does not exist for the template case.
My feeling is that a general rule is better than a general rule plus an exception. The general rule is that you need a destination type to be sure that the operation will succeed. The exception is when there is only one template in the set and only then when you provide values for all of the template arguments.
It is true that in some cases you can provide a shorthand, but only if you encourage a fragile coding style (that will cause programs to break when additional templates are added).
I think the standard needs to specify one way or the other how this case should be handled. My recommendation would be that it is ill-formed.
Nico Josuttis: Consider the following example:
template <int VAL> int add (int elem) { return elem + VAL; } std::transform(coll.begin(), coll.end(), coll.begin(), add<10>);
If John's recommendation is adopted, this code will become ill-formed. I bet there will be a lot of explanation for users necessary why this fails and that they have to change add<10> to something like (int (*)(int))add<10>.
This example code is probably common practice because this use of the STL is typical and is accepted in many current implementations. I strongly urge that this issue be resolved in favor of keeping this code valid.
Bill Gibbons: I find this rather surprising. Shouldn't a template-id which specifies all of the template arguments be treated like a declaration-only explicit instantiation, producing a set of ordinary function declarations? And when that set happens to contain only one function, shouldn't the example code work?
(See also issue 250.)
Notes from 04/01 meeting:
The consensus of the group was that the add example should not be an error.
Proposed resolution (October 2002):
In 13.4 add to the end of paragraph 2:
[Note: As described in 13.10.2 [temp.arg.explicit], if deduction fails and the function template name is followed by an explicit template argument list, the template-id is then examined to see whether it identifies a single function template specialization. If it does, the template-id is considered to be an lvalue for that function template specialization. The target type is not used in that determination.]
In 13.10.2 [temp.arg.explicit] paragraph 2 insert before the first example:
In contexts where deduction is done and fails, or in contexts where deduction is not done, if a template argument list is specified and it, along with any default template arguments, identifies a single function template specialization, then the template-id is an lvalue for the function template specialization.
Change the first example of 13.10.2 [temp.arg.explicit] paragraph 2:
template<class X, class Y> X f(Y); void g() { int i = f<int>(5.6); // Y is deduced to be double int j = f(5.6); // ill-formed: X cannot be deduced }to read:
template<class X, class Y> X f(Y); void g() { int i = f<int>(5.6); // Y is deduced to be double int j = f(5.6); // ill-formed: X cannot be deduced f<void>(f<int, bool>); // Y for outer f deduced to be // int (*)(bool) f<void>(f<int>); // ill-formed: f<int> does not denote a // single template function specialization }
Note: This interacts with the resolution of issue 226 (default template arguments for function templates).
[Moved to DR at 4/01 meeting.]
Is the intent of 12.4.3.2 [over.assign] paragraph 1 that all assignment operators be non-static member functions (including operator+=, operator*=, etc.) or only simple assignment operators (operator=)?
Notes from 04/00 meeting:
Nearly all references to "assignment operator" in the IS mean operator= and not the compound assignment operators. The ARM was specific that this restriction applied only to operator=. If it did apply to compound assignment operators, it would be impossible to overload these operators for bool operands.
Proposed resolution (04/01):
Change the title of 7.6.19 [expr.assign] from "Assignment operators" to "Assignment and compound assignment operators."
Change the first sentence of 7.6.19 [expr.assign] paragraph 1 from
There are several assignment operators, all of which group right-to-left. All require a modifiable lvalue as their left operand, and the type of an assignment expression is that of its left operand. The result of the assignment operation is the value stored in the left operand after the assignment has taken place; the result is an lvalue.
to
The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue with the type and value of the left operand after the assignment has taken place.
Additional note (10/00): Paragraphs 2-6 of 7.6.19 [expr.assign] should all be understood to apply to simple assignment only and not to compound assignment operators.
[Voted into WP at April, 2006 meeting.]
Lets start with the proposed solution. In 12.4.6 [over.ref], replace line ...
postfix-expression -> id-expression.... with the lines ...
postfix-expression -> templateopt id-expression(This then is a copy of the two lines in 7.6.1 [expr.post] covering "->dtor")
postfix-expression -> pseudo-destructor-name
Alternatively remove the sentence "It implements class member access using ->" and the syntax line following.
Reasons:
Currently stdc++ is inconsistent when handling expressions of the form "postfixexpression->scalar_type_dtor()": If "postfixexpression" is a pointer to the scalar type, it is OK, but if "postfixexpression" refers to any smart pointer class (e.g. iterator or allocator::pointer) with class specific CLASS::operator->() returning pointer to the scalar type, then it is ill-formed; so while c++98 does allow CLASS::operator->() returning pointer to scalar type, c++98 prohibits any '->'-expression involving this overloaded operator function.
Not only is this behaviour inconsistent, but also when comparing the corresponding chapters of c++pl2 and stdc++98 it looks like an oversight and unintended result. Mapping between stdc++98 and c++pl2:
c++pl2.r.5.2 -> 5.2 [expr.post]For the single line of c++pl2.r.5.2 covering "->dtor", 5.2 [expr.post] has two lines. Analogously c++pl2.r.5.2.4 has been doubled to 5.2.4 [expr.pseudo] and 5.2.5 [expr.ref]. From 13.5.6 [over.ref], the sentence forbiding CLASS::operator->() returning pointer to scalar type has been removed. Only the single line of c++pl2.r.13.4.6 (<-> c++pl2.r.5.2's single line) has not gotten its 2nd line when converted into 13.5.6 [over.ref].
c++pl2.r.5.2.4 -> 5.2.4 [expr.pseudo] + 5.2.5 [expr.ref]
c++pl2.r.13.4 -> 13.3.1.2 [over.match.oper]
c++pl2.r.13.4.6 -> 13.5.6 [over.ref]
Additionally GCC32 does is right (but against 13.5.6 [over.ref]).
AFAICS this would not break old code except compilers like VC7x and Comeau4301.
It does not add new functionality, cause any expression class_type->scalar_type_dtor() even today can be substituted through (*class_type).scalar_type_dtor().
Without this fix, template functions like some_allocator<T>::destroy(p) must use "(*p).~T()" or "(*p).T::~T()" when calling the destructor, otherwise the simpler versions "p->~T()" or "p->T::~T()" could be used.
Sample code, compiled with GCC32, VC7[1] and Comeau4301:
struct A {};//any class template <class T> struct PTR { T& operator* () const; T* operator-> () const; }; template <class T> void f () { { T* p ; p = new T ; (*p).T::~T() ;//OK p = new T ; (*p).~T() ;//OK p = new T ; p->T::~T() ;//OK p = new T ; p->~T() ;//OK } { PTR<T> p = PTR<T>() ; (*p).T::~T() ;//OK (*p).~T() ;//OK p.operator->() ;//OK !!! p->T::~T() ;//GCC32: OK; VC7x,Com4301: OK for A; ERROR w/ int p->~T() ;//GCC32: OK; VC7x,Com4301: OK for A; ERROR w/ int } } void test () { f <A> (); f <int>(); }
Proposed resolution (April, 2005):
Change 12.4.6 [over.ref] paragraph 1 as indicated:
operator-> shall be a non-static member function taking no parameters. It implements the class member access
usingsyntax that uses ->postfix-expression -> templateopt id-expression
postfix-expression -> pseudo-destructor-nameAn expression x->m is interpreted as (x.operator->())->m for a class object x of type T if T::operator->() exists and if the operator is selected as the best match function by the overload resolution mechanism (12.2 [over.match]).
[Voted into WP at March 2004 meeting.]
During a discussion over at the boost mailing list (www.boost.org), we came across the following "puzzle":
struct A { template< typename T > operator T() const; } a; template<> A::operator float() const { return 1.0f; } int main() { float f = 1.0f * a; }
The code is compiled without errors or warnings from EDG-based compilers (Comeau, Intel), but rejected from others (GCC, MSVC [7.1]). The question: Who is correct? Where should I file the bug report?
To explain the problem: The EDG seems to see 1.0f*a as a call to the unambiguous operator*(float,float) and thus casts 'a' to 'float'. The other compilers have several operators (float*float, float*double, float*int, ...) available and thus can't decide which cast is appropriate. I think the latter is the correct behaviour, but I'd like to hear some comments from the language lawyers about the standard's point of view on this problem.
Andreas Hommel: Our compiler also rejects this code:
Error : function call 'operator*(float, {lval} A)' is ambiguous 'operator*(float, unsigned long long)' 'operator*(float, int)' 'operator*(float, unsigned int)' 'operator*(float, long)' 'operator*(float, unsigned long)' 'operator*(float, float)' 'operator*(float, double)' 'operator*(float, long double)' 'operator*(float, long long)' Test.cp line 12 float f = 1.0f * a;
Is this example really legal? It was my understanding that all candidates from 12.5 [over.built] participate in overload resolution.
Daveed Vandevoorde: I believe the EDG-based compiler is right. Note that the built-in operator* requires "usual arithmetic conversions" (see 7.6.5 [expr.mul] paragraph 2 and Clause 7 [expr] paragaph 9). This means that there is no candidate taking (float, double) arguments: Only (float, float) or (double, double).
Since your first argument is of type float, the (float, float) case is preferred over the (double, double) case (the latter would require a floating-point promotion).
Stave Adamczyk: Daveed's statement is wrong; as Andreas says, the prototypes in 12.5 [over.built] paragraph 12 have pairs of types, not the same type twice. However, the list of possibilities considered in Andreas' message is wrong also: 12.5 [over.built] paragraph 12 calls for pairs of promoted arithmetic types, and float is not a promoted type (it promotes to double -- see 7.3.8 [conv.fpprom]).
Nevertheless, the example is ambiguous. Let's look at the overload resolution costs. The right operand is always going to have a user-defined-conversion cost (the template conversion function will convert directly to the const version of the second parameter of the prototype). The left operand is always going to have a promotion (float --> double) or a standard conversion (anything else). So the cases with promotions are better than the others. However, there are several of those cases, with second parameters of type int, unsigned int, long, unsigned long, double, and long double, and all of those are equally good. Therefore the example is ambiguous.
Here's a reduced version that should be equivalent:
struct A { template <typename T> operator T() const; } a; void f(double, int); void f(double, unsigned int); void f(double, long); void f(double, unsigned long); void f(double, double); void f(double, long double); int main() { f(1.0f, a); // Ambiguous }
Personally, I think this is evidence that 12.5 [over.built] doesn't really do quite what it should. But the standard is clear, if possibly flawed.
Andreas Hommel: You are right, "float" is not a promoted arithmetic type, this is a bug in our compiler.
However, the usual arithmetic conversions (Clause 7 [expr] paragraph 9) do not promote the floating point types, so
float operator+(float, float);is a legal built-in operator function, so I wonder if it shouldn't be included in the candidate list.
Steve Adamczyk: Hmm, the definition of the term in 12.5 [over.built] paragraph 2 is highly ambiguous:
Similarly, the term promoted arithmetic type refers to promoted integral types plus floating types.I can't tell if that's "promoted integral types plus (all) floating types" or "promoted integral types plus (promoted) floating types". I thought the latter was intended, but indeed the usual arithmetic conversions could give you "float + float", so it makes sense that float would be one of the possibilities. We should discuss this to make sure everyone has the same interpretation.
Proposed Resolution (October 2003):
Change the second sentence of 13.6 paragraph 2 as follows:
Similarly, the term promoted arithmetic type refers topromoted integral types plus floating typesfloating types plus promoted integral types.
[Voted into WP at April 2003 meeting.]
Clause 13 [temp] paragraph 7 allows class templates to be declared exported, including member classes and member class templates (implicitly by virtue of exporting the containing template class). However, paragraph 8 does not exclude exported class templates from the statement that
An exported template need only be declared (and not necessarily defined) in a translation unit in which it is instantiated.This is an incorrect implication; however, it is also not dispelled in 13.9.2 [temp.inst] paragraph 6:
If an implicit instantiation of a class template specialization is required and the template is declared but not defined, the program is ill-formed.This wording says nothing about the translation unit in which the definition must be provided. Contrast this with 13.9.3 [temp.explicit] paragraph 3:
A definition of a class template or a class member template shall be in scope at the point of the explicit instantiation of the class template or class member template.
Suggested resolution:
(See also issue 212.)
Notes from 04/00 meeting:
John Spicer opined that even though Clause 13 [temp] paragraph 7 speaks of "declaring a class template exported," that does not mean that the class template is "an exported template" in the sense of paragraph 8. He suggested clarifying paragraph 7 to that effect instead of the change to paragraph 8 suggested above, and questioned the need for a change to 13.9.2 [temp.inst].
Notes from the 4/02 meeting:
This is resolved by the proposed changes for issue 323.
[Voted into WP at April 2003 meeting.]
The standard doesn't seem to describe whether the keyword export should appear on exported template declarations that are not used or defined in that particular translation unit.
For example:
// File 1: template<typename T> void f(); // export omitted // File 2: export template<typename T> void f() {} int main() { f<int>(); }
Another example is:
// File 1: struct S { template<typename T> void m(); }; // File 2: struct S { template<typename T> void m(); }; export template<typename T> void S::m() {} int main() { S s; S.m<int>(); }
I think both examples should be clarified to be invalid. If a template is exported in one translation unit, it should be declared export in all translation units in which it appears.
With the current wording, it seems that even the following is valid:
// File 1: export template<typename T> void f(); // export effectively ignored // File 2: template<typename T> void f() {} // Inclusion model void g() { f<int>(); } // File 3: void g(); template<typename T> void f() {} // Inclusion model int main() { g(); f<int>(); }
In fact, I think the declaration in "File 1" could be a definition and this would still satisfy the the requirements of the standard, which definitely seems wrong.
Proposed Resolution (revised October 2002):
Replace Clause 13 [temp] paragraphs 6, 7, and 8 by the following text:
A template-declaration may be preceded by the export keyword. Such a template is said to be exported. Declaring exported a class template is equivalent to declaring exported all of its non-inline member functions, static data members, member classes, member class templates, and non-inline member function templates.
If a template is exported in one translation unit, it shall be exported in all translation units in which it appears; no diagnostic is required. A declaration of an exported template shall appear with the export keyword before any point of instantiation (13.8.4.1 [temp.point]) of that template in that translation unit. In addition, the first declaration of an exported template containing the export keyword must not follow the definition of that template. The export keyword shall not be used in a friend declaration.
Templates defined in an unnamed namespace, inline functions, and inline function templates shall not be exported. An exported non-class template shall be defined only once in a program; no diagnostic is required. An exported non-class template need only be declared (and not necessarily defined) in a translation unit in which it is instantiated.
A non-exported non-class template must be defined in every translation unit in which it is implicitly instantiated (13.9.2 [temp.inst]), unless the corresponding specialization is explicitly instantiated (13.9.3 [temp.explicit]) in some translation unit; no diagnostic is required.
Note: This change also resolves issues 204 and 335.
[Voted into WP at April 2003 meeting.]
The syntax for "export" permits it only on template declarations. Clause 13 [temp] paragraph 6 further restricts "export" to appear only on namespace scope declarations. This means that you can't export a member template of a non-template class, as in:
class A { template <class T> void f(T); };You can, of course, put export on the definition:
export template <class T> void A<T>::f(T){}but in order for the template to be used from other translation units (the whole point of export) the declaration in the other translation unit must also be declared export.
There is also the issue of whether or not we should permit this usage:
export struct A { template <class T> void f(T); };My initial reaction is to retain this prohibition as all current uses of "export" are preceding the "template" keyword.
If we eliminate the requirement that "export" precede "template" there is a similar issue regarding this case, which is currently prohibited:
template <class T> struct B { export void f(); };My preference is still to permit only "export template".
Notes from the 4/02 meeting:
This is resolved by the proposed changes for issue 323.
[Voted into WP at the October, 2006 meeting.]
Taken literally, Clause 13 [temp] paragraph 2 does not permit operator functions to be templates:
In a function template declaration, the declarator-id shall be a template-name (i.e., not a template-id).
and, in 13.3 [temp.names] paragraph 1, a template-name is defined to be simply an identifier.
Issue 301 considered and rejected the idea of changing the definition of template-name to include operator-function-ids and conversion-function-ids. Either that decision should be reconsidered or the various references in the text to template-name should be examined to determine if they should also mention the non-identifier possibilities for function template names.
Proposed resolution (April, 2006):
This issue is resolved by the resolution of issue 301.
[Voted into WP at April 2003 meeting.]
John Spicer: Where can default values for the template parameters of template template parameters be specified and where so they apply?
For normal template parameters, defaults can be specified only in class template declarations and definitions, and they accumulate across multiple declarations in the same way that function default arguments do.
I think that defaults for parameters of template template parameters should be handled differently, though. I see no reason why such a default should extend beyond the template declaration with which it is associated. In other words, such defaults are a property of a specific template declaration and are not part of the interface of the template.
template <class T = float> struct B {}; template <template <class _T = float> class T> struct A { inline void f(); inline void g(); }; template <template <class _T> class T> void A<T>::f() { T<> t; // Okay? (proposed answer - no) } template <template <class _T = char> class T> // Okay? (proposed answer - yes) void A<T>::g() { T<> t; // T<char> or T<float>? (proposed answer - T<char>) } int main() { A<B> ab; ab.f(); }
I don't think this is clear in the standard.
Gabriel Dos Reis: On the other hand I fail to see the reasons why we should introduce yet another special rule to handle that situation differently. I think we should try to keep rules as uniform as possible. For default values, it has been the case that one should look for any declaration specifying default values. Breaking that rules doesn't buy us anything, at least as far as I can see. My feeling is that [allowing different defaults in different declarations] is very confusing.
Mike Miller: I'm with John on this one. Although we don't have the concept of "prototype scope" for template parameter lists, the analogy with function parameters would suggest that the two declarations of T (in the template class definition and the template member function definition) are separate declarations and completely unrelated. While it's true that you accumulate default arguments on top-level declarations in the same scope, it seems to me a far leap to say that we ought also to accumulate default arguments in nested declarations. I would expect those to be treated as being in different scopes and thus not to share default argument information.
When you look up the name T in the definition of A<T>::f(), the declaration you find has no default argument for the parameter of T, so T<> should not be allowed.
Proposed Resolution (revised October 2002):
In 13.2 [temp.param], add the following as a new paragraph at the end of this section:
A template-parameter of a template template-parameter is permitted to have a default template-argument. When such default arguments are specified, they apply to the template template-parameter in the scope of the template template-parameter. [Example:template <class T = float> struct B {}; template <template <class TT = float> class T> struct A { inline void f(); inline void g(); }; template <template <class TT> class T> void A<T>::f() { T<> t; // error - TT has no default template argument } template <template <class TT = char> class T>void A<T>::g() { T<> t; // OK - T<char> }-- end example]
[Voted into WP at April, 2007 meeting.]
According to 13.2 [temp.param] paragraph 3, the following fragment is ill-formed:
template <class T> class X{ friend void T::foo(); };
In the friend declaration, the T:: part is a nested-name-specifier (9.3 [dcl.decl] paragraph 4), and T must be a class-name or a namespace-name (_N4567_.5.1.1 [expr.prim.general] paragraph 7). However, according to 13.2 [temp.param] paragraph 3, it is only a type-name. The fragment should be well-formed, and instantiations of the template allowed as long as the actual template argument is a class which provides a function member foo. As a result of this defect, any usage of template parameters in nested names is ill-formed, e.g., in the example of 13.8 [temp.res] paragraph 2.
Notes from 04/00 meeting:
The discussion at the meeting revealed a self-contradiction in the current IS in the description of nested-name-specifiers. According to the grammar in _N4567_.5.1.1 [expr.prim.general] paragraph 7, the components of a nested-name-specifier must be either class-names or namespace-names, i.e., the constraint is syntactic rather than semantic. On the other hand, 6.5.5 [basic.lookup.qual] paragraph 1 describes a semantic constraint: only object, function, and enumerator names are ignored in the lookup for the component, and the program is ill-formed if the lookup finds anything other than a class-name or namespace-name. It was generally agreed that the syntactic constraint should be eliminated, i.e., that the grammar ought to be changed not to use class-or-namespace-name.
A related point is the explicit prohibition of use of template parameters in elaborated-type-specifiers in 9.2.9.5 [dcl.type.elab] paragraph 2. This rule was the result of an explicit Committee decision and should not be unintentionally voided by the resolution of this issue.
Proposed resolution (04/01):
Change _N4567_.5.1.1 [expr.prim.general] paragraph 7 and A.5 [gram.expr] from
to
This resolution depends on the resolutions for issues 245 (to change the name lookup rules in elaborated-type-specifiers to include all type-names) and 283 (to categorize template type-parameters as type-names).
Notes from 10/01 meeting:
There was some sentiment for going with simply identifier in front of the "::", and stronger sentiment for going with something with a more descriptive name if possible. See also issue 180.
Notes from April 2003 meeting:
This was partly resolved by the changes for issue 125. However, we also need to add a semantic check in 6.5.5 [basic.lookup.qual] to allow T::foo and we need to reword the first sentence of 6.5.5 [basic.lookup.qual].
Proposed resolution (October, 2004):
Change 6.5.5 [basic.lookup.qual] paragraph 1 as follows:
The name of a class or namespace member can be referred to after the :: scope resolution operator (_N4567_.5.1.1 [expr.prim.general]) applied to a nested-name-specifier that nominates its class or namespace. During the lookup for a name preceding the :: scope resolution operator, object, function, and enumerator names are ignored. If the name foundis not a class-name (Clause 11 [class]) or namespace-name (9.9.2 [namespace.def])does not designate a class or namespace, the program is ill-formed. [...]
Notes from the April, 2005 meeting:
The 10/2004 resolution does not take into account the fact that template type parameters do not designate class types in the context of the template definition. Further drafting is required.
Proposed resolution (April, 2006):
Change 6.5.5 [basic.lookup.qual] paragraph 1 as follows:
The name of a class or namespace member can be referred to after the :: scope resolution operator (_N4567_.5.1.1 [expr.prim.general]) applied to a nested-name-specifier that nominates its class or namespace. During the lookup for a name preceding the :: scope resolution operator, object, function, and enumerator names are ignored. If the name foundis not a class-name (Clause 11 [class]) or namespace-name (9.9.2 [namespace.def])does not designate a namespace or a class or dependent type, the program is ill-formed. [...]
[Voted into WP at April 2003 meeting.]
The prohibition of default template arguments for function templates is a misbegotten remnant of the time where freestanding functions were treated as second class citizens and required all template arguments to be deduced from the function arguments rather than specified.
The restriction seriously cramps programming style by unnecessarily making freestanding functions different from member functions, thus making it harder to write STL-style code.
Suggested resolution:
Replace
A default template-argument shall not be specified in a function template declaration or a function template definition, nor in the template-parameter-list of the definition of a member of a class template.
by
A default template-argument shall not be specified in the template-parameter-list of the definition of a member of a class template.
The actual rules are as stated for arguments to class templates.
Notes from 10/00 meeting:
The core language working group was amenable to this change. Questions arose, however, over the interaction between default template arguments and template argument deduction: should it be allowed or forbidden to specify a default argument for a deduced parameter? If it is allowed, what is the meaning: should one or the other have priority, or is it an error if the default and deduced arguments are different?
Notes from the 10/01 meeting:
It was decided that default arguments should be allowed on friend declarations only when the declaration is a definition. It was also noted that it is not necessary to insist that if there is a default argument for a given parameter all following parameters have default arguments, because (unlike in the class case) arguments can be deduced if they are not specified.
Note that there is an interaction with issue 115.
Proposed resolution (revised October 2002):
In 13.2 [temp.param] paragraph 9, replace
A default template-argument may be specified in a class template declaration or a class template definition. A default template-argument shall not be specified in a function template declaration or a function template definition, nor in the template-parameter-list of the definition of a member of a class template.
with
A default template-argument may be specified in a template declaration. A default template-argument shall not be specified in the template-parameter-lists of the definition of a member of a class template that appears outside of the member's class.
In 13.2 [temp.param] paragraph 9, replace
A default template-argument shall not be specified in a friend template declaration.
with
A default template-argument shall not be specified in a friend class template declaration. If a friend function template declaration specifies a default template-argument, that declaration shall be a definition and shall be the only declaration of the function template in the translation unit.
In 13.2 [temp.param] paragraph 11, replace
If a template-parameter has a default template-argument, all subsequent template-parameters shall have a default template-argument supplied.
with
If a template-parameter of a class template has a default template-argument, all subsequent template-parameters shall have a default template-argument supplied. [Note: This is not a requirement for function templates because template arguments might be deduced (13.10.3 [temp.deduct]).]
In 13.10 [temp.fct.spec] paragraph 1, replace
Template arguments can either be explicitly specified when naming the function template specialization or be deduced (13.10.3 [temp.deduct]) from the context, e.g. from the function arguments in a call to the function template specialization.
with
Template arguments can be explicitly specified when naming the function template specialization, deduced from the context (13.10.3 [temp.deduct]), e.g., deduced from the function arguments in a call to the function template specialization), or obtained from default template arguments.
In 13.10.2 [temp.arg.explicit] paragraph 2, replace
Trailing template arguments that can be deduced (13.10.3 [temp.deduct]) may be omitted from the list of explicit template-arguments.
with
Trailing template arguments that can be deduced (13.10.3 [temp.deduct]) or obtained from default template-arguments may be omitted from the list of explicit template-arguments.
In 13.10.3 [temp.deduct] paragraph 1, replace
The values can be either explicitly specified or, in some cases, deduced from the use.
with
The values can be explicitly specified or, in some cases, be deduced from the use or obtained from default template-arguments.
In 13.10.3 [temp.deduct] paragraph 4, replace
The resulting substituted and adjusted function type is used as the type of the function template for template argument deduction. When all template arguments have been deduced, all uses of template parameters in nondeduced contexts are replaced with the corresponding deduced argument values. If the substitution results in an invalid type, as described above, type deduction fails.
with
The resulting substituted and adjusted function type is used as the type of the function template for template argument deduction. If a template argument has not been deduced, its default template argument, if any, is used. [Example:
template <class T, class U = double> void f(T t = 0, U u = 0); void g() { f(1, 'c'); // f<int,char>(1,'c') f(1) // f<int,double>(1,0) f(); // error: T cannot be deduced f<int>(); // f<int,double>(0,0) f<int,char>(); // f<int,char>(0,0) }---end example]
When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters in nondeduced contexts are replaced with the corresponding deduced or default argument values. If the substitution results in an invalid type, as described above, type deduction fails.
[Voted into WP at October 2005 meeting.]
Is the following well-formed?
class policy {}; class policy_interface {}; template <class POLICY_INTERFACE> class aph { protected: typedef POLICY_INTERFACE PI; }; template <class POLICY, class BASE, class PI = typename BASE::PI> class ConcretePolicyHolder : public BASE, protected POLICY {}; ConcretePolicyHolder < policy , aph < policy_interface > > foo; void xx() { }
The issue is whether the access to the default argument type BASE::PI is checked before or after it is known that BASE is a base class of the template. To some extent, one needs to develop the list of template arguments (and therefore evaluate the default argument) before one can instantiate the template, and one does not know what base classes the template has until it has been instantiated.
Notes from April 2003 meeting:
Shortened example:
class B { protected: typedef int A; }; template<class T, class U = typename T::A> class X : public T { };
The convincing argument here is that if we had only the declaration of the template (including the default argument), we would expect it to be usable in exactly the same way as the version with the definition. However, the special access needed is visible only when the definition is available. So the above should be an error, and information from the definition cannot affect the access of the default arguments.
Proposed Resolution (April 2003):
Add a new paragraph 16 to 13.2 [temp.param] after paragraph 15:
Since a default template-argument is encountered before any base-clause there is no special access to members used in a default template-argument. [Example:class B {}; template <class T> class C { protected: typedef T TT; }; template <class U, class V = typename U::TT> class D : public U {}; D <C<B> > d; // access error, C::TT is protected--- end example]
Notes from October 2003 meeting:
We decided that template parameter default arguments should have their access checked in the context where they appear without special access for the entity declared (i.e., they are different than normal function default arguments). One reason: we don't know the instance of the template when we need the value. Second reason: compilers want to parse and throw away the form of the template parameter default argument, not save it and check it for each instantiation.
Class templates should be treated the same as function templates in this regard. The base class information is in the same category as friend declarations inside the class itself -- not available. If the body were used one would need to instantiate it in order to know whether one can name it.
Proposed resolution (October, 2004):
Add the following as a new paragraph following the last paragraph of 11.8 [class.access] (but before the new paragraph inserted by the resolution of issue 372, if adopted):
The names in a default template-argument (13.2 [temp.param]) have their access checked in the context in which they appear rather than at any points of use of the default template-argument. [Example:
class B {}; template <class T> class C { protected: typedef T TT; }; template <class U, class V = typename U::TT> class D : public U {}; D <C<B> >* d; // access error, C::TT is protected—end example]
[Voted into WP at April 2003 meeting.]
Consider the following example:
template<class T> struct X { virtual void f(); }; template<class T> struct Y { void g(X<T> *p) { p->template X<T>::f(); } };
This is an error because X is not a member template; 13.3 [temp.names] paragraph 5 says:
If a name prefixed by the keyword template is not the name of a member template, the program is ill-formed.
In a way this makes perfect sense: X is found to be a template using ordinary lookup even though p has a dependent type. However, I think this makes the use of the template prefix even harder to teach.
Was this intentionally outlawed?
Proposed Resolution (4/02):
Elide the first use of the word "member" in 13.3 [temp.names] paragraph 5 so that its first sentence reads:
If a name prefixed by the keyword template is not the name of amembertemplate, the program is ill-formed.
[Voted into WP at the October, 2006 meeting.]
The grammar for a template-name is:
That's not right; consider:
template <class T> T operator+(const T&, const T&); template <> S operator+<S>(const S&, const S&);
This is ill-formed according to the standard, since operator+ is not a template-name.
Suggested resolution:
I think the right rule is
John Spicer adds that there's some question about whether conversion functions should be included, as they cannot have template argument lists.
Notes from 4/02 meeting:
If the change is made as a syntax change, we'll need a semantic restriction to avoid operator+<int> as a class. Clark Nelson will work on a compromise proposal -- not the minimal change to the syntax proposed, not the maximal change either.
Clark Nelson (April 2003):
The proposed solution (adding operator-function-id as an alternative for template-name) would have a large impact on the language described by the grammar. Specifically, for example, operator+<int> would become a syntactically valid class-name.
On the other hand, a change with (I believe) exactly the desired effect on the language accepted, would be to modify operator-function-id itself:
(Steve Adamczyk: this change was already made by issue 38 and is in TC1.)
Then there is the first sentence of 13.3 [temp.names] paragraph 3:
After name lookup (6.5 [basic.lookup]) finds that a name is a template-name, if this name is followed by a <, the < is always taken as the beginning of a template-argument-list and never as a name followed by the less-than operator.
This description seems to be adequate for names of class templates. As far as I can tell, the only ambiguity it resolves is from something that starts with new X <, in the scope of a class template X. But as far as I can tell is already inadequate for names of function templates, and is even worse for operator function templates.
Probably < should always be interpreted as introducing a template-argument-list if any member of the overload set is a function template. After all, function pointers are very rarely compared for ordering, and it's not clear what other rule might be workable.
I'm inclined to propose the simplest rule possible for operator-function-ids: if one is followed by <, then what follows is interpreted as a template-argument-list, unconditionally. Of course, if no template for that operator has been declared, then there's an error.
Also, note that if the operator in question is < or <<, it is possible to run into a problem similar to the famous >> nested template argument list closing delimiter problem. However, since in this case (at least) one of the < characters has a radically different interpretation than the other, and for other reasons as well, this is unlikely to be nearly as much of a practical problem as the >> problem.
Notes from April 2003 meeting:
We felt that the operator functions should not be special-cased. They should be treated like any other name.
September 2003:
Clark Nelson has provided the changes in N1490=03-0073.
Notes from October 2003 meeting:
We reviewed Clark Nelson's N1490. Clark will revise it and introduce a new syntax term for an identifier or the name of an operator function.
Notes from the April, 2005 meeting:
The CWG suggested a new approach to resolving this issue: the existing term template-id will be renamed to class-template-id, the term template-id will be defined to include operator functions with template arguments, and any current uses of template-id (such as in the definition of elaborated-type-specifier) where an operator function is not appropriate will be changed to refer to class-template-id.
Proposed resolution (April, 2006):
As specified in document J16/05-0156 = WG21 N1896, except that:
In change 9 (_N4868_.6.5.6 [basic.lookup.classref]), omit the change from “entire postfix-expression” to “nested-name-specifier.”
In change a (6.5.5.2 [class.qual] paragraph 1, third bullet), omit the change from “entire postfix-expression” to “qualified-id.”
In change b (6.5.5.3 [namespace.qual] paragraph 1), omit the change from “entire postfix-expression” to “qualified-id.”
(Some of these omitted changes are addressed by issue 562.)
(This resolution also resolves issue 534.)
[Voted into WP at the October, 2006 meeting.]
For the same reasons that issue 382 proposes for relaxation of the requirements on typename, it would make sense to allow the ::template disambiguator outside of templates.
See also issues 11, 30, 96, and 109.
Proposed resolution (October, 2005):
Change 13.3 [temp.names] paragraph 5 as indicated:
If a name prefixed by the keyword template is not the name of a template, the program is ill-formed. [Note: the keyword template may not be applied to non-template members of class templates. —end note]Furthermore, names of member templates shall not be prefixed by the keyword template if the postfix-expression or qualified-id does not appear in the scope of a template.[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 nested-name-specifier or the expression on the left of the -> or ., or the nested-name-specifieris not dependent on a template-parameter, or the use does not appear in the scope of a template. —end note]
[Moved to DR at 4/01 meeting.]
Is it permitted to jump from a handler of a function-try-block into the body of the function?
Clause 14 [except] paragraph 2 would appear to disallow such a jump:
A goto, break, return, or continue statement can be used to transfer control out of a try block or handler, but not into one.
However, 14.4 [except.handle] paragraph 14 mentions only constructors and destructors for the prohibition:
If the handlers of a function-try-block contain a jump into the body of a constructor or destructor, the program is ill-formed.
Is this paragraph simply reemphasizing the more general restriction, or does it assume that such a jump would be permitted for functions other than constructors or destructors? If the former interpretation is correct, it is confusing and should be either eliminated or turned into a note. If the latter interpretation is accurate, Clause 14 [except] paragraph 2 must be revised.
(See also issue 98.)
Proposed resolution (04/01):
Delete 14.4 [except.handle] paragraph 14.
[Voted into WP at the October, 2006 meeting.]
I'm not really sure what the standard says about this. Personally, I'd like for it to be ill-formed, but I can't find any words that I can interpret to say so.
template<class T> class X { protected: typedef T Type; }; template<class T> class Y { }; template<class T, template<class> class T1, template<class> class T2> class Z: public T2<typename T1<T>::Type>, public T1<T> { }; Z<int, X, Y> z;
John Spicer: I don't think the standard really addresses this case. There is wording about access checking of things used as template arguments, but that doesn't address accessing members of the template argument type (or template) from within the template.
This example is similar, but does not use template template arguments.
class X { private: struct Type {}; }; template <class T> struct A { typename T::Type t; }; A<X> ax;
This gets an error from most compilers, though the standard is probably mute on this as well. An error makes sense -- if there is no error, there is a hole in the access checking. (The special rule about no access checks on template parameters is not a hole, because the access is checked on the type passed in as an argument. But when you look up something in the scope of a template parameter type, you need to check the access to the member found.)
The logic in the template template parameter case should be similar: anytime you look up something in a template-dependent class, the member's access must be checked, because it could be different for different template instances.
Proposed Resolution (October 2002):
Change the last sentence of 13.4 [temp.arg] paragraph 3 from:
For a template-argument of class type, the template definition has no special access rights to the inaccessible members of the template argument type.to:
For a template-argument that is a class type or a class template, the template definition has no special access rights to the members of the template-argument. [Example:template <template <class TT> class T> class A { typename T<int>::S s; }; template <class U> class B { private: struct S { /* ... */ }; }; A<B> b; // ill-formed, A has no access to B::S-- end example]
Daniel Frey posts on comp.std.c++ in July 2003: I just read DR 372 and I think that the problem presented is not really discussed/solved properly. Consider this example:
class A { protected: typedef int N; }; template< typename T > class B {}; template< typename U > class C : public U, public B< typename U::N > {}; C< A > x;
The question is: If C is derived from A as above, is it allowed to access A::N before the classes opening '{'?
The main problem is that you need to access U's protected parts in C's base-clause. This pattern is common when using policies, Andrei's Loki library was bitten by it as he tried to make some parts of the policies 'protected' but some compilers rejected the code. They were right to reject it, I think it's 11.8.4 [class.friend]/2 that applies here and prevents the code above to be legal, although it addresses a different and reasonable example. To me, it seems wrong to reject the code as it is perfectly reasonable to write such stuff. The questions are:
Steve Adamczyk: In other words, the point of the issue is over what range access derived from base class specifiers is granted, and whether any part of that range is the base specifier list itself, either the parts afterwards or the whole base specifier list. (Clark Nelson confirms this is what he was asking with the original question.) Personally, I find it somewhat disturbing that access might arrive incrementally; I'd prefer that the access happen all at once, at the opening brace of the class.
Notes from October 2003 meeting:
We decided it makes sense to delay the access checking for the base class specifiers until the opening brace of the class is seen. In other words, the base specifiers will be checked using the full access available for the class, and the order of the base classes is not significant in that determination. The implementors present all said they already had code to handle accumulation of delayed access checks, because it is already needed in other contexts.
Proposed resolution (October, 2004):
Change the last sentence of 13.4 [temp.arg] paragraph 3 as indicated:
For a template-argumentofthat is a class type or a class template, the template definition has no special access rights to theinaccessiblemembers of thetemplate argument typetemplate-argument. [Example:template <template <class TT> class T> class A { typename T<int>::S s; }; template <class U> class B { private: struct S { /* ... */ }; }; A<B> b; // ill-formed, A has no access to B::S—end example]
Insert the following as a new paragraph after the final paragraph of 11.8 [class.access]:
The access of names in a class base-specifier-list are checked at the end of the list after all base classes are known. [Example:
class A { protected: typedef int N; }; template<typename T> class B {}; template<typename U> class C : public B<typename U::N>, public U {}; C<A> x; // OK: A is a base class so A::N in B<A::N> is accessible
—end example]
Notes from the April, 2005 meeting:
The 10/2004 resolution is not sufficient to implement the CWG's intent to allow these examples: 11.8 [class.access] paragraph 1 grants protected access only to “members and friends” of derived classes, not to their base-specifiers. The resolution needs to be extended to say either that access in base-specifiers is determined as if they were members of the class being defined or that access is granted to the class as an entity, including its base-specifiers. See also issue 500, which touches on the same issue and should be resolved in the same way.
Proposed resolution (October, 2005):
Change the second bullet of 11.8 [class.access] paragraph 1 as indicated:
protected; that is, its name can be used only by
members and friends of the class in which it is declared, and by
members and friends of classes derived from this class by
classes derived from that class, and by their friends (see
11.8.5 [class.protected]).
Change 11.8 [class.access] paragraph 2 as indicated:
A member of a class can also access all the namesdeclared in the class of which it is a memberto which the class has access.
Change 11.8 [class.access] paragraph 6 as indicated:
All access controls in 11.8 [class.access] affect the ability to access a class member name from a particular scope.The access control for names used in the definition of a class member that appears outside of the member's class definition is done as if the entire member definition appeared in the scope of the member's class.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...
Change the example and commentary in 11.8 [class.access] paragraphs 6-7 as indicated:
[Example:
class A { typedef int I; // private member I f(); friend I g(I); static I x; 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; struct D: A::B, A { };
Here, all the uses of A::I are well-formed because A::f and A::x are members of class A and g is a friend 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 access checking of base-specifiers must be deferred until the entire base-specifier-list has been seen. —end example]
In 11.8.4 [class.friend] paragraph 2, replace the following text:
Declaring a class to be a friend implies that the names of private and protected members from the class granting friendship can be accessed in declarations of members of the befriended class. [Note: this means that access to private and protected names is also granted to member functions of the friend class (as if the functions were each friends) and to the static data member definitions of the friend class. This also means that private and protected type names from the class granting friendship can be used in the base-clause of a nested class of the friend class. However, the declarations of members of classes nested within the friend class cannot access the names of private and protected members from the class granting friendship. Also, because the base-clause of the friend class is not part of its member declarations, the base-clause of the friend class cannot access the names of the private and protected members from the class granting friendship. For example,
class A { class B { }; friend class X; }; class X: A::B { // ill-formed: A::B cannot be accessed // in the base-clause for X A::B mx; // OK: A::B used to declare member of X class Y: A::B { // OK: A::B used to declare member of X A::B my; // ill-formed: A::B cannot be accessed // to declare members of nested class of X }; };—end note]
with:
Declaring a class to be a friend implies that the names of private and protected members from the class granting friendship can be accessed in the base-specifiers and member declarations of the befriended class. [Example:
class A { class B { }; friend class X; }; struct X: A::B { // OK: A::B accessible to friend A::B mx; // OK: A::B accessible to member of friend class Y { A::B my; // OK: A::B accessible to nested member of friend }; };—end example]
Change the last sentence of 13.4 [temp.arg] paragraph 3 as indicated:
For a template-argument
ofthat is a class type or a class template, the template definition has no special access rights to theinaccessiblemembers of thetemplate argument type.template-argument. [Example:template <template <class TT> class T> class A { typename T<int>::S s; }; template <class U> class B { private: struct S { /* ... */ }; }; A<B> b; // ill-formed, A has no access to B::S—end example]
Change 11.4.12 [class.nest] paragraph 4 as indicated:
Like a member function, a friend function (11.8.4 [class.friend]) defined within a nested class is in the lexical scope of that class; it obeys the same rules for name binding as a static member function of that class (11.4.9 [class.static]), but itandhas no special access rights to members of an enclosing class.
(Note: this resolution also resolves issues 494 and 500.)
[Moved to DR at 4/01 meeting.]
Section 13.4.2 [temp.arg.type] paragraph 2 says
A local type, a type with no linkage, an unnamed type or a type compounded from any of these types shall not be used as a template-argument for a template type-parameter.
It probably wasn't intended that classes with unnamed members should be included in this list, but they are arguably compounded from unnamed types.
Proposed resolution (04/01):
In 13.4.2 [temp.arg.type] paragraph 2, change
A local type, a type with no linkage, an unnamed type or a type compounded from any of these types shall not be used as a template-argument for a template type-parameter.
to
The following types shall not be used as a template-argument for a template type-parameter:
- a type whose name has no linkage
- an unnamed class or enumeration type that has no name for linkage purposes (9.2.4 [dcl.typedef])
- a cv-qualified version of one of the types in this list
- a type created by application of declarator operators to one of the types in this list
- a function type that uses one of the types in this list
[Voted into WP at October 2005 meeting.]
The standard does not permit a null value to be used as a nontype template argument for a nontype template parameter that is a pointer.
This code is accepted by EDG, Microsoft, Borland and Cfront, but rejected by g++ and Sun:
template <int *p> struct A {}; A<(int*)0> ai;
I'm not sure this was ever explicitly considered by the committee. Is there any reason to permit this kind of usage?
Jason Merrill: I suppose it might be useful for a program to be able to express a degenerate case using a null template argument. I think allowing it would be harmless.
Notes from October 2004 meeting:
CWG decided that it would be desirable to allow null pointers as nontype template arguments, even though they are not representable in some current ABIs. There was some discussion over whether to allow a bare 0 to be used with a pointer nontype template parameter. The following case was decisive:
template<int i> void foo(); template<int* i> void foo(); ... foo<0>();
The current wording of 13.4 [temp.arg] paragraph 7 disambiguates the function call in favor of the int version. If the null pointer conversion were allowed for pointer nontype template parameters, this case would become ambiguous, so it was decided to require a cast.
Proposed resolution (April, 2005):
In 13.4.3 [temp.arg.nontype] paragraph 1, insert the following after the third bullet:
Add the indicated text to the note in the second bullet of 13.4.3 [temp.arg.nontype] paragraph 5:
[Note: In particular, neither the null pointer conversion (7.3.12 [conv.ptr]) nor the derived-to-base conversion (7.3.12 [conv.ptr]) are applied. Although 0 is a valid template-argument for a non-type template-parameter of integral type, it is not a valid template-argument for a non-type template-parameter of pointer type. However, (int*)0 is a valid template-argument for a non-type template-parameter of type “pointer to int.” —end note]
Replace the normative wording of 13.6 [temp.type] paragraph 1 with the following:
Two template-ids refer to the same class or function if
- their template-names refer to the same template, and
- their corresponding type template-arguments are the same type, and
- their corresponding non-type template-arguments of integral or enumeration type have identical values, and
- their corresponding non-type template-arguments of pointer type refer to the same external object or function or are both the null pointer value, and
- their corresponding non-type template-arguments of pointer-to-member type refer to the same class member or are both the null member pointer value, and
- their corresponding non-type template-argumentss for template parameters of reference type refer to the same external object or function, and
- their corresponding template template-arguments refer to the same template.
[Voted into WP at April, 2007 meeting as part of paper N2258.]
One of the requirements for two template-ids to refer to the same class or function (13.6 [temp.type] paragraph 1) is that
If we have some template of the form
template <unsigned char c> struct A;
does this imply that A<'\001'> and A<257> (for an eight-bit char) refer to different specializations?
Jens Maurer: Looks like it should say something like, “their corresponding converted non-type template arguments of integral or enumeration type have identical values.”
Proposed resolution (April, 2007):
The change to 13.6 [temp.type] paragraph 1 shown in document J16/07-0118 = WG21 N2258, in which the syntactic non-terminal template-argument is changed to the English term “template argument” is sufficient to remove the confusion about whether the value before or after conversion is used in matching template-ids.
[Voted into the WP at the September, 2008 meeting.]
In order for two template-ids to refer to the same function, 13.6 [temp.type] paragraph 1, bullet 1 requires that
their template-names refer to the same template
This makes it impossible for two template-ids referring to operator function templates to be equivalent, because only simple-template-ids have a template-name, and a template-id referring to an operator function template is not a simple-template-id (13.3 [temp.names] paragraph 1) .
Suggested resolution:
Change 13.6 [temp.type] paragraph 1, bullet 1 to read,
their template-names or operator-function-ids refer to the same template
Proposed resolution (June, 2008):
Change 13.6 [temp.type] paragraph 1, first bullet, as follows:
their template-names or operator-function-ids refer to the same template, and
[Voted into WP at April, 2007 meeting.]
The wholesale replacement of the phrase “template function” by the resolution of issue 105 seems to have overlooked the similar phrase “template conversion function.” This phrase appears a number of times in 12.2.4.2.3 [over.ics.user] paragraph 3, 13.7.3 [temp.mem] paragraphs 5-8, and 13.10.3 [temp.deduct] paragraph 1. It should be systematically replaced in similar fashion to the resolution of issue 105.
Proposed resolution (October, 2006):
Change 12.2.4.2.3 [over.ics.user] paragraph 3 as follows:
If the user-defined conversion is specified by atemplate conversion functionspecialization of a conversion function template, the second standard conversion sequence must have exact match rank.
Change 13.7.3 [temp.mem] paragraph 5 as follows:
A specialization of atemplate conversion functionconversion function template is referenced in the same way as a non-template conversion function that converts to the same type.
Change 13.7.3 [temp.mem] paragraph 6 as follows:
A specialization of atemplate conversion functionconversion function template is not found by name lookup. Instead, anytemplate conversion functionsconversion function templates visible in the context of the use are considered.
Change 13.7.3 [temp.mem] paragraph 7 as follows:
Ausing-declarationusing-declaration in a derived class cannot refer to a specialization of atemplate conversion functionconversion function template in a base class.
Change 13.7.3 [temp.mem] paragraph 8 as follows:
Overload resolution (12.2.4.3 [over.ics.rank]) and partial ordering (13.7.7.3 [temp.func.order]) are used to select the best conversion function among multipletemplate conversion functionsspecializations of conversion function templates and/or non-template conversion functions.
Change 13.10.3.4 [temp.deduct.conv] paragraph 1 as follows:
Template argument deduction is done by comparing the return type of thetemplate conversion functionconversion function template (call it P) with the type that is required as the result of the conversion (call it A) as described in 13.10.3.6 [temp.deduct.type].
[Voted into WP at October 2003 meeting.]
13.7.5 [temp.friend] paragraph 5 says:
When a function is defined in a friend function declaration in a class template, the function is defined at each instantiation of the class template. The function is defined even if it is never used. The same restrictions on multiple declarations and definitions which apply to non-template function declarations and definitions also apply to these implicit definitions. [Note: if the function definition is ill-formed for a given specialization of the enclosing class template, the program is ill-formed even if the function is never used. ]
This means that the following program is invalid, even without the call of f(ai):
template <class T> struct A { friend void f(A a) { g(a); } }; int main() { A<int> ai; // f(ai); // Error if f(ai) is actually called }
The EDG front end issues an error on this case even if f(ai) is never called. Of the compilers I tried (g++, Sun, Microsoft, Borland) we are the only ones to issue such an error.
This issue came up because there is a library that either deliberately or accidentally makes use of friend functions that are not valid for certain instantiations.
The wording in the standard is the result of a deliberate decision made long ago, but given the fact that most implementations do otherwise it raises the issue of whether we did the right thing.
Upon further investigation, the current rule was adopted as the resolution to issue 6.47 in my series of template issue papers. At the time the issue was discussed (7/96) most compilers did evaluate such friends. So it seems that a number of compilers have changed their behavior since then.
Based on current practice, I think the standard should be changed to evaluate such friends only when used.
Proposed resolution (October 2002):
Change section 13.7.5 [temp.friend] paragraph 5 from:
When a function is defined in a friend function declaration in a class template, the function is defined at each instantiation of the class template. The function is defined even if it is never used. The same restrictions on multiple declarations and definitions which apply to non-template function declarations and definitions also apply to these implicit definitions. [Note: if the function definition is ill-formed for a given specialization of the enclosing class template, the program is ill-formed even if the function is never used. ]to:
When a function is defined in a friend function declaration in a class template, the function is instantiated when the function is used. The same restrictions on multiple declarations and definitions that apply to non-template function declarations and definitions also apply to these implicit definitions.Note the change from "which" to "that" in the last sentence.
[Voted into WP at October 2004 meeting.]
13.7.5 [temp.friend] paragraph 2 was overlooked when the changes for issue 166 were made.
The friend declaration of f<>(int) is now valid.
A friend function declaration that is not a template declaration and in which the name of the friend is an unqualified template-id shall refer to a specialization of a function template declared in the nearest enclosing namespace scope. [Example:namespace N { template <class T> void f(T); void g(int); namespace M { template <class T> void h(T); template <class T> void i(T); struct A { friend void f<>(int); // ill-formed - N::f friend void h<>(int); // OK - M::h friend void g(int); // OK - new decl of M::g template <class T> void i(T); friend void i<>(int); // ill-formed - A::i }; } }--end example]
Proposed Resolution (October 2003):
Remove 13.7.5 [temp.friend] paragraph 2:
A friend function declaration that is not a template declaration and in which the name of the friend is an unqualified template-id shall refer to a specialization of a function template declared in the nearest enclosing namespace scope. [Example:namespace N { template <class T> void f(T); void g(int); namespace M { template <class T> void h(T); template <class T> void i(T); struct A { friend void f<>(int); // ill-formed - N::f friend void h<>(int); // OK - M::h friend void g(int); // OK - new decl of M::g template <class T> void i(T); friend void i<>(int); // ill-formed - A::i }; } }--end example]
[Moved to DR at 4/02 meeting.]
The example in 13.7.6.1 [temp.spec.partial.general] paragraph 6 is incorrect. It reads,
template<class T> struct A { class C { template<class T2> struct B { }; }; }; // partial specialization of A<T>::C::B<T2> template<class T> template<class T2> struct A<T>::C::B<T2*> { }; A<short>::C::B<int*> absip; // uses partial specialization
Because C is a class rather than a struct, the use of the name B is inaccessible.
Proposed Resolution (10/01):
Change class C to struct C in the example in 13.7.6.1 [temp.spec.partial.general] paragraph 6. The example becomes
template<class T> struct A { struct C { template<class T2> struct B { }; }; }; // partial specialization of A<T>::C::B<T2> template<class T> template<class T2> struct A<T>::C::B<T2*> { }; A<short>::C::B<int*> absip; // uses partial specialization
[Voted into WP at the October, 2006 meeting.]
According to 13.7.6.1 [temp.spec.partial.general] paragraph 1,
If a template is partially specialized then that partial specialization shall be declared before the first use of that partial specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required.
This leaves the impression that an explicit instantiation of the primary template may precede the declaration of an applicable partial specialization. Is the following example well-formed?
template<typename T> class X{ public: void foo(){}; }; template class X<void *>; template<typename T> class X<T*>{ public: void baz(); }; void bar() { X<void *> x; x.foo(); }
Proposed resolution (October, 2005):
Replace the last sentence of 13.7.6.1 [temp.spec.partial.general] paragraph 1:
If a template is partially specialized then that partial specialization shall be declared before the first use of that partial specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required.
with:
A partial specialization shall be declared before the first use of a class template specialization that would make use of the partial specialization as the result of an implicit or explicit instantiation in every translation unit in which such a use occurs; no diagnostic is required.
[Voted into WP at October 2003 meeting.]
In 13.7.7.3 [temp.func.order], partial ordering is explained in terms of template argument deduction. However, the exact procedure for doing so is not specified. A number of details are missing, they are explained as sub-issues below.
template<class T> void g(T); // #1 template<class T> void g(T&); // #2Here, #2 is at least as specialized as #1: With a synthetic type U, #2 becomes g(U&); argument deduction against #1 succeeds with T=U&. However, #1 is not at least as specialized as #2: Deducing g(U) against g(T&) fails. Therefore, the second template is more specialized than the first, and the call g(x) is not ambiguous.
template<class S> void g(S); // #1 template<class T> void g(T const &); // #3Here, #3 is clearly at least as specialized as #1. To determine whether #1 is at least as specialized as #3, a unique type U is synthesized, and deduction of g<U>(U) is performed against #3. Following the rules in 13.10.3.2 [temp.deduct.call], deduction succeeds with T=U. Since the template argument is U, and the deduced template parameter is also U, we have an exact match between the template parameters. Even though the conversion from U to U const & is an exact match, it is not clear whether the added qualification should be taken into account, as it is in other places.
Issue 200 covers a related issue, illustrated by the following example:
template <class T> T f(int); template <class T, class U> T f(U); void g() { f<int>(1); }
Even though one template is "obviously" more specialized than the other, deduction fails in both directions because neither function parameter list allows template parameter T to be deduced.
(See also issue 250.)
Nathan Sidwell:
13.7.7.3 [temp.func.order] describes the partial ordering of function templates. Paragraph 5 states,
A template is more specialized than another if, and only if, it is at least as specialized as the other template and that template is not at least as specialized as the first.To paraphrase, given two templates A & B, if A's template parameters can be deduced by B, but B's cannot be deduced by A, then A is more specialized than B. Deduction is done as if for a function call. In particular, except for conversion operators, the return type is not involved in deduction. This leads to the following templates and use being unordered. (This example is culled from G++ bug report 4672 http://gcc.gnu.org/cgi-bin/gnatsweb.pl?cmd=view&pr=4672)
template <typename T, class U> T checked_cast(U from); //#1 template <typename T, class U> T checked_cast(U * from); //#2 class C {}; void foo (int *arg) { checked_cast <C const *> (arg); }In the call,
#1 can be deduced with T = 'C const *' and U = 'int *'
#2 can be deduced with T = 'C const *' and U = 'int'
It looks like #2 is more specialized that #1, but 13.7.7.3 [temp.func.order] does not make it so, as neither template can deduce 'T' from the other template's function parameters.
Possible Resolutions:
There are several possible solutions, however through experimentation I have discounted two of them.
Option 1:
When deducing function ordering, if the return type of one of the templates uses a template parameter, then return types should be used for deduction. This, unfortunately, makes existing well formed programs ill formed. For example
template <class T> class X {}; template <class T> X<T> Foo (T *); // #1 template <class T> int Foo (T const *); // #2 void Baz (int *p1, int const *p2) { int j = Foo (p2); //#3 }Here, neither #1 nor #2 can deduce the other, as the return types fail to match. Considering only the function parameters gives #2 more specialized than #1, and hence makes the call #3 well formed.
Option 2:
As option 1, but only consider the return type when deducing the template whose return type involves template parameters. This has the same flaw as option 1, and that example is similarly ill formed, as #1's return type 'X<T,0>' fails to match 'int' so #1 cannot deduce #2. In the converse direction, return types are not considered, but the function parameters fail to deduce.
Option 3:
It is observed that the original example is only callable with a template-id-expr to supply a value for the first, undeducible, parameter. If that parameter were deducible it would also appear within at least one of the function parameters. We can alter paragraph 4 of [temp.func.order] to indicate that it is not necessary to deduce the parameters which are provided explicitly, when the call has the form of a template-id-expr. This is a safe extension as it only serves to make ill formed programs well formed. It is also in line with the concept that deduction for function specialization order should proceed in a similar manner to function calling, in that explicitly provided parameter values are taken into consideration.
Suggested resolution:
Insert after the first sentence of paragraph 4 in 13.7.7.3 [temp.func.order]
Should any template parameters remain undeduced, and the function call be of the form of a template-id-expr, those template parameters provided in the template-id-expr may be arbitrarily synthesized prior to determining whether the deduced arguments generate a valid function type.
See also issue 200.
(April 2002) John Spicer and John Wiegley have written a paper on this. See 02-0051/N1393.
Proposed resolution (October 2002):
Change 13.7.7.3 [temp.func.order] paragraph 2 to read:
Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the function parameter types, or in the case of a conversion function the return type. The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process.
Change 13.7.7.3 [temp.func.order] paragraph 3 to read:
To produce the transformed template, for each type, non-type, or template template parameter synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template.
Change 13.7.7.3 [temp.func.order] paragraph 4 to read (note: the section reference should refer to the section added to 13.10.3 [temp.deduct] below):
Using the transformed function template's function parameter list, or in the case of a conversion function its transformed return type, perform type deduction against the function parameter list (or return type) of the other function. The mechanism for performing these deductions is given in 14.8.2.x.
Remove the text of 13.7.7.3 [temp.func.order] paragraph 5 but retain the example. The removed text is:
A template is more specialized than another if, and only if, it is at least as specialized as the other template and that template is not at least as specialized as the first.
Insert the following section before 14.8.2.5 (Note that this would either be a new 14.8.2.4, or would be given a number like 14.8.2.3a. If neither of these is possible from a troff point of view, this could be made 14.8.2.5. )
Deducing template arguments when determining the partial ordering of function templates (temp.deduct.partial)
Template argument deduction is done by comparing certain types associated with the two function templates being compared.
Two sets of types are used to determine the partial ordering. For each of the templates involved there is the original function type and the transformed function type. [Note: The creation of the transformed type is described in 13.7.7.3 [temp.func.order].] The deduction process uses the transformed type as the argument template and the original type of the other template as the parameter template. This process is done twice for each type involved in the partial ordering comparison: once using the transformed template-1 as the argument template and template-2 as the parameter template and again using the transformed template-2 as the argument template and template-1 as the parameter template.
The types used to determine the ordering depend on the context in which the partial ordering is
- In the context of a function call, the function parameter types are used.
- In the context of a call to a conversion operator, the return types of the conversion function templates are used.
- In other contexts (13.7.7.3 [temp.func.order]), the function template's function type is used.
Each type from the parameter template and the corresponding type from the argument template are used as the types of P and A.
Before the partial ordering is done, certain transformations are performed on the types used for partial ordering:
- If P is a reference type, P is replaced by the type referred to.
- If A is a reference type, A is replaced by the type referred to.
If both P and A were reference types (before being replaced with the type referred to above), determine which of the two types (if any) is more cv-qualified than the other; otherwise the types are considered to be equally cv-qualified for partial ordering purposes. The result of this determination will be used below.
Remove any top-level cv-qualifiers:
- If P is a cv-qualified type, P is replaced by the cv-unqualified version of P.
- If A is a cv-qualified type, A is replaced by the cv-unqualified version of A.
Using the resulting types P and A the deduction is then done as described in (13.10.3.6 [temp.deduct.type]). If deduction succeeds for a given type, the type from the argument template is considered to be at least as specialized as the type from the parameter template.
If, for a given type, deduction succeeds in both directions (i.e., the types are identical after the transformations above) if the type from the argument template is more cv-qualified than the type from the parameter template (as described above) that type is considered to be more specialized than the other. If neither type is more cv-qualified than the other then neither type is more specialized than the other.
If for each type being considered a given template is at least as specialized for all types and more specialized for some set of types and the other template is not more specialized for any types or is not at least as specialized for any types, then the given template is more specialized than the other template. Otherwise, neither template is more specialized than the other.
In most cases, all template parameters must have values in order for deduction to succeed, but for partial ordering purposes a template parameter may remain without a value provided it is not used in the types being used for partial ordering. [Note: A template parameter used in a non-deduced context is considered used.]
[Example:
template <class T> T f(int); // #1 template <class T, class U> T f(U); // #2 void g() { f<int>(1); // Calls #1 }--end example]
[Moved to DR at 4/02 meeting.]
Mike Miller: A question about typename came up in the discussion of issue 68 that is somewhat relevant to the idea of omitting typename in contexts where it is clear that a type is required: consider something like
template <class T> class X { friend class T::nested; };Is typename required here? If so, where would it go? (The grammar doesn't seem to allow it anywhere in an elaborated-type-specifier that has a class-key.)
Bill Gibbons: The class applies to the last identifier in the qualified name, since all the previous names must be classes or namespaces. Since the name is specified to be a class it does not need typename. [However,] it looks like 13.8 [temp.res] paragraph 3 requires typename and the following paragraphs do not exempt this case. This is not what we agreed on.
Proposed resolution (04/01):
In 13.8 [temp.res] paragraph 5, change
The keyword typename is not permitted in a base-specifier or in a mem-initializer; in these contexts a qualified-name that depends on a template-parameter (13.8.3 [temp.dep]) is implicitly assumed to be a type name.
to
A qualified name used as the name in a mem-initializer-id, a base-specifier, or an elaborated-type-specifier (in the class-key and enum forms) is implicitly assumed to name a type, without the use of the typename keyword. [Note: the typename keyword is not permitted by the syntax of these constructs.]
(The expected resolution for issue 254 will remove the typename forms from the grammar for elaborated-type-specifier. If that resolution is adopted, the parenthetical phrase "(in the class-key and enum forms)" in the preceding wording should be removed because those will be the only forms of elaborated-type-specifier.)
This has been consolidated with the edits for some other issues. See N1376=02-0034.
[Voted into WP at April 2003 meeting.]
The following example from 13.8 [temp.res] paragraph 4:
struct A { struct X { }; int X; }; template<class T> void f(T t) { typename T::X x; // ill-formed: finds the data member X // not the member type X }
is not ill-formed. The intent of the example is obvious, but some mention should be made that it is only ill-formed when T=A. For other T's, it could be well formed.
Proposed resolution (October 2002):
In 13.8 [temp.res] paragraph 4, replace the example with:
struct A { struct X { }; int X; } ; struct B { struct X { }; } ; template<class T> void f(T t) { typename T::X x; } void foo() { A a; B b; f(b); // OK -- T::X refers to B::X. f(a); // error: T::X refers to the data member A::X not // the struct A::X. }
[Voted into WP at October 2005 meeting.]
P. J. Plauger, among others, has noted that typename is hard to use, because in a given context it's either required or forbidden, and it's often hard to tell which. It would make life easier for programmers if typename could be allowed in places where it is not required, e.g., outside of templates.
Notes from the April 2003 meeting:
There was unanimity on relaxing this requirement on typename. The question was how much to relax it. Everyone agreed on allowing it on all qualified names, which is an easy fix (no syntax change required). But should it be allowed other places? P.J. Plauger said he'd like to see it allowed anywhere a type name is allowed, and that it could actually be a decades-late assist for the infamous "the ice is thin here" typedef problem noted in K&R I.
Proposed resolution (April 2003):
Replace the text at the start of 13.8 [temp.res] paragraph 3:
A qualified-id that refers to a type and in which the nested-name-specifier depends on a template-parameter (13.8.3 [temp.dep]) shall be prefixed by the keyword typename to indicate that the qualified-id denotes a type, forming an elaborated-type-specifier (9.2.9.5 [dcl.type.elab]).
With:
The keyword typename can only be applied to a qualified-id. A qualified-id that refers to a type and in which the nested-name-specifier depends on a template-parameter (13.8.3 [temp.dep]) shall be prefixed by the keyword typename to indicate that the qualified-id denotes a type, forming an elaborated-type-specifier (9.2.9.5 [dcl.type.elab]). If a qualified-id which has been prefixed by the keyword typename does not denote a type the program is ill-formed. [ Note: The keyword is only required on a qualified-id within a template declaration or definition in which the nested-name-specifier depends on a template-parameter. ]
Remove 13.8 [temp.res] paragraph 5:
The keyword typename shall only be used in template declarations and definitions, including in the return type of a function template or member function template, in the return type for the definition of a member function of a class template or of a class nested within a class template, and in the type-specifier for the definition of a static member of a class template or of a class nested within a class template. The keyword typename shall be applied only to qualified names, but those names need not be dependent. The keyword typename shall be used only in contexts in which dependent names can be used. This includes template declarations and definitions but excludes explicit specialization declarations and explicit instantiation declarations. The keyword typename is not permitted in a base-specifier or in a mem-initializer; in these contexts a qualified-id that depends on a template-parameter (temp.dep) is implicitly assumed to be a type name.
Note: the claim here that a qualified name preceded by typename forms an elaborated type specifier conflicts with the changes made in issue 254 (see N1376=02-0034), which introduces typename-specifier.
Notes from October 2003 meeting:
We considered whether typename should be allowed in more places, and decided we only wanted to allow it in qualified names (for now at least).
Core issue 254 changed elaborated-type-specifier to typename-specifier. It also changed 13.8 [temp.res] paragraph 5, which this proposed resolution deletes.
See also issue 468.
Proposed resolution (October, 2004):
Change 13.8 [temp.res] paragraph 3 as follows:
AWhen a qualified-idthat refers to a type andin which the nested-name-specifier depends on a template-parameter (13.8.3 [temp.dep]) is intended to refer to a type, it shall be prefixed by the keyword typenameto indicate that the qualified-id denotes a type, forming a typename-specifier. If the qualified-id in a typename-specifier does not denote a type, the program is ill-formed.
Change 13.8 [temp.res] paragraph 5 as follows:
The keyword typename shall only be used in template declarations and definitions, including in the return type of a function template or member function template, in the return type for the definition of a member function of a class template or of a class nested within a class template, and in the type-specifier for the definition of a static member of a class template or of a class nested within a class template. The keyword typename shall be applied only to qualified names, but those names need not be dependent. The keyword typename shall be used only in contexts in which dependent names can be used. This includes template declarations and definitions but excludes explicit specialization declarations and explicit instantiation declarations.A qualified name used as the name in a mem-initializer-id, a base-specifier, or an elaborated-type-specifier is implicitly assumed to name a type, without the use of the typename keyword. [Note: the typename keyword is not permitted by the syntax of these constructs. —end note]
[Voted into WP at October 2004 meeting.]
Paragraph 6 of 13.8 [temp.res] is obsolete as result of issue 224, and needs to be revised.
Within the definition of a class template or within the definition of a member of a class template, the keyword typename is not required when referring to the unqualified name of a previously declared member of the class template that declares a type. The keyword typename shall always be specified when the member is referred to using a qual- ified name, even if the qualifier is simply the class template name. [Example:template<class T> struct A { typedef int B; A::B b; // ill-formed: typename required before A::B void f(A<T>::B); // ill-formed: typename required before A<T>::B typename A::B g(); // OK };]
Proposed Resolution:
Change 13.8 [temp.res] paragraph 6 as follows
Within the definition of a class template or within the definition of a member of a class template, the keyword typename is not required when referring to the unqualified name of a previously declared member of the class template that declares a type.The keyword typename shall always be specified when the member is referred to using a qualified name, even if the qualifier is simply the class template name.[Example:template<class T> struct A { typedef int B; B b; // ok, no typename requiredA::B b; // ill-formed: typename required before A::B void f(A<T>::B); // ill-formed: typename required before A<T>::B typename A::B g(); // OK};The keyword typename is required whether the qualified name is A or A<T> because A or A<T> are synonyms within a class template with the parameter list <T>.]
[Voted into WP at April, 2006 meeting.]
Part of the resolution for issue 224 was the addition of the phrase “but does not refer to a member of the current instantiation” to 13.8 [temp.res] paragraph 3. When the resolution of issue 382 was added to the current working draft, however, that phrase was inadvertently removed. Equivalent phrasing should be restored.
Proposed resolution (April, 2006):
Replace the first sentence of 13.8 [temp.res] paragraph 3 with the following text:
When a qualified-id is intended to refer to a type that is not a member of the current instantiation (13.8.3.2 [temp.dep.type]) and its nested-name-specifier depends on a template-parameter (13.8.3 [temp.dep]), it shall be prefixed by the keyword typename, forming a typename-specifier.
[Voted into the WP at the June, 2008 meeting.]
13.8 [temp.res] paragraphs 2 and 4 read,
A name used in a template declaration or definition and that is dependent on a template-parameter is assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified by the keyword typename.
If a specialization of a template is instantiated for a set of template-arguments such that the qualified-id prefixed by typename does not denote a type, the specialization is ill-formed.
It is not clear whether this is intended to, or is sufficient to, render a specialization ill-formed if a dependent qualified-id that is not prefixed by typename actually does denote a type. For example,
int i; template <class T> void f() { T::x * i; // declaration or multiplication!? } struct Foo { typedef int x; }; struct Bar { static int const x = 5; }; int main() { f<Bar>(); // multiplication f<Foo>(); // declaration! }
I think that the specialization for Foo should be ill-formed.
Proposed resolution (February, 2008):
Add the following after 13.8 [temp.res] paragraph 5:
If, for a given set of template arguments, a specialization of a template is instantiated that refers to a qualified-id that denotes a type, and the nested-name-specifier of the qualified-id depends on a template parameter, the qualified-id shall either be prefixed by typename or shall be used in a context in which it implicitly names a type as described above. [Example:
template <class T> void f(int i) { T::x * i; // T::x must not be a type } struct Foo { typedef int x; }; struct Bar { static int const x = 5; }; int main() { f<Bar>(1); // OK f<Foo>(1); // error: Foo::x is a type }—end example]
[Voted into WP at the October, 2006 meeting.]
Implementations vary in their treatment of the following code:
struct A {
int foo_;
};
template <typename T> struct B: public A { };
template <typename T> struct C: B<T> {
int foo() {
return A::foo_; // #1
}
};
int f(C<int>* p) {
return p->foo();
}
According to one analysis, because the expression A::foo_ on line #1 is non-dependent, it must be analyzed in the definition context. It that context, it violates the restrictions of 11.4 [class.mem] paragraph 10 on how the name of a nonstatic data member of a class can be used and thus should be treated as an error.
On the other hand, the description of the transformation of an id-expression into a class member access expression (11.4.3 [class.mfct.non.static] paragraph 3) does not have any special treatment of templates; when C<int>::foo() is instantiated, the reference to A::foo_ turns out to be to a base class member and is thus transformed into (*this).A::foo_ and is thus not an error.
Proposed resolution (October, 2005):
Change 11.4.3 [class.mfct.non.static] paragraph 3 as indicated:
When an id-expression (_N4567_.5.1.1 [expr.prim.general]) that is not part of a class member access syntax (7.6.1.5 [expr.ref]) and not used to form a pointer to member (7.6.2.2 [expr.unary.op]) is used in the body of a non-static member function of class X or used in the mem-initializer for a constructor of class X, if name lookup (6.5.3 [basic.lookup.unqual]) resolves the name in the id-expression to a non-static non-type member ofclass X or of a base class of Xsome class C, the id-expression is transformed into a class member access expression (7.6.1.5 [expr.ref]) using (*this) (_N4868_.11.4.3.2 [class.this]) as the postfix-expression to the left of the . operator. [Note: If C is not X or a base class of X, the class member access expression is ill-formed. —end note]The member name then refers to the member of the object for which the function is called.Similarly during name lookup...
[Voted into WP at the October, 2006 meeting.]
The description of dependent function calls in 13.8.3 [temp.dep] paragraph 1 applies only to identifiers in postfix-notation function calls and to operator notation calls for operator functions:
In an expression of the form:
postfix-expression ( expression-listopt )
where the postfix-expression is an identifier, the identifier denotes a dependent name if and only if any of the expressions in the expression-list is a type-dependent expression (13.8.3.3 [temp.dep.expr]). If an operand of an operator is a type-dependent expression, the operator also denotes a dependent name.
It would appear from the related passage in 13.8.4.2 [temp.dep.candidate] paragraph 1 that the description of postfix-notation function calls should apply to all unqualified-ids that are not template-ids, including operator-function-ids, not just to identifiers:
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...
Proposed resolution (October, 2005):
Change 13.8.3 [temp.dep] paragraph 1 as indicated:
...In an expression of the form:
postfix-expression ( expression-listopt )
where the postfix-expression is an
identifierunqualified-id but not a template-id, theidentifierunqualified-id denotes a dependent name if and only if any of the expressions in the expression-list is a type-dependent expression (13.8.3.3 [temp.dep.expr])...
Change 13.8.4.2 [temp.dep.candidate] paragraph 1 as indicated:
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 (6.5.3 [basic.lookup.unqual], 6.5.4 [basic.lookup.argdep]) except that...
(See also issue 561.)
[Moved to DR at 10/01 meeting.]
The definition of when a type is dependent, given in 13.8.3.2 [temp.dep.type], is essentially syntactic: if the reference is a qualified-id and one of the class-names in the nested-name-specifier is dependent, the type is dependent. This approach leads to surprising results:
template <class T> class X { typedef int I; I a; // non-dependent typename X<T>::I b; // dependent typename X::I c; // dependent (X is equivalent to X<T>) };
Suggested resolution:
The decision on whether a name is dependent or non-dependent should be based on lookup, not on the form of the name: if the name can be looked up in the definition context and cannot be anything else as the result of specialization, the name should be non-dependent.
See papers J16/00-0028 = WG21 N1251 and J16/00-0056 = WG21 N1279.
Proposed resolution (10/00):
Replace section 13.8.3.2 [temp.dep.type] with the following:
In the definition of a class template, a nested class of a class template, a member of a class template, or a member of a nested class of a class template, a name refers to the current instantiation if it is
- the injected-class-name (Clause 11 [class]) of the class template or nested class,
- in the definition of a primary class template, the name of the class template followed by the template argument list of the primary template (as described below) enclosed in <>,
- in the definition of a nested class of a class template, the name of the nested class referenced as a member of the current instantiation, or
- in the definition of a partial specialization, the name of the class template followed by the template argument list of the partial specialization enclosed in <>.
The template argument list of a primary template is a template argument list in which the nth template argument has the value of the nth template parameter of the class template.
A template argument that is equivalent to a template parameter (i.e., has the same constant value or the same type as the template parameter) can be used in place of that template parameter in a reference to the current instantiation. In the case of a nontype template argument, the argument must have been given the value of the template parameter and not an expression involving the template parameter.
[Example:
template <class T> class A { A* p1; // A is the current instantiation A<T>* p2; // A<T> is the current instantiation A<T*> p3; // A<T*> is not the current instantiation ::A<T>* p4; // ::A<T> is the current instantiation class B { B* p1; // B is the current instantiation A<T>::B* p2; // A<T>::B is the current instantiation typename A<T*>::B* p3; // A<T*>::B is not the // current instantiation }; }; template <class T> class A<T*> { A<T*>* p1; // A<T*> is the current instantiation A<T>* p2; // A<T> is not the current instantiation }; template <class T1, class T2, int I> struct B { B<T1, T2, I>* b1; // refers to the current instantiation B<T2, T1, I>* b2; // not the current instantiation typedef T1 my_T1; static const int my_I = I; static const int my_I2 = I+0; static const int my_I3 = my_I; B<my_T1, T2, my_I>* b3; // refers to the current instantiation B<my_T1, T2, my_I2>* b4; // not the current instantiation B<my_T1, T2, my_I3>* b5; // refers to the current instantiation };—end example]
A name is a member of the current instantiation if it is
- An unqualified name that, when looked up, refers to a member of a class template. [Note: This can only occur when looking up a name in a scope enclosed by the definition of a class template.]
- A qualified-id in which the nested-name-specifier refers to the current instantiation.
[Example:
template <class T> class A { static const int i = 5; int n1[i]; // i refers to a member of the current instantiation int n2[A::i]; // A::i refers to a member of the current instantiation int n3[A<T>::i]; // A<T>::i refers to a member of the current instantiation int f(); }; template <class T> int A<T>::f() { return i; // i refers to a member of the current instantiation }—end example]
A name is a member of an unknown specialization if the name is a qualified-id in which the nested-name-specifier names a dependent type that is not the current instantiation.
A type is dependent if it is
- a template parameter,
- a member of an unknown specialization,
- a nested class that is a member of the current instantiation,
- a cv-qualified type where the cv-unqualified type is dependent,
- a compound type constructed from any dependent type,
- an array type constructed from any dependent type or whose size is specified by a constant expression that is value-dependent, or
- a template-id in which either the template name is a template parameter or any of the template arguments is a dependent type or an expression that is type-dependent or value-dependent.
[Note: Because typedefs to not introduce new types, but instead simply refer to other types, a name that refers to a typedef that is a member of the current instantiation is dependent only if the type referred to is dependent.]
In 13.8.3.3 [temp.dep.expr] paragraph 3, replace
- a nested-name-specifier that contains a class-name that names a dependent type.
with
- a nested-name-specifier or qualified-id that names a member of an unknown specialization.
In 13.8.3.3 [temp.dep.expr], add the following paragraph:
A class member access expression (7.6.1.5 [expr.ref]) is type-dependent if the type of the referenced member is dependent. [Note: In an expression of the form x.y or xp->y the type of the expression is usually the type of the member y of the class of x (or the class pointed to by xp). However, if x or xp refers to a dependent type that is not the current instantiation, the type of y is always dependent. If x or xp refers to a non-dependent type or refers to the current instantiation, the type of y is the type of the class member access expression.]
In 13.8 [temp.res] paragraph 3, replace
A qualified-name that refers to a type and that depends on a template-parameter (13.8.3 [temp.dep]) shall be prefixed by the keyword typename.
with
A qualified-id that refers to a type and that depends on a template-parameter (13.8.3 [temp.dep]) but does not refer to a member of the current instantiation shall be prefixed by the keyword typename.
Note: the wording for this paragraph was changed in TC1. The words shown here are the pre-TC1 words.
In 13.3 [temp.names] paragraph 4, replace
When the name of a member template specialization appears after . or -> in a postfix-expression, or after a nested-name-specifier in a qualified-id, and the postfix-expression or qualified-id explicitly depends on a template-parameter (13.8.3 [temp.dep]), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.
with
When the name of a member template specialization appears after . or -> in a postfix-expression, or after a nested-name-specifier in a qualified-id, and the postfix-expression or qualified-id explicitly depends on a template-parameter (13.8.3 [temp.dep]) but does not refer to a member of the current instantiation (13.8.3.2 [temp.dep.type]), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.
In 13.8.2 [temp.local] paragraph 2, remove the following text, which was added for issue 108. The updated definition of dependent name now addresses this case.
Within the scope of a class template, when the unqualified name of a nested class of the class template is referred to, it is equivalent to the name of the nested class qualified by the name of the enclosing class template. [Example:
template <class T> struct A { class B {}; // B is equivalent to A::B, which is equivalent to A<T>::B, // which is dependent. class C : B { }; };—end example]
[Voted into WP at October 2005 meeting.]
As far as I can tell, the standard doesn't say whether "offsetof(...)" is type-dependent. In the abstract, it shouldn't be -- an "offsetof" expression is always of type "size_t". But the standard doesn't say to what the definition of the macro is, so I don't think one can deduce that it will always be considered non-dependent by a conforming compiler.
John Spicer: (1) I agree that you can't know if offsetof is dependent because you don't know what it expands to. (2) In principle, offsetof should be like sizeof -- it is value-dependent if its argument is type-dependent.
Mark Mitchell: I think we should say that: (a) offsetof is not type-dependent, and (b) offsetof is value dependent iff the first argument is type-dependent
Everyone is using slightly different builtins to implement this functionality, and I don't think that there's any guarantee that they're all behaving the same here.
Notes from the March 2004 meeting:
Note that any such requirement would be in the library section, not core.
Proposed resolution (October, 2004):
At the end of 13.8.3.3 [temp.dep.expr] paragraph 4, add after the list that ends with throw assignment-expression:
[Note: For the standard library macro offsetof, see 17.2 [support.types]. —end note]
At the end of 13.8.3.4 [temp.dep.constexpr] paragraph 2, add after the list that ends with sizeof(type-id):
[Note: For the standard library macro offsetof, see 17.2 [support.types]. —end note]
In 17.2 [support.types] paragraph 4, replace
The macro offsetof accepts a restricted set of type arguments in this International Standard. If type is not a POD structure or a POD union the results are undefined. The result of applying the offsetof macro to a field that is a static data member or a function member is undefined.
with
The macro offsetof(type, member-designator) accepts a restricted set of type arguments in this International Standard. If type is not a POD structure or a POD union ( Clause 11 [class]), the results are undefined. The expression offsetof(type, member-designator) is never type-dependent (13.8.3.3 [temp.dep.expr]) and it is value-dependent (13.8.3.4 [temp.dep.constexpr]) if and only if type is dependent. The result of applying the offsetof macro to a field that is a static data member or a function member is undefined.
[Note: the original wording shown here reflects the resolutions of library issues 306 and 449.]
[Voted into WP at October 2005 meeting.]
The example in 13.8 [temp.res] paragraph 9 is incorrect, according to 13.8.4.2 [temp.dep.candidate] . The example reads,
void f(char); template <class T> void g(T t) { f(1); // f(char); f(T(1)); // dependent f(t); // dependent dd++; // not dependent // error: declaration for dd not found } void f(int); double dd; void h() { g(2); // will cause one call of f(char) followed // by two calls of f(int) g('a'); // will cause three calls of f(char) }Since 13.8.4.2 [temp.dep.candidate] says that only Koenig lookup is done from the instantiation context, and since 6.5.4 [basic.lookup.argdep] says that fundamental types have no associated namespaces, either the example is incorrect (and f(int) will never be called) or the specification in 13.8.4.2 [temp.dep.candidate] is incorrect.
Notes from 04/00 meeting:
The core working group agreed that the example as written is incorrect and should be reformulated to use a class type instead of a fundamental type. It was also decided to open a new issue dealing more generally with Koenig lookup and fundamental types.
(See also issues 213 and 225.)
Proposed resolution (April, 2005):
Change the example in 13.8 [temp.res] paragraph 9 as follows:
void f(char); template <class T> void g(T t) { f(1); // f(char); f(T(1)); // dependent f(t); // dependent dd++; // not dependent // error: declaration for dd not found } enum E { e }; void f(intE); double dd; void h() { g(2e); // will cause one call of f(char) followed // by two calls of f(intE) g('a'); // will cause three calls of f(char) }
[Moved to DR at 4/02 meeting.]
According to 13.9 [temp.spec] paragraph 5,
No program shall explicitly instantiate any template more than once, both explicitly instantiate and explicitly specialize a template, or specialize a template more than once for a given set of template-arguments.
This rule has an impact on library issue 120. Library authors would like to have the freedom to specialize (or not) various library functions without having to document their choices, while users need the flexibility to explicitly instantiate library functions in certain translation units.
If this rule could be slightly weakened, it would reduce the need for constraining either the library author or the programmer. For instance, the rule might be recast to say that if a specialization is followed by an explicit instantiation in the same translation unit, the explicit instantiation is ignored. A specialization and an explicit instantiation of the same template in two different translation units would still be an error, no diagnostic required.
Proposed resolution (04/01):
Replace the first sentence of 13.9 [temp.spec] paragraph 5,
No program shall explicitly instantiate any template more than once, both explicitly instantiate and explicitly specialize a template, or specialize a template more than once for a given set of template-arguments.
by
For a given template and a given set of template-arguments,
- an explicit instantiation shall appear at most once in a program,
- an explicit specialization shall be defined at most once according to 6.3 [basic.def.odr] in a program, and
- both an explicit instantiation and a declaration of an explicit specialization shall not appear in a program unless the explicit instantiation follows a declaration of the explicit specialization.
Replace 13.9.3 [temp.explicit] paragraph 4,
The definition of a non-exported function template, a non-exported member function template, or a non-exported member function or static data member of a class template shall be present in every translation unit in which it is explicitly instantiated.
by
For a given set of template parameters, if an explicit instantiation of a template appears after a declaration of an explicit specialization for that template, the explicit instantiation has no effect. Otherwise, the definition of a non-exported function template, a non-exported member function template, or a non-exported member function or static data member of a class template shall be present in every translation unit in which it is explicitly instantiated.
[Moved to DR at October 2002 meeting.]
A template is implicitly instantiated because of a "pointer conversion" on an argument. This was intended to include related-class conversions, but it also inadvertently includes conversions to void*, null pointer conversions, cv-qualification conversions and the identity conversion.
It is not clear whether a reinterpret_cast of a pointer should cause implicit instantiation.
Proposed resolution (10/01): Replace 13.9.2 [temp.inst] paragraph 4, up to the example, with the following:
A class template specialization is implicitly instantiated if the class type is used in a context that requires a completely-defined object type or if the completeness of the class type might affect the semantics of the program. [Note: in particular, if the semantics of an expression depend on the member or base class lists of a class template specialization, the class template specialization is implicitly generated. For instance, deleting a pointer to class type depends on whether or not the class declares a destructor, and conversion between pointer to class types depends on the inheritance relationship between the two classes involved. ]
This version differs from the previous version is its use of the word "might" in the first sentence.
(See also issue 212.)
[Voted into WP at April, 2006 meeting.]
The example in 13.9.2 [temp.inst] paragraph 4 has a typographical error: the third parameter of function g should be D<double>* ppp, but it is missing the *:
template <class T> class B { /* ... */ }; template <class T> class D : public B<T> { /* ... */ }; void f(void*); void f(B<int >*); void g(D<int>* p, D<char>* pp, D<double> ppp) { f(p); // instantiation of D<int> required: call f(B<int>*) B<char>* q = pp; // instantiation of D<char> required: // convert D<char>* to B<char>* delete ppp; // instantiation of D<double> required }
Proposed resolution (October, 2005):
As suggested.
[Voted into WP at October 2005 meeting.]
In 13.9.3 [temp.explicit] paragraph 7 we read:
The explicit instantiation of a class template specialization implies the instantiation of all of its members not previously explicitly specialized in the translation unit containing the explicit instantiation.
Is "member" intended to mean "non-inherited member?" If yes, maybe it should be clarified since 11.7 [class.derived] paragraph 1 says,
Unless redefined in the derived class, members of a base class are also considered to be members of the derived class.
Proposed resolution (October, 2004):
Fixed by the resolution of issue 470.
[Voted into WP at October 2005 meeting.]
13.9.3 [temp.explicit] paragraph 7 says,
The explicit instantiation of a class template specialization implies the instantiation of all of its members not previously explicitly specialized in the translation unit containing the explicit instantiation.
It's not clear whether this “implied” instantiation is implicit or explicit instantiation. It makes a difference in cases like the following:
template <typename T> struct foo { struct bar { }; }; template struct foo<int>; // #1 template struct foo<int>::bar; // #2
If the instantiation of foo<int>::bar implied by #1 is implicit, the explicit instantiation in #2 is well-formed. Otherwise, #2 violates the requirement in 13.9 [temp.spec] that
No program shall explicitly instantiate any template more than once ... for a given set of template-arguments.
It's also unclear whether the implied instantiation applies only to direct members of the class template or to inherited members, as well.
John Spicer: I have always interpreted this as meaning only the members declared in the class, not those inherited from other classes. This is what EDG does, and appears to be what g++, Microsoft and Sun do, too. I also think this is the correct thing for the Standard to require. If I were to derive a class from a class in the standard library, an explicit instantiation of my class should not cause the explicit instantiation of things in the standard library (because the library might provide such explicit instantiations, thus causing my program to run afoul of the "can't instantiate more than once" rule).
Proposed resolution (October, 2004):
Change 13.9.3 [temp.explicit] paragraph 7 as follows:
The explicit instantiation of a class template specializationimplies the instantiation of allalso explicitly instantiates each of its membersnot(not including members inherited from base classes) whose definition is visible at the point of instantiation and that has not been previously explicitly specialized in the translation unit containing the explicit instantiation.
[Voted into the WP at the April, 2007 meeting as part of paper J16/07-0095 = WG21 N2235.]
The Standard does not definitively say when the inline specifier may be used in an explicit instantiation. For example, the following would seem to be innocuous, as the function being instantiated is already inline:
template <typename T> struct S { void f() { } }; template inline void S<int>::f();
However, presumably one would want to prohibit something like:
template <typename T> void f(T) { } template inline void f(int);
9.2.3 [dcl.fct.spec] paragraph 4 (after application of the resolution of issue 317) comes close to covering the obvious problematic cases:
If the definition of a function appears in a translation unit before its first declaration as inline, the program is ill-formed. If a function with external linkage is declared inline in one translation unit, it shall be declared inline in all translation units in which it appears; no diagnostic is required.
This would seem to prohibit the latter case, but apparently would not handle an exported template that was instantiated as inline (because the definition might not appear in the same translation unit as the inline instantiation). It would be better to make a clear statement regarding the use of inline in explicit instantiations.
Notes from the April, 2006 meeting:
The CWG favored completely disallowing the inline keyword in explicit instantiation directives.
[Moved to DR at 4/01 meeting.]
Some compilers reject the following:
struct A { template <int I> void f(); template <> void f<0>(); };on the basis of 13.9.4 [temp.expl.spec] paragraph 2:
An explicit specialization shall be declared in the namespace of which the template is a member, or, for member templates, in the namespace of which the enclosing class or enclosing class template is a member. An explicit specialization of a member function, member class or static data member of a class template shall be declared in the namespace of which the class template is a member. ...claiming that the specialization above is not "in the namespace of which the enclosing class ... is a member". Elsewhere, declarations are sometimes required to be "at" or "in" "namespace scope", which is not what it says here. Paragraph 17 says:
A member or a member template may be nested within many enclosing class templates. If the declaration of an explicit specialization for such a member appears in namespace scope, the member declaration shall be preceded by a template<> for each enclosing class template that is explicitly specialized.The qualification "if the declaration ... appears in namespace scope", implies that it might appear elsewhere. The only other place I can think of for a member specialization is in class scope.
Was it the intent of the committee to forbid the construction above? (Note that A itself is not a template.) If so, why?
Proposed resolution (04/01): In-class specializations of member templates are not allowed. In 13.9.4 [temp.expl.spec] paragraph 17, replace
If the declaration of an explicit specialization for such a member appears in namespace scope...with
In an explicit specialization for such a member...
Notes from 04/00 meeting:
This issue was kept in "review" status for two major reasons:
Notes from 10/00 meeting:
The core working group felt that the value of additional clarity here outweighs the potential disadvantages that were noted at the preceding meeting.
[Moved to DR at 4/02 meeting.]
Consider this example:
namespace N { template <class T> void f(T){} template <class T> void g(T){} template <> void f(int); template <> void f(char); template <> void g(char); } using namespace N; namespace M { template <> void N::f(char){} // prohibited by standard template <class T> void g(T){} template <> void g(char){} // specialization of M::g or ambiguous? template void f(long); // instantiation of N::f? } template <class T> void g(T){} template <> void N::f(char){} // okay template <> void f(int){} // is this a valid specialization of N::f? template void g(int); // instantiation of ::g(int) or ambiguous?
The question here is whether unqualified names made visible by a using-directive can be used as the declarator in an explicit instantiation or explicit specialization.
Note that this question is already answered for qualified names in 9.3.4 [dcl.meaning] paragraph 1. In a qualified name such as N::f, f must be a member of class or namespace N, not a name made visible in N by a using-directive (or a using-declaration, for that matter).
The standard does not, as far as I can tell, specify the behavior of these cases one way or another.
My opinion is that names from using-directives should not be considered when looking up the name in an unqualified declarator in an explicit specialization or explicit instantiation. In such cases, it is reasonable to insist that the programmer know exactly which template is being specialized or instantiated, and that a qualified name must be used if the template is a member of a namespace.
As the example illustrates, allowing names from using-directives to be used would also have the affect of making ambiguous otherwise valid instantiation and specialization directives.
Furthermore, permitting names from using-directives would require an additional rule to prohibit the explicit instantiation of an entity in one namespace from being done in another (non-enclosing) namespace (as in the instantiation of f in namespace M in the example).
Mike Miller: I believe the explicit specialization case is already covered by _N4868_.9.8.2.3 [namespace.memdef] paragraph 2, which requires using a qualified name to define a namespace member outside its namespace.
John Spicer: _N4868_.9.8.2.3 [namespace.memdef] deals with namespace members. An explicit specialization directive deals with something that is a specialization of a namespace member. I don't think the rules in _N4868_.9.8.2.3 [namespace.memdef] could be taken to apply to specializations unless the standard said so explicitly.
Proposed resolution (suggested 04/01, proposed 10/01):
(The first change below will need to be revised in accordance with the resolution of issue 284 to add a cross-reference to the text dealing with class names.)
Add in 13.9.3 [temp.explicit] paragraph 2 before the example:
An explicit instantiation shall appear in an enclosing namespace of its template. If the name declared in the explicit instantiation is an unqualified name, the explicit instantiation shall appear in the namespace where its template is declared. [Note: Regarding qualified names in declarators, see 9.3.4 [dcl.meaning].]
Change the first sentence of _N4868_.9.8.2.3 [namespace.memdef] paragraph 1 from
Members of a namespace can be defined within that namespace.
to
Members (including explicit specializations of templates (13.9.4 [temp.expl.spec])) of a namespace can be defined within that namespace.
Change the first sentence of _N4868_.9.8.2.3 [namespace.memdef] paragraph 2 from
Members of a named namespace can also be defined...
to
Members (including explicit specializations of templates (13.9.4 [temp.expl.spec])) of a named namespace can also be defined...
Change the last sentence of 13.9.4 [temp.expl.spec] paragraph 2 from
If the declaration is not a definition, the specialization may be defined later in the namespace in which the explicit specialization was declared, or in a namespace that encloses the one in which the explicit specialization was declared.
to
If the declaration is not a definition, the specialization may be defined later (_N4868_.9.8.2.3 [namespace.memdef]).
[Voted into WP at April 2003 meeting.]
The examples corrected by issue 24 are still wrong in one case.
In item #4 (a correction to the example in paragraph 18), the proposed resolution is:
template<class T1> class A { template<class T2> class B { template<class T3> void mf1(T3); void mf2(); }; }; template<> template<class X> class A<int>::B { }; template<> template<> template<class T> void A<int>::B<double>::mf1(T t) { } template<class Y> template<> void A<Y>::B<double>::mf2() { } // ill-formed; B<double> is specialized but // its enclosing class template A is not
The explicit specialization of member A<int>::B<double>::mf1 is ill-formed. The class template A<int>::B is explicitly specialized and contains no members, so any implicit specialization (such as A<int>::B<double>) would also contain no members.
Proposed Resolution (4/02):
Fix the example in 13.9.4 [temp.expl.spec] paragraph 18 to read:
template<class T1> class A { template<class T2> class B { template<class T3> void mf1(T3); void mf2(); }; }; template<> template<class X> class A<int>::B { template<class T> void mf1(T); }; template<> template<> template<class T> void A<int>::B<double>::mf1(T t) { } template<class Y> template<> void A<Y>::B<double>::mf2() { } // ill-formed; B<double> is specialized but // its enclosing class template A is not
[Voted into WP at April 2003 meeting.]
In 13.10.3 [temp.deduct], attempting to create an array of abstract class type should be included in the list of things that cause type deduction to fail.
Proposed Resolution (4/02):
In 13.10.3 [temp.deduct] paragraph 2 amend the bullet item:
Attempting to create an array with an element type that is void, a function type, or a reference type, or attempting to create an array with a size that is zero or negative.
To the following:
Attempting to create an array with an element type that is void, a function type,ora reference type, or an abstract class type, or attempting to create an array with a size that is zero or negative.
[Voted into WP at October 2003 meeting.]
I understand the rules in 13.10.3 [temp.deduct] paragraph 2 are meant to be an exhaustive list of what can cause type deduction to fail.
Consider:
template<typename U,U u> struct wrap_t; template<typename U> static yes check( wrap_t<U,U(0)>* ); struct X { X(int); }; int main() { check<X>(0); }
I can see 2 reasons this might cause type deduction to fail:
Neither case is mentioned in 13.10.3 [temp.deduct] paragraph 2, nor do I see a DR mentioning these.
Proposed resolution (October 2002):
Add after the fourth-to-last bullet of 13.10.3 [temp.deduct] paragraph 2:
- Attempting to give an invalid type to a nontype template parameter. [Example:
template <class T, T> struct S {}; template <class T> int f(S<T, T()>*); struct X {}; int i0 = f<X>(0);]
[Voted into WP at March 2004 meeting.]
The following example (simplified from a posting to comp.lang.c++.moderated) is accepted by some compilers (e.g., EDG), but not by other (e.g., g++).
struct S { static int const I = 42; }; template<int N> struct X {}; template<typename T> void f(X<T::I>*) {} template<typename T> void f(X<T::J>*) {} int main() { f<S>(0); }
The wording in the standard that normally would cover this (third sub-bullet in 13.10.3 [temp.deduct] paragraph 2) says:
Attempting to use a type in the qualifier portion of a qualified name that names a type when that type does not contain the specified member, or if the specified member is not a type where a type is required.(emphasis mine). If the phrase "that names a type" applies to "a qualified name," then the example is invalid. If it applies to "the qualifier portion," then it is valid (because the second candidate is simply discarded).
I suspect we want this example to work. Either way, I believe the sub-bullet deserves clarification.
Notes from April 2003 meeting:
We agreed that the example should be valid. The phrase "that names a type" applies to "the qualifier portion."
Proposed resolution (October 2003):
In 13.10.3 [temp.deduct], paragraph 2, bullet 3, sub-bullet 3, replace
Attempting to use a type in the qualifier portion of a qualified name that names a type when that type does not contain the specified member, or if the specified member is not a type where a type is required.
With
Attempting to use a type in a nested-name-specifier of a qualified-id when that type does not contain the specified member, or
- the specified member is not a type where a type is required, or
- the specified member is not a template where a template is required, or
- the specified member is not a nontype where a nontype is required.
[Example:
Replace the example that follows the above text with
template <int I> struct X { }; template <template <class T> class> struct Z {}; template <class T> void f(typename T::Y*){} template <class T> void g(X<T::N>*){} template <class T> void h(Z<T::template TT>*){} struct A {}; struct B { int Y; }; struct C { typedef int N; }; struct D { typedef int TT; }; int main() { // Deduction fails in each of these cases: f<A>(0); // A does not contain a member Y f<B>(0); // The Y member of B is not a type g<C>(0); // The N member of C is not a nontype h<D>(0); // The TT member of D is not a template }]
[Voted into WP at April, 2006 meeting.]
According to 13.10.3 [temp.deduct] paragraph 2,
If a substitution in a template parameter or in the function type of the function template results in an invalid type, type deduction fails.
That would seem to apply to cases like the following:
template <class T> T f(T&){} void f(const int*){} int main() { int a[5]; f(a); }
Here, the return type of f is deduced as int[5], which is invalid according to 9.3.4.6 [dcl.fct] paragraph 6. The outcome of this example, then, should presumably be that type deduction fails and overload resolution selects the non-template function. However, the list of reasons in 13.10.3 [temp.deduct] for which type deduction can fail does not include function and array types as a function return type. Those cases should be added to the list.
Proposed resolution (October, 2005):
Change the last sub-bullet of 13.10.3 [temp.deduct] paragraph 2 as indicated:
Attempting to create a function type in which a parameter has a type of void, or in which the return type is a function type or array type.
[Voted into the WP at the June, 2008 meeting as paper N2657.]
It is not clear how to handle the following example:
struct S {
template <typename T> S(const T&);
};
void f(const S&);
void f(int);
void g() {
enum E { e };
f(e); // ill-formed?
}
Three possibilities suggest themselves:
Fail during overload resolution. In order to perform overload resolution for the call to f, the declaration of the required specialization of the S constructor must be instantiated. This instantiation uses a local type and is thus ill-formed (13.4.2 [temp.arg.type] paragraph 2) , rendering the example as a whole ill-formed, as well.
Treat this as a type-deduction failure. Although it is not listed currently among the causes of type-deduction failure in 13.10.3 [temp.deduct] paragraph 2, it could plausibly be argued that instantiating a function declaration with a local type as a template type-parameter falls under the rubric of “If a substitution in a template parameter or in the function type of the function template results in an invalid type” and thus should be a type-deduction failure. The result would be that the example is well-formed because f(const S&) would be removed from the list of viable functions.
Fail only if the function selected by overload resolution requires instantiation with a local type. This approach would require that the diagnostic resulting from the instantiation of the function type during overload resolution be suppressed and either regenerated or regurgitated once overload resolution is complete. (The example would be well-formed under this approach because f(int) would be selected as the best match.)
(See also issue 489.)
Notes from the April, 2005 meeting:
The question in the original example was whether there should be an error, even though the uninstantiable template was not needed for calling the best-matching function. The broader issue is whether a user would prefer to get an error or to call a “worse” non-template function in such cases. For example:
template<typename T> void f(T); void f(int); void g() { enum E { e }; f(e); // call f(int) or get an error? }
It was observed that the type deduction rules are intended to model, albeit selectively, the other rules of the language. This would argue in favor of the second approach, a type-deduction failure, and the consensus of the group was that the incremental benefit of other approaches was not enough to outweigh the additional complexity of specification and implementation.
Proposed resolution (October, 2005):
Add a new sub-bullet following bullet 3, sub-bullet 7 ("Attempting to give an invalid type to a non-type template parameter") of 13.10.3 [temp.deduct] paragraph 2:
Attempting to use a local or unnamed type as the value of a template type parameter.
Additional note (December, 2005):
The Evolution Working Group is currently considering an extension that would effectively give linkage to some (but perhaps not all) types that currently have no linkage. If the proposed resolution above is adopted and then later a change along the lines that the EWG is considering were also adopted, the result would be a silent change in the result of overload resolution, because the newly-acceptable specializations would become part of the overload set. It is not clear whether that possibility is sufficient reason to delay adoption of this resolution or not.
Notes from the April, 2007 meeting:
The Evolution Working Group is now actively pursuing an extension that would allow local and/or nameless types to be used as template arguments, so this resolution will be held in abeyance until the outcome of that proposal is known.
Notes from the June, 2008 meeting:
Paper N2657, adopted at the Sophia Antipolis (June, 2008) meeting, removed the restriction against local and unnamed types as template parameters. The example is now well-formed.
[Voted into WP at March 2004 meeting.]
The current definition of the C++ language speaks about nondeduced contexts only in terms of deducing template arguments from a type 13.10.3.6 [temp.deduct.type] paragraph 4. Those cases, however, don't seem to be the only ones when template argument deduction is not possible. The example below illustrates that:
namespace A { enum ae { }; template<class R, class A> int foo(ae, R(*)(A)) { return 1; } } template<typename T> void tfoo(T) { } template<typename T> int tfoo(T) { return 1; } /*int tfoo(int) { return 1; }*/ int main() { A::ae a; foo(a, &tfoo); }
Here argument-dependent name lookup finds the function template 'A::foo' as a candidate function. None of the function template's function parameter types constitutes a nondeduced context as per 13.10.3.6 [temp.deduct.type] paragraph 4. And yet, quite clearly, argument deduction is not possible in this context. Furthermore it is not clear what a conforming implementation shall do when the definition of the non-template function '::tfoo' is uncommented.
Suggested resolution:
Add the following as a new paragraph immediately before paragraph 3 of 13.10.3.2 [temp.deduct.call]:
After the above transformations, in the event of P being a function type, a pointer to function type, or a pointer to member function type and the corresponding A designating a set of overloaded (member) functions with at least one of the (member) functions introduced by the use of a (member) function template name (12.3 [over.over]) or by the use of a conversion function template (13.10.3.4 [temp.deduct.conv]), the whole function call expression is considered to be a nondeduced context. [Example:
namespace A { enum ae { }; template<class R, class A> int foo(ae, R(*)(A)) { return 1; } } template<typename T> void tfoo(T) { } template<typename T> int tfoo(T) { return 1; } int tfoo(int) { return 1; } int main() { A::ae a; foo(a, &tfoo); // ill-formed, the call is a nondeduced context using A::foo; foo<void,int>(a, &tfoo); // well-formed, the address of the spe- // cialization 'void tfoo<int>(int)' is // the second argument of the call }
Notes from October 2002 meeting:
There was agreement that deduction should fail but it's still possible to get a result -- it's just not a "nondeduced context" in the sense of the standard.
The presence of a template in the overload set should not automatically disqualify the overload set.
Proposed Resolution (April 2003, revised October 2003):
In 13.10.3.6 [temp.deduct.type] paragraph 4 replace:
The nondeduced contexts are:with:
- The nested-name-specifier of a type that was specified using a qualified-id.
- A type that is a template-id in which one or more of the template-arguments is an expression that references a template-parameter.
The nondeduced contexts are:
- The nested-name-specifier of a type that was specified using a qualified-id.
- A non-type template argument or an array bound that is an expression that references a template-parameter.
- A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done.
- A function parameter for which argument deduction cannot be done because the associated function argument is a function, or a set of overloaded functions (12.3 [over.over]), and one or more of the following apply:
- more than one function matches the function parameter type (resulting in an ambiguous deduction), or
- no function matches the function parameter type, or
- the set of functions supplied as an argument contains one or more function templates.
In 13.10.3.2 [temp.deduct.call], add after paragraph 3:
When P is a function type, pointer to function type, or pointer to member function type:
- If the argument is an overload set containing one or more function templates, the parameter is treated as a nondeduced context.
- If the argument is an overload set (not containing function templates), trial argument deduction is attempted using each of the members of the set. If deduction succeeds for only one of the overload set members, that member is used as the argument value for deduction. If deduction succeeds for more than one member of the overload set the parameter is treated as a nondeduced context.
[Example:
// Only one function of an overload set matches the call so the function // parameter is a deduced context. template <class T> int f(T (*p)(T)); int g(int); int g(char); int i = f(g); // calls f(int (*)(int))--end example][Example:
// Ambiguous deduction causes the second function parameter to be a // nondeduced context. template <class T> int f(T, T (*p)(T)); int g(int); char g(char); int i = f(1, g); // calls f(int, int (*)(int))--end example][Example:
// The overload set contains a template, causing the second function // parameter to be a nondeduced context. template <class T> int f(T, T (*p)(T)); char g(char); template <class T> T g(T); int i = f(1, g); // calls f(int, int (*)(int))--end example]
In 13.10.3.6 [temp.deduct.type] paragraph 14, replace:
If, in the declaration of a function template with a non-type template-parameter, the non-type template-parameter is used in an expression in the function parameter-list, the corresponding template-argument must always be explicitly specified or deduced elsewhere because type deduction would otherwise always fail for such a template-argument.With:
If, in the declaration of a function template with a non-type template parameter, the non-type template parameter is used in an expression in the function parameter list, the expression is a nondeduced context.
Replace the example with:
[Example:template<int i> class A { /* ... */ }; template<int i> void g(A<i+1>); template<int i> void f(A<i>, A<i+1>); void k() { A<1> a1; A<2> a2; g(a1); //error: deduction fails for expression i+1 g<0>(a1); //OK f(a1, a2); // OK }--end example]
In 13.10.3.6 [temp.deduct.type] paragraph 16, replace:
A template-argument can be deduced from a pointer to function or pointer to member function argument if the set of overloaded functions does not contain function templates and at most one of a set of overloaded functions provides a unique match.
With:
A template-argument can be deduced from a function, pointer to function, or pointer to member function type.
[Voted into WP at the October, 2006 meeting.]
Consider the following example:
char* cmdline3_[1] = {}; template<class charT> void func(const charT* const argv[]) {} int main() { func(cmdline3_); }
In terms of the process described in 13.10.3.2 [temp.deduct.call], P is const charT* const * and A is char*[1]. According to the first bullet in paragraph 2, the type used in deduction is not A but “the pointer type produced by the array-to-pointer standard conversion.”
According to paragraph 4,
In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above). However, there are three cases that allow a difference:
In this example, the deduced A is not identical to the transformed A, because the deduced A has additional cv-qualification, so the three exceptions must be examined to see if they apply. The only one that might apply is the second bullet of paragraph 4:
- A can be another pointer or pointer to member type that can be converted to the deduced A via a qualification conversion (7.3.6 [conv.qual]).
However, A is not a pointer type but an array type; this provision does not apply and deduction fails.
It has been argued that the phrase “after the type A is transformed as described above” should be understood to apply to the A in the three bullets of paragraph 4. If that is the intent, the wording should be changed to make that explicit.
Proposed resolution (October, 2005):
Add the indicated words to 13.10.3.2 [temp.deduct.call] paragraph 4:
In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above). However, there are three cases that allow a difference:
If the original P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than the transformed A.
The transformed A can be another pointer or pointer to member type that can be converted to the deduced A via a qualification conversion (7.3.6 [conv.qual]).
If P is a class, and P has the form template-id, then the transformed A can be a derived class of the deduced A. Likewise, if P is a pointer to a class of the form template-id, the transformed A can be a pointer to a derived class pointed to by the deduced A.
[Voted into the WP at the September, 2008 meeting as part of paper N2757.]
There are a couple of minor problems with the rvalue reference wording in the WP. The non-normative note in 13.10.3.2 [temp.deduct.call] paragraph 3 says,
[Note: The effect of this rule for lvalue arguments and rvalue reference parameters is that deduction in such cases will fail unless the function parameter is of the form cv T&& (13.10.3.6 [temp.deduct.type]). —end note]
It turns out that this isn't correct. For example:
template <class T> void g(basic_string<T> && ); ... basic_string<char> s; g(s); // Note says that it should fail, we want it to call // g<char>(basic_string<char>&&)
Additionally, consider this case:
template <class T> void f(const T&&); ... int i; f(i);
If we deduce T as int& in this case then f(i) calls f<int&>(int&), which seems counterintuitive. We prefer that f<int>(const int&&) be called. Therefore, we would like the wording clarified that the A& deduction rule in 13.10.3.2 [temp.deduct.call] paragraph 3 applies only to the form T&& and not to cv T&& as the note currently implies.
These are minor tweaks to the rvalue reference wording and a fallout from issue 540. In particular, the major applications of move semantics and perfect forwarding are not impacted with respect to the original intentions of the rvalue reference work by these suggestions.
Suggested resolution:
Change 13.10.3.2 [temp.deduct.call] paragraph 3 as follows:
If P is
an rvalue reference typeof the form T&&, where T is a template parameter, and the argument is an lvalue,the type A& is used in place of A for type deductionT is deduced as A&. [Example: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)—end example]
[Note: The effect of this rule for lvalue arguments and rvalue reference parameters is that deduction in such cases will fail unless the function parameter is of the form cv T&& (13.10.3.6 [temp.deduct.type]). —end note]
Proposed resolution (August, 2008):
Change 13.10.3.2 [temp.deduct.call] paragraph 3 as follows:
If P is
an rvalue reference typeof the form T&&, where T is a template parameter, and the argument is an lvalue, the type A& is used in place of A for type deduction. [Example: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)—end example]
[Note: The effect of this rule for lvalue arguments and rvalue reference parameters is that deduction in such cases will fail unless the function parameter is of the form cv T&& (13.10.3.6 [temp.deduct.type]). —end note]
[Voted into WP at April 2003 meeting.]
Consider:
struct S { template <class T> operator T& (); }; int main () { S s; int i = static_cast<int&> (s); }13.10.3.4 [temp.deduct.conv] says that we strip the reference from int&, but doesn't say anything about T&. As a result, P (T&) and A (int) have incompatible forms and deduction fails.
Proposed Resolution (4/02):
Change the last chunk of 13.10.3.4 [temp.deduct.conv] paragraph 2 from
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.to
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.
[Voted into WP at October 2003 meeting.]
We ran into an issue concerning qualification conversions when doing template argument deduction for conversion functions.
The question is: What is the type of T in the conversion functions called by this example? Is T "int" or "const int"?
If T is "int", the conversion function in class A works and the one in class B fails (because the return expression cannot be converted to the return type of the function). If T is "const int", A fails and B works.
Because the qualification conversion is performed on the result of the conversion function, I see no benefit in deducing T as const int.
In addition, I think the code in class A is more likely to occur than the code in class B. If the author of the class was planning on returning a pointer to a const entity, I would expect the function to have been written with a const in the return type.
Consequently, I believe the correct result should be that T is int.
struct A { template <class T> operator T***() { int*** p = 0; return p; } }; struct B { template <class T> operator T***() { const int*** p = 0; return p; } }; int main() { A a; const int * const * const * p1 = a; B b; const int * const * const * p2 = b; }
We have just implemented this feature, and pending clarification by the committee, we deduce T as int. It appears that g++ and the Sun compiler deduce T as const int.
One way or the other, I think the standard should be clarified to specify how cases like this should be handled.
Notes from October 2002 meeting:
There was consensus on having the deduced type be "int" in the above.
Proposed resolution (April 2003):
Add to the end of 13.10.3.4 [temp.deduct.conv] (as a new paragraph following paragraph 3):
When the deduction process requires a qualification conversion for a pointer or pointer to member type as described above, the following process is used to determine the deduced template argument values:
If A is a type cv1,0 pointer to ... cv 1,n-1 pointer to cv1,n T1
and P is a type cv2,0 pointer to ... cv2,n-1 pointer to cv2,n T2
The cv-unqualified T1 and T2 are used as the types of A and P respectively for type deduction.
[Example:
struct A { template <class T> operator T***(); }; A a; const int * const * const * p1 = a; // T is deduced as int, not const int-- end example]
[Moved to DR at 4/01 meeting.]
Paragraph 4 lists contexts in which template formals are not deduced. Were template formals in an expression in the array bound of an array type specification intentionally left out of this list? Or was the intent that such formals always be explicitly specified? Otherwise I believe the following should be valid:
template <int I> class IntArr {}; template <int I, int J> void concat( int (&d)[I+J], const IntArr<I>& a, const IntArr<J>& b ) {} int testing() { IntArr<2> a; IntArr<3> b; int d[5]; concat( d, a, b ); }Can anybody shed some light on this?
From John Spicer:
Expressions involving nontype template parameters are nondeduced contexts, even though they are omitted from the list in 13.10.3.6 [temp.deduct.type] paragraph 4. See 13.10.3.6 [temp.deduct.type] paragraphs 12-14:
...
Proposed resolution (04/01): In 13.10.3.6 [temp.deduct.type] paragraph 4, add a third bullet:
[Moved to DR at October 2002 meeting.]
Paragraph 9 of 13.10.3.6 [temp.deduct.type] enumerates the forms that the types P and A need to have in order for template argument deduction to succeed.
For P denoting a pointer to function the paragraph lists the following forms as allowing for template argument deduction:
type(*)(T) T(*)() T(*)(T)
On the other hand, no provision has been made to accommodate similar cases for references to functions, which in light of the wording of 13.10.3.6 [temp.deduct.type] paragraph 11 means that the program below is ill-formed (some of the C++ compilers do not reject it however):
template<typename Arg, typename Result, typename T> Result foo_r(Result(& rf)(Arg), T x) { return rf(Arg(x)); } template<typename Arg, typename Result, typename T> Result foo_p(Result(* pf)(Arg), T x) { return pf(Arg(x)); } #include <iostream> int show_arg(char c) { std::cout << c << ' '; if(std::cout) return 0; return -1; } int main() { // The deduction int (& rf1)(int(&)(char), double) = foo_r; // shall fail here // While here int (& rf2)(int(*)(char), double) = foo_p; // it shall succeed return rf2(show_arg, 2); }
Proposed resolution (10/01, same as suggested resolution):
In the list of allowable forms for the types P and A in paragraph 9 of 13.10.3.6 [temp.deduct.type] replace
type(*)(T) T(*)() T(*)(T)
by
type(T) T() T(T)
[Voted into WP at the October, 2006 meeting.]
13.10.3.6 [temp.deduct.type] paragraph 5 reads:
The non-deduced contexts are:
The nested-name-specifier of a type that was specified using a qualified-id.
A non-type template argument or an array bound that is an expression that references a template parameter.
A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done.
A function parameter for which argument deduction cannot be done because the associated function argument is a function, or a set of overloaded functions (12.3 [over.over]), and one or more of the following apply:
more than one function matches the function parameter type (resulting in an ambiguous deduction), or
no function matches the function parameter type, or
the set of functions supplied as an argument contains one or more function templates.
An array bound that is an expression that references a template-parameter.
There are two problems with this list:
The last bullet is redundant with the second bullet. This appears to have been the result of applying the resolutions of issues 70 and 352 independently instead of in coordination.
The second bullet appears to be contradicted by the statement in paragraph 8 saying that an argument can be deduced if P and A have the forms type[i] and template-name<i>.
The intent of the wording in bullet 2 appears to have been that deduction cannot be done if the template parameter is a sub-expression of the template argument or array bound expression and that it can be done if it is the complete expression, but the current wording does not say that very clearly. (Similar wording also appears in 13.8.3.2 [temp.dep.type] paragraph 3 and 13.10.3.6 [temp.deduct.type] paragraph 14.)
Proposed resolution (October, 2005):
Change 13.10.3.6 [temp.deduct.type] paragraph 5 as indicated:
The non-deduced contexts are:
The nested-name-specifier of a type that was specified using a qualified-id.
A non-type template argument or an array bound
that is an expression thatin either of which a subexpression references a template parameter.A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done.
A function parameter for which argument deduction cannot be done because the associated function argument is a function, or a set of overloaded functions (12.3 [over.over]), and one or more of the following apply:
more than one function matches the function parameter type (resulting in an ambiguous deduction), or
no function matches the function parameter type, or
the set of functions supplied as an argument contains one or more function templates.
An array bound that is an expression that references a template-parameter.
Change 13.10.3.6 [temp.deduct.type] paragraph 14 as indicated:
If, in the declaration of a function template with a non-type template parameter, the non-type template parameter is used inan expressiona subexpression in the function parameter list, the expression is a non-deduced context as specified above...
Change 13.8.3.2 [temp.dep.type] paragraph 3 as indicated:
A template argument that is equivalent to a template parameter (i.e., has the same constant value or the same type as the template parameter) can be used in place of that template parameter in a reference to the current instantiation. In the case of a non-type template argument, the argument must have been given the value of the template parameter and not an expressioninvolvingthat contains the template parameter as a subexpression...
[Voted into WP at the October, 2006 meeting.]
Mike Miller: In fact, now that I've looked more closely, that appears not to be the case. (At least, it's not the error I get when I compile his example.) Here's a minimal extract (without the inflammatory using-directive :-) that illustrates what I think is going on:
template <typename _Iterator> struct iterator_traits { typedef typename _Iterator::difference_type difference_type; }; template <typename _InputIterator> inline typename iterator_traits<_InputIterator>::difference_type distance(_InputIterator, _InputIterator); double distance(const int&, const int&); void f() { int i = 0; int j = 0; double d = distance(i, j); }
What happens is that iterator_traits<int> is instantiated as part of type deduction for the function template distance, and the instantiation fails. (Note that it can't be instantiation of distance<int>, as I had originally posited, because in this case only a declaration, not a definition, of that template is in scope.)
John Spicer: Yes, I believe that is what is going on.
Mike Miller: I seem to recall that there was some discussion of questions related to this during the core meetings in Oxford. I think Steve Adamczyk said something to the effect that it's infeasible to suppress all instantiation errors during template type deduction and simply call any such errors a deduction failure. (I could be misremembering, and I could be misapplying that comment to this situation.)
John Spicer: Regardless of other conditions in which this may apply, I don't think it would be reasonable for compilers to have to do "speculative instantiations" during template argument deduction. One class instantiation could kick off a series of other instantiations, etc.
Mike Miller: I don't see anything in the Standard that tells me whether it's legitimate or not to report an error in this case. I hope John or another template expert can enlighten me on that.
John Spicer: My opinion is that, because this case is not among those enumerated that cause deduction failure (rather than being ill-formed) that reporting an error is the right thing to do.
Mike Miller: I am still interested, though, in the question of why 13.10.4 [temp.over] says that viable function template specializations are instantiated, even if they are not selected by overload resolution.
John Spicer: I believe the wording in 13.10.4 [temp.over] is incorrect. I researched this and found that a change was made during the clause 14 restructuring that was incorporated in March of 1996. The prior wording was "the deduced template arguments are used to generate a single template function". This was changed to "deduced template arguments are used to instantiate a single function template specialization". I believe this resulted from what was basically a global replace of "generate" with "instantiate" and of "template function" with "function template specialization". In this case, the substitution changed the meaning. This paragraph needs reworking.
Proposed resolution (April, 2006):
Change 13.10.4 [temp.over] paragraph 1 as indicated:
...For each function template, if the argument deduction and checking succeeds, the template-arguments (deduced and/or explicit) are used toinstantiatesynthesize the declaration of a single function template specialization which is added to the candidate functions set to be used in overload resolution. If, for a given function template, argument deduction fails, no such function is added to the set of candidate functions for that template. The complete set of candidate functions includes all thefunction templates instantiated in this waysynthesized declarations and all of the non-template overloaded functions of the same name. Thefunction template specializationssynthesized declarations are treated like any other functions in the remainder of overload resolution, except as explicitly noted in 12.2.4 [over.match.best].
[Moved to DR at 4/01 meeting.]
Paragraph 7 of 14.2 [except.throw] discusses which exception is thrown by a throw-expression with no operand.
May an expression which has been "finished" (paragraph 7) by an inner catch block be rethrown by an outer catch block?
catch(...) // Catch the original exception { try{ throw; } // rethrow it at an inner level // (in reality this is probably // inside a function) catch (...) { } // Here, an exception (the original object) // is "finished" according to 15.1p7 wording // 15.1p7 says that only an unfinished exception // may be rethrown. throw; // Can we throw it again anyway? It is // certainly still alive (15.1p4). }
I believe this is ok, since the paragraph says that the exception is finished when the "corresponding" catch clause exits. However since we have two clauses, and only one exception, it would seem that the one exception gets "finished" twice.
Proposed resolution (04/01):
In 14.2 [except.throw] paragraph 4, change
When the last handler being executed for the exception exits by any means other than throw; ...to
When the last remaining active handler for the exception exits by any means other than throw; ...
In 14.2 [except.throw] paragraph 6, change
A throw-expression with no operand rethrows the exception being handled.to
A throw-expression with no operand rethrows the currently handled exception (14.4 [except.handle]).
Delete 14.2 [except.throw] paragraph 7.
Add the following before 14.2 [except.throw] paragraph 6:
An exception is considered caught when a handler for that exception becomes active (14.4 [except.handle]). [Note: an exception can have active handlers and still be considered uncaught if it is rethrown.]
Change 14.4 [except.handle] paragraph 8 from
An exception is considered handled upon entry to a handler. [Note: the stack will have been unwound at that point.]to
A handler is considered active when initialization is complete for the formal parameter (if any) of the catch clause. [Note: the stack will have been unwound at that point.] Also, an implicit handler is considered active when std::terminate() or std::unexpected() is entered due to a throw. A handler is no longer considered active when the catch clause exits or when std::unexpected() exits after being entered due to a throw.
The exception with the most recently activated handler that is still active is called the currently handled exception.
In 14.4 [except.handle] paragraph 16, change "exception being handled" to "currently handled exception."
[Voted into WP at March 2004 meeting.]
14.2 [except.throw] paragraph 3 says that the type of a throw expression shall not be a pointer or reference to an incomplete type. But an expression never has reference type.
Proposed Resolution (October 2003):
Change the penultimate sentence of 14.2 [except.throw] paragraph 3 as follows:
The type of the throw-expression shall not be an incomplete type, or a pointeror referenceto an incomplete type other than (possibly cv-qualified) void, other than void*, const void*, volatile void*, or const volatile void*.
[Voted into WP at April, 2006 meeting.]
I have noticed a couple of confusing and overlapping passages dealing with copy elision. The first is 14.2 [except.throw] paragraph 5:
If the use of the temporary object can be eliminated without changing the meaning of the program except for the execution of constructors and destructors associated with the use of the temporary object (6.7.7 [class.temporary]), then the exception in the handler can be initialized directly with the argument of the throw expression.
The other is 14.4 [except.handle] paragraph 17:
If the use of a temporary object can be eliminated without changing the meaning of the program except for execution of constructors and destructors associated with the use of the temporary object, then the optional name can be bound directly to the temporary object specified in a throw-expression causing the handler to be executed.
I think these two passages are intended to describe the same optimization. However, as is often the case where something is described twice, there are significant differences. One is just different terminology — is “the exception in the handler” the same as “the object declared in the exception-declaration or, if the exception-declaration does not specify a name, a temporary object of that type” (14.4 [except.handle] paragraph 16)?
More significant, there is a difference in which kinds of throw-expressions are eligible for the optimization. In 14.2 [except.throw] paragraph 5, it appears that any object is a candidate, while in 14.4 [except.handle] paragraph 17 the thrown object must be a temporary (“the temporary object specified in a throw-expression”). For example, it's not clear looking at these two passages whether the copy of a local automatic can be elided. I.e., by analogy with the return value optimization described in 11.4.5.3 [class.copy.ctor] paragraph 15:
X x; return x; // copy may be elided X x; throw x; // unclear whether copy may be elided
Which brings up another point: 11.4.5.3 [class.copy.ctor] paragraph 15 purports to be an exhaustive list in which copy elision is permitted even if the constructor and/or destructor have side effects; however, these two passages describe another case that is not mentioned in 11.4.5.3 [class.copy.ctor] paragraph 15.
A final point of confusion: in the unoptimized abstract machine, there are actually two copies in throwing and handling an exception: the copy from the object being thrown to the exception object, and the copy from the exception object to the object or temporary in the exception-declaration. 14.2 [except.throw] paragraph 5 speaks only of eliminating the exception object, copying the thrown object directly into the exception-declaration object, while 14.4 [except.handle] paragraph 17 refers to directly binding the exception-declaration object to the thrown object (if it's a temporary). Shouldn't these be separated, with a throw of an automatic object or temporary being like the return value optimization and the initialization of the object/temporary in the exception-declaration being a separate optimizable step (which could, presumably, be combined to effectively alias the exception-declaration onto the thrown object)?
(See paper J16/04-0165 = WG21 N1725.)
Proposed resolution (April, 2005):
Add two items to the bulleted list in 11.4.5.3 [class.copy.ctor] paragraph 15 as follows:
This elision of copy operations is permitted in the following circumstances (which may be combined to eliminate multiple copies):
in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object with the same cv-unqualified type as the function return type, the copy operation can be omitted by constructing the automatic object directly into the function's return value
in a throw-expression, when the operand is the name of a non-volatile automatic object, the copy operation from the operand to the exception object (14.2 [except.throw]) can be omitted by constructing the automatic object directly into the exception object
when a temporary class object that has not been bound to a reference (6.7.7 [class.temporary]) would be copied to a class object with the same cv-unqualified type, the copy operation can be omitted by constructing the temporary object directly into the target of the omitted copy
when the exception-declaration of an exception handler (Clause 14 [except]) declares an object of the same type (except for cv-qualification) as the exception object (14.2 [except.throw]), the copy operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration
Change 14.2 [except.throw] paragraph 5 as follows:
If the use of the temporary object can be eliminated without changing the meaning of the program except for the execution of constructors and destructors associated with the use of the temporary object (6.7.7 [class.temporary]), then the exception in the handler can be initialized directly with the argument of the throw expression.When the thrown object is a class object,andthe copy constructorused to initialize the temporary copy is notand the destructor shall be accessible,the program is ill-formed (even when the temporary object could otherwise be eliminated)even if the copy operation is elided (11.4.5.3 [class.copy.ctor]).Similarly, if the destructor for that object is not accessible, the program is ill-formed (even when the temporary object could otherwise be eliminated).
Change 14.4 [except.handle] paragraph 17 as follows:
If the use of a temporary object can be eliminated without changing the meaning of the program except for execution of constructors and destructors associated with the use of the temporary object, then the optional name can be bound directly to the temporary object specified in a throw-expression causing the handler to be executed.The copy constructor and destructor associated with the object shall be accessible evenwhen the temporary object is eliminatedif the copy operation is elided (11.4.5.3 [class.copy.ctor]).
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
According to 14.3 [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 principal constructor (11.9.3 [class.base.init]) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object's destructor will be invoked. Should a constructor for an element of an automatic array throw an exception, only the constructed elements of that array will be destroyed.
The requirement for destruction of array elements explicitly applies only to automatic arrays, and one might conclude from the context that only automatic class objects are in view as well, although that is not explicitly stated. What about local static arrays and class objects? Are they intended also to be subject to the requirement that fully-constructed subobjects are to be destroyed?
Proposed resolution (October, 2006):
Change 14.3 [except.ctor] paragraph 2 as follows:
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 principal constructor (11.9.3 [class.base.init]) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object's destructor will be invoked.Should a constructor for an element of an automatic array throw an exception, only the constructed elements of that array will be destroyed.If the object or array was allocated in a new-expression, the matching deallocation function (6.7.6.5.3 [basic.stc.dynamic.deallocation], 7.6.2.8 [expr.new], 11.4.11 [class.free]), if any, is called to free the storage occupied by the object.
[Moved to DR at 4/01 meeting.]
In 14.5 [except.spec] paragraph 2:
An exception-specification shall appear only on a function declarator in a function, pointer, reference or pointer to member declaration or definition.Does that mean in the top-level function declarator, or one at any level? Can one, for example, specify an exception specification on a pointer-to-function parameter of a function?
void f(int (*pf)(float) throw(A))Suggested answer: no. The exception specifications are valid only on the top-level function declarators.
However, if exception specifications are made part of a function's type as has been tentatively agreed, they would have to be allowed on any function declaration.
There is already an example of an exception specification for a parameter in the example in 14.5 [except.spec] paragraph 1.
Proposed resolution (04/01): Change text in 14.5 [except.spec] paragraph 1 from:
An exception-specification shall appear only on a function declarator in a function, pointer, reference or pointer to member declaration or definition.to:
An exception-specification shall appear only on a function declarator for a function type, pointer to function type, reference to function type, or pointer to member function type that is the top-level type of a declaration or definition, or on such a type appearing as a parameter or return type in a function declarator.
(See also issues 25, 92, and 133.)
[Voted into WP at October 2004 meeting.]
In Clause 15 [cpp], paragraph 1, the control-line non-terminal symbol is defined in terms of the identifier-list non-terminal, which is never defined within the standard document.
The same definition is repeated in A.13 [gram.cpp].
I suggest that the following definition is added to Clause 15 [cpp], paragraph 1, after the one for replacement-list:
This should be repeated again in A.13 [gram.cpp], again after the one for replacement-list. It might also be desirable to include a third repetition in 15.7 [cpp.replace], paragraph 9.
Proposed Resolution (Clark Nelson, Dec 2003):
In Clause 15 [cpp], paragraph 1, immediately before the definition of replacement-list, add:
If the correct TROFF macros are used, the definition will appear automatically in appendix A. It doesn't need to be repeated in 16.3p9.
With respect to the question of having the preprocessor description be synchronized with C99, this would fall into the category of a justified difference. (Other justified differences include those for Boolean expressions, alternative tokens, and terminology differences.)
[Voted into WP at the October, 2006 meeting.]
The motivation for this issue is a desire to write portable programs which will work with any conforming implementation.
The C++ Standard (15.3 [cpp.include]) provides two forms of #include directives, with the <...> form being described (15.3 [cpp.include] paragraph 2) as "for a header", and the "..." form (15.3 [cpp.include] paragraph 3) as for "the source file" identified between the delimiters. When the standard uses the term "header", it often appears to be limiting the term to apply to the Standard Library headers only. Users of the standard almost always use the term "header" more broadly, to cover all #included source files, but particularly those containing interface declarations.
Headers, including source files, can be categorized according to their origin and usage:
Existing practice varies widely, but it is fairly easy to find users advocating:
Do any of the practices A, B, or C result in programs which can be rejected by a conforming implementation?
The first defect is that readers of the standard have not been able to reach consensus on the answers to the above question.
A second possible defect is that if A, B, or C can be rejected by a conforming implementation, then the standard should be changed because would mean there is a wide variance between the standard and existing practice.
Matt Austern: I really only see two positions:
I agree that the standard should clarify which of those two is the case (I imagine it'll hinge on finding one crucual sentence that either says "implementation defined" or "unspecified"), but from the standpoint of portability I don't see much difference between the two. I claim that, with either of those two interpretations, using #include <foo> is already nonportable.
(Of course, I claim that almost anything having to do with headers, including the #include "foo" form, is also nonportable. In practice there's wide variation in how compilers handle paths, especially relative paths.)
Beman Dawes: The whole issue can be resolved by replacing "header" with "header or source file" in 15.3 [cpp.include] paragraph 2. That will bring the standard into alignment with existing practice by both users and implementations. The "header and/or source file" wording is used at least three other places in the standard where otherwise there might be some confusion.
John Skaller: In light of Andrew Koenig's comments, this doesn't appear to be the case, since the mapping of include names to file names is implementation defined, and therefore source file inclusion cannot be made portable within the ISO C/C++ standards (since that provision obviously cannot be removed).
A possible idea is to create a binding standard, outside the C/C++ ISO Standards, which specifies not only the path lookup mechanism but also the translation from include names to file names. Clearly that is OS dependent, encoding dependent, etc, but there is no reason not to have a binding standard for Unix, Windows, etc, and specify these bindings in such a way that copying directories from one OS to the other can result in programs working on both OS's.
Andy Koenig: An easier solution might be to specify a (presumably unbounded, or bounded only by implementation capacity) collection of header-file names that every implementation must make it possible for programs to access somehow, without specifying exactly how.
Notes from October 2002 meeting:
This was discussed at some length. While there was widespread agreement that such inclusion is inherently implementation-dependent, we agreed to try to add wording that would make it clear that implementations are permitted (but not required) to allow inclusion of files using the <...> form of #include.
Proposed resolution (April, 2005):
Change 15.3 [cpp.include] paragraph 7 from:
[Example: The most common uses of #include preprocessing directives are as in the following:#include <stdio.h> #include "myprog.h"—end example]
to:
[Note: Although an implementation may provide a mechanism for making arbitrary source files available to the < > search, in general programmers should use the < > form for headers provided with the implementation, and the " " form for sources outside the control of the implementation. For instance:#include <stdio.h> #include <unistd.h> #include "usefullib.h" #include "myprog.h"—end note]
Notes from October, 2005 meeting:
Some doubt was expressed as to whether the benefit of this non-normative clarification outweighs the overall goal of synchronizing clause 16 with the corresponding text in the C99 Standard. As a result, this issue is being left in “review” status to allow further discussion.
Additional notes (October, 2006):
WG14 takes no position on this change.
[Moved to DR at 10/01 meeting.]
The main defect is in the library, where the binder template can easily lead to reference-to-reference situations.
Proposed resolution (04/01):
If a typedef TD names a type "reference to cv1 S," an attempt to create the type "reference to cv2 TD" creates the type "reference to cv12" S," where cv12 is the union of the cv-qualifiers cv1 and cv2. Redundant qualifiers are ignored. [Example:
int i; typedef int& RI; RI& r = i; // r has the type int& const RI& r = i; // r has the type const int&—end example]
If a template-argument for a template-parameter T names a type "reference to cv1 S," an attempt to create the type "reference to cv2 T" creates the type "reference to cv12 S," where cv12 is the union of the cv-qualifiers cv1 and cv2. Redundant cv-qualifiers are ignored. [Example:
template <class T> class X { f(const T&); /* ... */ }; X<int&> x; // X<int&>::f has the parameter type const int&—end example]
Attempting to createa reference to a reference type ora reference to void.
(See also paper J16/00-0022 = WG21 N1245.)
[Voted into WP at July, 2009 meeting.]
According to _N3225_.7.6.4 [dcl.attr.final] paragraph 2, overriding a virtual function with the [[final]] attribute renders a program ill-formed, but no diagnostic is required. This is easily diagnosable and a diagnostic should be required in this case.
Notes from the March, 2009 meeting:
This specification was a deliberate decision on the part of the EWG; the general rule was that it should be possible to ignore attributes without changing the meaning of a program. However, the consensus of the CWG was that violation of the [[final]] attribute should require a diagnostic.
Proposed resolution (March, 2009):
Change _N3225_.7.6.4 [dcl.attr.final] paragraph 2 as follows:
If a virtual member function f in some class B is marked final and in a class D derived from B a function D::f overrides B::f, the program is ill-formed; no diagnostic required.[Footnote: If an implementation does not emit a diagnostic it should execute the program as if final were not present. —end footnote]
[Voted into WP at March, 2010 meeting.]
According to _N3225_.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 _N3225_.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 _N3225_.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 };
[Voted into WP at March, 2010 meeting as document N3077.]
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.
Notes from the March, 2010 meeting:
The CWG decided not to deprecate trigraphs, acknowledging that there are communities in which they are viewed as necessary. Instead, it was decided to address what was considered to be the most pressing issue regarding trigraphs, that is, recognizing trigraph sequences inside raw string literals.
[Voted into WP at March, 2010 meeting as document N3049.]
The grammar for nested-name-specifier in _N4567_.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.
[Voted into WP at March, 2010 meeting.]
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 _N4567_.5.1.1 [expr.prim.general] paragraph 3,
The keyword this shall be used only inside a non-static class member function body (11.4.2 [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 _N4567_.5.1.1 [expr.prim.general] paragraph 2 as follows:
...The keyword this shall be used only inside the body of a non-static
classmember functionbody(11.4.2 [class.mfct]) of the nearest enclosing class or in a brace-or-equal-initializer for a non-static data member (11.4 [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]
[Voted into WP at October, 2009 meeting.]
The resolution of issue 613, as reflected in the sixth bullet of _N4567_.5.1.1 [expr.prim.general] paragraph 10, allows an id-expression designating a non-static data member to be used
- if... it is the sole constituent of an unevaluated operand, except for optional enclosing parentheses.
The requirement that the id-expression be the “sole constituent” of the unevaluated operand seems unnecessarily strict, forbidding such plausible use cases as
struct S { int ar[42]; }; int i = sizeof(S::ar[0]);
or the use of the member as a function argument in template metaprogramming. The more general version of the restriction seems not to be very difficult to implement and may actually represent a simplification in some implementations.
Proposed resolution (July, 2009):
Change _N4567_.5.1.1 [expr.prim.general] paragraph 10 as follows:
...
if that id-expression denotes a non-static data member
and it is the sole constituent of appears in an
unevaluated operand, except for optional enclosing
parentheses. [Example:
struct S { int m; }; int i = sizeof(S::m); // OK int j = sizeof(S::m + 42); //error: reference to non-static member in subexpressionOK
—end example]
[Voted into WP at October, 2009 meeting.]
The bullets in _N4885_6.7.5.5.4 [basic.stc.dynamic.safety] paragraph 2 do not appear to cover the following example:
int& i = *new int(5); // do something with i delete &i;
Should &i be a safely-derived pointer value?
Proposed resolution (September, 2009):
Change _N4885_6.7.5.5.4 [basic.stc.dynamic.safety] paragraph 2, bullet 2, as follows:
[Voted into WP at March, 2010 meeting.]
According to _N4885_.20.10.5 [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, _N4885_6.7.5.5.4 [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 (_N4885_.20.10.5 [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 _N4885_6.7.5.5.4 [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 ifIfa 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 (_N4885_.20.10.5 [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.
[Voted into WP at March, 2010 meeting.]
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 _N5001_.9.12.4 [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 (6.9.2 [intro.multithread]) each lvalue-to-rvalue conversion (7.3.2 [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.
[Voted into WP at March, 2010 meeting as document N3055.]
According to Clause 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 Clause 3 [intro.defs], “dynamic type,” as follows:
the type of the most derived object (6.7.2 [intro.object]) to whichthe lvalue denoted byan lvalue or an rvalue-reference (Clause 7 [expr]) expression refers. [Example: if a pointer (9.3.4.2 [dcl.ptr]) p whose static type is “pointer to class B” is pointing to an object of class D, derived from B (11.7 [class.derived]), the dynamic type of the expression *p is “D.” References (9.3.4.3 [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 7.6.1.8 [expr.typeid] paragraph 2.
(See also issues 846 and 863.)
Additional note, September, 2009:
Yet another complication is the statement in 7.2.1 [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.
[Voted into WP at March, 2010 meeting.]
There are several instances of undefined behavior in lexical processing:
5.2 [lex.phases] paragraph 1, phase 2: a universal-character-name resulting from a line splice.
5.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.
5.2 [lex.phases] paragraph 1, phase 4: a universal-character-name resulting from macro token concatenation.
5.6 [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 5.2 [lex.phases] paragraph 1 phase 2 as follows:
...If aA 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 undefinedshall be processed as if an additional new-line character were appended to the file.
Change 5.6 [lex.header] paragraph 2 as follows:
IfThe appearance of either of the characters ' or \,or of either of the character sequences /* or //appearsin a q-char-sequence oraan h-char-sequence is conditionally-supported with implementation-defined semantics,oras is the appearance of the character "appearsinaan h-char-sequence, the behavior is undefined. [Footnote: Thus, a sequencesof characters that resembles an escape sequences cause undefined behaviormight 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]
[Voted into WP at October, 2009 meeting.]
WG14 accepted DR 279 regarding the rule known colloquially as the L'x'=='x' rule. This change was made to C99 in TC2. The Austin Group subsequently opened DR 321 against TC2, observing that the change made in TC2 would invalidate existing conforming C code that relied on the L'x'=='x' rule.
DR 321 is now closed and will be included in the CD3 to C99. This change defines a new standard macro, which WG14 drafted as follows:
__STDC_MB_MIGHT_NEQ_WC__: The integer constant 1, intended to indicate that there might be some character x in the basic character set, such that 'x' need not be equal to L'x'.
WG14 requests that WG21 adopt this revision and this macro in C++0x.
Proposed resolution (July, 2009):
Add the following to 15.12 [cpp.predefined] paragraph 2:
- __STDC_MB_MIGHT_NEQ_WC__
- The integer constant 1, intended to indicate that, in the encoding for wchar_t, a member of the basic character set need not have a code value equal to its value when used as the lone character in an ordinary character literal.
[Voted into WP at March, 2010 meeting.]
According to 5.3.1 [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 5.3.1 [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 membersare implementation-defined, and any additional membersare locale-specific.
[Voted into WP at October, 2009 meeting.]
5.7 [lex.ppnumber] paragraph 2 says,
A preprocessing number does not have a type or a value; it acquires both after a successful conversion (as part of translation phase 7, 5.2 [lex.phases]) to an integral literal token or a floating literal token.
However, preprocessing directives are executed in phase 4, and the evaluation of constant-expressions in #if directives requires that preprocessing numbers have values.
Proposed resolution (July, 2009):
Change 5.7 [lex.ppnumber] paragraph 2 as follows:
A preprocessing number does not have a type or a value; it acquires both after a successful conversion(as part of translation phase 7 (5.2 [lex.phases]))to an integral literal token or a floating literal token.
[Voted into WP at October, 2009 meeting.]
According to 5.13.3 [lex.ccon] paragraph 2,
A character literal that begins with the letter L, such as L'x', is a wide-character literal. A wide-character literal has type wchar_t. The value of a wide-character literal containing a single c-char has value equal to the numerical value of the encoding of the c-char in the execution wide-character set.
A c-char that is a universal character name might, when translated to the execution character set, result in a multi-character sequence that is larger than can be represented in a wchar_t. There is wording that prevents this in char16_t literals, but not for wchar_t literals. This seems undesirable.
Proposed resolution (July, 2009):
Change 5.13.3 [lex.ccon] paragraph 2 as follows:
...The value of a wide-character literal containing a single c-char has value equal to the numerical value of the encoding of the c-char in the execution wide-character set, unless the c-char has no representation in the execution wide-character set, in which case the value is implementation-defined. [Note: The type wchar_t is able to represent all members of the execution wide-character set, see 6.8.2 [basic.fundamental]. —end note]. The value of a wide-character literal containing multiple c-chars is implementation-defined.
Change 5.13.3 [lex.ccon] paragraph 5 as follows:
A universal-character-name is translated to the encoding, in the appropriate execution character set, of the character named...
[Voted into WP at October, 2009 meeting.]
The description of concatenation of string literals in 5.13.5 [lex.string] paragraph 11 does not mention raw strings explicitly, so it is not clear whether, and if so, how, they combine with non-raw strings.
Notes from the March, 2009 meeting:
A raw string should be considered equivalent to the corresponding non-raw string in string literal concatenation.
Proposed resolution (September, 2009):
In 5.13.5 [lex.string], replace the definition of string-literal with:
Change 5.13.5 [lex.string] paragraph 5 as follows:
AAfter translation phase 6, a string literal that does not begin withu8, u, U, or Lan encoding-prefix is an ordinary string literal, and is initialized with the given characters.
Change 5.13.5 [lex.string] paragraph 12 as follows:
In translation phase 6 (5.2 [lex.phases]), adjacent string literals are concatenated. If both string literals have the sameprefixencoding-prefix, the resulting concatenated string literal has thatprefixencoding-prefix. If one string literal has noprefixencoding-prefix, it is treated as a string literal of the sameprefixencoding-prefix as the other operand. If a UTF-8 string literal token is adjacent to a wide string literal token, the program is ill-formed. Any other concatenations are conditionally supported with implementation-defined behavior. [Note: This concatenation is an interpretation, not a conversion. Because the interpretation happens in translation phase 6 (after each character from each literal has been translated into a value from the appropriate character set), a string literal's initial rawness has no effect on the interpretation or well-formedness of the concatenation. —end note] [Example:...
(Note: this resolution also resolves issue 834.)
[Voted into WP at October, 2009 meeting.]
According to 5.13.5 [lex.string] paragraph 4,
A string literal that does not begin with u8, u, U, or L is an ordinary string literal, and is initialized with the given characters.
This is not as clear as it could be that a string like u8R"[xxx]" is not an ordinary string literal, because the string's prefix is not one of those listed (i.e., it's not obvious that possible substrings of the prefix are in view). This would be clearer if it simply said,
A string literal with no prefix or a prefix of R is an ordinary string literal.
Proposed resolution (September, 2009):
This issue is resolved by the resolution of issue 790.
[Voted into WP at March, 2010 meeting as document N3077.]
The specification of raw string literals interacts poorly with the specification of preprocessing tokens. The grammar in 5.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 5.13.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 5.13.5 [lex.string] as follows:
Change 5.13.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]
[Voted into WP at March, 2010 meeting.]
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 5.3.1 [lex.charset] paragraph 2, could not contain a UCN designating a member of the basic source character set.
Proposed resolution (October, 2009):
Change 5.3.1 [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.
[Voted into WP at March, 2010 meeting.]
5.13.9 [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 16.3.3.3.4.2 [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 5.13.9 [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 ofcharacters (or code points)code units in str (i.e., its length excluding the terminating null character)...
[Voted into WP at March, 2010 meeting as document N2993.]
There are a number of specifications in the Standard that should also apply to references. For example:
6.1 [basic.pre] paragraphs 3-4 indicate that a reference cannot have a name because it is not an entity. (See also issue 485.)
6.5.3 [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.
6.6 [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.)
6.7.6.2 [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.
[Voted into WP at October, 2009 meeting.]
When user-defined literals were added, a new form of operator function was created. Presumably many of the existing specifications that deal with operator-function-ids (the definition of name, for instance, in paragraph 4 of 6.1 [basic.pre]) should also apply to literal-operator-ids.
Proposed resolution (June, 2009):
Change 6.1 [basic.pre] paragraph 4 as follows:
A name is a use of an identifier (5.11 [lex.name]), operator-function-id (12.4 [over.oper]), literal-operator-id (12.6 [over.literal]), conversion-function-id (11.4.8.3 [class.conv.fct]), or template-id (13.3 [temp.names]) that denotes an entity or label (8.7.6 [stmt.goto], 8.2 [stmt.label]).
Change _N4567_.5.1.1 [expr.prim.general] paragraph 3 as follows:
The operator :: followed by an identifier, a qualified-id,oran operator-function-id, or a literal-operator-id is a primary-expression. Its type is specified by the declaration of the identifier, qualified-id,oroperator-function-id, or literal-operator-id. The result is the entity denoted by the identifier, qualified-id,oroperator-function-id, or literal-operator-id. The result is an lvalue if the entity is a function or variable. The identifier, qualified-id,oroperator-function-id, or literal-operator-id shall have global namespace scope or be visible in global scope because of a using-directive (9.9.4 [namespace.udir])...
Add the following production to the grammar for qualified-id in _N4567_.5.1.1 [expr.prim.general] paragraph 7:
Add the following production to the grammar for template-id in 13.3 [temp.names] paragraph 1:
Change 13.3 [temp.names] paragraph 3 as follows:
After name lookup (6.5 [basic.lookup]) finds that a name is a template-name, or that an operator-function-id or a literal-operator-id refers to a set of overloaded functions any member of which is a function template...
Change 13.6 [temp.type] bullet 1.1 as follows:
their template-names, or
operator-function-ids, or literal-operator-ids
refer to the same template, and
[Voted into WP at March, 2010 meeting.]
At least in the new wording for 7.5.6 [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 6.1 [basic.pre] paragraph 3.
Proposed resolution (October, 2009):
Change 6.1 [basic.pre] paragraph 3 as follows:
An entity is a value, object, variable, reference, function, enumerator, type, class member, template, template specialization, namespace,orparameter pack, or this.
Change 6.3 [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 (11.4.3 [class.mfct.non.static])). A virtual member function...
Delete 7.5.6 [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 (6.3 [basic.def.odr]).
[Voted into WP at March, 2010 meeting as document N2993.]
6.3 [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.
[Voted into WP at October, 2009 meeting.]
Sections 6.4.3 [basic.scope.block] to 6.4.7 [basic.scope.class] define and summarize different kinds of scopes in a C++ program. However it is missing a description for the scope of template parameters. I believe a section is needed there — even though some information may be found in clause 14.
Proposed resolution (September, 2009):
Insert the following as a new paragraph following 6.4.2 [basic.scope.pdecl] paragraph 8:
The point of declaration of a template parameter is immediately after its complete template-parameter. [Example:typedef unsigned char T; template<class T = T // Lookup finds the typedef name of unsigned char. , T //Lookup finds the template parameter. N = 0> struct A {};—end example]
Delete 13.2 [temp.param] paragraph 14:
[Drafting note: This change conflicts with the resolution for issue 187 but is in accord with widespread implementation practice.]A template-parameter shall not be used in its own default argument.
Insert the following as a new section following 6.4.8 [basic.scope.enum]:
Template Parameter Scope [basic.scope.temp]
The declarative region of the name of a template parameter of a template template-parameter is the smallest template-parameter-list in which the name was introduced.
The declarative region of the name of a template parameter of a template is the smallest template-declaration in which the name was introduced. Only template parameter names belong to this declarative region; any other kind of name introduced by the declaration of a template-declaration is instead introduced into the same declarative region where it would be introduced as a result of a non-template declaration of the same name. [Example:
namespace N { template<class T> struct A{}; // line 2 template<class U> void f(U){} // line 3 struct B { template<class V>friend int g(struct C*); // line 5 }; }The declarative regions of T, U and V are the template-declarations on lines 2, 3 and 5, respectively. But the names A, f, g and C all belong to the same declarative region—namely, the namespace-body of N. (g is still considered to belong to this declarative region in spite of its being hidden during qualified and unqualified name lookup.) —end example]
The potential scope of a template parameter name begins at its point of declaration (6.4.2 [basic.scope.pdecl]) and ends at the end of its declarative region. [Note: this implies that a template-parameter can be used in the declaration of subsequent template-parameters and their default arguments but cannot be used in preceding template-parameters or their default arguments. For example,
template<class T, T* p, class U = T> class X { /* ... */ }; template<class T> void f(T* p = new T);This also implies that a template-parameter can be used in the specification of base classes. For example,
template<class T> class X : public Array<T> { /* ... */ }; template<class T> class Y : public T { /* ... */ };The use of a template parameter as a base class implies that a class used as a template argument must be defined and not just declared when the class template is instantiated. —end note]
The declarative region of the name of a template parameter is nested within the immediately-enclosing declarative region. [Note: as a result, a template-parameter hides any entity with the same name in an enclosing scope (_N4868_.6.4.10 [basic.scope.hiding]). [Example:
typedef int N; template<N X, typename N, template<N Y> class T> struct A;Here, X is a non-type template parameter of type int and Y is a non-type template parameter of the same type as the second template parameter of A. —end example] —end note]
[Note: because the name of a template parameter cannot be redeclared within its potential scope (13.8.2 [temp.local]), a template parameter's scope is often its potential scope. However, it is still possible for a template parameter name to be hidden; see 13.8.2 [temp.local]. —end note]
Delete 13.2 [temp.param] paragraph 13, including the example:
The scope of a template-parameter extends...
Delete 13.8.2 [temp.local] paragraph 6, including the note and example:
The scope of a template-parameter extends...
[Voted into WP at March, 2010 meeting.]
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 6.4.3 [basic.scope.block].
Proposed resolution (October, 2009):
Change 6.4.2 [basic.scope.pdecl] paragraph 2 as follows:
[Note: a
nonlocalname from an outer scope remains visible up to the point of declaration of thelocalname that hides it. [Example:const int i = 2; { int i[i]; }declares a
localblock-scope array of two integers. —end example] —end note]
Change the section heading of 6.4.3 [basic.scope.block] from “Local scope” to “Block scope.”
Change 6.4.3 [basic.scope.block] paragraph 1 as follows:
A name declared in a block (8.4 [stmt.block]) is local to that block; it has block scope. Its potential scope begins at its point of declaration (6.4.2 [basic.scope.pdecl]) and ends at the end of itsdeclarative regionblock. A variable declared at block scope is a local variable.
Change 6.4.3 [basic.scope.block] paragraph 3 as follows:
The namein a catch exception-declarationdeclared in an exception-declaration is local to thehandlerhandler and shall not be redeclared in the outermost block of thehandlerhandler.
Change _N4868_.6.4.10 [basic.scope.hiding] paragraph 3 as follows:
In a member function definition, the declaration of alocalname at block scope hides the declaration of a member of the class with the same name...
Change 6.6 [basic.link] paragraph 8 as follows:
...Moreover, except as noted, a name declaredin a localat block scope (6.4.3 [basic.scope.block]) has no linkage...
Change 6.9.3.3 [basic.start.dynamic] paragraph 1 as follows:
...For an object of array or class type, all subobjects of that object are destroyed before anylocalblock-scope object with static storage duration initialized during the construction of the subobjects is destroyed.
Change 6.9.3.3 [basic.start.dynamic] paragraph 2 as follows:
If a function contains alocalblock-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 destroyedlocalblock-scope object. Likewise, the behavior is undefined if thefunction-localblock-scope object is used indirectly (i.e., through a pointer) after its destruction.
Change 6.9.3.3 [basic.start.dynamic] paragraph 3 as follows:
If the completion of the initialization of anon-localnon-block-scope object with static storage duration is sequenced before a call to std::atexit (see <cstdlib>, 17.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 anon-localnon-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 8.4 [stmt.block] paragraph 1 as follows:
...A compound statement defines alocalblock scope (6.4 [basic.scope])...
Change 8.5 [stmt.select] paragraph 1 as follows:
...The substatement in a selection-statement (each substatement, in the else form of the if statement) implicitly defines alocalblock scope (6.4 [basic.scope])...
Change 8.5 [stmt.select] paragraph 5 as follows:
If a condition can be syntactically resolved as either an expression or the declaration of alocalblock-scope name, it is interpreted as a declaration.
Change 8.6 [stmt.iter] paragraph 2 as follows:
The substatement in an iteration-statement implicitly defines alocalblock scope (6.4 [basic.scope]) which is entered and exited each time through the loop.
Change 8.9 [stmt.dcl] paragraph 3 as follows:
...A program that jumps84 from a point where alocalvariable with automatic storage duration...
Change 8.9 [stmt.dcl] paragraph 4 as follows:
The zero-initialization (9.5 [dcl.init]) of alllocalblock-scope objects with static storage duration (6.7.6.2 [basic.stc.static]) or thread storage duration (6.7.6.3 [basic.stc.thread]) is performed before any other initialization takes place. Constant initialization (6.9.3.2 [basic.start.static]) of alocalblock-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 otherlocalblock-scope objects...
Change 8.9 [stmt.dcl] paragraph 5 as follows:
The destructor for alocalblock-scope object with static or thread storage duration will be executed if and only if the variable was constructed. [Note: 6.9.3.3 [basic.start.dynamic] describes the order in whichlocalblock-scope objects with static and thread storage duration are destroyed. —end note]
Change 9.6 [dcl.fct.def] paragraph 7 as follows:
In the function-body, a function-local predefined variable denotes alocalblock-scope object of static storage duration that is implicitly defined (see 6.4.3 [basic.scope.block]).
Change the example in 11.3 [class.name] paragraph 2 as follows:
... void g() { struct s; // hide global struct s // with alocalblock-scope declaration ...
Change the example in 11.3 [class.name] paragraph 3 as follows:
... void g(int s) { struct s* p = new struct s; // global s p->a = s; //localparameter s }
[Voted into WP at March, 2010 meeting.]
When 6.5.3 [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 6.5.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 6.5.2 [class.member.lookup] will be added to 6.5.3 [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 6.5.3 [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-idthe declarator-id is first looked up in the scope of the member function's class (6.5.2 [class.member.lookup]). If it is not found, or if the name is part of a template-argument ina template-idthe 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]
[Voted into the WP at the March, 2009 meeting.]
The resolution of issue 33 added the following wording in 6.5.4 [basic.lookup.argdep]:
In addition, if the argument is the name or address of a set of overloaded functions and/or function templates, its associated classes and namespaces are the union of those associated with each of the members of the set: the namespace in which the function or function template is defined and the classes and namespaces associated with its (non-dependent) parameter types and return type.
This wording is self-contradictory: although it claims that the treatment of overload sets is intended to be “the union of those associated with each of the members of the set,” it says that the namespace of which each function or function template is a member is to be considered an associated namespace. That is different from the case of a non-overloaded function argument; in that case, because only the type of the argument is considered, the namespace of which the function is a member is not an associated namespace. This should be rectified so that overloaded and unoverloaded functions really are treated the same.
Proposed resolution (June, 2008):
Change 6.5.4 [basic.lookup.argdep] paragraph 2 as follows:
...In addition, if the argument is the name or address of a set of overloaded functions and/or function templates, its associated classes and namespaces are the union of those associated with each of the members of the set: the namespace in which the function or function template is defined and, i.e., the classes and namespaces associated with its (non-dependent) parameter types and return type.
[Voted into WP at October, 2009 meeting.]
During the discussion of issue 704, some people expressed a desire to reconsider whether parentheses around the name of the function in a function call should suppress argument-dependent lookup, on the basis that this is overly subtle and not obvious. Others pointed out that this technique is used (both intentionally and inadvertently) in existing code and changing the behavior could cause problems.
It was also observed that the normative text that specifies this behavior is itself subtle, relying an a very precise interpretation of the preposition used in 6.5.4 [basic.lookup.argdep] paragraph 1:
When an unqualified name is used as the postfix-expression in a function call...
This is taken to mean that something like (f)(x) is not subject to argument-dependent lookup because the name f is used in but not as the postfix-expression. This could be confusing, especially in light of the use of the term postfix-expression to refer to the name inside the parentheses, not to the parenthesized expression, in 12.2.2.2 [over.match.call] paragraph 1. If the decision is to preserve this effect of a parenthesized name in a function call, the wording should probably be revised to specify it more explicitly.
Notes from the September, 2008 meeting:
The CWG agreed that the suppression of argument-dependent lookup by parentheses surrounding the postfix-expression is widely known and used in the C++ community and must be preserved. The wording should be changed to make this effect clearer.
Proposed resolution (September, 2008):
Change 6.5.4 [basic.lookup.argdep] paragraph 1 as follows:
Whenan unqualified name is used asthe postfix-expression in a function call (7.6.1.3 [expr.call]) is an unqualified-id, other namespaces not considered during the usual unqualified lookup (6.5.3 [basic.lookup.unqual]) may be searched...
Proposed resolution (September, 2009):
Change 6.5.4 [basic.lookup.argdep] paragraph 1 as follows:
When
an unqualified name is used asthe postfix-expression in a function call (7.6.1.3 [expr.call]) is an unqualified-id, other namespaces not considered during the usual unqualified lookup (6.5.3 [basic.lookup.unqual]) may be searched, and in those namespaces, namespace- scope friend function declarations (11.8.4 [class.friend]) not otherwise visible may be found. These modifications to the search depend on the types of the arguments (and for template template arguments, the namespace of the template argument). [Example:namespace N { struct S { }; void f(S); } void g() { N::S s; f(s); // calls N::f (f)(s); // error: N::f not considered; parentheses prevent argument-dependent lookup }—end example]
[Voted into WP at March, 2010 meeting.]
The recent addition to support inherited constructors changed 6.5.5.2 [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 6.5.5.2 [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 11 [class]), or
in a using-declaration (9.10 [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...
[Voted into WP at March, 2010 meeting as part of document N3079.]
The algorithm for namespace-qualified lookup is given in 6.5.5.3 [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 (9.9.2 [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 9.9.2 [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 6.5.5.3 [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 (9.9.2 [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 (9.9.2 [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. Ifif 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 (9.10 [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:...
[Voted into WP at October, 2009 meeting.]
The resolution of issue 389 makes code like
static struct { int i; int j; } X;
ill-formed. This breaks a lot of code for no apparent reason, since the name X is not known outside the translation unit in which it appears; there is therefore no danger of collision and no need to mangle its name.
There has also been recent discussion on the email reflectors as to whether the restrictions preventing use of types without linkage as template arguments is needed or not, with the suggestion that a mechanism like that used to give members of the unnamed namespace unique names could be used for unnamed and local types. See also issue 488, which would become moot if types without linkage could be used as template parameters.
Notes from the October, 2005 meeting:
The Evolution Working Group is discussing changes that would address this issue. CWG will defer consideration until the outcome of the EWG discussions is clear.
Notes from the April, 2006 meeting:
The CWG agreed that the restriction in 6.6 [basic.link] paragraph 8 on use of a type without linkage should apply only to variables and functions with external linkage, not to variables and functions with internal linkage (i.e., the example should be accepted). This is a separate issue from the question before the EWG and should be resolved independently.
Additional note (April, 2006):
Even the restriction of the rule to functions and objects with external linkage may not be exactly what we want. Consider an example like:
namespace { struct { int i; } s; }
The variable s has external linkage but can't be named outside its translation unit, so there's again no reason to prohibit use of a type without linkage in its declaration.
Notes from the June, 2008 meeting:
Paper N2657, adopted at the June, 2008 meeting, allows local and unnamed types to be used as template parameters. That resolution is narrowly focused, however, and does not address this issue.
Proposed resolution (June, 2009):
Change 6.6 [basic.link] paragraph 8 as follows:
...A type without linkage shall not be used as the type of a variable or function with external linkage, unless
the variable or function has
extern "C"C language linkage (9.12 [dcl.link]), orthe variable or function is declared within an unnamed namespace (9.9.2 [namespace.def]), or
the variable or function is not used (6.3 [basic.def.odr]) or is defined in the same translation unit.
[Drafting note: the context shown for the preceding resolution assumes that the resolution for issue 757 has been applied.]
[Voted into the WP at the March, 2009 meeting.]
According to 6.6 [basic.link] paragraph 3,
A name having namespace scope (6.4.6 [basic.scope.namespace]) has internal linkage if it is the name of
an object, reference, function or function template that is explicitly declared static or,
an object or reference that is explicitly declared const and neither explicitly declared extern nor previously declared to have external linkage;
It is not possible to declare a reference to be const.
Proposed resolution (March, 2008):
Change 6.6 [basic.link] paragraph 3 as indicated (note addition of punctuation in the first bullet):
A name having namespace scope (6.4.6 [basic.scope.namespace]) has internal linkage if it is the name of
an object, reference, function, or function template that is explicitly declared static; or,
an object
or referencethat is explicitly declared const and neither explicitly declared extern nor previously declared to have external linkage; ora data member of an anonymous union.
[Voted into WP at July, 2009 meeting.]
Paper N2657, adopted at the June, 2008 meeting, removed the prohibition of local and unnamed types as template arguments. As part of the change, 6.6 [basic.link] paragraph 8 was modified to read,
A type without linkage shall not be used as the type of a variable or function with linkage, unless
the variable or function has extern "C" linkage (9.12 [dcl.link]), or
the type without linkage was named using a dependent type (13.8.3.2 [temp.dep.type]).
Because a type without linkage can only be named as a dependent type, there are still some potentially useful things that cannot be done:
template <class T> struct A { friend void g(A, T); // this can't be defined later void h(T); // this cannot be explicitly specialized }; template <class T> void f(T) { A<T> at; g(at, (T)0); } enum { e }; void g(A<decltype(e)>, decltype(e)){} // not allowed int main() { f(e); }
These deficiencies could be addressed by allowing types without linkage to be used as the type of a variable or function, but with the requirement that any such entity that is used must also be defined in the same translation unit. This would allow issuing a compile-time, instead of a link-time, diagnostic if the definition were not provided, for example. It also seems to be easier to implement than the current rules.
Proposed resolution (March, 2009):
Change 6.6 [basic.link] paragraph 8 as follows:
...A type without linkage shall not be used as the type of a variable or function with linkage, unless
the variable or function has extern "C" linkage (9.12 [dcl.link]), or
the type without linkage was named using a dependent type (13.8.3.2 [temp.dep.type])the variable or function is not used (6.3 [basic.def.odr]) or is defined in the same translation unit.[Note: in other words, a type without linkage contains a class or enumeration that cannot be named outside its translation unit. An entity with external linkage declared using such a type could not correspond to any other entity in another translation unit of the program and thus
is not permittedmust be defined in the translation unit if it is used. Also note that classes with linkage may contain members whose types do not have linkage, and that typedef names are ignored in the determination of whether a type has linkage. —end note] [Example:void f() { struct A { int x; }; // no linkage extern A a; // ill-formed typedef A B; extern B b; // ill-formed }
—end example]
[Example:template <class T> struct A { // in A<X>, the following is allowed because the type with no linkage // X is named using template parameter T. friend void f(A, T){} }; template <class T> void g(T t) { A<T> at; f(at, t); } int main() { class X {} x; g(x); }template <typename T> struct B { void g(T){} void h(T); friend void i(B, T){} }; void f() { struct A { int x; }; // no linkage A a = {1}; B<A> ba; // declares B<A>::g(A) and B<A>::h(A) ba.g(a); // OK ba.h(a); // error: B<A>::h(A) not defined in the translation unit i(ba, a); // OK }—end example]
[Drafting note: issue 527 also changes part of the same text.]
[Voted into WP at March, 2010 meeting.]
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 6.6 [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 (6.4.3 [basic.scope.block]) 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 (9.2.4 [dcl.typedef])) and the name has linkage; or
it is an unnamed class or enumeration member of a class with linkage; or
...
[Voted into WP at March, 2010 meeting.]
Read literally, 6.7.4 [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 6.7.4 [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.
SuchFor an object under construction or destruction, see 11.9.5 [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.
SuchFor an object under construction or destruction, see 11.9.5 [class.cdtor]. Otherwise, such an lvalue refers to allocated storage...
[Voted into the WP at the March, 2009 meeting.]
In describing the order of destruction of temporaries, 6.7.7 [class.temporary] paragraphs 4-5 say,
There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression...
The second context is when a reference is bound to a temporary... A temporary bound to the returned value in a function return statement (8.7.4 [stmt.return]) persists until the function exits.
The following example illustrates the issues here:
struct S { ~S(); }; S& f() { S s; // #1 return (S(), // #2 S()); // #3 }
If the return type of f() were simply S instead of S&, the two temporaries would be destroyed at the end of the full-expression in the return statement in reverse order of their construction, followed by the destruction of the variable s at block-exit, i.e., the order of destruction of the S objects would be #3, #2, #1.
Because the temporary #3 is bound to the returned value, however, its lifetime is extended beyond the end of the full-expression, so that S object #2 is destroyed before #3.
There are two problems here. First, it is not clear what “until the function exits” means. Does it mean that the temporary is destroyed as part of the normal block-exit destructions, as described in 8.7 [stmt.jump] paragraph 2:
On exit from a scope (however accomplished), destructors (11.4.7 [class.dtor]) are called for all constructed objects with automatic storage duration (6.7.6.4 [basic.stc.auto]) (named objects or temporaries) that are declared in that scope, in the reverse order of their declaration.
Or is the point of destruction for #3 after the destruction of the “constructed objects... that are declared [emphasis mine] in that scope” (because temporary #3 was not “declared”)? I.e., should #3 be destroyed before or after #1?
The other problem is that, according to the recollection of one of the participants responsible for this wording, the intent was not to extend the lifetime of #3 but simply to emphasize that its lifetime ended before the function returned, i.e., that the result of f() could not be used without causing undefined behavior. This is also consistent with the treatment of this example by many implementations; MSVC++, g++, and EDG all destroy #3 before #2.
Suggested resolution:
Change 6.7.7 [class.temporary] paragraph 5 as indicated:
AThe lifetime of a temporary bound to the returned value in a function return statement (8.7.4 [stmt.return])persists until the function exitsis not extended; it is destroyed at the end of the full-expression in the return statement.
Proposed resolution (June, 2008):
Change 6.7.7 [class.temporary] paragraph 5 as follows (converting the running text into a bulleted list and making the indicated edits to the wording):
... The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:as specified below.
A temporary bound to a reference member in a constructor's ctor-initializer (11.9.3 [class.base.init]) persists until the constructor exits.
A temporary bound to a reference parameter in a function call (7.6.1.3 [expr.call]) persists until the completion of the full expression containing the call.
AThe lifetime of a temporary bound to the returned value in a function return statement (8.7.4 [stmt.return])persists until the function exitsis not extended; the temporary is destroyed at the end of the full-expression in the return statement.The destruction of a temporary whose lifetime is not extended...
[Voted into WP at October, 2009 meeting.]
The std::memcpy library function is singled out for special treatment in 6.8 [basic.types] paragraph 3:
For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the value of obj1 is copied into obj2, using the std::memcpy library function, obj2 shall subsequently hold the same value as obj1.
This specification should not be restricted to std::memcpy but should apply to any bytewise copying, including std::memmove (as is done in the footnote in the preceding paragraph, for example).
Proposed resolution (July, 2009):
Change 6.8 [basic.types] paragraph 3 as follows:
For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if thevalue ofunderlying bytes (6.7.1 [intro.memory]) making up obj1isare copied into obj2, using the std::memcpy library function[Footnote: By using, for example, the library functions (16.4.2.3 [headers]) std::memcpy or std::memmove. —end footnote], obj2 shall subsequently hold the same value as obj1. [Example:...
[Voted into WP at October, 2009 meeting.]
The execution requirements on a conforming implementation are described twice in the Standard, once in 6.9.1 [intro.execution] paragraphs 5-6 and again in paragraph 11. These descriptions differ in at least a couple of important ways:
The most significant discrepancy has to do with the way output is described. In paragraph 11, the least requirements are described in terms of data written at program termination, clearly allowing arbitrary buffering, whereas in paragraph 6, the observable behavior is described in terms of calls to I/O functions. For example, there are compilers which transform a call to printf with a single argument into a call to fputs. That's valid under paragraph 11, but not under paragraph 6.
Also, in paragraph 6, volatile accesses and I/O operations are included in a single sequence, suggesting that they are equally constrained by sequencing requirements, whereas in paragraph 11, they are clearly not.
There are also editorial discrepancies that should be cleaned up.
Proposed resolution (September, 2009):
The resolution of issue 785 also resolves this issue.
[Voted into WP at October, 2009 meeting.]
In the presence of threads, it is no longer appropriate to characterize the abstract machine as having an “execution sequence.”
Proposed resolution (September, 2009):
Change 6.9.1 [intro.execution] paragraph 3 as follows:
...An instance of the abstract machine can thus have more than one possible executionsequencefor a given program and a given input.
Change 6.9.1 [intro.execution] paragraph 5 as follows:
A conforming implementation executing a well-formed program shall produce the same observable behavior as one of the possibleexecution sequencesexecutions of the corresponding instance of the abstract machine with the same program and the same input. However, if any such executionsequencecontains an undefined operation, this International Standard places no requirement on the implementation executing that program with that input (not even with regard to operations preceding the first undefined operation).
Delete 6.9.1 [intro.execution] paragraph 6, including the footnote:
The observable behavior of the abstract machine is its sequence of reads and writes to volatile data and calls to library I/O functions. [Footnote: An implementation can offer additional library I/O functions as an extension. Implementations that do so should treat calls to those functions as “observable behavior” as well. —end footnote]
Change 6.9.1 [intro.execution] paragraph 9 as follows:
The least requirements on a conforming implementation are:
Access to volatile objects are evaluated strictly according to the rules of the abstract machine.
At program termination, all data written into files shall be identical to one of the possible results that execution of the program according to the abstract semantics would have produced.
The input and output dynamics of interactive devices shall take place in such a fashion that
prompting messages actually appear prior to a program waitingprompting output is actually delivered before a program waits for input. What constitutes an interactive device is implementation-defined.These collectively are referred to as the observable behavior of the program. [Note: more stringent correspondences between abstract and actual semantics may be defined by each implementation. —end note]
(Note; this resolution also resolves issue 612.)
[Voted into WP at October, 2009 meeting.]
In general, the description of the memory model is very careful to specify when the objects under discussion are atomic or non-atomic. However, there are a few cases where it could be clearer.
Proposed resolution (March, 2009):
Modify 6.9.2 [intro.multithread] paragraph 5 as follows:
All modifications to a particular atomic object M occur in some particular total order, called the modification order of M. If A and B are modifications of an atomic object M and A happens before (as defined below) B, then A shall precede B in the modification order of M, which is defined below. [Note: This states that the modification orders must respect happens before. —end note] [Note: There is a separate order for eachscalaratomic object. There is no requirement that these can be combined into a single total order for all objects. In general this will be impossible since different threads may observe modifications to different variables in inconsistent orders. —end note]
Modify 6.9.2 [intro.multithread] paragraph 7 as follows:
Certain library calls synchronize with other library calls performed by another thread. In particular, an atomic operation A that performs a release operation on an atomic object M synchronizes with an atomic operation B that performs an acquire operation on M and reads a value written by any side effect in the release sequence headed by A...
Modify 6.9.2 [intro.multithread] paragraph 12 as follows:
A visible side effect A on
ana scalar object or bit-field 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 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 undefined. —end note] ...
[Voted into WP at March, 2010 meeting.]
6.9.2 [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. 6.9.1 [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 6.9.2 [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, thenthere is a data race, andthe behavior is either unspecified or undefined. —end note]...
[Voted into WP at October, 2009 meeting.]
The term “thread” is introduced but not defined in 6.9.2 [intro.multithread] paragraph 1. A definition is needed.
Proposed resolution (September, 2009):
Chamge 6.9.2 [intro.multithread] paragraph 1 as follows:
A thread of execution (a.k.a. thread) is a single flow of control within a program, including the initial invocation of a specific top-level function, and recursively including every function invocation subsequently executed by the thread. [Note: When one thread creates another, the initial call to the top-level function of the new thread is executed by the new thread, not by the creating thread. —end note] Every thread in a program can potentially access every object and function in the program. [Footnote: An object with automatic or thread storage duration (6.7.6 [basic.stc]) is associated with one specific thread, and can be accessed by a different thread only indirectly through a pointer or reference (6.8.4 [basic.compound]). —end footnote] Under a hosted implementation, a C++ program can have more than onethread of execution (a.k.a. thread)thread running concurrently...
[Voted into WP at October, 2009 meeting.]
6.9.3.1 [basic.start.main] paragraph 4 discusses the effects of calling std::exit but says nothing about std::quick_exit.
Proposed resolution (July, 2009):
Change 6.9.3.1 [basic.start.main] paragraph 4 as follows:
It should be stated in 6.9.3.1 [basic.start.main] that it a program that defines main as deleted is ill-formed.
Proposed resolution (July, 2009):
Change 6.9.3.1 [basic.start.main] paragraph 3 as follows:
...A program that declares main to be inline, static, or constexpr, or that defines main as deleted, is ill-formed...
[Voted into WP at October, 2009 meeting.]
According to 6.9.3.3 [basic.start.dynamic] paragraph 1,
Destructors (11.4.7 [class.dtor]) for initialized objects with static storage duration are called as a result of returning from main and as a result of calling std::exit (17.5 [support.start.term]).
It is unclear, in the presence of delegating constructors, exactly what an “initialized object” is. 6.7.4 [basic.life] paragraph 1 says that the lifetime of an object does not begin until it is completely initialized, i.e., when its principal constructor finishes execution. 14.3 [except.ctor] paragraph 2 says that an exception during the construction of class object only invokes destructors for fully-constructed base and member sub-objects (those for which the principal constructor has completed). On the other hand, the destructor for a complete class object is called if its non-delegating constructor has completed, even if the principal constructor has not yet finished. Which of these models is appropriate for the behavior of std::exit?
Notes from the March, 2009 meeting:
The CWG agreed that the destructor for a complete object should be called by std::exit if its non-delegating constructor has finished, just as for an exception.
Notes from the July, 2009 meeting:
The CWG decided that the direction adopted at the March, 2009 meeting was incorrect. Instead, the model should be the way completely-constructed base and member subobjects are handled: their destructors are called when an exception is thrown but not when std::exit is called.
Proposed resolution (July, 2009):
Change 6.9.3.3 [basic.start.dynamic] paragraph 1 as follows:
Destructors (11.4.7 [class.dtor]) for initialized objects (that is, objects whose lifetime (6.7.4 [basic.life]) has begun) with static storage duration are called as a result of returning from main and as a result of calling std::exit (17.5 [support.start.term]). Destructors for initialized objects with thread storage duration...
[Voted into WP at March, 2010 meeting.]
17.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 6.9.3.3 [basic.start.dynamic]. Paragraph 1 of 6.9.3.3 [basic.start.dynamic] 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>, 17.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 6.9.3.3 [basic.start.dynamic] paragraph 3 as follows:
If the completion of the initialization ofa non-localan object with static storage duration is sequenced before a call to std::atexit (see <cstdlib>, 17.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 ofa non-localan 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...
Lisa Lippincott mentioned this case to me:
A[0] = 0; A[A[0]] = 1;
This seems to use the old value of A[0] other than to calculate the new value, which is said to be undefined, but it also seems reasonable, since the old value is used in order to select the object to modify, so there's no ordering ambiguity.
Steve Adamczyk: the ordering rule referred to is in Clause 7 [expr] paragraph 4.
Notes from the March 2004 meeting:
Clark Nelson mentions that the C committee may have done something on this.
Note (July, 2009):
This issue was resolved by the adoption of the “sequenced before” wording.
[Voted into WP at October, 2009 meeting.]
Evaluating an expression like 1/0 is intended to produce undefined behavior during the execution of a program but be ill-formed at compile time. The wording for this is in Clause 7 [expr] paragraph 4:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expression appears where an integral constant expression is required (7.7 [expr.const]), in which case the program is ill-formed.
The formulation “appears where an integral constant expression is required” is intended as an acceptable Standardese circumlocution for “evaluated at compile time,” a concept that is not directly defined by the Standard. It is not clear that this formulation adequately covers constexpr functions.
Notes from the September, 2008 meeting:
The CWG felt that the concept of “compile-time evaluation” needs to be defined for use in discussing constexpr functions. There is a tension between wanting to diagnose errors at compile time versus not diagnosing errors that will not actually occur at runtime. In this context, a constexpr function might never be invoked, either in a constant expression context or at runtime, although the requirement that the expression in a constexpr function be a potential constant expression could be interpreted as triggering the provisions of Clause 7 [expr] paragraph 4.
There are also contexts in which it is not known in advance whether an expression must be constant or not, notably in the initializer of a const integer variable, where the nature of the initializer determines whether the variable can be used in constant expressions or not. In such a case, it is not clear whether an erroneous expression should be considered ill-formed or simply non-constant (and thus subject to runtime undefined behavior, if it is ever evaluated). The consensus of the CWG was that an expression like 1/0 should simply be considered non-constant; any diagnostic would result from the use of the expression in a context requiring a constant expression.
Proposed resolution (July, 2009):
This issue is resolved by the resolution of issue 699.
[Voted into WP at October, 2009 meeting.]
A number of the operators described in Clause 7 [expr] take operands of enumeration type, relying on the “usual arithmetic conversions” (Clause 7 [expr] paragraph 10) to convert them to an appropriate integral type. The assumption behind this pattern is invalid when one or more of the operands has a scoped enumeration type.
Each operator that accepts operands of enumeration type should be evaluated as to whether the operation makes sense for scoped enumerations (for example, it is probably a good idea to allow comparison of operands having the same scoped enumeration type and conditional expressions in which the second and third operands have the same scoped enumeration type) and, if so, create a special case. The usual arithmetic conversions should not be invoked for scoped enumeration types.
(See also issue 880.)
Proposed resolution (July, 2009):
Change Clause 7 [expr] paragraph 10 as follows:
...This pattern is called the usual arithmetic conversions, which are defined as follows:
If either operand is of scoped enumeration type (9.8.1 [dcl.enum]), no conversions are performed, and if the other operand does not have the same type, the expression is ill-formed.
If either operand is of type long double...
Change 7.6.1.2 [expr.sub] paragraph 1 as follows:
...One of the expressions shall have the type “pointer to T” and the other shall have unscoped enumeration or integral type...
Change 7.6.2 [expr.unary] paragraphs 7-8 and 10 as follows:
The operand of the unary + operator shall have arithmetic, unscoped enumeration, or pointer type...
The operand of the unary - operator shall have arithmetic or unscoped enumeration type...
The operand of ~ shall have integral or unscoped enumeration type...
Change 7.6.2.8 [expr.new] paragraph 6 as follows:
...The expression in a noptr-new-declarator shall be of integral type, unscoped enumeration type, or a class type for which a single non-explicit conversion function to integral or unscoped enumeration type exists (11.4.8 [class.conv]). If the expression...
Change 7.6.5 [expr.mul] paragraph 2 as follows:
The operands of * and / shall have arithmetic or unscoped enumeration type; the operands of % shall have integral or unscoped enumeration type....
Change 7.6.6 [expr.add] paragraph 1-2 as follows:
...For addition, either both operands shall have arithmetic or unscoped enumeration type, or one operand shall be a pointer to a completely-defined effective object type and the other shall have integral or unscoped enumeration type.
For subtraction, one of the following shall hold:
both operands have arithmetic or unscoped enumeration type; or
both operands are pointers to cv-qualified or cv-unqualified versions of the same completely-defined effective object type; or
the left operand is a pointer to a completely-defined effective object type and the right operand has integral or unscoped enumeration type.
Change 7.6.7 [expr.shift] paragraph 1 as follows:
...The operands shall be of integral or unscoped enumeration type...
Change 7.6.9 [expr.rel] paragraph 4 as follows:
If both operands (after conversions) are of arithmetic or enumeration type, each of the operators shall yield true if the specified relationship is true and false if it is false.
Change 7.6.11 [expr.bit.and] paragraph 1 as follows:
...The operator applies only to integral or unscoped enumeration operands.
Change 7.6.12 [expr.xor] paragraph 1 as follows:
...The operator applies only to integral or unscoped enumeration operands.
Change 7.6.13 [expr.or] paragraph 1 as follows:
...The operator applies only to integral or unscoped enumeration operands.
[Voted into WP at March, 2010 meeting as part of document N3055.]
The adoption of paper N2844 made it ill-formed to attempt to bind an rvalue reference to an lvalue, but the example in Clause 7 [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 Clause 7 [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]
[Voted into WP at March, 2010 meeting as document N3055.]
The status of rvalue references to functions is not clear in the current wording. For example, 7.2.1 [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, Clause 7 [expr] paragraph 6 says,
If an expression initially has the type “rvalue reference to T” (9.3.4.3 [dcl.ref], 9.5.4 [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, 7.6.1.3 [expr.call] paragraph 1 says,
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 (7.3.4 [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 7.3.4 [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.
[Voted into WP at October, 2009 meeting.]
The deprecated conversion from string literal to pointer to (non-const) character in 7.3.3 [conv.array] paragraph 2 has been extended to apply to char16_t and char32_t types, but not to UTF8 and raw string literals. Is this disparity intentional? Should it be extended to all new string types, reverted to just the original character types, or revoked altogether?
Additional places in the Standard that may need to change include 14.2 [except.throw] paragraph 3 and 12.2.4.3 [over.ics.rank] paragraph 3.
Additional discussion (August, 2008):
The removal of this conversion for current string literals would affect overload resolution for existing programs. For example,
struct S { S(const char*); }; int f(char *); int f(X); int i = f("hello");
If the conversion were removed, the result would be a quiet change in behavior. Another alternative to consider would be a required diagnostic (without making the program ill-formed).
Notes from the September, 2008 meeting:
The CWG agreed that the deprecated conversion should continue to apply to the literals to which it applied in C++ 2003. Consensus was not reached regarding whether it should apply only to those literals or to all the new literals as well, although it was agreed that the current situation in which it applies to some, but not all, of the new literals is unacceptable.
Notes from the July, 2009 meeting:
The CWG reached consensus that the deprecated conversion should be removed altogether.
Proposed resolution (September, 2009):
Remove 7.3.3 [conv.array] paragraph 2:
A string literal (5.13.5 [lex.string]) with no prefix, with a u prefix, with a U prefix, or with an L prefix can be converted to an rvalue of type “pointer to char”, “pointer to char16_t”, “pointer to char32_t”, or “pointer to wchar_t”, respectively. In any case, the result is a pointer to the first element of the array. This conversion is considered only when there is an explicit appropriate pointer target type, and not when there is a general need to convert from an lvalue to an rvalue. [Note: this conversion is deprecated. See Annex Clause Annex D [depr]. —end note] For the purpose of ranking in overload resolution (12.2.4.2.2 [over.ics.scs]), this conversion is considered an array-to-pointer conversion followed by a qualification conversion (7.3.6 [conv.qual]). [Example: "abc" is converted to “pointer to const char” as an array-to-pointer conversion, and then to “pointer to char” as a qualification conversion. —end example]
Delete the indicated text from the third sub-bullet of the first bullet of paragraph 3 of 12.2.4.3 [over.ics.rank]:
S1 and S2 differ only in their
qualification conversion and yield similar types T1 and
T2 (7.3.6 [conv.qual]), respectively, and the
cv-qualification signature of type T1 is a proper subset of
the cv-qualification signature of type T2, and S1
is not the deprecated string literal array-to-pointer conversion
(4.2). [Example: ...
Delete the note from 14.2 [except.throw] paragraph 3 as follows:
A throw-expression initializes a temporary object, called the exception object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T”, respectively.[Note: the temporary object created for a throw-expression that is a string literal is never of type char*, char16_t*, char32_t*, or wchar_t*; that is, the special conversions for string literals from the types “array of const char”, “array of const char16_t”, “array of const char32_t”, and “array of const wchar_t” to the types “pointer to char”, “pointer to char16_t”, “pointer to char32_t”, and “pointer to wchar_t”, respectively (7.3.3 [conv.array]), are never applied to a throw-expression. —end note]The temporary is an lvalue...
Change the discussion of 5.13.5 [lex.string] in C.7.2 [diff.lex] as follows:
Change: String literals made const
The type of a string literal is changed... “array of const wchar_t.”char* p = "abc"; // valid in C, invalid in C++
...
Difficulty of converting:
Simple syntactic transformation, because string literals can be converted to char*; (7.3.3 [conv.array]). The most common cases are handled by a new but deprecated standard conversionSyntactic transformation. The fix is to add a cast:char* p = "abc"; // valid in C, deprecated in C++ char* q = expr ? "abc" : "de"; // valid in C, invalid in C++void f(char*) { char* p = (char*)"abc"; // cast added f(p); f((char*)"def"); // cast added }
Delete _N3000_.D.4 [depr.string]:
D.4 Implicit conversion from const strings [depr.string]
The implicit conversion from const to non-const qualification for string literals (7.3.3 [conv.array]) is deprecated.
[Voted into WP at July, 2009 meeting.]
According to 7.3.7 [conv.prom] paragraph 2,
An rvalue of an unscoped enumeration type (9.8.1 [dcl.enum]) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration (i.e. the values in the range bmin to bmax as described in 9.8.1 [dcl.enum]): int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int.
This wording may have surprising behavior in this case:
enum E: long { e }; void f(int); void f(long); void g() { f(e); // Which f is called? }
Intuitively, as the programmer has explicitly expressed preference for long as the underlying type, he/she might expect f(long) to be called. However, if long and int happen to have the same size, then e is promoted to int (as it is the first type in the list that can represent all values of E) and f(int) is called instead.
According to 9.8.1 [dcl.enum] the underlying type of an enumeration is always well-defined for both the fixed and the non-fixed cases, so it makes sense simply to promote to the underlying type unless such a type would itself require promotion.
Suggested resolution:
In 7.3.7 [conv.prom] paragraph 2, replace all the text from “An rvalue of an unscoped enumeration type” through the end of the paragraph with the following:
An rvalue of an unscoped enumeration type (9.8.1 [dcl.enum]) is converted to an rvalue of its underlying type if it is different from char16_t, char32_t, wchar_t, or has integer conversion rank greater than or equal to int. Otherwise, it is converted to an rvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int.
(Note that this wording no longer needs to mention extended integer types as special cases.)
Proposed resolution (August, 2008):
Move the following text from 7.3.7 [conv.prom] paragraph 2 into a separate paragraph, making the indicated changes, and add the following new paragraph after it:
An rvalue of an unscoped enumeration type whose underlying type is not fixed (9.8.1 [dcl.enum]) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration (i.e. the values in the range bmin to bmax as described in 9.8.1 [dcl.enum]): int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int. If none of the types in that list can represent all the values of the enumeration, an rvalue of an unscoped enumeration type can be converted to an rvalue of the extended integer type with lowest integer conversion rank (7.3.15 [conv.bool]) greater than the rank of long long in which all the values of the enumeration can be represented. If there are two such extended types, the signed one is chosen.
An rvalue of an unscoped enumeration type whose underlying type is fixed (9.8.1 [dcl.enum]) can be converted to an rvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, an rvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to an rvalue of the promoted underlying type.
[Voted into WP at July, 2009 meeting.]
The current wording of 7.3.11 [conv.fpint] paragraph 2 does not specify what should happen when converting an integer value that is outside the representable range of the target floating point type. The C99 Standard covers this case explicitly in 6.3.1.4 paragraph 2:
When a value of integer type is converted to a real floating type, if the value being converted can be represented exactly in the new type, it is unchanged. If the value being converted is in the range of values that can be represented but cannot be represented exactly, the result is either the nearest higher or nearest lower representable value, chosen in an implementation-defined manner. If the value being converted is outside the range of values that can be represented, the behavior is undefined.
While the current C++ specification requires defined behavior in all cases, the C specification allows for use of NaNs and traps, if those are needed for efficiency.
Notes from the September, 2008 meeting:
The CWG agreed that the C approach should be adopted.
Proposed resolution (March, 2009):
Change 7.3.11 [conv.fpint] paragraph 2 as indicated:
An rvalue of an integer type or of an unscoped enumeration type can be converted to an rvalue of a floating point type. The result is exact if possible.OtherwiseIf the value being converted is in the range of values that can be represented but cannot be represented exactly, it is an implementation-defined choice of either the next lower or higher representable value. [Note: loss of precision occurs if the integral value cannot be represented exactly as a value of the floating type. —end note] If the value being converted is outside the range of values that can be represented, the behavior is undefined. If the source type is bool, the value false is converted to zero and the value true is converted to one.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
There is not a single example of a lambda-expression in their specification. The Standard would be clearer if a few judiciously-chosen examples were added.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
How does name binding work in nested lambda-expressions? For example,
void f1() { float v; []() { return [v]() { return v; } } } void f2() { float v; [v]() { return [v]() { return v; } } }
According to 7.5.6 [expr.prim.lambda] paragraph 3,
A name in the lambda-capture shall be in scope in the context of the lambda expression, and shall be this or shall refer to a local variable or reference with automatic storage duration.
One possible interpretation is that the lambda expression in f1 is ill-formed because v is used in the compound-statement of the outer lambda expression but does not appear in its effective capture set. However, the appearance of v in the inner lambda-capture is not a “use” in the sense of 6.3 [basic.def.odr] paragraph 2, because a lambda-capture is not an expression, and it's not clear whether the reference in the inner lambda expression's return expression should be considered a use of the automatic variable or of the member of the inner lambda expression's closure object.
Similarly, the lambda expression in f2 could be deemed to be ill-formed because the reference to v in the inner lambda expression's lambda-capture would refer to the field of the outer lambda-expression's closure object, not to a local automatic variable; however, it's not clear whether the inner lambda expression should be evaluated in situ or as part of the generated operator() member of the outer lambda expression's closure object.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Is a lambda expression permitted in a default argument expression for a block-scope function declaration? For example,
void g() { void f(std::reference_closure<void()> rc = []() {}); f(); }
This was not discussed in either the Evolution Working Group nor in the Core Working Group, and it is possible that some of the same implementation difficulties that led to prohibiting use of automatic variables in such default argument expressions (9.3.4.7 [dcl.fct.default] paragraph 7) might also apply to closure objects, even though they are not automatic variables.
(See also issue 772.)Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The current wording of 7.5.6 [expr.prim.lambda] is not clear as to how name lookup is to be performed for names appearing in the compound-statement of a lambda expression. Consider, for example:
int fac(int n) { return [=]{ return n <= 1 ? 1 : n*operator()(n-1); }(); }
There is no operator() in scope in the context of the lambda expression. Consequently, according to bullet 5 of paragraph 10, the reference to operator() is not transformed to the class member access syntax but appears untransformed in the closure object's function call operator, where presumably it is interpreted as a recursive call to itself.
A similar question (although it does not involve name lookup per se) arises with respect to use of this in the compound-statement of a lambda expression that does not appear in the body of a non-static member function; for example,
void f() { float v; [v]() { return v+this->v; } }
this cannot refer to anything except the closure object, so are the two references to v equivalent?
The crux of this question is whether the lookups for names in the compound-statement are done in the context of the lambda expression or from the call operator of the closure object. The note at the end of bullet 10.5 would tend to support the latter interpretation:
[Note: Reference to captured variables or references within the compound-statement refer to the data members of F. —end note]
Another possible interpretation of the current wording is that there are two distinct compound-statements in view: the compound-statement that is part of the lambda-expression and the body of the closure object's function call operator that is “obtained from” the former. If this is the intended interpretation, one way of addressing the issues regarding the operator() example above would be to state that it is an error if the lookup of a name in the compound-statement fails, making the example ill-formed.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
According to 7.5.6 [expr.prim.lambda] paragraph 7, the appearance of a lambda expression results in the definition of a class “considered to be defined at the point where the lambda expression occurs.” It is not clear whether that means that a lambda expression cannot appear at any point where it is not permitted to define a class type. For example, 9.3.4.6 [dcl.fct] paragraph 10 says, “Types shall not be defined in return or parameter types.” Does that mean that a function declaration like
void f(int a[sizeof ([]{ return 0; })]);
is ill-formed, because the parameter type defines the closure class for the lambda expression? (Issue 686 lists many contexts in which type definitions are prohibited. Each of these should be examined to see whether a lambda expression should be allowed or prohibited there.)
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The grammar in 7.5.6 [expr.prim.lambda] for lambda-parameter specifies that a declarator must be present, i.e., that all lambda-parameters must be named. This also has the effect of prohibiting a lambda like [](void){}. It is not clear that there is a good reason for these restrictions; programmers could reasonably expect that lambda-parameters were like ordinary function parameters in these regards.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The grammar in 7.5.6 [expr.prim.lambda] for lambda-parameter-declaration does not allow for an ellipsis. Is this a desirable restriction?
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
7.5.6 [expr.prim.lambda] paragraph 13 says simply,
The closure object is initialized by direct-initializing each member N of F with the local variable or reference named N; the member t is initialized with this.
The mechanism for this initialization is not specified. In particular, does the closure class have a default constructor that performs this initialization?
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Functions and function objects behave differently with respect to argument-dependent lookup. In particular, the associated namespaces of a function's parameters and return types, but not the namespace in which the function is declared, are associated namespaces of the function; the exact opposite is true of a function object. The Committee should consider rectifying that disparity; however, in the absence of such action, an explicit decision should be made as to whether lambdas are more function-like or object-like with respect to argument-dependent lookup. For example:
namespace M { struct S { }; } namespace N { void func(M::S); struct { void operator()(M::S); } fn_obj; const auto& lambda = [](M::S){}; } void g() { f(N::func); // assoc NS == M, not N f(N::fn_obj); // assoc NS == N, not M f(N::lambda); // assoc NS == ?? }
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
7.5.6 [expr.prim.lambda] paragraph 13 ties the effective lifetime of a closure object with members captured by reference to the innermost block scope in which the lambda appears, rather than to the lifetime of the objects to which the references are bound. This seems too restrictive.
Notes from the March, 2009 meeting:
Making the suggested change would be problematic for an implementation in which the “reference members” were actually implemented using offsets from a captured stack pointer and in which nested blocks were pushed onto the stack (to optimize space for large local objects, for example).
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Consider an example like:
void f(vector<double> vec) { double x, y, z; fancy_algorithm(vec, [&]() { /* use x, y, and z in various ways */ }); }
7.5.6 [expr.prim.lambda] paragraph 8 requires that the closure class for this lambda will have three reference members, and paragraph 12 requires that it be derived from std::reference_closure, implying two additional pointer members. Although 9.3.4.3 [dcl.ref] paragraph 4 allows a reference to be implemented without allocation of storage, current ABIs require that references be implemented as pointers. The practical effect of these requirements is that the closure object for this lambda expression will contain five pointers. If not for these requirements, however, it would be possible to implement the closure object as a single pointer to the stack frame, generating data accesses in the function-call operator as offsets relative to the frame pointer. The current specification is too tightly constrained.
Lawrence Crowl:
The original intent was that the reference members could be omitted from the closure object by an implementation. The problem we had was that we want the call to f in
extern f(std::reference_closure<void()>); extern f(std::function<void()>); f([&](){});
to unambiguously bind to the reference_closure; using reference_closure can be an order of magnitude faster than using function.
(See also issue 751.)
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927. (See also document PL22.16/09-0035 = WG21 N2845, which partially addressed this issue by the removal of std::reference_clossure.)
[Voted into the WP at the July, 2009 meeting as part of N2927.]
During the discussion of issue 750, it was suggested that an implementation might be permitted to omit fields in the closure object of a lambda expression if the implementation does not need them to address the corresponding automatic variables. If permitted, this implementation choice might be visible to the program via inheritance. Consider:
void f() { int const N = 10; typedef decltype([&N](){}) F; struct X: F { void n() { float z[N]; } // Error? }; }
If it is implementation-defined or unspecified whether the reference member F::N will exist, then it is unknown whether the the reference to N in X::n() will be an error (because lookup finds F::N, which is private) or well-formed (because there is no F::N, so the reference is to the local automatic variable).
If implementations can omit fields, the implementation dependency might be addressed by either treating the lookup “as if” the fields existed, even if they are not present in the object layout, or by defining the names of the fields in the closure class to be unique identifiers, similar to the names of unnamed namespaces (9.9.2.2 [namespace.unnamed]).
Another suggestion was made that derivation from a closure class should be prohibited, at least for now. However, it was pointed out that inheritance is frequently used to give stateless function objects some state, suggesting a use case along the lines of:
template<class T> struct SomeState: T { // ... }; template<class F, typename T< void algo(T functor, ...) { SomeState<T< state(functor); ... } ... algo([](int a){ return 2*a; }) ...
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Consider the following example:
void f() { int const N = 10; [=]() mutable { N = 30; } // Okay: this->N has type int, not int const. N = 20; // Error. }
That is, the N that is a member of the closure object is not const, even though the captured variable is const. This seems strange, as capturing is basically a means of capturing the local environment in a way that avoids lifetime issues. More seriously, the change of type means that the results of decltype, overload resolution, and template argument deduction applied to a captured variable inside a lambda expression can be different from those in the scope containing the lambda expression, which could be a subtle source of bugs.
On the other hand, the copying involved in capturing has uses beyond avoiding lifetime issues (taking snapshots of values, avoiding data races, etc.), and the value of a cv-qualified object is not cv-qualified.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The specification of closure objects is missing a couple of important points regarding their destruction. First, although 7.5.6 [expr.prim.lambda] paragraph 11 mentions other implicitly-declared special member functions, it is silent on the destructor, leading to questions about whether the closure class has one or not.
Second, nothing is said about the timing of the destruction of a closure object: is it normally destroyed at the end of the full-expression to which the lambda expression belongs, and is its lifetime extended if the closure object is bound to a reference? These questions would be addressed if paragraph 2 defined the closure object as a temporary instead of just as an rvalue. (It should be noted that 7.6.1.4 [expr.type.conv] also does not define the conceptually-similar T() as a temporary.)
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927. (The question regarding the failure of 7.6.1.4 [expr.type.conv] failing to categorize T() as a temporary was split off into a separate issue; see issue 943.)
[Voted into the WP at the July, 2009 meeting as part of N2927.]
According to 7.5.6 [expr.prim.lambda] paragraph 10, the following lambda expressions are ill-formed because the return types of the generated operator() functions are an array type and a function type, respectively:
void f() { []{ return ""; }; []{ return f; }; }
It would seem reasonable to expect the array-to-pointer and function-to-pointer decay to apply to these return values and hence to the inferred return type of operator().
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
A lambda expression appearing in local scope presumably creates a local class (in the sense of 11.6 [class.local]) as the type of the closure object, because that class is “considered to be defined at the point where the lambda expression occurs” (7.5.6 [expr.prim.lambda] paragraph 7), and in the absence of any indication to the contrary that class must satisfy the restrictions of 11.6 [class.local] on local classes. One such restriction is that all its member functions must be defined within the class definition, making them inline. However, nothing is said about whether the function call operator for a non-local closure class is inline, and even for the local case it would be better if the specification were explicit.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
According to 7.5.6 [expr.prim.lambda] paragraph 11, the closure class “has a public move constructor that performs a member-wise move.” Although the terms “move constructor” and “member-wise move” are not currently defined (see issue 680), this presumably means that a lambda like [&i]{} results in a closure class similar to:
class F { int& i; public: F(&& other): i(std::move(other.i)) { } // etc. };
This constructor is ill-formed because it attempts to initialize an lvalue reference to non-const int with the rvalue returned by std::move.
It is not clear whether this should be handled by:
Not generating the move constructor.
Generating the declaration of the move constructor but only defining it (and giving the corresponding error) if the move constructor would be used, similar to the handling of other implicitly-defined special member functions.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The current wording does not state under what conditions, if ever, a closure class is a POD. It should either be explicitly unspecified or definitively stated that a closure class is never a POD, to allow implementations freedom to determine the contents of closure classes.
Notes from the March, 2009 meeting:
A closure class is neither standard-layout nor trivial.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
7.5.6 [expr.prim.lambda] paragraph 8, bullet 2, says of members of a closure class,
if the element is of the form & N, the data member has the name N and type “reference to object type of N”
Is an implementation free to use an rvalue reference as the type of this member, as only a “reference” is specified? (See issue 771; the move constructor would be well-formed if the reference member were an rvalue reference.)
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into WP at March, 2010 meeting as document N3052.]
A lambda with an empty capture list has identical semantics to a regular function type. By requiring this mapping we get an efficient lambda type with a known API that is also compatible with existing operating system and C library functions.
Notes from the July, 2009 meeting:
This functionality is part of the “unified function syntax” proposal and will be considered in that context.
[Voted into WP at March, 2010 meeting.]
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 7.5.6 [expr.prim.lambda] paragraph 5 as follows:
... followed by mutable. It isnotneither virtual nor declared volatile. Default arguments...
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The current specification does not adequately describe what happens when an array name is part of the effective capture set of a lambda expression. 7.5.6 [expr.prim.lambda] paragraph 13 says that the array member of the closure object is direct-initialized by the local array; however, 9.5 [dcl.init] paragraph 16 says that such an initialization is ill-formed. There are several possibilities for handling this problem:
This results in an array member of the closure object, which is initialized by copying each element, along the lines of 11.4.5.3 [class.copy.ctor] paragraph 8.
This results in a pointer member of the closure object, initialized to point to the first element of the array (i.e., the array lvalue decays to a pointer rvalue).
This is ill-formed.
This results in a reference-to-array member of the closure object, initialized to refer to the array, regardless of whether & was used or not.
This is ill-formed unless the capture is “by reference.”
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
7.5.6 [expr.prim.lambda] paragraph 5 says,
The compound-statement of a lambda expression shall use (6.3 [basic.def.odr]) an automatic variable or reference from the context where the lambda expression appears only if the name of the variable or reference is a member of the effective capture set...
The reference to 6.3 [basic.def.odr] makes clear that the technical meaning of “use” is in view here, and that the names of variables can appear without being captured if they are constants used as values or if they are unevaluated operands.
There appears to be a disconnect with the preceding paragraph, however, in the description of which variables are implicitly captured by a capture-default:
for each name v that appears in the lambda expression and denotes a local variable or reference with automatic storage duration in the context where the lambda expression appears and that does not appear in the capture-list or as a parameter name in the lambda-parameter-declaration-list...
It would be more consistent if only variables that were required by paragraph 5 to be captured were implicitly captured, i.e., if “that appears in the lambda expression” were replaced by “that is used (6.3 [basic.def.odr]) in the compound-statement of the lambda expression.” For example,
struct A { A(); A(const A&); ~A(); }; void f() { A a; int i = [=]() { return sizeof a; }(); }
Here, a will be captured (and copied), even though it is not “used” in the lambda expression.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Assuming that it is permitted to use a lambda as a default argument in a block-scope function declaration (see issue 754), it is presumably ill-formed for such a lambda expression to refer to a local automatic variable (9.3.4.7 [dcl.fct.default] paragraph 7). What does this mean for capture-defaults? For example,
void f() { int i = 1; void f(int = ([i]() { return i; })()); // Definitely an error void g(int = ([i]() { return 0; })()); // Probably an error void h(int = ([=]() { return i; })()); // Definitely an error void o(int = ([=]() { return 0; })()); // Okay? void p(int = ([]() { return sizeof i; })()); // Presumably okay }
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
According to 7.5.6 [expr.prim.lambda] paragraph 8, the “object type” of a captured function is the type to which the reference refers. That's clearly wrong when the captured reference is a reference to a function, because the resulting data member of the closure class will have a function type:
void f() { } void g() { void (&fr)() = f; [fr]{}; // Oops... }
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into WP at March, 2010 meeting.]
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 7.5.6 [expr.prim.lambda] paragraph 1 as follows:
Add a new paragraph at the end of 7.5.6 [expr.prim.lambda]:
A capture followed by an ellipsis is a pack expansion (13.7.4 [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 13.7.4 [temp.variadic] paragraph 4:
[Editorial note: the editor may wish to consider sorting the bullets in this list in order of section reference.]
[Voted into WP at March, 2010 meeting as document N3055.]
A cast to an rvalue reference type produces an rvalue, and rvalues must have complete types (7.2.1 [basic.lval] paragraph 9). However, none of the sections dealing with cast operators in 7.6.1 [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.
[Voted into WP at March, 2010 meeting.]
The current wording of 7.6.1.3 [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 7.6.1.3 [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 (17.14 [support.runtime]). [Note: This paragraph does not apply to arguments passed to a function parameter pack. Function parameter packs are expanded during template instantiation (13.7.4 [temp.variadic]), thus each such argument has a corresponding parameter when a function template specialization is actually called. —end note] The lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [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* (7.3.12 [conv.ptr]). After these conversions...
[Voted into WP at October, 2009 meeting.]
There are several places in the Standard that were overlooked when reference qualification of member functions was added. For example, 7.6.1.5 [expr.ref] paragraph 4, bullet 3, sub-bullet 2 says,
...if E1.E2 refers to a non-static member function, and the type of E2 is “function of parameter-type-list cv returning T”, then...
This wording incorrectly excludes member functions declared with a ref-qualifier.
Another place that should consider reference qualification is 7.6.4 [expr.mptr.oper]; it should not be possible to invoke an &-qualified member function with an rvalue object expression.
A third place is 9.10 [namespace.udecl] paragraph 15, which does not mention reference qualification in connection with the hiding/overriding of member functions brought in from a base class via a using-declaration.
Proposed resolution (September, 2009):
Change 7.6.1.5 [expr.ref] paragraph 4, bullet 3, sub-bullet 2 as follows:
...
...
Otherwise, if E1.E2 refers to a non-static member function and the type of E2 is “function of parameter-type-list cv ref-qualifieropt returning T”, then E1.E2 is an rvalue. The expression designates a non-static member function...
Change 7.6.4 [expr.mptr.oper] paragraph 6 as follows:
...—end example] In a .* expression whose object expression is an rvalue, if the second operand is a pointer to member function with ref-qualifier &, the program is ill-formed. In a ->* expression, or in a .* expression whose object expression is an lvalue, if the second operand is a pointer to member function with ref-qualifier &&, the program is ill-formed. The result of a .* expression is an lvalue only if its first operand is an lvalue and...
Change 9.10 [namespace.udecl] paragraph 15 as follows:
When a using-declaration brings names from a base class into a derived class scope, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list (9.3.4.6 [dcl.fct]),andcv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting). [Note:...
[Voted into the WP at the March, 2009 meeting.]
At least one implementation accepts the following example as well-formed (returning a null pointer at runtime), although others reject it at compile time:
struct A { virtual ~A(); }; struct B: private A { } b; A* pa = dynamic_cast<A*>(&b);
Presumably the intent of 7.6.1.7 [expr.dynamic.cast] paragraph 5 is that all up-casts (converting from derived to base) are to be handled at compile time, regardless of whether the class involved is polymorphic or not:
If T is “pointer to cv1 B” and v has type “pointer to cv2 D” such that B is a base class of D, the result is a pointer to the unique B subobject of the D object pointed to by v. Similarly, if T is “reference to cv1 B” and v has type cv2 D such that B is a base class of D, the result is the unique B subobject of the D object referred to by v... In both the pointer and reference cases, cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2, and B shall be an accessible unambiguous base class of D.
One explanation for the implementation that accepts the example at compile time is that the final sentence is interpreted as part of the condition for the applicability of this paragraph, so that this case falls through into the description of runtime checking that follows. This (mis-)interpretation is buttressed by the example in paragraph 9, which reads in significant part:
class A { virtual void f(); };
class B { virtual void g(); };
class D : public virtual A, private B {};
void g() {
D d;
B* bp;
bp = dynamic_cast<B*>(&d); // fails
}
The “fails” comment is identical to the commentary on the lines in the example where the run-time check fails. If the interpretation that paragraph 5 is supposed to apply to all up-casts, presumably this comment should change to “ill-formed,” or the line should be removed from the example altogether.
It should be noted that this interpretation (that the example is ill-formed and the runtime check applies only to down-casts and cross-casts) rejects some programs that could plausibly be accepted and actually work at runtime. For example,
struct B { virtual ~B(); }; struct D: private virtual B { }; void test(D* pd) { B* pb = dynamic_cast<B*>(pd); // #1 } struct D2: virtual B, virtual D {}; void demo() { D2 d2; B* pb = dynamic_cast<B*>(&d2); // #2 test(&d2); // #3 }
According to the interpretation that paragraph 5 applies, line #1 is ill-formed. However, converting from D2 to B (line #2) is well-formed; if the alternate interpretation were applied, the conversion in line #1 could succeed when applied to d2 (line #3).
One final note: the wording in 7.6.1.7 [expr.dynamic.cast] paragraph 8 is incorrect:
The run-time check logically executes as follows:
If, in the most derived object pointed (referred) to by v, v points (refers) to a public base class subobject of a T object, and if only one object of type T is derived from the subobject pointed (referred) to by v the result is a pointer (an lvalue referring) to that T object.
Otherwise, if v points (refers) to a public base class subobject of the most derived object, and the type of the most derived object has a base class, of type T, that is unambiguous and public, the result is a pointer (an lvalue referring) to the T subobject of the most derived object.
Otherwise, the run-time check fails.
All uses of T in this paragraph treat it as if it were a class type; in fact, T is the type to which the expression is being cast and thus is either a pointer type or a reference type, not a class type.
Proposed resolution (June, 2008):
Change 7.6.1.7 [expr.dynamic.cast] paragraph 5 as follows:
...In both the pointer and reference cases,cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2, and B shall be an accessible unambiguous base class of Dthe program is ill-formed if cv2 is greater cv-qualification than cv1 or if B is an inaccessible or ambiguous base class of D.
Change the comment in the example in 7.6.1.7 [expr.dynamic.cast] paragraph 9 as follows:
bp = dynamic_cast<B*>(&d); // fails ill-formed (not a run-time check)
Change 7.6.1.7 [expr.dynamic.cast] paragraph 8 as follows:
TheIf C is the class type to which T points or refers, the run-time check logically executes as follows:
If, in the most derived object pointed (referred) to by v, v points (refers) to a public base class subobject of a
TC object, and if only one object of typeTC is derived from the subobject pointed (referred) to by v the result is a pointer (an lvalue referring) to thatTC object.Otherwise, if v points (refers) to a public base class subobject of the most derived object, and the type of the most derived object has a base class, of type
TC, that is unambiguous and public, the result is a pointer (an lvalue referring) to theTC subobject of the most derived object.Otherwise, the run-time check fails.
[Voted into WP at October, 2009 meeting.]
The current wording of 7.6.1.9 [expr.static.cast] paragraph 9 does not permit conversion of a value of a scoped enumeration type to a floating point type. This was presumably an oversight during the specification of scoped enumerations and should be rectified.
Proposed resolution (July, 2009):
Change 7.6.1.9 [expr.static.cast] paragraph 9 as follows:
A value of a scoped enumeration type (9.8.1 [dcl.enum]) can be explicitly converted to an integral type. The value is unchanged if the original value can be represented by the specified type. Otherwise, the resulting value is unspecified. A value of a scoped enumeration type can also be explicitly converted to a floating point type; the result is the same as that of converting from the original value to the floating point type.
[Voted into the WP at the March, 2009 meeting.]
For years I've noticed that people will write code like this to get the address of an object's bytes:
void foo(long* p) { char* q = reinterpret_cast<char*>(p); // #1 // do something with the bytes of *p by using q }
When in fact the only portable way to do it according to the standard is:
void foo(long* p) { char* q = static_cast<char*>(static_cast<void*>(p)); // #2 // do something with the bytes of *p by using q }
I thought reinterpret_cast existed so that vendors could provide some weird platform-specific things. However, recently Peter Dimov pointed out to me that if we substitute a class type for long above, reinterpret_cast is required to work as expected by 11.4 [class.mem] paragraph 18:
A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa.
So there isn't a whole lot of flexibility to do something different and useful on non-class types. Are there any implementations for which #1 actually fails? If not, I think it would be a good idea to nail reinterpret_cast down so that the standard says it does what people (correctly) think it does in practice.
Proposed resolution (March, 2008):
Change 7.6.1.10 [expr.reinterpret.cast] paragraph 7 as indicated:
A pointer to an object can be explicitly converted to a pointer to an object of different type. When an rvalue v of type “pointer to T1” is converted to the type “pointer to cv T2,” the result is static_cast<cv T2*>(static_cast<cv void*>(v)) if both T1 and T2 are standard-layout types (6.8 [basic.types]) and the alignment requirements of T2 are no stricter than those of T1.Except that cConverting an rvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types 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, t. The result of any other suchapointer conversion is unspecified.
[Voted into WP at March, 2010 meeting.]
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, 7.6.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 6.7.2 [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 (6.9.1 [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 7.6.2.2 [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 (6.7.1 [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:...
[Voted into WP at March, 2010 meeting.]
The note in 7.6.1.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 7.6.1.10 [expr.reinterpret.cast] paragraph 2 as follows:
The reinterpret_cast operator shall not cast away constness (7.6.1.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 7.6.1.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...
[Voted into WP at October, 2009 meeting.]
Both const_cast (7.6.1.11 [expr.const.cast] paragraph 1) and reinterpret_cast (7.6.1.10 [expr.reinterpret.cast] paragraph 1) say,
If T is an lvalue reference type, the result is an lvalue; otherwise, the result is an rvalue and the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the expression v.
This introduces a contradiction in the text. According to 7.6.1.11 [expr.const.cast] paragraph 4,
The result of a reference const_cast refers to the original object.
However, the lvalue-to-rvalue conversion applied to the operand when the target is an rvalue reference type creates a temporary if the operand has class type (7.3.2 [conv.lval] paragraph 2), meaning that the result will not refer to the original object but to the temporary.
A similar problem exists for reinterpret_cast: according to 7.6.1.10 [expr.reinterpret.cast] paragraph 11,
a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators (and similarly for reinterpret_cast<T&&>(x)). The result refers to the same object as the source lvalue, but with a different type.
Here the issue is that the unary & operator used in the description requires an lvalue, but the lvalue-to-rvalue conversion is applied to the operand when the target is an rvalue reference type.
It would seem that the lvalue-to-rvalue conversion should not be applied when the target of the cast is an rvalue reference type.
Proposed resolution (July, 2009):
Change 7.6.1.10 [expr.reinterpret.cast] paragraph 1 as follows:
The result of the expression reinterpret_cast<T>(v) is the result of converting the expression v to type T. If T is an lvalue reference type, the result is an lvalue; if T is an rvalue reference type, the result is an rvalue; otherwise, the result is an rvalue and the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on thetheexpression v. Conversions that can be performed explicitly using reinterpret_cast are listed below. No other conversion can be performed explicitly using reinterpret_cast.
Change 7.6.1.11 [expr.const.cast] paragraph 1 as follows:
The result of the expression const_cast<T>(v) is of type T. If T is an lvalue reference type, the result is an lvalue; if T is an rvalue reference type, the result is an rvalue; otherwise, the result is an rvalue and the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the expression v. Conversions that can be performed explicitly using const_cast are listed below. No other conversion shall be performed explicitly using const_cast.
[Voted into WP at October, 2009 meeting.]
The rules in 7.6.1.11 [expr.const.cast] paragraphs 8 and following, defining “casting away constness,” do not cover a cast to an rvalue reference type.
Proposed resolution (September, 2009):
Change 7.6.1.11 [expr.const.cast] paragraph 9 as follows:
Casting from an lvalue of type T1 to an lvalue of type T2 usingaan lvalue reference cast, or casting from an expression of type T1 to an rvalue of type T2 using an rvalue reference cast, casts away constness if a cast from an rvalue of type “pointer to T1” to the type “pointer to T2” casts away constness.
[Voted into WP at March, 2010 meeting.]
7.6.1.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 7.6.1.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, anexpressionlvalue 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.
[Voted into WP at March, 2010 meeting.]
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. 7.6.2.2 [expr.unary.op] paragraph 3 should have language similar to 7.6.1.5 [expr.ref] paragraph 5 and should be mentioned in the note in 6.5.2 [class.member.lookup] paragraph 13.
Proposed resolution (October, 2009):
Change 7.6.2.2 [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 (6.5.2 [class.member.lookup]) of the class designated by the nested-name-specifier of the qualified-id....
Change 6.5.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 (7.3.13 [conv.mem], 7.6.1.5 [expr.ref], 7.6.2.2 [expr.unary.op], 11.8.3 [class.access.base]). —end note] [Example:...
[Voted into WP at October, 2009 meeting.]
There is no reason for the prohibition of using sizeof on “an enumeration type before all its enumerators have been declared” (7.6.2.5 [expr.sizeof] paragraph 1) if the underlying type of the enumeration is fixed.
Proposed resolution (July, 2009):
Change 7.6.2.5 [expr.sizeof] paragraph 1 as follows:
...The sizeof operator shall not be applied to an expression that has function or incomplete type, or to an enumeration type whose underlying type is not fixed before all its enumerators have been declared, or to the parenthesized name of such types, or to an lvalue that designates a bit-field...
[Voted into WP at October, 2009 meeting.]
7.6.2.6 [expr.alignof] paragraph 1 currently says regarding alignof,
The operand shall be a type-id representing a complete effective object type or a reference to a complete effective object type.
This prohibits taking the alignment of an array type with an unknown bound. There doesn't appear to be any reason for this restriction.
Proposed resolution (July, 2009):
Change 7.6.2.6 [expr.alignof] paragraph 1 as follows:
The operand shall be a type-id representing a complete effective object type or an array thereof or a reference to a complete effective object type.
[Voted into WP at October, 2009 meeting.]
Consider the following code, which uses double-checked locking (DCL):
Widget* Widget::Instance() { if (pInstance == 0) { // 1: first check lock<mutex> hold(mutW); // 2: acquire lock if (pInstance == 0) { // 3: second check pInstance = new Widget(); // 4: create and assign } } // 5: release lock }
We want this to be fully correct when pInstance is an atomic pointer to Widget. To get that result, we have to disallow any assignment to pInstance until after the new object is fully constructed. In other words, we want this to be an invalid transformation of line 4:
pInstance = operator new(sizeof(Widget)); new (pInstance) Widget;
I don't think it would be surprising if this were disallowed. For example, if the constructor were to throw an exception, I think many people would expect the variable not to be modified. I think the question is whether it's sufficiently clearly disallowed.
This could be clarified by stating (somewhere appropriate — probably either in 7.6.2.8 [expr.new] paragraph 16 or paragraph 22) that the initialization of the allocated object is sequenced before the value computation of the new-expression. Then by 7.6.19 [expr.assign] paragraph 1 (“In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.”), the initialization would have to be sequenced before the assignment.
This is probably not a problem for atomic<Widget*> because its operator= is a function, and function calls provide the necessary guarantees. But for the plain pointer assignment case, there's still a question about whether the sequencing of side effects is constrained as tightly as it should be. In fact, you don't even have to throw an exception from the constructor for there to be a question.
struct X { static X* p; X(); }; X* X::p = new X;
When the constructor for X is invoked by this new-expression, would it be valid for X::p to be non-null? If the answer is supposed to be “no,” then I think the Standard should express that intent more clearly.
Proposed resolution (March, 2008):
Change 7.6.2.8 [expr.new] paragraph 22 as indicated:
WhetherInitialization of the allocated object is sequenced before the value computation of the new-expression. It is unspecified whether the allocation function is called before evaluating the constructor arguments or after evaluating the constructor arguments but before entering the constructoris unspecified. It is also unspecified whether the arguments to a constructor are evaluated if the allocation function returns the null pointer or exits using an exception.
[Drafting note: The editor may wish to move paragraph 22 up to immediately follow paragraph 16 or 17. The paragraphs numbered 18-21 deal with the case where deallocation is done because initialization terminates with an exception, whereas paragraph 22 applies more to the initialization itself, described in paragraph 16.]
Notes from the September, 2008 meeting:
The proposed wording does not (but should) allow the call to the allocation function to occur in the middle of evaluating arguments for the constructor call.
Proposed resolution (July, 2009):
Change 7.6.2.8 [expr.new] paragraph 21 as follows:
Whether the allocation function is called before evaluating the constructor arguments or after evaluating the constructor arguments but before entering the constructor is unspecified.The invocation of the allocation function is indeterminately sequenced with respect to the evaluations of expressions in the new-initializer. Initialization of the allocated object is sequenced before the value computation of the new-expression. It isalsounspecified whetherthe arguments to a constructorexpressions in the new-initializer are evaluated if the allocation function returns the null pointer or exits using an exception.
[Drafting note: the editor may wish to consider moving this paragraph to follow paragraph 15 or 16. Paragraphs 17-19 deal with the case where deallocation is done because initialization terminates with an exception, whereas this paragraph applies more to the initialization itself (described in paragraph 15).]
[Voted into WP at October, 2009 meeting.]
The type of an allocated object wih the type specifier auto is determined by the rules of copy initialization, but the initialization applied will be direct initialization. This would affect classes which declare their copy constructor explicit, for instance. For consistency, use the same form of initiailization for the deduction as the new expression.
Proposed resolution (July, 2009):
Change 7.6.2.8 [expr.new] paragraph 2 as follows:
If the auto type-specifier appears in the type-specifier-seq of a new-type-id or type-id of a new-expression, the new-expression shall contain a new-initializer of the form
( assignment-expression )
The allocated type is deduced from the new-initializer as follows: Let
(e) bee be the assignment-expression in the new-initializer and T be the new-type-id or type-id of the new-expression, then the allocated type is the type deduced for the variable x in the invented declaration (9.2.9.7 [dcl.spec.auto]):T
x = ex(e);[Example:...
[Voted into WP at July, 2009 meeting as part of N2932.]
Throwing std::length_error (7.6.2.8 [expr.new] paragraph 7) for an attempt to allocate a too-large array brings in too much of the Standard library. A simpler exception, like std::bad_alloc, should be thrown instead.
Notes from the March, 2009 meeting:
The CWG was in favor of throwing an exception derived from std::bad_alloc. This would be upwardly compatible; it would be harmless for programs that currently catch std::bad_alloc, but would allow programs to treat the calculation overflow case separately if they wish.
[Voted into WP at July, 2009 meeting.]
The requirements for the operand of the delete operators are given in 7.6.2.9 [expr.delete] paragraph 2:
In either alternative, the value of the operand of delete may be a null pointer value. If it is not a null pointer value, in the first alternative (delete object), the value of the operand of delete shall be a pointer to a non-array object or a pointer to a subobject (6.7.2 [intro.object]) representing a base class of such an object (11.7 [class.derived]). If not, the behavior is undefined. In the second alternative (delete array), the value of the operand of delete shall be the pointer value which resulted from a previous array new-expression. If not, the behavior is undefined.
There are no restrictions on the type of a null pointer, only on a pointer that is not null. That seems wrong.
Proposed resolution (June, 2008):
Change 7.6.2.9 [expr.delete] paragraph 1 as follows:
...The operand shall have a pointer to object type, or a class type having a single non-explicit conversion function (11.4.8.3 [class.conv.fct]) to a pointer to object type...
Proposed resolution (September, 2008):
Change 7.6.2.9 [expr.delete] paragraph 1 as follows:
...The operand shall have a pointer to object type, or a class type having a single non-explicit conversion function (12.3.2) to a pointer to object type. [Footnote: This implies that an object cannot be deleted using a pointer of type void* because void is not an object type. —end footnote] ...
Delete the footnote at the end of 7.6.2.9 [expr.delete] paragraph 3:
...if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.[Footnote: This implies that an object cannot be deleted using a pointer of type void* because there are no objects of type void. —end footnote]
[Voted into WP at October, 2009 meeting.]
According to 7.6.7 [expr.shift] paragraph 2,
The value of E1 << E2 is E1 (interpreted as a bit pattern) left-shifted E2 bit positions; vacated bits are zero-filled. If E1 has an unsigned type, the value of the result is E1 multiplied by the quantity 2 raised to the power E2, reduced modulo ULLONG_MAX+1 if E1 has type unsigned long long int, ULONG_MAX+1 if E1 has type unsigned long int, UINT_MAX+1 otherwise.
This specification does not allow for extended types with rank greater than long long; in particular, it says that the value of a shifted unsigned extended type is truncated as if it were the same width as an unsigned int.
It's unclear that the second sentence has any normative value; it might be better to relegate it to a note or omit it than to correct it.
Proposed resolution (July, 2009):
Change 7.6.7 [expr.shift] paragraphs 2-3 as follows:
The value of E1 << E2 is E1
(interpreted as a bit pattern)left-shifted E2 bit positions; vacated bits are zero-filled. If E1 has an unsigned type, the value of the result is E1multiplied by the quantity 2 raised to the power E2× 2E2, reduced moduloULLONG_MAX+1 if E1 has type unsigned long long int, ULONG_MAX+1 if E1 has type unsigned long int, UINT_MAX+1 otherwise. [Note: the constants ULLONG_MAX, ULONG_MAX, and UINT_MAX are defined in the header <climits>. —end note]one more than the maximum value representable in the result type. Otherwise, if E1 has a signed type and nonnegative value, and E1 × 2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.The value of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1
divided by the quantity 2 raised to the power E2/ 2E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.
[Voted into WP at March, 2010 meeting.]
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 (7.3.12 [conv.ptr] paragraph 1), one might expect that 0 would be converted to std::nullptr_t and the two operands would compare equal, but 7.6.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. 7.6.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 7.6.16 [expr.cond].
Either 7.6.9 [expr.rel] and 7.6.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 7.6.16 [expr.cond] should be changed to make those operands well-formed.
Proposed resolution (October, 2009):
Change 7.6.9 [expr.rel] paragraph 2 as follows:
The usual arithmetic conversions are performed on operands of arithmetic or enumeration type. Pointer conversions (7.3.12 [conv.ptr]) and qualification conversions (7.3.6 [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 7.6.16 [expr.cond] bullet 6.3 as follows:
[Voted into WP at October, 2009 meeting.]
Consider the following example:
template <typename T> const T* f(bool b) { static T t1 = T(); static const T t2 = T(); return &(b ? t1 : t2); // error? }
According to 7.6.16 [expr.cond], this function is well-formed if T is a class type and ill-formed otherwise. If the second and third operands of a conditional expression are lvalues of the same class type except for cv-qualification, the result of the conditional expression is an lvalue; if they are lvalues of the same non-class type except for cv-qualification, the result is an rvalue.
This difference seems gratuitous and should be removed.
Proposed resolution (June, 2009):
Change 7.6.16 [expr.cond] paragraph 3 as follows:
Otherwise, if the second and third operand have different types, and either has (possibly cv-qualified) class type, or if both are lvalues of the same type except for cv-qualification, an attempt is made to convert each of those operands to the type of the other. The process for determining whether an operand expression E1 of type T1 can be converted to match an operand expression E2 of type T2 is defined as follows:
If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (7.3 [conv]) to the type “lvalue reference to T2”, subject to the constraint that in the conversion the reference must bind directly (9.5.4 [dcl.init.ref]) to E1.
If E2 is an rvalue, or if the conversion above cannot be done and at least one of the operands has (possibly cv-qualified) class type:
...
[Voted into the WP at the March, 2009 meeting.]
There appear to be two different specifications for when aliasing is permitted. One is in 7.2.1 [basic.lval] paragraph 15:
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
the dynamic type of the object,
a cv-qualified version of the dynamic type of the object,
a type similar (as defined in 7.3.6 [conv.qual]) to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union),
a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
a char or unsigned char type.
There is also a much more restrictive specification in 7.6.19 [expr.assign] paragraph 8:
If the value being stored in an object is accessed from another object that overlaps in any way the storage of the first object, then the overlap shall be exact and the two objects shall have the same type, otherwise the behavior is undefined.
This affects, for example, the definedness of operations on union members: when may a value be stored into one union member and accessed via another.
It should be noted that this conflict existed in C90 and is unchanged in C99 (see, for example, section 6.5 paragraph 7 and section 6.5.16.1 paragraph 3 of ISO/IEC 9899:1999, which directly parallel the sections cited above).
Notes from the October, 2006 meeting:
This issue is based on a misunderstanding of the intent of the wording in 7.6.19 [expr.assign] paragraph 8. Instead of being a general statement about aliasing, it's describing the situation in which the source of the value being assigned is storage that overlaps the storage of the target object. The proposed resolution should make that clearer rather than changing the specification.
Proposed resolution (June, 2008):
Add the following note at the end of 7.6.19 [expr.assign] paragraph 8:
If the value being stored in an object is accessed from another object that overlaps in any way the storage of the first object, then the overlap shall be exact and the two objects shall have the same type, otherwise the behavior is undefined. [Note: This restriction applies to the relationship between the left and right sides of the assignment operation; it is not a statement about how the target of the assignment may be aliased in general. See 7.2.1 [basic.lval]. —end note]
[Voted into WP at October, 2009 meeting.]
7.6.19 [expr.assign] paragraph 9 has the following example:
complex<double> z; z = { 1,2 }; // meaning z.operator=(1,2) z += { 1, 2 }; // meaning z.operator+=(1,2)
These comments make it look as if the assignment operator takes two arguments, which is obviously not the case. It would be better if the comments read something like
// meaning z.operator=(complex<double>(1,2))
or even
// meaning z.operator=({1,2}), resolves to // z.operator=(complex<double>(1,2)
Proposed resolution (July, 2009):
Change the example in 7.6.19 [expr.assign] paragraph 9 as follows:
[Example:
complex<double> z; z = { 1,2 }; // meaning z.operator=({1,2}) z += { 1, 2 }; // meaning z.operator+=({1,2}) int a, b; a = b = { 1 }; // meaning a=b=1; a = { 1 } = b; // syntax error—end example]
[Voted into the WP at the March, 2009 meeting.]
It was the intention of the constexpr proposal that implementations be required to evaluate floating-point expressions at compile time. This intention is not reflected in the actual wording of 7.7 [expr.const] paragraph 2, bullet 5:
This restriction has the effect of forbidding the use of floating-point expressions in integral constant expressions.
Proposed resolution (June, 2008):
Delete bullet 6 of 7.7 [expr.const] paragraph 2:
Notes from the June, 2008 meeting:
The CWG agreed with the intent of this issue, that floating-point calculations should be permitted in constant expressions, but acknowledged that this opens the possibility of differing results between compile time and run time. Such issues should be addressed non-normatively, e.g., via a “recommended practice” note like that of C99's 6.4.4.2 or in a technical report.
Proposed resolution (August, 2008):
Delete bullet 6 of 7.7 [expr.const] paragraph 2:
Add a new paragraph after 7.7 [expr.const] paragraph 3:
[Note: Although in some contexts constant expressions must be evaluated during program translation, others may be evaluated during program execution. Since this International Standard imposes no restrictions on the accuracy of floating-point operations, it is unspecified whether the evaluation of a floating-point expression during translation yields the same result as the evaluation of the same expression (or the same operations on the same values) during program execution. [Footnote: Nonetheless, implementations are encouraged to provide consistent results, irrespective of whether the evaluation was actually performed during translation or during program execution. —end footnote] [Example:
bool f() { char array[1 + int(1 + 0.2 - 0.1 - 0.1)]; // Must be evaluated during translation int size = 1 + int(1 + 0.2 - 0.1 - 0.1); // May be evaluated at runtime return sizeof(array) == size; }It is unspecified whether the value of f() will be true or false. —end example] —end note]
[Voted into WP at October, 2009 meeting.]
Bullet 12 of paragraph 2 of 7.7 [expr.const] says,
a class member access (7.6.1.5 [expr.ref]) unless its postfix-expression is of effective literal type or of pointer to effective literal type;
This wording needs to be clearer that the “effective literal type” provision applies only to the . form of member access and the “pointer to effective literal type” applies only to the -> form.
Proposed resolution (March, 2009):
Delete 7.7 [expr.const] bullet 2.11:
[Voted into WP at October, 2009 meeting.]
7.7 [expr.const] paragraph 2 allows an lvalue-to-rvalue conversion in a constant expression if it is applied to “an lvalue of effective integral type that refers to a non-volatile const variable or static data member initialized with constant expressions.” However, this does not require, as it presumably should, that the initialization occur in the same translation unit and precede the constant expression, nor that the static data member be initialized within the member-specification of its class.
Proposed resolution (March, 2009):
Change 7.7 [expr.const] paragraph 2, bullet 4, sub-bullet 1 as follows:
an lvalue of effective integral type that refers to a non-volatile const variable with a preceding initialization or to a non-volatile const static data member with an initialization in the class definition (11.4.9.3 [class.static.data]), in either case initialized with constant expressions, or
Additional note, June, 2009:
It has been suggested that the requirement that a static data member be initialized in the class definition is not actually needed but that static data members should be treated like other variable declarations -- a preceding definition with initialization should be sufficient. That is, given
extern const int i; const int i = 5; struct S { static const int j; }; const int S::j = 5; int a1[i]; int a2[S::j];
there doesn't appear to be a good rationale for making a1 well-formed and a2 ill-formed. Some major implementations accept the declaration of a2 without error.
Proposed resolution (July, 2009):
Change 7.7 [expr.const] paragraph 2, bullet 4, sub-bullet 1 as follows:
[Voted into WP at October, 2009 meeting.]
According to 7.7 [expr.const] paragraph 2, bullet 4, sub-bullet 1, a non-volatile const variable or static data member initialized with constant expressions can be used in an integral constant expression only if it is “of effective integral type.” Unscoped enumeration types should also be accepted in such contexts.
Proposed resolution (September, 2009):
Change 7.7 [expr.const] paragraph 2, bullet 4, sub-bullet 1 as indicated:
an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) unless it is applied to
an lvalue of effective integral or enumeration type that refers to a non-volatile const variable or static data member initialized with constant expressions, or
...
[Voted into WP at March, 2010 meeting as part of document N3078.]
7.7 [expr.const] paragraph 2 prohibits the unary & operator and an array-to-pointer conversion on operands with automatic and thread storage duration, but operands with dynamic storage duration are apparently allowed. Both these operations should be allowed only on operands with static storage duration.
[Voted into the WP at the March, 2009 meeting.]
The grammar in 9.1 [dcl.pre] paragraph 1 says that a declaration-seq is either declaration or declaration-seq declaration. Some declarations end with semicolons and others (e.g. function definitions and namespace declarations) don't. This means that users who put a semicolon after every declaration are technically writing ill-formed code. The trouble is that in this respect the standard is out of sync with reality. It's convenient to allow semicolons after every declaration, and there's no implementation difficulty in doing so. All existing compilers accept this, except in extra-pedantic mode. When all implementations disagree with the standard, it's time for the standard to change.
Suggested resolution:
In the grammar in 9.1 [dcl.pre] paragraph 11, change the second line in the definition of declaration-seq to
Proposed resolution (October, 2006):
Add the indicated lines to the grammar definitions in 9.1 [dcl.pre] paragraph 1:
declaration:
...
namespace-definition
empty-declaration
...
static_assert-declaration:
static_assert ( constant-expression , string-literal ) ;
empty-declaration:
;
Add the following as a new paragraph after 9.1 [dcl.pre] paragraph 4:
An empty-declaration has no effect.
[Voted into WP at March, 2010 meeting.]
According to 9.2 [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 9.2 [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 declarationIf 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:...
[Voted into WP at October, 2009 meeting.]
The current wording unintentionally restricts the use of the thread_local specifier in two contexts: block-scope extern variable declarations and static data members. These restrictions are in conflict with 9.2.2 [dcl.stc] paragraph 1.
Proposed resolution (July, 2009):
Change 9.2.2 [dcl.stc] paragraph 4 as follows:
The thread_local specifier shall be applied only to the names of objects or references of namespace scopeand, to the names of objects or references of 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 (6.7.6.3 [basic.stc.thread]).
[Voted into WP at October, 2009 meeting.]
The register keyword serves very little function, offering no more than a hint that a note says is typically ignored. It should be deprecated in this version of the standard, freeing the reserved name up for use in a future standard, much like auto has been re-used this time around for being similarly useless.
Notes from the March, 2009 meeting:
The consensus of the CWG was in favor of deprecating register.
Proposed resolution (September, 2009):
Change 9.2.2 [dcl.stc] paragraph 3 as follows:
A register specifier is a hint to the implementation that the object so declared will be heavily used. [Note: the hint can be ignored and in most implementations it will be ignored if the address of the object is taken. This use is deprecated (see [depr.register]). —end note]
Add a new section following _N3000_.D.4 [depr.string]:
register keyword [depr.register]
The use of the register keyword as a storage-class-specifier is deprecated (see 9.2.2 [dcl.stc]).
[Voted into WP at March, 2010 meeting.]
According to 9.2.2 [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 9.2.2 [dcl.stc] paragraph 4 as follows:
The thread_local specifier indicates that the named entity has thread storage duration (6.7.6.3 [basic.stc.thread]). It shall be applied only to the names of objects or references of namespacescope, to the names of objects or references ofor block scopethat 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 (6.7.6.3 [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.
[Voted into WP at October, 2009 meeting.]
9.2.2 [dcl.stc] paragraph 1 refers to “global anonymous unions.” This reference should include anonymous unions declared in a named namespace, not just in global scope (cf 11.5 [class.union] paragraph 3).
Proposed resolution (September, 2009):
Change 9.2.2 [dcl.stc] paragraph 1 as follows:
If a storage-class-specifier appears in a decl-specifier-seq, there can be no typedef specifier in the same decl-specifier-seq and the init-declarator-list of the declaration shall not be empty (except forglobalan anonymous unionsdeclared in a named namespace or in the global namespace, which shall be declared static (9.5))...
[Voted into WP at March, 2010 meeting.]
9.2.3 [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 9.2.3 [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.
[Voted into the WP at the March, 2009 meeting.]
9.2.4 [dcl.typedef] paragraph 1 says,
The typedef specifier shall not be used in a function-definition (9.6 [dcl.fct.def])...
Does this mean that the following is ill-formed?
void f() { typedef int INT; }
Proposed resolution (March, 2008):
Change 9.2.4 [dcl.typedef] paragraph 1 as follows:
...The typedef specifiershall not be used in a function-definition (9.6 [dcl.fct.def]), and itshall not be combined in a decl-specifier-seq with any other kind of specifier except a type-specifier, and it shall not be used in the declaration of a function parameter nor in the decl-specifier-seq of a function-definition (9.6 [dcl.fct.def])...
Proposed resolution (September, 2008):
Change 9.2.4 [dcl.typedef] paragraph 1 as follows:
...The typedef specifiershall not be used in a function-definition (9.6 [dcl.fct.def]), and itshall not be combined in a decl-specifier-seq with any other kind of specifier except a type-specifier, and it shall be used neither in the decl-specifier-seq of a parameter-declaration (9.3.4.6 [dcl.fct]) nor in the decl-specifier-seq of a function-definition (9.6 [dcl.fct.def]).
[Voted into WP at October, 2009 meeting.]
According to 9.2.6 [dcl.constexpr] paragraph 1,
The constexpr specifier shall be applied only to the definition of an object, function, or function template, or to the declaration of a static data member of a literal type (6.8 [basic.types]).
As a result, a constexpr member function cannot be simply declared in the class member-specification and defined later; it must be defined in its initial declaration.
This restriction was not part of the initial proposal but was added during the CWG review. However, the original intent is still visible in some of the wording in 9.2.6 [dcl.constexpr]. For example, paragraph 2 refers to applying the constexpr specifier to the “declaration” and not the “definition” of a function or constructor. Although that is formally correct, as definitions are also declarations, it could be confusing. Also, the example in paragraph 6 reads,
class debug_flag { public: explicit debug_flag(bool); constexpr bool is_on(); // error: debug_flag not literal type ...
when the proximate error is that is_on is only declared, not defined. There are also many occurrences of the constexpr specifier in the library clauses where the member function is only declared, not defined.
It's not clear how much simplification is gained by this restriction. There are reasons for defining ordinary inline functions outside the class member-specification (reducing the size and complexity of the class definition, separating interface from implementation, making the editing task easier if program evolution results in an inline function being made non-inline, etc.) that would presumably apply to constexpr member functions as well. It seems feasible to allow separate declaration and definition of a constexpr function; it would simply not be permitted to use it in a constant expression before the definition is seen (although it could presumably still be used in non-constant expressions in that region, like an ordinary inline function).
If the prohibition were relaxed to allow separate declaration and definition of constexpr member functions, some questions would need to be answered, such as whether the constexpr specifier must appear on both declaration and definition (the inline specifier need not). If it can be omitted in one or the other, there's a usability issue regarding the fact that constexpr implies const; the const qualifier would need to be specified explicitly in the declaration in which constexpr was omitted.
If the current restriction is kept, the library clauses should state in an introduction that a non-defining declaration of a constexpr member function should be considered “for exposition only” and not literal code.
Notes from the September, 2008 meeting:
In addition to the original issues described above, the question has arisen whether recursive constexpr functions are or should be permitted. Although they were originally desired by the proposers of the feature, they were prohibited out of an abundance of caution. However, the wording that specified the prohibition was changed during the review process, inadvertently permitting them.
The CWG felt that there are sufficient use cases for recursion that it should not be forbidden (although a new minimum for recursion depth should be added to Annex Clause Annex B [implimits]). If mutual recursion is to be allowed, forward declaration of constexpr functions must also be permitted (answering the original question in this issue). A call to an undefined constexpr function in the body of a constexpr function should be diagnosed when the outer constexpr function is invoked in a context requiring a constant expression; in all other contexts, a call to an undefined constexpr function should be treated as a normal runtime function call, just as if it had been invoked with non-constant arguments.
Proposed resolution (July, 2009):
Change Clause 7 [expr] paragraph 4 as follows:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expression appears where an integral constant expression is required (7.7 [expr.const]), in which case the program is ill-formed. [Note: most existing implementations of C++ ignore integer overflows. Treatment of division by zero, forming a remainder using a zero divisor, and all floating point exceptions vary among machines, and is usually adjustable by a library function. —end note]
Add the indicated text to 7.7 [expr.const] paragraph 2:
...
an invocation of a function other than a constexpr function or a constexpr constructor [Note: overload resolution (12.2 [over.match]) is applied as usual —end note];
a direct or indirect invocation of an undefined constexpr function or an undefined constexpr constructor outside the definition of a constexpr function or a constexpr constructor;
a result that is not mathematically defined or not in the range of representable values for its type;
...
Change 9.2.6 [dcl.constexpr] paragraph 1 as follows:
The constexpr specifier shall be applied only to the definition of an object, the declaration of a function
,or function template, ortothe declaration of a static data member of an effective literal type (6.8 [basic.types]). If any declaration of a function or function template has the constexpr specifier, then all its declarations shall contain the constexpr specifier. [Note: An explicit specialization can differ from the template declaration with respect to the constexpr specifier. —end note] [Note: function parameters cannot be declared constexpr. —end note] [Example:constexpr int square(int x); //OK, declarationconstexpr int square(int x) { // OK return x * x; }constexpr int bufsz = 1024; // OK, definition constexpr struct pixel { // error: pixel is a type int x; int y; constexpr pixel(int); // OK, declaration }; constexpr pixel::pixel(int a) : x(square(a)), y(square(a)) { } //OK, definition constexpr pixel small(2); // error: square not defined, so small(2) // not constant (7.7 [expr.const]), so constexpr not satisfied constexpr int square(int x) { // OK, definition return x * x; } constexpr pixel large(4); // OK, square defined int next(constexpr int x) { // error, not for parameters return x + 1; } extern constexpr int memsz; // error: not a definition—end example]
Add a new section following 16.4.6.5 [member.functions]:
Implementations shall provide definitions for any non-defining declarations of constexpr functions and constructors within the associated header files.
Add the following bullet to the list in Clause Annex B [implimits] paragraph 2:
Nested external specifications [1 024].
Recursive constexpr function invocations [512].
...
(This resolution also resolves issue 695.)
[Voted into WP at March, 2010 meeting as document N3078.]
It would be useful if constexpr functions and constructors could take
arguments via reference-to-const parameters.
[Voted into WP at March, 2010 meeting.]
The normative text in 9.2.9.2 [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 (7.7 [expr.const]).
These two sentences parallel the specifications of 9.2.2 [dcl.stc] paragraph 7 and 7.7 [expr.const]. However, the passages are not identical, leading to questions about whether the meanings are the same.
Proposed resolution (October, 2009):
Change 9.2.9.2 [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 (7.7 [expr.const]).[Note: Declaring a variable const can affect its linkage (9.2.2 [dcl.stc]) and its usability in constant expressions (7.7 [expr.const]). Asasdescribed in 9.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]
[Voted into WP at March, 2010 meeting as document N3049.]
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.
[Voted into WP at March, 2010 meeting.]
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 9.2.4 [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 13.4.2 [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 9.3.4.3 [dcl.ref]:
If a typedef (9.2.4 [dcl.typedef]), a type template-parameter (13.4.2 [temp.arg.type]), or a decltype-specifier (9.2.9.3 [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]
[Voted into WP at March, 2010 meeting.]
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 9.2.9.5 [dcl.type.elab] paragraph 1, #1 associates the attr attribute with class X for all subsequent references. On the other hand, 9.3.4 [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 6.4.2 [basic.scope.pdecl] bullet 6.1 as follows:
for a declaration of the form
the identifier is declared...
Change 6.5.6 [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 9.2.9.5 [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 (13.9.4 [temp.expl.spec]), an explicit instantiation (13.9.3 [temp.explicit]) or it has one of the following forms:
class-key attribute-specifieropt identifier
attribute-specifieropt;
...
Change the grammar in 9.8.1 [dcl.enum] paragraph 1 as follows:
Change the grammar in Clause 11 [class] paragraph 1 as follows:
[Voted into WP at March, 2010 meeting.]
The auto specifier can be used only in certain contexts, as specified in 9.2.9.7 [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 (8.4 [stmt.block]), in namespace scope (6.4.6 [basic.scope.namespace]), and in a for-init-statement (8.6.4 [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 9.2.9.7 [dcl.spec.auto] paragraph 3 as follows:
...Theauto 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.
[Voted into WP at July, 2009 meeting.]
One effect of the initializer-list proposal is that now we allow
auto x = { 1, 2, 3 }; // decltype(x) is std::initializer_list<int>
but not
auto ar[3] = { 1, 2, 3 }; // ill-formed
This seems unfortunate, as the code for the first could also support the second. Incidentally, I also failed to update the text in 9.2.9.7 [dcl.spec.auto] paragraph 3 which forbids the use of auto with braced-init-lists, so technically the first form above is currently ill-formed but has defined semantics.
Bjarne Stroustrup:
Is this the thin edge of a wedge? How about
vector<auto> v = { 1, 2, 3 };
and
template<class T> void f(vector<T>& v); f({1, 2, 3 });
(See also issue 625.)
Proposed resolution (March, 2009):
Change 9.2.9.7 [dcl.spec.auto] paragraph 3 as follows:
...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 )
[Drafting note: This change does not address the original issue of the inability to use auto with an array initializer, only the secondary issue of permitted the braced-init-list. The CWG explicitly decided not to support the array case.]
[Voted into WP at July, 2009 meeting.]
In listing the acceptable contexts in which the auto specifier may appear, 9.2.9.7 [dcl.spec.auto]) paragraph 4 mentions “the type-specifier-seq in a new-type-id” but not the type-id in the parenthesized form; that is, new auto (42) is well-formed but new (auto) (42) is not. This seems an unnecessary restriction, as well as contradicting 7.6.2.8 [expr.new] paragraph 2:
If the auto type-specifier appears in the type-specifier-seq of a new-type-id or type-id of a new-expression...
(See also issue 496.)
Proposed resolution (March, 2009):
Change 9.2.9.7 [dcl.spec.auto] paragraph 4 as follows:
The auto type-specifier can also be used in declaring an object in the condition of a selection statement (8.5 [stmt.select]) or an iteration statement (8.6 [stmt.iter]), in the type-specifier-seq inathe new-type-id or type-id of a new-expression (7.6.2.8 [expr.new]), in a for-range-declaration...
[Voted into WP at March, 2010 meeting.]
9.2.9.7 [dcl.spec.auto] paragraph 6 says,
Once the type of a declarator-id has been determined according to 9.3.4 [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 (9.5.5 [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 (13.10.3.2 [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. 13.10.3.2 [temp.deduct.call] uses the term “deduced A,” and that usage should be repeated here.
Proposed resolution (October, 2009):
Change 9.2.9.7 [dcl.spec.auto] paragraph 6 as follows:
...The type deduced for the variable d is then the deducedtypeA determined using the rules of template argument deduction...
[Voted into the WP at the July, 2009 meeting as part of N2927.]
It is currently unspecified whether a declaration like
f() -> struct S { };
should be parsed as a declaration of f whose return type is a class definition (which will be ill-formed according to 9.2.9 [dcl.type] paragraph 3) or as a definition of f whose return type is an elaborated-type-specifier.
Proposed resolution (June, 2009):
See document PL22.16/09-0117 = WG21 N2927.
In function, pointer, and pointer-to-member declarators, the attribute-specifier appertains to the type being declared, but the syntax has the attribute-specifieropt appearing before the full type is seen — i.e., before the cv-qualifier-seqopt and, for the function case, before the ref-qualifieropt. GNU attributes appear after these elements (and, for the function case, after the exception-specificationopt as well). It would be better, both logically and for consistency with existing practice, to move the attribute-specifieropt accordingly.
[Voted into WP at March, 2010 meeting as document N3064.]
This case is nonstandard by 9.3.4 [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 9.3.4 [dcl.meaning] as follows:
...A declarator-id shall not be qualified except for the definition of a member function (11.4.2 [class.mfct]) or static data member (11.4.9 [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 ofa previously declaredan explicit specialization outside of its namespace, or the declaration of a friend function that is a member of another class or namespace (11.8.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 (9.9.2 [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 13.9.4 [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 (9.9.2 [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 (_N4868_.9.8.2.3 [namespace.memdef]).A declaration of a function template or class template being explicitly specialized shall
be in scope at the point ofprecede the declaration ofanthe explicit specialization. [Note: a declaration, but not a definition of the template is required. —end note] The definition of a class or class template shallbe in scope at the point ofprecede 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 ofpreced 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 ( 11.4.4 [special]), the program is ill-formed.
[Voted into WP at March, 2010 meeting as part of document N3079.]
According to 9.3.4 [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 (9.9.2 [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 9.3.4 [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 namespacewithin that scopeset of that namespace (9.9.2 [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.)
[Voted into WP at March, 2010 meeting.]
Paragraph 7 of 9.3.4.5 [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 7 [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 9.3.4.5 [dcl.array] into a note and make the indicated changes:
[Note: Except where it has been declared for a class (12.4.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 (7.3.3 [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]
[Voted into WP at October, 2009 meeting.]
7.3.6 [conv.qual] paragraph 3 consists of a note reading,
[Note: Function types (including those used in pointer to member function types) are never cv-qualified (9.3.4.6 [dcl.fct]). —end note]
However, 9.3.4.6 [dcl.fct] paragraph 7 says,
A cv-qualifier-seq shall only be part of the function type...
This sounds like a contradiction, although formally it is not: a “function type with a cv-qualifier-seq” is not a “cv-qualified function type.” It would be helpful to make this distinction clearer.
Proposed resolution (March, 2009):
Change 9.3.4.6 [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. [Note: A function type that has a cv-qualifier-seq is not a cv-qualified type; there are no cv-qualified function types. —end note] The effect of a cv-qualifier-seq in a function declarator...
Change 6.8.5 [basic.type.qualifier] paragraph 3 as follows:
...See 9.3.4.6 [dcl.fct] and _N4868_.11.4.3.2 [class.this] regardingcv-qualifiedfunction types that have cv-qualifiers.
[Voted into WP at March, 2010 meeting as part of document N3079.]
9.3.4.6 [dcl.fct] paragraph 13 requires that a parameter pack, if present, must appear at the end of the parameter list. This restriction is not necessary when template argument deduction is not needed and is inconsistent with the way pack expansions are handled. It should be removed.
(See also issue 692.)
[Voted into WP at March, 2010 meeting.]
According to 6.4.4 [basic.scope.param] paragraph 1,
In a function declaration, or in any function declarator except the declarator of a function definition (9.6 [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 9.3.4.6 [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 (6.4 [basic.scope]). —end note]
Proposed resolution (February, 2010):
Change the note in 9.3.4.6 [dcl.fct] 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 ofthe parameter-declaration-clause since it goes out of scope at the end of the function declarator (6.4 [basic.scope])its function declarator because that is the extent of its potential scope (6.4.4 [basic.scope.param]). —end note]
[Voted into WP at March, 2010 meeting.]
9.3.4.7 [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 13.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 9.3.4.7 [dcl.fct.default] paragraph 4:
...In a given function declaration,alleach parameterssubsequent to a parameter with a default argument shall have a default argumentssupplied in this or a previous declarationsor shall be a function parameter pack. A default argument...
According to 9.5 [dcl.init] paragraph 5,
To zero-initialize an object of type T means:
...
if T is a reference type, no initialization is performed.
However, a reference is not an object, so this makes no sense.
Proposed resolution (March, 2010):
This issue is resolved by the resolution of issue 633 in document N2993.
[Voted into WP at March, 2010 meeting.]
9.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 (6.9.3.2 [basic.start.static] paragraph 2).
Proposed resolution (November, 2009):
Change 9.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-statican object with automatic or dynamic storage duration has indeterminate value. [Note: objects with static or thread storage duration are zero-initialized, see 6.9.3.2 [basic.start.static]. —end note].
[Voted into WP at March, 2010 meeting.]
The current wording of 9.5.2 [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 9.5.2 [dcl.init.aggr] paragraph 1 as follows:
An aggregate is an array or a class (Clause 11 [class]) with no user-provided constructors (11.4.5 [class.ctor]), no brace-or-equal-initializers for non-static data members (11.4 [class.mem]), no private or protected non-static data members (11.8 [class.access]), no base classes (11.7 [class.derived]), and no virtual functions (11.7.3 [class.virtual]).
[Voted into WP at October, 2009 meeting.]
The current specification of string initialization in 9.5.3 [dcl.init.string] leaves uninitialized all characters following the terminating '\0' of a character array with automatic storage duration. This is different from C99, in which string initialization is handled like aggregate initialization and all trailing characters are zeroed (6.7.8 paragraph 21).
(See also issue 694, in which we are considering following C99 in a somewhat similar case of zero-initializing trailing data.)
Proposed resolution (September, 2009):
Add a new paragraph following 9.5.3 [dcl.init.string] paragraph 2:
There shall not be more initializers than there are array elements. [Example:
char cv[4] = "asdf"; // error
is ill-formed since there is no space for the implied trailing '\0'. —end example]
If there are fewer initializers than there are array elements, then each element not explicitly initialized shall be zero-initialized (9.5 [dcl.init]).
[Voted into WP at October, 2009 meeting.]
9.5.3 [dcl.init.string] paragraph 1 says,
A char array (whether plain char, signed char, or unsigned char), char16_t array, char32_t array, or wchar_t array can be initialized by a string-literal (optionally enclosed in braces) with no prefix, with a u prefix, with a U prefix, or with an L prefix, respectively...
This formulation does not allow for raw and UTF-8 literals.
Proposed resolution (July, 2009):
Change 9.5.3 [dcl.init.string] paragraph 1 as follows:
A char array (whether plain char, signed char, or unsigned char), char16_t array, char32_t array, or wchar_t array can be initialized by astring-literal (optionally enclosed in braces) with no prefix, with a u prefix, with a U prefix, or with an L prefixnarrow character literal, char16_t string literal, char32_t string literal, or wide string literal, respectively; successive, or by an appropriately-typed string literal enclosed in braces. Successive characters of thestring-literalvalue of the string literal initialize thememberselements of the array. [Example: ...
[Voted into WP at October, 2009 meeting.]
The resolutions of issues 391 and 450 say that the reference is “bound to” the class or array rvalue, but it does not say that the reference “binds directly” to the initializer, as it does for the cases that fall under the first bullet in 9.5.4 [dcl.init.ref] paragraph 5. However, this phrasing is important in determining the implicit conversion sequence for an argument passed to a parameter with reference type (12.2.4.2.5 [over.ics.ref]), where paragraph 2 says,
When a parameter of reference type is not bound directly to an argument expression, the conversion sequence is the one required to convert the argument expression to the underlying type of the reference according to 12.2.4.2 [over.best.ics]. Conceptually, this conversion sequence corresponds to copy-initializing a temporary of the underlying type with the argument expression.
The above-mentioned issue resolutions stated that no copy is to be made in such reference initializations, so the determination of the conversion sequence does not reflect the initialization semantics.
Simply using the “binds directly” terminology in the new wording may not be the right approach, however, as there are other places in the Standard that also give special treatment to directly-bound references. For example, the first bullet of 7.6.16 [expr.cond] paragraph 3 says,
If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (7.3 [conv]) to the type “reference to T2,” subject to the constraint that in the conversion the reference must bind directly (9.5.4 [dcl.init.ref]) to E1.
The effect of simply saying that a reference “binds directly” to a class rvalue can be seen in this example:
struct B { };
struct D: B { };
D f();
void g(bool x, const B& br) {
x ? f() : br; // result would be lvalue
}
It is not clear that treating this conditional expression as an lvalue is a desirable outcome, even if the result of f() were to “bind directly” to the const B& reference.
Proposed resolution (June, 2009):
Change 9.5.4 [dcl.init.ref] paragraph 5 as follows:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
If the reference is an lvalue reference and the initializer expression
is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an lvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3” (this conversion is selected by enumerating the applicable conversion functions (12.2.2.7 [over.match.ref]) and choosing the best one through overload resolution (12.2 [over.match])),
then the reference is bound
directlyto the initializer expression lvalue in the first case, and the reference is boundand to the lvalue result of the conversion in the second case.In these cases the reference is said to bind directly to the initializer expression.[Note: the usual lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are not needed, and therefore are suppressed, when such direct bindings to lvalues are done. —end note][Example: ... —end example]
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference and the initializer expression shall be an rvalue. [Example: ... —end example]
If the initializer expression is an rvalue, with T2 a class type, and “cv1 T1” is reference-compatible with “cv2 T2,” the reference is bound to the object represented by the rvalue (see 7.2.1 [basic.lval]) or to a sub-object within that object.
[Example: ... —end example]
If the initializer expression is an rvalue, with T2 an array type, and “cv1 T1” is reference-compatible with “cv2 T2,” the reference is bound to the object represented by the rvalue (see 7.2.1 [basic.lval]).
Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy initialization (9.5 [dcl.init]). The reference is then bound to the temporary. If T1 is reference-related to T2, cv1 must be the same cv-qualification as, or greater cv-qualification than, cv2; otherwise, the program is ill-formed. [Example: ... —end example]
In all cases except the last (i.e., creating and initializing a temporary from the initializer expression), the reference is said to bind directly to the initializer expression.
Change 7.6.16 [expr.cond] bullet 3.1 as follows:
If E2 is an lvalue: E1 can be converted
to match E2 if E1 can be implicitly converted
(7.3 [conv]) to the type “lvalue reference to
T2”, subject to the constraint that in the conversion
the reference must bind directly (9.5.4 [dcl.init.ref]) to
E1 an lvalue.
[Voted into WP at October, 2009 meeting.]
Consider the following example:
struct A { }; struct B : public A { }; struct X { operator B(); }; X x; int main() { const A& r = x; return 0; }
It seems like the resolution of issue 391 doesn't actually cover this; X is not reference-compatible with A, so we go past the modified bullet (9.5.4 [dcl.init.ref] paragraph 5, bullet 2, sub-bullet 1), which reads:
If the initializer expression is an rvalue, with T2 a class type, and “cv1 T1” is reference-compatible with “cv2 T2,” the reference is bound to the object represented by the rvalue (see 7.2.1 [basic.lval]) or to a sub-object within that object.
and hit
Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy initialization (9.5 [dcl.init]). The reference is then bound to the temporary.
which seems to require that we create an A temporary copied from the return value of X::operator B() rather than bind directly to the A subobject. I think that the resolution of issue 391 should cover this situation as well, and the EDG compiler seems to agree with me.
(See also issue 896.)
Proposed resolution (September, 2009):
Change 9.5.4 [dcl.init.ref] paragraph 5 as follows:
If the reference is an lvalue reference...
Otherwise, the reference shall be an lvalue reference to a non-volatile const type...
If the initializer expression is an rvalue, with
T2 a class type, and “cv1 T1” is
reference-compatible with “cv2 T2,” the
reference is bound to the object represented by the rvalue (see
7.2.1 [basic.lval]) or to a sub-object within that
object. If T1 and T2 are class types
and
the initializer expression is an rvalue, and “cv1 T1” is reference-compatible with “cv2 T2,” or
T1 is not reference-related to T2, and the initializer expression can be implicitly converted to an rvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3” (this conversion is selected by enumerating the applicable conversion functions (12.2.2.7 [over.match.ref]) and choosing the best one through overload resolution (12.2 [over.match]) ),
then the reference is bound to the initializer expression rvalue in the first case, and to the object that is the result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object). [Example:
struct A { }; struct B : A { } b; extern B f(); const A& rca = f(); // Bound to the A subobject of the B rvalue. A&& rcb = f(); // Same as above struct X { operator B(); } x; const A& r = x; // Bound to the A subobject of the result of the conversion
—end example]
...
Editorial note: issue 589 makes edits to the top-level bullet preceding this one. The wording resulting from those edits should be changed for consistency with this wording so that the text there reads, “...in the first case and to the lvalue result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object).”
Change 12.2 [over.match] paragraph 2, last bullet as follows:
Change 12.2.2.7 [over.match.ref] paragraph 1 as follows:
Under the conditions specified in 9.5.4 [dcl.init.ref], a reference can be bound directly to an lvalue or class rvalue that is the result of applying a conversion function to an initializer expression. Overload resolution is used to select the conversion function to be invoked. Assuming that “cv1 T” is the underlying type of the reference being initialized, and “cv S” is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:
The conversion functions of S and its base classes are considered, except that for copy-initialization, only the non-explicit conversion functions are considered. Those that are not hidden within S and yield type “lvalue reference to cv2 T2” (when 9.5.4 [dcl.init.ref] requires an lvalue result), or “cv2 T2” or “rvalue reference to cv2 T2 (when 9.5.4 [dcl.init.ref] requires an rvalue result), where “cv1 T” is reference-compatible (9.5.4 [dcl.init.ref]) with “cv2 T2”, are candidate functions.
(Note: this resolution also resolves issue 896.)
[Voted into WP at March, 2010 meeting as document N3055.]
According to 9.5.4 [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.
[Voted into WP at October, 2009 meeting.]
Consider the following example:
struct A { } a; struct B { operator A&&() { return static_cast<A&&>(a); } }; A&& r = B();
One would expect that r would be bound to the object returned by B::operator A&&(), i.e., a. However, the logic in 9.5.4 [dcl.init.ref] paragraph 5 requires that the result of the conversion function be copied to a temporary and r bound to the temporary.
Probably the way to address this is to add another top-level bullet between the first and second that would essentially mimic the first bullet except dealing with rvalue references: direct binding to reference-compatible rvalues or to the reference-compatible result of a conversion function. (Note that this should only apply to class rvalues; the creation of a temporary for non-class rvalues is necessary to have an object for the reference to bind to.)
(See also issue 656.)
Proposed resolution (September, 2009):
This issue is resolved by the resolution of issue 656.
[Voted into WP at October, 2009 meeting.]
Both of the following initializations are ill-formed because of narrowing, although they were previously well-formed:
struct A { int i; } a = { 1.0 }; struct B { float f; } b = { 1.1 };
The first one doesn't seem like a big problem, as there probably isn't much code that has this kind of aggregate initialization. The second might be of more concern, because 1.1 is not representable in either float or double. Is the resulting loss of precision a kind of narrowing that we want to diagnose?
Notes from the September, 2008 meeting:
The CWG agreed that the second initialization should not be a narrowing error; furthermore, this exemption should apply not only to literals but to any floating-point constant expression. Instead of the current formulation, requiring exact bidirectional convertibility, the Standard should only require that the initializer value be within the representable range of the target type.
Proposed resolution (July, 2009):
Change 9.5.5 [dcl.init.list] paragraph 6 as follows:
A narrowing conversion is an implicit conversion
from a floating-point type to an integer type, or
from long double to double or float, or from double to float, except where the source is a constant expression and the actual value after conversion
will fit into the target type and will produce the original value when converted back to the original typeis within the range of values that can be represented (even if it cannot be represented exactly), or...
[Voted into WP at October, 2009 meeting.]
There are several problems with the wording of 9.5.5 [dcl.init.list] paragraph 4:
When an initializer list is implicitly converted to a std::initializer_list<E>, the object passed is constructed as if the implementation allocated an array of N elements of type E, where N is the number of elements in the initializer list. Each element of that array is initialized with the corresponding element of the initializer list converted to E, and the std::initializer_list<E> object is constructed to refer to that array. If a narrowing conversion is required to convert the element to E, the program is ill-formed.
First, an initializer list is not an expression, so it is not appropriate to refer to “implicitly convert[ing]” it, as is done in the first sentence.
Also, the conversion of the elements of the initializer list to the elements of the array is not specified to be either copy-initialization or direct-initialization. If this is intended to be viewed as an aggregate initialization, it would be copy-initialization, but that needs to be specified more clearly.
Finally, the initializer list can have nested initializer lists, so the references to converting the element also need to be cleaned up.
Proposed resolution (July, 2009):
Change 9.5.5 [dcl.init.list] paragraph 4 as follows:
When an initializer list is implicitly converted to aAn object of type std::initializer_list<E> is constructed from an initializer list, the object passed is constructedas if the implementation allocated an array of N elements of type E, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer listconverted to E, and the std::initializer_list<E> object is constructed to refer to that array. If a narrowing conversion is required toconvert the element to Einitialize any of the elements, the program is ill-formed. [Example:...
[Voted into WP at October, 2009 meeting.]
According to 9.5.5 [dcl.init.list] paragraph 3,
Otherwise, if T is a reference type, an rvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary.
This means, for an example like
int i; const int& r1{ i }; int&& r2{ i };
r1 is bound to a temporary containing the value of i, not to i itself, which seems surprising. Also, there's no prohibition here against binding the rvalue reference to an lvalue, as there is in 9.5.4 [dcl.init.ref] paragraph 5 bullet 2, so the initialization of r2 is well-formed, even though the corresponding non-list initialization int&& r3(i) is ill-formed.
There's also a question as to whether this bullet even applies to these examples. According to the decision tree in 9.5 [dcl.init] paragraph 16, initialization of a reference is dispatched to 9.5.4 [dcl.init.ref] in the first bullet, so these cases never make it to the third bullet sending the remaining braced-init-list cases to 9.5.5 [dcl.init.list]. If that's the correct interpretation, there's a problem with 9.5.4 [dcl.init.ref], since it doesn't deal with the braced-init-list cases, and the bullet in 9.5.5 [dcl.init.list] paragraph 3 dealing with references is dead code that's never used.
Proposed resolution (July, 2009):
Move the third bullet of the list in 9.5 [dcl.init] paragraph 16 to the top of the list:
If the initializer is a braced-init-list, the object is list-initialized (9.5.5 [dcl.init.list]).
If the destination type is a reference type, see 9.5.4 [dcl.init.ref].
...
Change 9.5.5 [dcl.init.list] paragraph 3, bullets 4 and 5, as follows:
Otherwise, if T is a reference to class type, or if T is any reference type and the initializer list has no elements, an rvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary. [Note:...
Otherwise (i.e., if T is not an aggregate, class
type, or reference), if the initializer list has a single
element...
[Voted into WP at March, 2010 meeting.]
The final set of declarations in the example following 9.5.5 [dcl.init.list] bullet 3.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 9.5.5 [dcl.init.list] paragraph 3:
If T is an aggregate, aggregate initialization is performed (9.5.2 [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 #2struct 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]
...
[Voted into WP at March, 2010 meeting as part of document N3079.]
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 9.5.5 [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]
IfOtherwise, 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]
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The grammar in 9.6 [dcl.fct.def] paragraph 2 incorrectly excludes late-specified return types and should be corrected.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into WP at March, 2010 meeting.]
According to 9.6 [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.
[Voted into WP at March, 2010 meeting.]
The only restriction placed on the use of “=default” in 9.6 [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 9.6 [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 (11.4.5 [class.ctor], 11.4.7 [class.dtor], 11.4.5.3 [class.copy.ctor]).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 (14.5 [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 (11.4.5 [class.ctor], 11.4.7 [class.dtor], 11.4.5.3 [class.copy.ctor]), 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 11.4.5 [class.ctor] paragraph 6 as follows:
This resolution also resolves issue 905. See also issue 667.
[Voted into WP at October, 2009 meeting.]
According to 9.6 [dcl.fct.def] paragraph 10, a deleted definition of a function must be its first declaration. It is not clear whether this requirement can be satisfied for the global allocation and deallocation functions. According to 6.7.6.5 [basic.stc.dynamic] paragraph 2, they are “implicitly declared in global scope in each translation unit of a program.” However, that does not specify where in the translation unit the declaration is considered to take place. This needs to be clarified.
Proposed resolution (July, 2009):
Change 9.6 [dcl.fct.def] paragraph 10 as follows:
...A deleted definition of a function shall be the first declaration of the function. An implicitly declared allocation or deallocation function (6.7.6.5 [basic.stc.dynamic]) shall not be defined as deleted. [Example:...
[Voted into WP at March, 2010 meeting.]
It is not clear whether the following definition of an explicit specialization of a member function template is permitted or not:
template <typenanme T> struct S { template <typename U> void f(); }; template <> template <typename U> void S<int>::f() = delete;
Is the explicit specialization the “first declaration” of the member function template?
(See also issue 845.)
Notes from the July, 2009 meeting:
The intent is that this usage should be supported.
Proposed resolution (October, 2009):
This issue is resolved by the resolution of issue 845.
[Voted into WP at October, 2009 meeting.]
9.6 [dcl.fct.def] paragraph 9 says,
A special member function that would be implicitly defined as deleted shall not be explicitly defaulted.
It would be more regular (and thus useful in generic programming) if such a member function were itself simply defined as deleted rather than being made ill-formed.
Proposed resolution (July, 2009):
Change 9.6 [dcl.fct.def] paragraph 9 as follows:
Only special member functions may be explicitly defaulted, and the implementation shall define them as if they had implicit definitions (11.4.5 [class.ctor], 11.4.7 [class.dtor], 11.4.5.3 [class.copy.ctor]).A special member function that would be implicitly defined as deleted shall not be explicitly defaulted.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...
Change 11.4.5 [class.ctor] paragraph 6 as follows:
A non-user-provided default constructor for a class is implicitly defined when it is used (6.3 [basic.def.odr]) to create an object of its class type (6.7.2 [intro.object]).If the implicitly-defined default constructor is explicitly defaulted but the corresponding implicit declaration would have been deleted, the program is ill-formed.The implicitly-defined or explicitly-defaulted default constructor...
Change 11.4.7 [class.dtor] paragraph 4 as follows:
A program is ill-formed if the class for which a destructor is implicitly defined or explicitly defaulted has:
if the implicitly-defined destructor is explicitly defaulted, but the corresponding implicit declaration would have been deleted.
a non-static data member of class type (or array thereof) with an inaccessible destructor, or
a base class with an inaccessible destructor.
Change 11.4.5.3 [class.copy.ctor] paragraph 7 as follows:
...[Note: the copy constructor is implicitly defined even if the implementation elided its use (6.7.7 [class.temporary]). —end note]A program is ill-formed if the implicitly-defined copy constructor is explicitly defaulted, but the corresponding implicit declaration would have been deleted.
Change 11.4.5.3 [class.copy.ctor] 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.A program is ill-formed if the implicitly-defined copy assignment operator is explicitly defaulted, but the corresponding implicit declaration would have been deleted.
[Voted into the WP at the March, 2009 meeting.]
According to 9.8.1 [dcl.enum] paragraph 6, the underlying type of an enumeration with an empty enumeration-list is determined as if the enumeration-list contained a single enumerator with value 0. Paragraph 7, which specifies the values of an enumeration and the minimum size of bit-field needed represent those values needs a similar provision for empty enumeration-lists.
Proposed resolution (March, 2008):
Add the indicated sentence to the end of 9.8.1 [dcl.enum] paragraph 5:
...It is possible to define an enumeration that has values not defined by any of its enumerators. If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0.
[Voted into WP at October, 2009 meeting.]
The type of an enumerator that has no initializing value in an enumeration whose underlying type is not fixed is given by the third bullet of 9.8.1 [dcl.enum] paragraph 5:
the type of the initializing value is the same as the type of the initializing value of the preceding enumerator unless the incremented value is not representable in that type, in which case the type is an unspecified integral type sufficient to contain the incremented value.
This does not address the case in which there is no such type, meaning that it is apparently undefined behavior. Other cases in which an enumeration value is unrepresentable are made ill-formed (see the preceding paragraph for an enumeration with a fixed underlying type and the following paragraph for the case in which the minimum and maximum values cannot be represented by a single type). It would be better if this case were ill-formed as well, instead of causing undefined behavior.
Proposed resolution (July, 2009):
Change 9.8.1 [dcl.enum] paragraph 5, bullet 3 as follows:
[Voted into WP at March, 2010 meeting.]
It is not clear from the specification in 9.9.2 [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.
[Voted into WP at March, 2010 meeting as part of document N3079.]
According to 9.9.2 [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 (13.9.3 [temp.explicit]) or explicitly specialized (13.9.4 [temp.expl.spec]) as though it were a member of the enclosing namespace.
However, that assertion is contradicted for class template specializations by Clause 11 [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 6.5.5.3 [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 Clause 11 [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 (9.9.2 [namespace.def]) of that namespace (i.e.,neithernot merely inheritednoror introduced by a using-declaration), and the class-specifier shall appear in a namespace enclosing the previous declaration.
Change 6.5.5.3 [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 (9.9.2 [namespace.def]) of that namespace. [Example:...
(Note: this resolution depends on the resolution for issue 861.)
[Voted into WP at October, 2009 meeting.]
According to 9.9.2 [namespace.def] paragraph 8,
Specifically, the inline namespace and its enclosing namespace are considered to be associated namespaces (6.5.4 [basic.lookup.argdep]) of one another, and a using-directive (9.9.4 [namespace.udir]) that names the inline namespace is implicitly inserted into the enclosing namespace.
There are two problems with this sentence. First, the concept of namespaces being associated with each other is undefined; 6.5.4 [basic.lookup.argdep] describes how namespaces are associated with types, not with other namespaces. Second, unlike unnamed namespaces, the location of the implicit using-directive is not specified.
Proposed resolution (July, 2009):
Change 9.9.2 [namespace.def] paragraph 8 as follows:
...Specifically, the inline namespace and its enclosing namespace areconsidered to be associated namespaces (6.5.4 [basic.lookup.argdep]) of one anotherboth added to the set of associated namespaces used in argument-dependent lookup (6.5.4 [basic.lookup.argdep]) whenever one of them is, and a using-directive (9.9.4 [namespace.udir]) that names the inline namespace is implicitly inserted into the enclosing namespace as for an unnamed namespace (9.9.2.2 [namespace.unnamed]). Furthermore...
[Voted into WP at October, 2009 meeting.]
In 9.9.2 [namespace.def] paragraph 1, an unnamed-namespace-definition is defined as
However, there is no provision for the inline keyword in the expansion of unnamed namespaces in 9.9.2.2 [namespace.unnamed] paragraph 1. Strictly interpreted, that would mean that the inline qualifier is ignored for unnamed namespaces.
Proposed resolution (September, 2009):
Change 9.9.2.2 [namespace.unnamed] paragraph 1 as follows:
An unnamed-namespace-definition behaves as if it were replaced by
inlineopt namespace unique { /* empty body */ } using namespace unique ; namespace unique { namespace-body }where inline appears if and only if it appears in the unnamed-namespace-definition, all occurrences of unique in a translation unit are replaced by the same identifier, and this identifier differs from all other identifiers in the entire program.87 [Example:...
[Voted into WP at March, 2010 meeting.]
According to 9.9.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 6.5.5.3 [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 9.9.4 [namespace.udir] paragraph 4 as follows:
[Voted into the WP at the March, 2009 meeting.]
The wording of 9.12 [dcl.link] paragraph 5 is suspect:
If two declarations of the same function or object specify different linkage-specifications (that is, the linkage-specifications of these declarations specify different string-literals), the program is ill-formed if the declarations appear in the same translation unit, and the one definition rule (3.2) applies if the declarations appear in different translation units.
But what if only one of the declarations has a linkage-specification, while the other is left with the default C++ linkage? Shouldn't this restriction be phrased in terms of the functions’ or objects’ language linkage rather than linkage-specifications?
(Additional note [wmm]: Is the ODR the proper vehicle for enforcing this requirement? This is dealing with declarations, not necessarily definitions. Shouldn't this say “ill-formed, no diagnostic required” instead of some vague reference to the ODR?)
Proposed resolution (June, 2008):
Change 9.12 [dcl.link] paragraph 5 as follows:
If two declarationsof the same function or objectdeclare functions with the same name and parameter-type-list (9.3.4.6 [dcl.fct]) to be members of the same namespace or declare objects with the same name to be members of the same namespacespecify different linkage-specifications (that is, the linkage-specifications of these declarations specify different string-literals)and the declarations give the names different language linkages, the program is ill-formedif the declarations appear in the same translation unit, and the one definition rule (6.3 [basic.def.odr]) applies; no diagnostic is required if the declarations appear in different translation units.
[Voted into WP at March, 2010 meeting as paper N3050.]
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.
[Voted into WP at March, 2010 meeting as document N3067.]
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 (7.6.2.8 [expr.new])
Preceding the type-specifier-seq in a condition (8.5 [stmt.select])
In a for-init-statement that is an expression-statement (8.6 [stmt.iter])
Preceding the type-specifier-seq in a for-range-declaration (8.6 [stmt.iter])
In a reference ptr-operator (9.3 [dcl.decl])
Preceding the type-specifier-seq in a type-id (9.3.2 [dcl.name])
Preceding the decl-specifier-seq in a parameter-declaration (9.3.4.6 [dcl.fct])
In a function-definition (9.6 [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 9.3.4.6 [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 (11.4 [class.mem])
Preceding the compound-statement of a try-block or handler (Clause 14 [except])
Preceding the type-specifier-seq of an exception-declaration (Clause 14 [except])
Another group of problems is the failure to specify to what a given attribute-specifier appertains:
In a condition (8.5 [stmt.select])
In a for-range-declaration (8.6.5 [stmt.ranged])
In a parameter-declaration (9.3.4.6 [dcl.fct])
In a conversion-type-id (11.4.8.3 [class.conv.fct])
There is also a problem in the specification of the interpretation of an initial attribute-specifier. 9.3.4 [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.
[Voted into WP at March, 2010 meeting.]
The terms “appertain” and “apply” are used in different ways in different subsections of 9.13 [dcl.attr]. A thorough editorial sweep of the entire section is needed to regularize their usage.
Proposed resolution (October, 2009):
Change 9.13.1 [dcl.attr.grammar] paragraph 4 as follows:
...If an attribute-specifier that appertains to some entity or statement contains an attribute thatdoes notis not allowed to apply to that entity or statement, the program is ill-formed...
Change 9.13.2 [dcl.align] paragraph 1 as follows:
...The attributecanmay be applied to a variable...
Change 9.13.10 [dcl.attr.noreturn] paragraph 1 as follows:
...The attributeappliesmay be applied to the declarator-id in a function declaration...
Change _N3225_.7.6.4 [dcl.attr.final] paragraph 1 as follows:
...The attributeappliesmay be applied to class definitions...
Change _N3225_.7.6.5 [dcl.attr.override] paragraph 1 as follows:
...The attributeappliesmay be applied to virtual member functions...
Change _N3225_.7.6.5 [dcl.attr.override] paragraph 3 as follows:
...The attributeappliesmay be applied to class members...
Change _N3225_.7.6.5 [dcl.attr.override] paragraph 5 as follows:
...The attributeappliesmay be applied to a class definition.
Change _N5001_.9.12.4 [dcl.attr.depend] paragraph 1 as follows:
...The attributeappliesmay be applied to the declarator-id of a parameter-declaration... The attribute may alsoappliesbe applied to the declarator-id of a function declaration...
[Voted into WP at July, 2009 meeting as N2933.]
Parameter packs should be expanded inside attributes. For example, it would be useful to specify the alignment of each element in a pack expansion using a parallel pack expansion.
[Voted into WP at March, 2010 meeting.]
9.13.1 [dcl.attr.grammar] paragraph 3 specifies that keywords can be used as attribute-tokens. However, the alternative tokens in 5.9 [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 9.13.1 [dcl.attr.grammar] paragraph 3 as follows:
...AIf a keyword (5.12 [lex.key]) or an alternative token (5.9 [lex.digraph]) that satisfies the syntactic requirements of an identifier (5.11 [lex.name]) is contained in an attribute-token, it is considered an identifier...
[Voted into WP at March, 2010 meeting as document N3063.]
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 9.3.3 [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]
[Voted into WP at March, 2010 meeting.]
According to 9.13.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 9.13.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
objectentity shall be the specified fundamental alignmentif 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
objectentity shall be that alignment...
Change 9.13.2 [dcl.align] paragraphs 4-6 as follows:
When multiple alignment attributes are specified for an
objectentity, 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
objectentity being declared.If the defining declaration of an
objectentity has an alignment attribute, any non-defining declaration of thatobjectentity 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 anobjectentity have different alignment attributes in different translation units.
Insert the following as a new paragraph after 9.13.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 9.13.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]
[Voted into WP at March, 2010 meeting.]
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.
[Voted into the WP at the March, 2009 meeting.]
The current wording defining a “common initial sequence” in 11.4 [class.mem] paragraph 17 does not address the case in which one member is a bit-field and the corresponding member is not:
Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types (and, for bit-fields, the same widths) for a sequence of one or more initial members.
Presumably the intent was something like, “(and, if one of the pair is a bit-field, the other is also a bit-field of the same width).”
Proposed Resolution (September, 2008):
Change 11.4 [class.mem] paragraph 18 as follows:
... Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types(and, for bit-fields, the same widths)and either neither member is a bit-field or both are bit-fields with the same widths for a sequence of one or more initial members.
[Voted into WP at October, 2009 meeting.]
According to 11.4 [class.mem] paragraph 1,
The enumerators of an enumeration (9.8.1 [dcl.enum]) defined in the class are members of the class... A member shall not be declared twice in the member-specification, except that a nested class or member class template can be declared and then later defined.
The enumerators of a scoped enumeration are not members of the containing class; the wording should be revised to apply only to unscoped enumerations.
The second part of the cited wording from 11.4 [class.mem] prohibits constructs like:
class C { public: enum E: int; private: enum E: int { e0 }; };
which might be useful in making the enumeration type, but not its enumerators, accessible.
Notes from the July, 2009 meeting:
According to 11.8.2 [class.access.spec] paragraph 4, the access must be the same for all declarations of a class member. The suggested usage given above violates that requirement: the second declaration of E declares the enumeration itself, not just the enumerators, to be private. The CWG did not feel that the utility of the suggested feature warranted the complexity of an exception to the general rule.
Proposed resolution (July, 2009):
Change 11.4 [class.mem] paragraph 1 as follows:
...The enumerators of an unscoped enumeration (9.8.1 [dcl.enum]) defined in the class are members of the class... A member shall not be declared twice in the member-specification, except that a nested class or member class template can be declared and then later defined, and except that an enumeration can be first introduced with an opaque-enum-declaration and then later be redeclared with an enum-specifier.
Change the example in 11.8.2 [class.access.spec] paragraph 4 as follows:
When a member is redeclared within its class definition, the access specified at its redeclaration shall be the same as at its initial declaration. [Example:
struct S { class A; enum E : int; private: class A { }; // error: cannot change access enum E : int { e0 }; // error: cannot change access };
—end example]
[Voted into WP at March, 2010 meeting.]
According to 11.4.5 [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 11.4.5 [class.ctor] bullet 5.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 11.4.5 [class.ctor] bullet 5.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
...
[Voted into WP at March, 2010 meeting.]
(From message 14555.)
The reasons for which an implicitly-declared default constructor is defined as deleted, given in 11.4.5 [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 11.4.5 [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 classhas class type M (or array thereof) and either M has no defaultconstructor, or ifconstructor or overload resolution (12.2 [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.
[Voted into WP at March, 2010 meeting as part of document N3079.]
Should the following class have a trivial copy assignment operator?
struct A { int& m; A(); A(const A&); };
11.4.5.3 [class.copy.ctor] 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 Clause 11 [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 9.6 [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 shalldefine them as if they hadprovide implicit definitions for them (11.4.5 [class.ctor], 11.4.7 [class.dtor], 11.4.5.3 [class.copy.ctor]), 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 11.4.5 [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 (9.6 [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 (9.6 [dcl.fct.def]) and if:
its class has no virtual functions (11.7.3 [class.virtual]) and no virtual base classes (11.7.2 [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.A
n implicitly-declareddefaulted 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 (12.2 [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 (11.7.3 [class.virtual]) and no virtual base classes (11.7.2 [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-provideddefault constructorfor a classthat is defaulted and not deleted is implicitly defined when it is used (6.3 [basic.def.odr]) to create an object of its class type (6.7.2 [intro.object]), or when it is explicitly defaulted after its first declaration. The implicitly-definedor explicitly-defaulteddefault 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 (11.9.3 [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 (9.2.6 [dcl.constexpr]), the implicitly-defined default constructor is constexpr. Before thenon-user-provideddefaulted 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 (14.5 [except.spec]). An explicitly-defaulted definition has no implicit exception-specification. —end note]
Change 11.4.7 [class.dtor] paragraphs 3-4 as follows:
If a class has no user-declared destructor, a destructor is
declaredimplicitly declared as defaulted (9.6 [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 (9.6 [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.A
n implicitly-declareddefaulted 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
ana deleted destructor or a destructor that is inaccessible from the implicitly-declared destructor, orany 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-provideddestructor that is defaulted and not defined as deleted is implicitly defined when it is used to destroy an object of its class type (6.7.6 [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-provideddefaulted destructor for a class is implicitly defined, all thenon-user-definednon-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 (14.5 [except.spec]). An explictly defaulted definition has no implicit exception-specification. —end note]
Change 11.4.5.3 [class.copy.ctor] paragraphs 4-9 as follows:
If the class definition does not explicitly declare a copy constructor, one is
declared implicitlyimplicitly declared as defaulted (9.6 [dcl.fct.def]). Thus......An implicitly-declared copy constructor is an inline public member of its class. A
n implicitly-declareddefaulted copy constructor for a class X is defined as deleted if X has: ...A copy constructor for class X is
trivialtrivial if it isnotneither user-provided nor deleted(9.6 [dcl.fct.def])and if...A
non-user-providedcopy 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 (6.7.7 [class.temporary]). —end note]Before the
non-user-provideddefaulted copy constructor for a class is implicitly defined, all non-user-provided copy constructors...The implicitly-defined
or explicitly-defaultedcopy constructor for a non-union class X performs...The implicitly-defined
or explicitly-defaultedcopy constructor for a union X copies the object representation (6.8 [basic.types]) of X.
Change 11.4.5.3 [class.copy.ctor] paragraphs 11-15 as follows:
If the class definition does not explicitly declare a copy assignment operator, one is
declared implicitlyimplicitly declared as defaulted (9.6 [dcl.fct.def])......A
n implicitly-declareddefaulted 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
notneither user-provided nor deleted and if...A
non-user-providedcopy 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-provideddefaulted copy assignment operator for a class is implicitly defined...The implicitly-defined
or explicitly-defaultedcopy 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-defaultedcopy assignment operator. [Example:...The implicitly-defined
or explicitly-defaultedcopy assignment operator for a union X copies the object representation (6.8 [basic.types]) of X.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Although the term “move constructor” appears multiple times in the library clauses and is referenced in the newly-added text for the lambda feature, it is not defined anywhere.
Notes from the June, 2008 meeting:
The only reference to “move constructor” in the core language clauses of the Standard is in 7.5.6 [expr.prim.lambda] paragraph 10; there are no semantic implications of the term. This issue will be addressed by using a function signature instead of the term, thus allowing the library section to provide a definition that is appropriate for its needs.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into WP at March, 2010 meeting.]
11.4.5.3 [class.copy.ctor] 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 11.4.5.3 [class.copy.ctor] paragraph 17 second bullet as follows:
[Voted into WP at March, 2010 meeting as document N3053.]
A constructor of the form T::T(T&&) is a candidate function for copy construction; however, the declaration of such a constructor does not inhibit the implicit declaration and definition of a copy constructor. This can lead to surprising results. We should consider suppressing the implicit copy constructor if a move constructor is declared.
[Voted into WP at July, 2009 meeting.]
How does copy assignment for unions work? For example,
union U { int a; float b; }; void f() { union U u = { 5 }; union U v; v = u; // what happens here? }
11.5 [class.union] is silent on the issue, therefore it seems that 11.4.5.3 [class.copy.ctor] applies. There is no special case for unions, thus paragraph 13 (memberwise assignment of subobjects) seems to apply. That would seem to imply these actions in the compiler-generated copy assignment operator:
v.a = u.a; v.b = u.b;
And this is just wrong. For example, the lifetime of v.a ends once the second assignment reuses the memory of v.a.
We should probably prescribe “memcpy” copying for unions (both for the copy constructor and the assignment operator) unless the user provided his own special member function.
Proposed resolution (March, 2008):
Change 11.4.5.3 [class.copy.ctor] paragraph 8 as follows:
The implicitly-defined or explicitly-defaulted copy constructor for a non-union class X performs a memberwise copy of its subobjects...
Add a new paragraph after 11.4.5.3 [class.copy.ctor] paragraph 8:
The implicitly-defined or explicitly-defaulted copy constructor for a union X where all members have a trivial copy constructor copies the object representation (6.8 [basic.types]) of X. [Note: The behavior is undefined if X is not a trivial type. —end note]
Change 11.4.5.3 [class.copy.ctor] paragraph 13 as follows:
The implicitly-defined or explicitly-defaulted copy assignment operator for a non-union class X performs memberwise assignment of its subobjects...
Add a new paragraph after 11.4.5.3 [class.copy.ctor] paragraph 13:
The implicitly-defined or explicitly-defaulted copy assignment operator for a union X where all members have a trivial copy assignment operator copies the object representation (6.8 [basic.types]) of X. [Note: The behavior is undefined if X is not a trivial type. —end note]
Notes from the September, 2008 meeting:
The proposed wording needs to be updated to reflect the changes adopted in papers N2757 and N2762, resolving issue 683, which require “no non-trivial” special member functions instead of “a trivial” function. Also, the notes regarding undefined behavior are incorrect, because the member functions involved are defined as deleted when there are non-trivial members.
Proposed resolution (October, 2008):
Change 11.4.5.3 [class.copy.ctor] paragraph 8 as follows:
The implicitly-defined or explicitly-defaulted copy constructor for a non-union class X performs a memberwise copy of its subobjects...
Add a new paragraph following 11.4.5.3 [class.copy.ctor] paragraph 8:
The implicitly-defined or explicitly-defaulted copy constructor for a union X copies the object representation (6.8 [basic.types]) of X.
Change 11.4.5.3 [class.copy.ctor] paragraph 13 as follows:
Add a new paragraph following 11.4.5.3 [class.copy.ctor] paragraph 13:
The implicitly-defined or explicitly-defaulted copy assignment operator for a union X copies the object representation (6.8 [basic.types]) of X.
[Voted into WP at July, 2009 meeting.]
The recent changes in the handling of initialization have not touched the requirement that the in-class initializer for a const static data member must be of the form = assignment-expression and not a braced-init-list. It would be more consistent and general to allow the braced form as well.
Proposed resolution (March, 2009):
Change 7.7 [expr.const] paragraph 3 as follows:
...as enumerator initializers (9.8.1 [dcl.enum]),as static member initializers (11.4.9.3 [class.static.data]),and as integral or enumeration non-type template arguments (13.6 [temp.type]).
Change 11.4.9.3 [class.static.data] paragraph 3 as follows:
If a static data member is of const effective literal type, its declaration in the class definition can specify a brace-or-equal-initializerwith anin which every initializer-clause that is an assignment-expression is aintegralconstant expression. A static data member of effective literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializerwith anin which every initializer-clause that is an assignment-expression is aintegralconstant expression. [Note: In both these cases, the member may appear inintegralconstant expressions. —end note] The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.
[Drafting note: this change also corrects an editorial error resulting from overlapping changes that inadvertently retained the original restriction that only members of integral type could be initialized inside the class definition.]
[Voted into WP at July, 2009 meeting.]
Unions are no longer forbidden to have static data members; however, much of the wording of 11.5 [class.union] (and possibly other places in the Standard) is still written with that assumption and refers only to “data members” when clearly non-static data members are in view. From paragraph 1, for example:
In a union, at most one of the data members can be active at any time... The size of a union is sufficient to contain the largest of its data members...
Proposed resolution (March, 2009):
Change the footnote in 6.8.5 [basic.type.qualifier] paragraph 1 as follows:
The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and non-static data members of unions.
Change 7.2.1 [basic.lval] bullet 15.6 as follows:
Change 7.6.9 [expr.rel] bullet 2.5 as follows:
Change 9.13.2 [dcl.align] paragraph 8 as follows:
[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]
Change 9.5.2 [dcl.init.aggr] paragraph 15 as follows:
When a union is initialized with a brace-enclosed initializer, the braces shall only contain an initializer-clause for the first non-static data member of the union...
Change 11.5 [class.union] paragraph 1 as follows:
In a union, at most one of the non-static data members can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time. [Note: one special guarantee is made in order to simplify the use of unions: If a standard-layout union contains several standard-layout structs that share a common initial sequence (11.4 [class.mem]), and if an object of this standard-layout union type contains one of the standard-layout structs, it is permitted to inspect the common initial sequence of any of standard-layout struct members; see 11.4 [class.mem]. —end note] The size of a union is sufficient to contain the largest of its non-static data members. Each non-static data member is allocated as if it were the sole member of a struct. A union can have...
[Voted into WP at October, 2009 meeting.]
According to 11.7.3 [class.virtual] paragraph 2:
Then in any well-formed class, for each virtual function declared in that class or any of its direct or indirect base classes there is a unique final overrider that overrides that function and every other overrider of that function. The rules for member lookup (6.5.2 [class.member.lookup]) are used to determine the final overrider for a virtual function in the scope of a derived class but ignoring names introduced by using-declarations.
I think that description is wrong on at least a couple of counts. First, consider the following example:
struct A { virtual void f(); }; struct B: A { }; struct C: A { void f(); }; struct D: B, C { };
What is the “unique final overrider” of A::f() in D? According to 11.7.3 [class.virtual] paragraph 2, we determine that by looking up f in D using the lookup rules in 6.5.2 [class.member.lookup]. However, that lookup determines that f in D is ambiguous, so there is no “unique final overrider” of A::f() in D. Consequently, because “any well-formed class” must have such an overrider, D must be ill-formed.
Of course, we all know that D is not ill-formed. In fact, 11.7.3 [class.virtual] paragraph 10 contains an example that illustrates exactly this point:
struct A { virtual void f(); }; struct B1 : A { // note non-virtual derivation void f(); }; struct B2 : A { void f(); }; struct D : B1, B2 { // D has two separate A subobjects };In class D above there are two occurrences of class A and hence two occurrences of the virtual member function A::f. The final overrider of B1::A::f is B1::f and the final overrider of B2::A::f is B2::f.
It appears that the requirement for a “unique final overrider” in 11.7.3 [class.virtual] paragraph 2 needs to say something about sub-objects. Whatever that “something” is, you can't just say “look up the name in the derived class using 6.5.2 [class.member.lookup].”
There's another problem with using the 6.5.2 [class.member.lookup] lookup to specify the final overrider: name lookup just looks up the name, while the overriding relationship is based not only on the name but on a matching parameter-type-list and cv-qualification. To illustrate this point:
struct X { virtual void f(); }; struct Y: X { void f(int); }; struct Z: Y { };
What is the “unique final overrider” of X::f() in A? Again, 11.7.3 [class.virtual] paragraph 2 says you're supposed to look up f in Z to find it; however, what you find is Y::f(int), not X::f(), and that's clearly wrong.
Proposed Resolution (December, 2006):
Change 11.7.3 [class.virtual] paragraph 2 as follows:
Then in any well-formed class, for each virtual function declared in that class or any of its direct or indirect base classes there is a unique final overrider that overrides that function and every other overrider of that function. The rules for member lookup (6.5.2 [class.member.lookup]) are used to determine the final overrider for a virtual function in the scope of a derived class but ignoring names introduced by using-declaration s.A virtual member function vf of a class C is a final overrider unless the most derived class (6.7.2 [intro.object]) of which C is a base class (if any) declares or inherits another member function that overrides vf. In a derived class, if a virtual member function of a base class subobject has more than one final overrider, the program is ill-formed.
Proposed resolution (July, 2009):
Change 11.7.3 [class.virtual] paragraph 2 as follows:
...
Then in any well-formed class, for each virtual function declared in that class or any of its direct or indirect base classes there is a unique final overrider that overrides that function and every other overrider of that function. The rules for member lookup (6.5.2 [class.member.lookup]) are used to determine the final overrider for a virtual function in the scope of a derived class but ignoring names introduced by using-declarations.A virtual member function C::vf of a class object S is a final overrider unless the most derived class (6.7.2 [intro.object]) of which S is a base class subobject (if any) declares or inherits another member function that overrides vf. In a derived class, if a virtual member function of a base class subobject has more than one final overrider, the program is ill-formed. [Example: ... —end example] [Example:struct A { virtual void f(); }; struct B: A { }; struct C: A { void f(); }; struct D: B, C { }; // OK; A::f and C::f are the final overriders // for the B and C subobjects, respectively—end example]
[Voted into WP at July, 2009 meeting as N2928.]
There should be a way to detect errors in overriding a virtual function.
Proposed resolution (July, 2009):
This issue is resolved by paper PL22.16/09-0118 = WG21 N2928.
[Voted into WP at March, 2010 meeting.]
11.7.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 11.7.3 [class.virtual] bullet 5.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
...
[Voted into the WP at the March, 2009 meeting.]
11.9 [class.init] paragraph 2 says,
When an array of class objects is initialized (either explicitly or implicitly), the constructor shall be called for each element of the array, following the subscript order;
That implies that, given
struct POD { int x; }; POD data[10] = {};
this should call the implicitly declared default ctor 10 times, leaving 10 uninitialized ints, rather than value initialize each member of data, resulting in 10 initialized ints (which is required by 9.5.2 [dcl.init.aggr] paragraph 7).
I suggest rephrasing along the lines:
When an array is initialized (either explicitly or implicitly), each element of the array shall be initialized in turn, following the subscript order;
This would allow for PODs and other classes with a dual nature under value/default initialization, and cover copy initialization for arrays too.
Proposed resolution (October, 2006):
Change 11.9 [class.init] paragraph 3 as follows:
When an array of class objects is initialized (either explicitly or implicitly) and the elements are initialized by constructor, the constructor shall be called for each element of the array, following the subscript order; see 9.3.4.5 [dcl.array].
[Voted into WP at October, 2009 meeting.]
Must a constructor for an abstract base class provide a mem-initializer for each virtual base class from which it is directly or indirectly derived? Since the initialization of virtual base classes is performed by the most-derived class, and since an abstract base class can never be the most-derived class, there would seem to be no reason to require constructors for abstract base classes to initialize virtual base classes.
It is not clear from the Standard whether there actually is such a requirement or not. The relevant text is found in 11.9.3 [class.base.init] paragraph 6:
All sub-objects representing virtual base classes are initialized by the constructor of the most derived class (6.7.2 [intro.object]). If the constructor of the most derived class does not specify a mem-initializer for a virtual base class V, then V's default constructor is called to initialize the virtual base class subobject. If V does not have an accessible default constructor, the initialization is ill-formed. A mem-initializer naming a virtual base class shall be ignored during execution of the constructor of any class that is not the most derived class.
This paragraph requires only that the most-derived class's constructor have a mem-initializer for virtual base classes. Should the silence be construed as permission for constructors of classes that are not the most-derived to omit such mem-initializers?
Christopher Lester, on comp.std.c++, March 19, 2004: If any of you reading this posting happen to be members of the above working group, I would like to encourage you to review the suggestion contained therein, as it seems to me that the final tenor of the submission is both (a) correct (the silence of the standard DOES mandate the omission) and (b) describes what most users would intuitively expect and desire from the C++ language as well.
The suggestion is to make it clearer that constructors for abstract base classes should not be required to provide initialisers for any virtual base classes they contain (as only the most-derived class has the job of initialising virtual base classes, and an abstract base class cannot possibly be a most-derived class).
For example:
struct A { A(const int i, const int j) {}; }; struct B1 : virtual public A { virtual void moo()=0; B1() {}; // (1) Look! not "B1() : A(5,6) {};" }; struct B2 : virtual public A { virtual void cow()=0; B2() {}; // (2) Look! not "B2() : A(7,8) {};" }; struct C : public B1, public B2 { C() : A(2,3) {}; void moo() {}; void cow() {}; }; int main() { C c; return 0; };
I believe that, by not expressly forbidding it, the standard does (and should!) allow the above code. However, as the standard doesn't expressly allow it either (have I missed something?) there appears to be room for misunderstanding. For example, g++ version 3.2.3 (and maybe other versions as well) rejects the above code with messages like:
In constructor `B1::B1()': no matching function for call to `A::A()' candidates are: A::A(const A&) A::A(int, int)
Fair enough, the standard is perhaps not clear enough. But it seems to be a shame that although this issue was first raised in 2000, we are still living with it today.
Note that we can work-around, and persuade g++ to compile the above by either (a) providing a default constructor A() for A, or (b) supplying default values for i and j in A(i,j), or (c) replace the construtors B1() and B2() with the forms shown in the two comments in the above example.
All three of these workarounds may at times be appropriate, but equally there are other times when all of these workarounds are particularly bad. (a) and (b) may be very bad if you are trying to enforce string contracts among objects, while (c) is just barmy (I mean why did I have to invent random numbers like 5, 6, 7 and 8 just to get the code to compile?).
So to to round up, then, my plea to the working group is: "at the very least, please make the standard clearer on this issue, but preferrably make the decision to expressly allow code that looks something like the above"
Proposed resolution (July, 2009):
Add the indicated text (moved from paragraph 11) to the end of 11.9.3 [class.base.init] paragraph 7:
...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. A mem-initializer where the mem-initializer-id names a virtual base class is ignored during execution of a constructor of any class that is not the most derived class.
Change 11.9.3 [class.base.init] paragraph 8 as follows:
If a given non-static data member or base class is not named by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (11.7.4 [class.abstract]), then
if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initialized as specified in 9.5 [dcl.init];
otherwise, if the entity is a variant member (11.5 [class.union]), no initialization is performed;
otherwise, the entity is default-initialized (9.5 [dcl.init]).
[Note: An abstract class (11.7.4 [class.abstract]) is never a most derived class, thus its constructors never initialize virtual base classes, therefore the corresponding mem-initializers may be omitted. —end note] After the call to a constructor for class X has completed...
Change 11.9.3 [class.base.init] paragraph 10 as follows:
Initialization
shall proceedproceeds in the following order:
First, and only for the constructor of the most derived class
as described below(6.7.2 [intro.object]), virtual base classesshall beare initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base class names in the derived class base-specifier-list.Then, direct base classes
shall beare initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).Then, non-static data members
shall beare initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).Finally, the compound-statement of the constructor body is executed.
[Note: the declaration order is mandated to ensure that base and member subobjects are destroyed in the reverse order of initialization. —end note]
Remove all normative text in 11.9.3 [class.base.init] paragraph 11, keeping the example:
All subobjects representing virtual base classes are initialized by the constructor of the most derived class (6.7.2 [intro.object]). If the constructor of the most derived class does not specify a mem-initializer for a virtual base class V, then V's default constructor is called to initialize the virtual base class subobject. If V does not have an accessible default constructor, the initialization is ill-formed. A mem-initializer naming a virtual base class shall be ignored during execution of the constructor of any class that is not the most derived class.[Example:...
[Voted into WP at October, 2009 meeting.]
11.9.3 [class.base.init] paragraph 5 forbids initializing multiple members of a union via mem-initializers:
If a ctor-initializer specifies more than one mem-initializer for the same member, for the same base class or for multiple members of the same union (including members of anonymous unions), the ctor-initializer is ill-formed.
However, there is no corresponding restriction against specifying brace-or-equal-initializers for multiple union members, nor for a non-overlapping pair of brace-or-equal-initializer and mem-initializer. This is presumably an oversight.
Proposed resolution (July, 2009):
Change 11.5 [class.union] paragraph 1 as follows:
...If a union contains a non-static data member of reference type the program is ill-formed. At most one non-static data member of a union shall have a brace-or-equal-initializer. [Note:...
Change 11.9.3 [class.base.init] paragraph 5 as follows:
...If a ctor-initializer specifies more than one mem-initializer for the same member,or for the same base classor for multiple members of the same union (including members of anonymous unions), the ctor-initializer is ill-formed.
Change 11.9.3 [class.base.init] paragraph 8 as follows:
...An attempt to initialize more than one non-static data member of a union renders the program ill-formed. After the call to a constructor for class X has completed...
[Voted into WP at March, 2010 meeting.]
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 11.9.5 [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 6.9.2 [intro.multithread]). —end note]
Proposed resolution (October, 2009):
Add the following as a new paragraph at the end of 6.7.4 [basic.life]:
In this section, “before” and “after” refer to the “happens before” relation (6.9.2 [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]
[Voted into WP at March, 2010 meeting.]
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 12.2 [over.match] paragraph 1 as follows:
...how well (for non-static member functions) the object matches theimpliedimplicit object parameter...
Change 12.2.2 [over.match.funcs] paragraph 4 as follows:
...For conversion functions, the function is considered to be a member of the class of theimplicitimplied object argument for the purpose of defining the type of the implicit object parameter...
Change the footnote in 12.2.4 [over.match.best] bullet 1.1 as follows:
[Voted into WP at October, 2009 meeting.]
There are several problems with the phrasing of 12.2.2.2 [over.match.call] paragraphs 1 and 3. Paragraph 1 reads,
Recall from 7.6.1.3 [expr.call], that a function call is a postfix-expression, possibly nested arbitrarily deep in parentheses, followed by an optional expression-list enclosed in parentheses:( ... (opt postfix-expression ) ... )opt ( expression-listopt )
Overload resolution is required if the postfix-expression is the name of a function, a function template (13.7.7 [temp.fct]), an object of class type, or a set of pointers-to-function.
Aside from the fact that directly addressing the reader (“Recall that...”) is stylistically incongruous with the rest of the Standard, as well as the fact that 7.6.1.3 [expr.call] doesn't mention parentheses at all, this wording does not cover member function calls: a member access expression isn't “the name” of anything. This should perhaps be reworded to refer to being either an id-expression or the id-expression in a member access expression. This could be either by using two lines in the “of the form” citation or in the discussion following the syntax reference.
In addition, paragraph 3 refers to “a postfix-expression of the form &F,” which is an oxymoron: &F is a unary-expression, not a postfix-expression. One possibility would be to explicitly include the parentheses needed in this case, i.e., “a postfix-expression of the form (&F)...”
Proposed resolution (September, 2009):
Replace the entirety of 12.2.2.2 [over.match.call] with the following two paragraphs:
In a function call (7.6.1.3 [expr.call])
postfix-expression ( expression-listopt )
if the postfix-expression denotes a set of overloaded functions and/or function templates, overload resolution is applied as specified in 12.2.2.2.2 [over.call.func]. If the postfix-expression denotes an object of class type, overload resolution is applied as specified in 12.2.2.2.3 [over.call.object].
If the postfix-expression denotes the address of a set of overloaded functions and/or function templates, overload resolution is applied using that set as described above. If the function selected by overload resolution is a non-static member function, the program is ill-formed. [Note: The resolution of the address of an overload set in other contexts is described in 12.3 [over.over]. —end note]
[Voted into WP at October, 2009 meeting.]
According to 12.2.2.4 [over.match.ctor],
When objects of class type are direct-initialized (9.5 [dcl.init]), or copy-initialized from an expression of the same or a derived class type (9.5 [dcl.init])... [the] argument list is the expression-list within the parentheses of the initializer.
However, in copy initialization (using the “=” notation), there need be no parentheses. What is the argument list in that case?
Proposed resolution (June, 2009):
Change 12.2.2.4 [over.match.ctor] paragraph 1 as follows:
...The argument list is the expression-list or assignment-expressionwithin the parenthesesof theinitializerinitializer.
[Voted into WP at March, 2010 meeting.]
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 12.2.2.5 [over.match.copy] paragraph 1 second bullet as follows:
[Voted into the WP at the March, 2009 meeting.]
11.4.8.3 [class.conv.fct] paragraph 1 says,
A conversion function is never used to convert a (possibly cv-qualified) object to the (possibly cv-qualified) same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to it), or to (possibly cv-qualified) void.
At what point is this enforced, and how is it enforced?
Consider this test case:
struct abc; struct xyz { xyz(); xyz(xyz &); operator xyz& (); // #1 operator abc& (); // #2 }; struct abc : xyz {}; void foo(xyz &); void bar() { foo (xyz ()); }
If such conversion functions are part of the overload set, #1 is a better conversion than #2 to convert the temporary xyz object to a non-const reference required for foo's operand. If such conversion functions are not part of the overload set, then #2 would be selected, and AFAICT the program would be well formed.
If the conversion functions are not part of the overload set, then it would seem one cannot take their address. For instance, adding the following line to the above test case would find no suitable function:
xyz &(xyz::*ptr) () = &xyz::operator xyz &;
Notes from the October, 2007 meeting:
The intent of 11.4.8.3 [class.conv.fct] paragraph 1 is that overload resolution not be attempted at all for the listed cases; that is, if the target type is void, the object's type, or a base of the object's type, the conversion is done directly without considering any conversion functions. Consequently, the questions about whether the conversion function is part of the overload set or not are moot. The wording will be changed to make this clearer.
Proposed Resolution (October, 2007):
Change the footnote in 11.4.8.3 [class.conv.fct] paragraph 1 as follows:
A conversion function is never used to convert a (possibly cv-qualified) object to the (possibly cv-qualified) same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to it), or to (possibly cv-qualified) void. [Footnote: These conversions are considered as standard conversions for the purposes of overload resolution (12.2.4.2 [over.best.ics], 12.2.4.2.5 [over.ics.ref]) and therefore initialization (9.5 [dcl.init]) and explicit casts (7.6.1.9 [expr.static.cast]). A conversion to void does not invoke any conversion function (7.6.1.9 [expr.static.cast]). Even though never directly called to perform a conversion, such conversion functions can be declared and can potentially be reached through a call to a virtual conversion function in a base class —end footnote]
Additional note (March, 2008):
A slight change to the example above indicates that there is a need for a normative change as well as the clarification of the rationale in the October, 2007 proposed resolution. If the declaration of foo were changed to
void foo(const xyz&);
with the current wording, the call foo(xyz()) would be interpreted as foo(xyz().operator abc&()) instead of binding the parameter directly to the rvalue, which is clearly wrong.
Proposed resolution (March, 2008):
Change the footnote in 11.4.8.3 [class.conv.fct] paragraph 1 as described in the October, 2007 proposed resolution.
Change 9.5.4 [dcl.init.ref] paragraph 5 as follows:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
If the initializer expression
is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an lvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3” [Footnote: This requires a conversion function (11.4.8.3 [class.conv.fct]) returning a reference type. —end footnote] (this conversion is selected by enumerating the applicable conversion functions (12.2.2.7 [over.match.ref]) and choosing the best one through overload resolution (12.2 [over.match])),
then...
[Drafting note: this resolution makes the example in the issue description ill-formed.]
[Voted into WP at October, 2009 meeting.]
12.2.3 [over.match.viable] paragraph 3 says,
If the parameter has reference type, the implicit conversion sequence includes the operation of binding the reference, and the fact that a reference to non-const cannot be bound to an rvalue can affect the viability of the function (see 12.2.4.2.5 [over.ics.ref]).
This should say “lvalue reference to non-const,” as is correctly stated in 12.2.4.2.5 [over.ics.ref] paragraph 3.
Proposed resolution (July, 2009):
Change 12.2.3 [over.match.viable] paragraph 3 as follows:
If the parameter has reference type, the implicit conversion sequence includes the operation of binding the reference, and the fact thataan lvalue reference to non-const cannot be bound to an rvalue can affect the viability of the function (see 12.2.4.2.5 [over.ics.ref]).
[Voted into WP at July, 2009 meeting.]
The overload resolution rules for ranking a template against a non-template function differ for conversion functions in a surprising way. 12.2.4 [over.match.best] lists four checks, the last three concern this report. For the non-conversion operator case, checks 2 and 3 are applicable, whereas for the conversion operator case checks 3 and 4 are applicable. Checks 2 and 4 concern the ranking of argument and return value conversion sequences respectively. Check 3 concerns only the templatedness of the functions being ranked, and will prefer a non-template to a template. Notice that this check happens after argument conversion sequence ranking, but before return value conversion sequence ranking. This has the effect of always selecting a non-template conversion operator, as the following example shows:
struct C { inline operator int () { return 1; } template <class T> inline operator T () { return 0; } }; inline long f (long x) { return x; } int main (int argc, char *argv[]) { return f (C ()); }
The non-templated C::operator int function will be selected, rather than the apparently better C::operator long<long> instantiation. This is a surprise, and resulted in a bug report where the user expected the template to be selected. In addition some C++ compilers have implemented the overload ranking as if checks 3 and 4 were transposed.
Is this ordering accidental, or is there a rationale?
Notes from the April, 2005 meeting:
The CWG agreed that the template/non-template distinction should be the final tie-breaker.
Proposed resolution (March, 2007):
In the second bulleted list of 12.2.4 [over.match.best] paragraph 1, move the second and third bullets to the end of the list, to read as follows:
for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,
the context is an initialization by user-defined conversion (see 9.5 [dcl.init], 12.2.2.6 [over.match.conv], and 12.2.2.7 [over.match.ref]) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type, [Example: ... —end example] or, if not that,
- F1 is a non-template function and F2 is a function template specialization, or, if not that,
F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in 13.7.7.3 [temp.func.order].
[Voted into WP at March, 2010 meeting.]
12.2.4.2 [over.best.ics] paragraph 4 says,
However, when considering the argument of a user-defined conversion function that is a candidate by 12.2.2.4 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, by 12.2.2.8 [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 12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv], or 12.2.2.7 [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 12.2.4.2 [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 12.2.2.4 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, by 12.2.2.8 [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 12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv], or 12.2.2.7 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences areallowedconsidered.
[Voted into WP at March, 2010 meeting.]
According to 12.2.4.2.5 [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 12.2.2 [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 9.5.4 [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 12.2.4.2.5 [over.ics.ref] paragraph 3 as follows:
AExcept for an implicit object parameter, for which see 12.2.2 [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 12.2.2 [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 9.5.4 [dcl.init.ref]). —end note]
[Voted into WP at July, 2009 meeting.]
We need another bullet in 12.2.4.3 [over.ics.rank], along the lines of:
List-initialization sequence L1 is a better conversion sequence than list-initialization sequence L2 if L1 converts to std::initializer_list<X> for some X and L2 does not.
This is necessary to make the following example work:
#include <initializer_list> struct string { string (const char *) {} template <class Iter> string (Iter, Iter); }; template <class T, class U> struct pair { pair (T t, U u) {} }; template<class T, class U> struct map { void insert (pair<T,U>); void insert (std::initializer_list<pair<T,U> >) {} }; int main() { map<string,string> m; m.insert({ {"this","that"}, {"me","you"} }); }
Proposed resolution (March, 2009):
Add a new top-level bullet at the end of the current list in 12.2.4.3 [over.ics.rank] paragraph 3:
[Voted into WP at March, 2010 meeting.]
Conversion of a pointer or pointer to member to bool is given special treatment as a tiebreaker in overload resolution in 12.2.4.3 [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 12.2.4.3 [over.ics.rank] bullet 4.1 as follows:
[Voted into WP at July, 2009 meeting.]
12.5 [over.built] paragraph 7 posits the existence of built-in candidate operator* functions “for every function type T.” However, only non-static member function types can contain a cv-qualifier or ref-qualifier (9.3.4.6 [dcl.fct] paragraph 7), and a reference to such a type cannot be initialized (7.6.1.5 [expr.ref] paragraph 4, bullet 3, sub-bullet 2). (See also _N2914_.14.10.4 [concept.support] paragraph 10, which disallows references to function types with cv-qualifiers but is silent on ref-qualifiers.)
Proposed resolution (March, 2009):
Change 12.5 [over.built] paragraph 7 as follows:
For every function type T that does not have cv-qualifiers or a ref-qualifier, there exist candidate operator functions of the formT & operator*(T*);
Change _N2914_.14.10.4 [concept.support] paragraph 7 as follows:
Requires: for every type T that is an object type, a function type that does not have cv-qualifiers or a ref-qualifier, or cv void, a concept map PointeeType<T> is implicitly defined in namespace std.
Change _N2914_.14.10.4 [concept.support] paragraph 11 as follows:
Requires: for every type T that is an object type, a function type that does not have cv-qualifiers or a ref-qualifier, or a reference type, a concept map ReferentType<T> is implicitly defined in namespace std.
[Voted into WP at October, 2009 meeting.]
12.5 [over.built] paragraph 15 restricts the built-in comparison operators to
every T, where T is an enumeration type or pointer to effective object type
This omits both pointers to function types and pointers to void.
Proposed resolution (July, 2009):
Add a new paragraph following 7.6.9 [expr.rel] paragraph 2:
Change 7.6.10 [expr.eq] paragraph 1 as follows:
...Pointersto objects or functionsof the same type (after pointer conversions) can be compared for equality...
Change 12.5 [over.built] paragraph 15 as follows:
[Voted into WP at March, 2010 meeting.]
12.5 [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 12.5 [over.built] paragraph 25 as follows:
For every type T, where T is a pointer,
orpointer-to-member, or scoped enumeration type, there exist candidate operator functions of the formT operator?(bool, T , T );
[Voted into WP at March, 2010 meeting.]
The list of overloads for user-defined literal operators given in 12.6 [over.literal] paragraph 3 should include signatures for char, wchar_t, char16_t, and char32_t.
Proposed resolution (November, 2009):
Change 12.6 [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
[Voted into WP at March, 2010 meeting as document N3065.]
Exported templates were a great idea that is generally understood to have failed. In the decade since the standard was adopted, only one implementation has appeared. No current vendors appear interested in creating another. We tentatively suggest this makes the feature ripe for deprecation. Our main concern with deprecation is that it might turn out that exported constrained templates become an important compile-time optimization, as the constraints would be checked once in the exported definition and not in each translation unit consuming the exported declarations.
Notes from the March, 2010 meeting:
It was decided to remove export altogether, rather than deprecating it.
[Voted into WP at October, 2009 meeting.]
Nontype template parameters are currently allowed to have rvalue reference type (13.2 [temp.param] bullet 4.3 just says “reference,” not “lvalue reference”). However, with the change of N2844 voted in (which prohibits rvalue references from binding to lvalues), I can't think of any way to specify a valid template argument for a parameter of rvalue reference type. If that's the case, should we restrict nontype template parameters to lvalue reference types?
Proposed resolution (July, 2009):
Change 13.2 [temp.param] paragraph 4, bullet 3 as follows:
[Voted into WP at March, 2010 meeting.]
7.7 [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 13.4.3 [temp.arg.nontype] bullet 5.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 13.4.3 [temp.arg.nontype] bullet 1.1 as follows:
[Voted into WP at March, 2010 meeting.]
According to 13.4.4 [temp.arg.template] paragraph 3,
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 (13.7.4 [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 13.4.4 [temp.arg.template] paragraph 2 as follows:
Y<A> ya; //ill-formed: a template parameter pack does not match a template parameterOK Y<B> yb; //ill-formed: a template parameter pack does not match a template parameterOK Y<C> yc; // OK
[Voted into WP at March, 2010 meeting.]
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 13.7.2.5 [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 13.8.3.3 [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 (13.7.2.5 [temp.static]). Expressions of the following forms are type-dependent only if...
[Voted into WP at March, 2010 meeting.]
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 13.7.5 [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: 13.9.4 [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 13.7.5 [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 };
[Voted into WP at October, 2009 meeting.]
Although it is a reasonable assumption that a template-declaration in which the declaration is an alias-declaration declares a template alias, that is not said explicitly in 13.7.8 [temp.alias] nor, apparently, anywhere else.
Proposed resolution (September, 2009):
Change 13.7.8 [temp.alias] paragraph 1 as follows:
A template-declaration in which the declaration is an alias-declaration (clause 7) declares the identifier to be a template alias. Atemplate alias declarestemplate alias is a name for a family of types. The name of the template alias is a template-name.
[Voted into the WP at the March, 2009 meeting.]
13.8.3 [temp.dep] paragraph 3 reads,
In the definition of a class template or a member of a class template, if a base class of the class template depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.
This wording applies only to definitions of class templates and members of class templates. That would make the following program ill-formed (but it probably should be well-formed):
struct B{ void f(int); }; template<class T> struct D: B { }; template<class T> void g() { struct B{ void f(); }; struct A: D<T> { B m; }; A a; a.m.f(); // Presumably, we want ::g()::B::f(), not ::B::f(int) } int main () { g<int>(); return 0; }
I suspect the wording should be something like
In the definition of a class template or a class defined (directly or indirectly) within the scope of a class template or function template, if a base class...
That should also include deeply nested classes in templates, local classes of non-template member functions of member classes of class templates, etc.
Proposed resolution (October, 2006):
Change 13.8.3 [temp.dep] paragraph 3 as follows:
In the definition of a class or class templateor a member of a class template, if a base classof the class templatedepends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.
[Voted into WP at March, 2010 meeting.]
13.8.3.3 [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 13.8.3.2 [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 13.8.3.3 [temp.dep.expr] paragraph 3 as follows:
An id-expression is type-dependent if it contains:
an identifier
that wasassociated 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.
[Voted into WP at March, 2010 meeting.]
According to 13.8.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 (6.5.3 [basic.lookup.unqual], 6.5.4 [basic.lookup.argdep]) except that:
For the part of the lookup using unqualified name lookup (6.5.3 [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 (6.5.4 [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 13.10.2 [temp.arg.explicit] paragraph 8 clearly indicates that argument-dependent lookup is to be performed for template-ids, and 6.5.4 [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 (6.5.5.3 [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.8.4 [class.friend]).
Proposed resolution (October, 2009):
Change 13.8.3 [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 (13.8.3.3 [temp.dep.expr])...
Change 13.8.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 (6.5.3 [basic.lookup.unqual], 6.5.4 [basic.lookup.argdep]) except that:
For the part of the lookup using unqualified name lookup (6.5.3 [basic.lookup.unqual]), only function declarations
with external linkagefrom the template definition context are found.For the part of the lookup using associated namespaces (6.5.4 [basic.lookup.argdep]), only function declarations
with external linkagefound in either the template definition context or the template instantiation context are found.
[Voted into WP at March, 2010 meeting.]
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 13.9.3 [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 13.9.3 [temp.explicit] does not express this intent clearly enough, however.
Suggested resolution:
Change 13.9.3 [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 13.9.3 [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 13.9.3 [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
onlyan 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,otherexplicit instantiation declarations have the effect of suppressing the implicit instantiation of the entity to which they refer...
[Voted into WP at March, 2010 meeting.]
13.9.3 [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 13.9.3 [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.
[Voted into WP at March, 2010 meeting.]
13.9.3 [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 13.9.3 [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 Ntemplate 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
[Voted into WP at October, 2009 meeting.]
The list of entities that can be explicitly specialized in 13.9.4 [temp.expl.spec] paragraph 1 includes member templates of class templates but not member templates of non-template classes. This omission could lead to the conclusion that such member templates cannot be explicitly specialized. (Note, however, that paragraph 3 refers to “an explicit specialization for a member template of [a] class or class template.”)
Proposed resolution (July, 2009):
Change 13.9.4 [temp.expl.spec] paragraph 1 as follows:
An explicit specialization of any of the following:
...
member class template of a class or class template
non-deleted member function template of a class or class template
can be declared...
[Voted into WP at October, 2009 meeting.]
13.9.4 [temp.expl.spec] paragraphs 15-16 contain the following note:
[Note: there is no syntax for the definition of a static data member of a template that requires default initialization.template<> X Q<int>::x;This is a declaration regardless of whether X can be default initialized (9.5 [dcl.init]). —end note]
While this note is still accurate, the C++0x list initialization syntax provides a way around the restriction, which could be useful if the class is not copyable or movable but has a default constructor. Perhaps the note should be updated to mention that possibility?
Proposed resolution (July, 2009):
Change 13.9.4 [temp.expl.spec] paragraphs 15-16 as follows:
An explicit specialization of a static data member of a template is a definition if the declaration includes an initializer; otherwise, it is a declaration. [Note:
there is no syntax for theThe definition of a static data member of a template that requires default initialization.must use a braced-init-list:template<> X Q<int>::x; // declaration template<> X Q<int>::x (); // error: declares a function template<> X Q<int>::x {}; // definition
This is a declaration regardless of whether X can be default initialized (9.5 [dcl.init]).—end note]
[Voted into WP at March, 2010 meeting.]
According to 13.9.4 [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, 9.6 [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 13.9.4 [temp.expl.spec] paragraph 14 as follows:
An explicit specialization of a function template is inline only if it isexplicitlydeclaredto bewith the inline specifier or defined as deleted, and independently of whether its function template is inline. [Example:...
[Voted into WP at October, 2009 meeting.]
A customer of ours recently brought the following example to our attention. There's some question as to whether the Standard adequately addresses this example, and if it does, whether the outcome is what we'd like to see. Here's the example:
struct Abs {
virtual void x() = 0;
};
struct Der: public Abs {
virtual void x();
};
struct Cnvt {
template <typename F> Cnvt(F);
};
void foo(Cnvt a);
void foo(Abs &a);
void f() {
Der d;
Abs *a = &d;
foo(*a); // #1
return 0;
}
The question is how to perform overload resolution for the call at #1. To do that, we need to determine whether foo(Cnvt) is a viable function. That entails deciding whether there is an implicit conversion sequence that converts Abs (the type of *a in the call) to Cnvt (12.2.3 [over.match.viable] paragraph 3), and that involves a recursive invocation of overload resolution.
The initialization of the parameter of foo(Cnvt) is a case of copy-initialization of a class by user-defined conversion, so the candidate functions are the converting constructors of Cnvt (12.2.2.5 [over.match.copy] paragraph 1), of which there are two: the implicitly-declared copy constructor and the constructor template.
According to 13.9.2 [temp.inst] paragraph 8,
If a function template or a member function template specialization is used in a way that involves overload resolution, a declaration of the specialization is implicitly instantiated (13.10.4 [temp.over]).
Template argument deduction results in “synthesizing” (13.10.4 [temp.over] paragraph 1) (or “instantiating,” 13.9.2 [temp.inst] paragraph 8) the declaration
Cnvt::Cnvt(Abs)
Because Abs is an abstract class, this declaration violates the restriction of 11.7.4 [class.abstract] paragraph 3 (“An abstract class shall not be used as a parameter type...”), and because a parameter of an abstract class type does not cause a deduction failure (it's not in the bulleted list in 13.10.3 [temp.deduct] paragraph 2), the program is ill-formed. This error is reported by both EDG and Microsoft compilers, but not by g++.
It seems unfortunate that the program would be rendered ill-formed by a semantic violation in a declaration synthesized solely for the purpose of overload resolution analysis; foo(Cnvt) would not be selected by overload resolution, so Cnvt::Cnvt(Abs) would not be instantiated.
There's at least some indication that a parameter with an abstract class type should be a deduction failure; an array element of abstract class type is a deduction failure, so one might expect that a parameter would be, also.
(See also issue 339; this question might be addressed as part of the direction described in the notes from the July, 2007 meeting.)
Notes from the June, 2008 meeting:
Paper N2634, adopted at the June, 2008 meeting, replaces the normative list of specific errors accepted as deduction failures by a general statement covering all “invalid types and expressions in the immediate context of the function type and its template parameter types,” so the code is now well-formed. However, the previous list is now a note, and the note should be updated to mention this case.
Proposed resolution (August, 2008):
Add a new bullet following the last bullet of the note in 13.10.3 [temp.deduct] paragraph 8 as follows:
Attempting to create a function type in which a parameter type or the return type is an abstract class type (11.7.4 [class.abstract]).
[Voted into WP at March, 2010 meeting.]
The adoption of paper N2844 made it ill-formed to attempt to bind an rvalue reference to an lvalue. However, the example in 13.10.3.2 [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 13.10.3.2 [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.)
[Voted into WP at October, 2009 meeting.]
13.10.3.2 [temp.deduct.call] paragraph 3 says,
If P is of the form T&&, where T is a template parameter, and the argument is an lvalue, the type A& is used in place of A for type deduction.
The type references in that sentence are inconsistent with the normal usage in the Standard; they should instead refer to “an rvalue reference to a cv-unqualified template parameter” and “lvalue reference to A.”
Proposed resolution (July, 2009):
Change 13.10.3.2 [temp.deduct.call] paragraph 3 as follows:
If P is a cv-qualified type, the top level cv-qualifiers of P's type are ignored for type deduction. If P is a reference type, the type referred to by P is used for type deduction. If P isof the form T&&, where T is a template parameter,an rvalue reference to a cv-unqualified template parameter and the argument is an lvalue, the typeA&“lvalue reference to A” is used in place of A for type deduction.
[Voted into WP at March, 2010 meeting.]
An expression used in an if statement is implicitly converted to type bool (8.5 [stmt.select]). According to the rules of template argument deduction for conversion functions given in 13.10.3.4 [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 13.10.3.4 [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.
[Voted into WP at March, 2010 meeting.]
The rules for deducing function template arguments from a conversion function template include provisions in 13.10.3.4 [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.
[Voted into WP at March, 2010 meeting.]
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 13.10.3.4 [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 bullet 2.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 13.10.3.4 [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 9.5 [dcl.init], 12.2.2.6 [over.match.conv], and 12.2.2.7 [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 13.10.3.6 [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 (7.3.3 [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 (7.3.4 [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).]
[Voted into the WP at the March, 2009 meeting.]
According to 14.2 [except.throw] paragraph 3,
The type of the throw-expression shall not be an incomplete type, or a pointer to an incomplete type other than (possibly cv-qualified) void.
This disallows cases like the following, because str has an incomplete type (an array of unknown size):
extern const char str[]; void f() { throw str; }
The array-to-pointer conversion is applied to the operand of throw, so there's no problem creating the exception object, which is the reason for the restriction on incomplete types. I believe this case should be permitted.
Notes from the April, 2005 meeting:
The CWG agreed that the example should be permitted. Note that the reference to throw-expression in the cited text is incorrect; a throw-expression includes the throw keyword and is always of type void. This wording problem is addressed in the proposed resolution for issue 475.
Proposed resolution (October, 2006)
Change 14.2 [except.throw] paragraph 3 as indicated:
...The type of the throw-expression shall notIf the type of the exception object would be an incomplete type, or a pointer to an incomplete type other than (possibly cv-qualified) void the program is ill-formed...
[Voted into WP at March, 2010 meeting.]
14.2 [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() (17.9.7 [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 14.2 [except.throw] paragraph 4 as follows:
The memory for thetemporary copy of the exception being thrownexception object is allocated in an unspecified way, except as noted in 6.7.6.5.2 [basic.stc.dynamic.allocation].The temporary persists as long as there is a handler being executed for that exception. In particular, ifIf a handler exits byexecuting a throw; statement, that passes controlrethrowing, control is passed to another handler for the same exception, so the temporary remains. The exception object is destroyed after eitherWhenthe last remaining active handler for the exception exits by any means other thanthrow;rethrowing, or the last object of type std::exception_ptr (17.9.7 [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 theThe implementation may then deallocate the memory for thetemporaryexception 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.
[Voted into WP at March, 2010 meeting as paper N3051.]
Exception specifications have proven close to worthless in practice, while adding a measurable overhead to programs. The feature should be deprecated. The one exception to the rule is the empty throw specification which could serve a legitimate optimizing role if the requirement to call std::unexpected were relaxed in this case.
Notes from the July, 2009 meeting:
The consensus of the CWG was in favor of deprecating exception specifications. Further discussion, and with a wider constituency, is needed to determine a position on the status of throw().
(See also issue 814.)
[Voted into WP at March, 2010 meeting.]
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 (14.2 [except.throw] paragraph 3) and in handlers (14.4 [except.handle] paragraph 2), but that was apparently overlooked in the description of exception-specifications.
Proposed resolution (February, 2010):
Change 14.5 [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
anany explicit specialization, of that function shall have an exception-specification with the same set oftype-idsadjusted 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 oftype-idsadjusted 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 oftype-idsadjusted types as other declarations of that function. A diagnostic is required only if the sets oftype-idsadjusted types are different within a single translation unit.
[Voted into the WP at the March, 2009 meeting.]
The destruction of local static objects occurs at the same time as that of non-local objects (6.9.3.3 [basic.start.dynamic] paragraph 1) and the execution of functions registered with std::atexit (paragraph 3). According to 14.6.2 [except.terminate] paragraph 1, std::terminate is called if a destructor for a non-local object or a function registered with std::atexit exits via an exception, but the Standard is silent about the result of throwing an exception from a destructor for a local static object. Presumably this is an oversight and the same rules should apply to destruction of local static objects.
Proposed resolution (September, 2008):
Change 14.6.2 [except.terminate] paragraph 1, fourth bullet as indicated, and add an additional bullet to follow it:
when construction or destruction of a non-local
object with static or thread storage duration exits using an
exception (6.9.3.2 [basic.start.static]), or
when destruction of an object with static or thread storage duration exits using an exception (6.9.3.3 [basic.start.dynamic]), or
[Voted into WP at October, 2009 meeting.]
The description of preprocessing expressions in 15.2 [cpp.cond] paragraph 4 says,
The resulting tokens comprise the controlling constant expression which is evaluated according to the rules of 5.19 using arithmetic that has at least the ranges specified in 17.3 [support.limits], except that all signed and unsigned integer types act as if they have the same representation as, respectively, intmax_t or uintmax_t (18.3.2).
However, this does not address the type implicitly assigned to integral literals. For example, in an implementation where int is 32 bits and long long is 64 bits, is a literal like 0xffffffff signed or unsigned? WG14 adopted DR 265 to deal with this issue in the essentially-identical wording in C99; we should probably follow suit for C++.
Proposed Resolution (July, 2009):
Change 15.2 [cpp.cond] paragraph 4 as follows:
...and then each preprocessing token is converted into a token. The resulting tokens comprise the controlling constant expression which is evaluated according to the rules of 7.7 [expr.const] using arithmetic that has at least the ranges specified in 17.3 [support.limits], except that. For the purposes of this token conversion and evaluation all signed and unsigned integer types act as if they have the same representation as, respectively, intmax_t or uintmax_t (_N3035_.18.4.2 [stdinth])[Footnote: Thus on an implementation where std::numeric_limits<int>::max() is 0x7FFF and std::numeric_limits<unsigned int>::max() is 0xFFFF, the integer literal 0x8000 is signed and positive within a #if expression even though it is unsigned in translation phase 7 (5.2 [lex.phases]). —end footnote]. This includes interpreting character literals...
[Voted into WP at October, 2009 meeting.]
15.2 [cpp.cond] paragraph 1 states,
The expression that controls conditional inclusion shall be an integral constant expression except that: it shall not contain a cast...
The prohibition of casts is vacuous and misleading: as pointed out in the footnote in that paragraph,
Because the controlling constant expression is evaluated during translation phase 4, all identifiers either are or are not macro names — there simply are no keywords, enumeration constants, and so on.
As a result, there can be no casts, which require either keywords or identifiers that resolve to types in order to be recognized as casts. The wording on casts should be removed and replaced by a note recognizing this implication.
Notes from the April, 2007 meeting:
The CWG agreed with this suggested resolution; however, the reference is in the “Preprocessing Directives” clause, which WG21 intends to keep in as close synchronization as possible with the corresponding wording in the C Standard. Any change here must therefore be done in consultation with WG14. Clark Nelson will fulfill this liaison function.
It was also noted that the imminent introduction of constexpr also has the potential for a similar kind of confusion, so the proposed resolution should address both casts and constexpr.
Proposed resolution (July, 2009):
Change 15.2 [cpp.cond] paragraph 1 as follows:
The expression that controls conditional inclusion shall be an integral constant expression except that:it shall not contain a cast;identifiers (including those lexically identical to keywords)...
[Voted into WP at October, 2009 meeting.]
Clause 15 [cpp] refers in several places to “character string literals” without specifying whether they are narrow or wide strings. For instance, what kind of string does the # operator (15.7.3 [cpp.stringize]) produce?
15.8 [cpp.line] paragraph 1 says,
The string literal of a #line directive, if present, shall be a character string literal.
Is “character string literal” intended to mean a narrow string literal? (Also, there is no string-literal mentioned in the grammatical descriptions of #line; paragraph 4 reads,
which is apparently intended to suggest a string literal but does not use the term.)
15.12 [cpp.predefined] should also specify what kind of character string literals are produced by the various string-valued predefined macros.
Notes from the July, 2007 meeting:
The CWG affirmed that all the string literals mentioned in Clause 15 [cpp] are intended to be narrow strings.
Proposed resolution (September, 2008)
Change the footnote in Clause 15 [cpp] paragraph 1 as follows:
Thus, preprocessing directives are commonly called “lines.” These “lines” have no other syntactic significance, as all white space is equivalent except in certain situations during preprocessing (see the #characterstring literal creation operator in 15.7.3 [cpp.stringize], for example).
Change 15.7.3 [cpp.stringize] paragraph 2 as follows:
If, in the replacement list, a parameter is immediately preceded by a # preprocessing token, both are replaced by a singlecharacterordinary string literal (5.13.5 [lex.string]) preprocessing token that contains the spelling of the preprocessing token sequence for the corresponding argument... Otherwise, the original spelling of each preprocessing token in the argument is retained in thecharacterordinary string literal, except for special handling for producing the spelling of string literals and character literals: a \ character is inserted before each " and \ character of a character literal or string literal (including the delimiting " characters). If the replacement that results is not a validcharacterordinary string literal, the behavior is undefined. Thecharacterordinary string literal corresponding to an empty argument is "". The order of evaluation of # and ## operators is unspecified.
Change 15.7.6 [cpp.scope] paragraph 6 as follows:
To illustrate the rules for creatingcharacterordinary string literals and concatenating tokens, the sequence... or, after concatenation of thecharacterordinary string literals...
Change 15.8 [cpp.line] paragraph 1 as follows:
The string literal of a #line directive, if present, shall bea characteran ordinary string literal.
Change 15.8 [cpp.line] paragraph 4 as follows:
...and changes the presumed name of the source file to be the contents of thecharacterordinary string literal.
Change 15.12 [cpp.predefined] paragraph 1 as follows:
__DATE__
The date of translation of the source file (
a characteran ordinary string literal of the form...__FILE__
The presumed name of the source file (
a characteran ordinary string literal)....
__TIME__
The time of translation of the source file (
a characteran ordinary string literal of the form...
Notes from the September, 2008 meeting:
The proposed resolution will be discussed with the C Committee before proceeding, as it is expected that the next revision of the C Standard will also adopt new forms of string literals.
Additional notes (May, 2009):
At its most recent meeting, the C Committee decided to keep the existing term, “character string literal.”
One possibility for maintaining compatible phraseology with the C Standard would be to replace the occurrences of “ordinary string literal” in 5.13.5 [lex.string] with “character string literal,” instead of the extensive set of changes above.
Another possibility would be to leave the references in Clause 15 [cpp] unchanged and just insert a prefatory comment near the beginning that every occurrence of “character string literal” refers to a string-literal with no prefix. (The use of “ordinary string literal” in the preceding edits is problematic in that the phrase includes raw string literals as well as unprefixed literals.)
Proposed resolution (July, 2009):
Change 15.7.3 [cpp.stringize] paragraph 2 as follows:
A character string literal is a string-literal with no prefix. If, in the replacement list, a parameter is immediately preceded by a # preprocessing token...
Change the fifteenth bullet of Annex Clause Annex B [implimits] paragraph 2 as follows:
[Voted into WP at October, 2009 meeting.]
The limit of 17 recursively-nested template instantiations is too small for modern programming practices such as template metaprogramming. It is unclear, however, whether this is a useful metric; see this paper for an example that honors the limit but results in over 750 billion instantiations.
Notes from the July, 2009 meeting:
The consensus of the CWG was to increase the limit to 1024.
Proposed resolution (September, 2009):
Change Clause Annex B [implimits], the fourth bullet from the end, as follows:
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to _N4527_.12.9 [class.inhctor] paragraph 3, the exception specification for an inheriting constructor has the same exception specification as the inherited constructor. This ignores the exception specifications of default constructors for base classes and nonstatic data members and of functions called in brace-or-equals-initializers of nonstatic data members.
Proposed resolution (August, 2011):
Delete the indicated bullet of _N4527_.12.9 [class.inhctor] paragraph 2:
Change _N4527_.12.9 [class.inhctor] paragraph 3 as follows:
...[Note: Default arguments are not inherited. An exception-specification is implied as specified in 14.5 [except.spec]. —end note]
Change 14.5 [except.spec] paragraph 14 as follows:
An inheriting constructor (_N4527_.12.9 [class.inhctor]) and an implicitly declared special member function ( 11.4.4 [special]) shall have an exception-specification. If f is an inheriting constructor or an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f's implicit definition; f shall allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions if every function it directly invokes allows no exceptions. [Note: an instantiation of an inheriting constructor template has an implied exception-specification as if it were a non-template inheriting constructor. —end note] [Example:...
[Moved to DR at the April, 2013 meeting.]
According to _N4527_.12.9 [class.inhctor] paragraph 3,
For each non-template constructor in the candidate set of inherited constructors other than a constructor having no parameters or a copy/move constructor having a single parameter, a constructor is implicitly declared with the same constructor characteristics unless there is a user-declared constructor with the same signature in the class where the using-declaration appears.
It is not clear whether that determination is intended to include constructors declared after the point of the using-declaration or not.
Proposed resolution (February, 2013):
Change 11.4 [class.mem] paragraph 2 as follows:
A class is considered a completely-defined object type (6.8 [basic.types]) (or complete type) at the closing } of the class-specifier. Within the class member-specification, the class is regarded as complete within function bodies, default arguments, using-declarations introducing inheriting constructors (_N4527_.12.9 [class.inhctor]), and brace-or-equal-initializers for non-static data members (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification.
Change 11.4.4 [special] paragraph 1 as follows:
...See 11.4.5 [class.ctor], 11.4.7 [class.dtor] and 11.4.5.3 [class.copy.ctor]. —end note] An implicitly-declared special member function is declared at the closing } of the class-specifier. Programs shall not define implicitly-declared special member functions.
Change _N4527_.12.9 [class.inhctor] paragraph 3 as follows:
For each non-template constructor in the candidate set of inherited constructors other than a constructor having no parameters or a copy/move constructor having a single parameter, a constructor is implicitly declared with the same constructor characteristics unless there is a user-declared constructor with the same signature in the complete class where the using-declaration appears. Similarly, for each constructor template in the candidate set of inherited constructors, a constructor template is implicitly declared with the same constructor characteristics unless there is an equivalent user-declared constructor template (13.7.7.2 [temp.over.link]) in the complete class where the using-declaration appears. [Note: Default arguments are not inherited. An exception-specification is implied as specified in 14.5 [except.spec]. —end note]
Additional note (January, 2013):
A question has been raised as to whether it is necessary to prohibit inheriting constructors from base classes that are also enclosing classes when the derived class is defined outside the member-specification of the enclosing class. This issue has been returned to "review" status to allow discussion of this question.
Additional note (February, 2013):
It was observed that it is not permitted to derive from an incomplete class, which prevents the problem intended to be addressed by the prohibition of inheriting constructors from an enclosing class without disallowing such usage when the nested class is defined outside its enclosing class. That restriction has been removed from the proposed resolution.
[Moved to DR at the October, 2012 meeting.]
The current wording of the Standard does not describe what happens when a decltype-specifier is used as a nested-name-specifier and the type denoted by the decltype-specifier is neither a class type nor an enumeration type. Such nested-name-specifiers should be prohibited, presumably somewhere around paragraphs 8-10 of _N4567_.5.1.1 [expr.prim.general]. (The corresponding prohibition for named types is handled as part of lookup in 6.5.5 [basic.lookup.qual] paragraph 1.)
Proposed resolution (February, 2012):
Add the following immediately after the grammar in _N4567_.5.1.1 [expr.prim.general] paragraph 8 and move the text following that point into a new paragraph:
The type denoted by a decltype-specifier in a nested-name-specifier shall be a class or enumeration type.
A nested-name-specifier that denotes a class...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The incompatibility described appears not to exist.
Proposed resolution (August, 2011):
Delete the second entry in _N4750_.C.1.3 [diff.conv], i.e., the one headed by
Change: Only pointers to non-const and non-volatile objects may be implicitly converted to void*
The following example illustrates an incompatibility between C++11 and C++14:
struct S { int m = 1; }; // C++11: S is non-aggregate // C++14: S is AGGREGATE struct X { operator int(); operator S(); }; int main() { X a{}; S b{a}; // C++11: valid, copy constr S(a.operator S()) is called here // C++14: valid, aggregate initialization { a.operator int() } printf("%d\n", b.m); }
This should be documented in Annex Clause Annex C [diff].
Notes from the May, 2015 meeting:
This will be handled editorially; the status has been set to "review" to check that the editorial change has been made.
[Moved to DR at the October, 2012 meeting.]
According to _N4868_.9.8.2.3 [namespace.memdef] paragraph 3,
If a friend declaration in a non-local class first declares a class or function95 the friend class or function is a member of the innermost enclosing namespace. The name of the friend is not found by unqualified lookup (6.5.3 [basic.lookup.unqual]) or by qualified lookup (6.5.5 [basic.lookup.qual]) until a matching declaration is provided in that namespace scope (either before or after the class definition granting friendship).
This wording does not, but probably should, apply to friend declarations of function templates and class templates as well.
Proposed resolution (February, 2012):
Change 6.5.4 [basic.lookup.argdep] paragraph 1 as follows:
...in those namespaces, namespace-scope friend function or function template declarations (11.3) not otherwise visible may be found...
Change _N4868_.9.8.2.3 [namespace.memdef] paragraph 3 as follows:
Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class,orfunction, class template, or function template95 the friendclass or functionis a member of the innermost enclosing namespace. The name of the friend is not found by unqualified lookup (6.5.3 [basic.lookup.unqual]) or by qualified lookup (6.5.5 [basic.lookup.qual]) until a matching declaration is provided in that namespace scope (either before or after the class definition granting friendship). If a friend function or function template is called, its name may be found by the name lookup that considers functions from namespaces and classes associated with the types of the function arguments (6.5.4 [basic.lookup.argdep]). If the name in a friend declaration...
[Moved to DR at the April, 2013 meeting.]
According to _N4868_.9.8.2.3 [namespace.memdef] paragraph 3,
Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class or function95 the friend class or function is a member of the innermost enclosing namespace. The name of the friend is not found by unqualified lookup (6.5.3 [basic.lookup.unqual]) or by qualified lookup (6.5.5 [basic.lookup.qual]) until a matching declaration is provided in that namespace scope (either before or after the class definition granting friendship).
Taken literally, that would mean the following example is ill-formed:
namespace N { struct A { friend int f(); }; } int N::f() { return 0; } int i = N::f(); // ill-formed: N::f not found
because the definition of N::f appears in global scope rather than in namespace scope.
Proposed resolution (October, 2012):
Change _N4868_.9.8.2.3 [namespace.memdef] paragraph 3 as follows:
Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class or function95 the friend class or function is a member of the innermost enclosing namespace.The name of the friend is not found byThe friend declaration does not by itself make the name visible to unqualified lookup (6.5.3 [basic.lookup.unqual]) orbyqualified lookup (6.5.5 [basic.lookup.qual]). [Note: The name of the friend will be visible in its namespace ifuntila matching declaration is providedin thatat namespace scope (either before or after the class definition granting friendship). —end note] If a friend function is called...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to _N4868_.11.4.3.2 [class.this] paragraph 2,
In a const member function, the object for which the function is called is accessed through a const access path; therefore, a const member function shall not modify the object and its non-static data members.
This is clearly overstating the case: mutable members can be modified, a const_cast can be used, and class member access expressions not involving this can also allow the object to be modified. The effect of the cv-qualification of a member function on the type of *this is clear from the preceding paragraph; this statement appears both unnecessary and incorrect.
Proposed resolution (August, 2011):
Merge _N4868_.11.4.3.2 [class.this] paragraphs 1 and 2 and change the text as follows:
In the body of a non-static (11.4.2 [class.mfct]) member function, the keyword this is a prvalue expression whose value is the address of the object for which the function is called. The type of this in a member function of a class X is X*. If the member function is declared const, the type of this is const X*, if the member function is declared volatile, the type of this is volatile X*, and if the member function is declared const volatile, the type of this is const volatile X*.In[Note: thus in a const member function, the object for which the function is called is accessed through a const access path; therefore, a const member function shall not modify the object and its non-static data members. —end note] [Example:...
[Moved to DR at the October, 2012 meeting.]
The current Standard says that any use of an invalid pointer value produces undefined behavior (6.7.6.5.3 [basic.stc.dynamic.deallocation] paragraph 4). This includes not only dereferencing the pointer but even just fetching its value. The reason for this draconian restriction is that some architectures in the past used dedicated address registers for pointer loads and stores and they could fault if, for example, a segment number in a pointer was not currently mapped.
It is not clear whether such restrictions are necessary with architectures currently in use or reasonably foreseen. This should be investigated to see if the restriction can be loosened to apply only to dereferencing the pointer.
Proposed resolution (February, 2012):
Change 6.7.6.5.3 [basic.stc.dynamic.deallocation] paragraph 4 as follows:
If the argument given to a deallocation function in the standard library is a pointer that is not the null pointer value (7.3.12 [conv.ptr]), the deallocation function shall deallocate the storage referenced by the pointer, rendering invalid all pointers referring to any part of the deallocated storage.The effect of using an invalid pointer value (including passing it to a deallocation function) is undefinedIndirection through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior. Any other use of an invalid pointer value has implementation-defined behavior. [Footnote:On someSome implementations, itmight define that copying an invalid pointer value causes a system-generated runtime fault. —end footnote]
[Moved to DR at the April, 2013 meeting.]
The example in _N5001_.9.12.4 [dcl.attr.depend] paragraph 4 reads,
/* Translation unit A. */ struct foo { int* a; int* b; }; std::atomic<struct foo *> foo_head[10]; int foo_array[10][10]; [[carries_dependency]] struct foo* f(int i) { return foo_head[i].load(memory_order_consume); } [[carries_dependency]] int g(int* x, int* y) { return kill_dependency(foo_array[*x][*y]); } /* Translation unit B. */ [[carries_dependency]] struct foo* f(int i); [[carries_dependency]] int* g(int* x, int* y); int c = 3; void h(int i) { struct foo* p; p = f(i); do_something_with(g(&c, p->a)); do_something_with(g(p->a, &c)); }
There appear to be two errors in this example. First, g is declared as returning int in TU A but int* in TU B. Second, paragraph 6 says,
Function g's second argument has a carries_dependency attribute
but that is not reflected in the example.
Proposed resolution (August, 2012):
Change the example in _N5001_.9.12.4 [dcl.attr.depend] paragraph 4 as follows:
/* Translation unit A. */ struct foo { int* a; int* b; }; std::atomic<struct foo *> foo_head[10]; int foo_array[10][10]; [[carries_dependency]] struct foo* f(int i) { return foo_head[i].load(memory_order_consume); }[[carries_dependency]]int g(int* x, int* y [[carries_dependency]]) { return kill_dependency(foo_array[*x][*y]); } /* Translation unit B. */ [[carries_dependency]] struct foo* f(int i);[[carries_dependency]] int*int g(int* x, int* y [[carries_dependency]]); int c = 3; void h(int i) { struct foo* p; p = f(i); do_something_with(g(&c, p->a)); do_something_with(g(p->a, &c)); }
Change _N5001_.9.12.4 [dcl.attr.depend] paragraph 6 as follows:
Function g's secondargumentparameter has a carries_dependency attribute, but its firstargumentparameter does not. Therefore, function h's first call to g carries a dependency into g, but its second call does not. The implementation might need to insert a fence prior to the second call to g.
[Moved to DR at the April, 2013 meeting.]
The C++ Standard uses the phrase “indeterminate value” without defining it. C99 defines it as “either an unspecified value or a trap representation.” Should C++ follow suit?
In addition, 7.3.2 [conv.lval] paragraph 1 says that applying the lvalue-to-rvalue conversion to an “object [that] is uninitialized” results in undefined behavior; this should be rephrased in terms of an object with an indeterminate value.
Proposed resolution (October, 2012):
Change 7.3.2 [conv.lval] paragraphs 1 and 2 as follows (including changing the running text of paragraph 2 into bullets):
A glvalue (7.2.1 [basic.lval]) of a non-function, non-array type T can be converted to a prvalue.53 If T is an incomplete type, a program that necessitates this conversion is ill-formed.
If the object to which the glvalue 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.If T is a non-class type, the type of the prvalue is the cv-unqualified version of T. Otherwise, the type of the prvalue is T.54When an lvalue-to-rvalue conversion occurs in an unevaluated operand or a subexpression thereof (Clause 7 [expr]) the value contained in the referenced object is not accessed. In all other cases, the result of the conversion is determined according to the following rules:
If T is (possibly cv-qualified) std::nullptr_t, the result is a null pointer constant (7.3.12 [conv.ptr]).
Otherwise, if
the glvalueT has a class type, the conversion copy-initializes a temporary of type T from the glvalue and the result of the conversion is a prvalue for the temporary.Otherwise, if the object to which the glvalue refers contains an invalid pointer value (6.7.6.5.3 [basic.stc.dynamic.deallocation], _N4885_6.7.5.5.4 [basic.stc.dynamic.safety]), the behavior is implementation-defined.
Otherwise, if T is a (possibly cv-qualified) unsigned character type (6.8.2 [basic.fundamental]), and the object to which the glvalue refers contains an indeterminate value (7.6.2.8 [expr.new], 9.5 [dcl.init], 11.9.3 [class.base.init]), and that object does not have automatic storage duration or the glvalue was the operand of a unary & operator or it was bound to a reference, the result is an unspecified value. [Footnote: The value may be different each time the lvalue-to-rvalue conversion is applied to the object. An unsigned char object with indeterminate value allocated to a register might trap. —end footnote]
Otherwise, if the object to which the glvalue refers contains an indeterminate value, the behavior is undefined.
Otherwise, if the glvalue has (possibly cv-qualified) type std::nullptr_t, the prvalue result is a null pointer constant (7.3.12 [conv.ptr]).Otherwise, the value contained in the object indicated by the glvalue is the prvalue result.
Change 7.6.1.5 [expr.ref] paragraph 4 second bullet as follows:
If E2 is a static data member...
...If E1 is an lvalue, then E1.E2 is an
lvalue; if E1 is an xvalue, then
otherwise E1.E2 is an xvalue; otherwise, it is a
prvalue. Let the notation...
If E2 is a (possibly overloaded) member function...
Change 7.6.4 [expr.mptr.oper] paragraph 6 as follows:
...The result of a .* expression whose second operand is a pointer to a data member isof the same value category (7.2.1 [basic.lval]) as its first operandan lvalue if the first operand is an lvalue and an xvalue otherwise. The result of a .* expression whose second operand is a pointer to a member function...
This resolution also resolves issues 129, 240, 312, 623, and 1013.
(See also issue 1213.)
Additional note (August, 2012):
It was observed that the phrase in the fourth bullet of the change to 7.3.2 [conv.lval] paragraph 2 that reads “is not a local variable” should probably be changed to “does not have automatic storage duration,” because objects with static storage duration are zero-initialized and thus cannot have an indeterminate value. The issue was returned to "review" status for discussion of this point.
[Moved to DR at the April, 2013 meeting.]
The Standard uses the phrase, “user-defined type,” but it is not clear what it is intended to mean. For example, 16.4.5.2.1 [namespace.std] paragraph 1 says,
A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type...
Are types defined in the Standard library “user-defined?”
9.2.9.3 [dcl.type.simple] paragraph 2 says,
The auto specifier is a placeholder for a type to be deduced (9.2.9.7 [dcl.spec.auto]). The other simple-type-specifiers specify either a previously-declared user-defined type or one of the fundamental types (6.8.2 [basic.fundamental]).
implying that all non-fundamental types are “user-defined.”
A definition is needed, as well as a survey of uses of the term to ensure consistency with the definition.
Proposed resolution (October, 2012):
Change 9.2.9.3 [dcl.type.simple] paragraph 2 as follows:
The auto specifier is a placeholder for a type to be deduced (9.2.9.7 [dcl.spec.auto]). The other simple-type-specifiers specify either a previously-declareduser-definedtype, a type determined from an expression, or one of the fundamental types (6.8.2 [basic.fundamental]). Table 10 summarizes the valid combinations of simple-type-specifiers and the types they specify.
Change 7.3 [conv] paragraph 4 as follows:
[Note: For
user-definedclass types, user-defined conversions are considered as well; see 11.4.8 [class.conv]. In general, an implicit conversion sequence (12.2.4.2 [over.best.ics]) consists of a standard conversion sequence followed by a user-defined conversion followed by another standard conversion sequence. —end note]
Change the example in 12.2.2.3 [over.match.oper] paragraph 1 as follows:
... void f(void) { const char* p= "one" + "two"; // ill-formed because neither // operand hasuser-definedclass or enumeration type int I = 1 + 1; // Always evaluates to 2 even if //user-definedclass or enumeration types exist which // would perform the operation. }
[Moved to DR at the April, 2013 meeting.]
The verb “access” is used in various places in the Standard (see 6.7.4 [basic.life] paragraphs 5 and 6 and 7.2.1 [basic.lval] paragraph 10) but is not defined. C99 defines it as
<execution-time action> to read or modify the value of an object
(See also issue 1530.)
Proposed resolution (March, 2013):
Add the following to Clause 3 [intro.defs]:
access
<execution-time action> to read or modify the value of an object
Change 6.9.1 [intro.execution] paragraph 12 as follows:
AccessingReading an object designated by a volatile glvalue (7.2.1 [basic.lval]), modifying an object, calling...
Change 6.9.2 [intro.multithread] paragraph 4 as follows:
Two expression evaluations conflict if one of them modifies a memory location (6.7.1 [intro.memory]) and the other oneaccessesreads or modifies the same memory location.
Change 6.9.2 [intro.multithread] paragraph 24 as follows:
The implementation may assume that any thread will eventually do one of the following:
...
accessread or modify a volatile object, or...
Change 7.6.19 [expr.assign] paragraph 8 as follows:
If the value being stored in an object isaccessed fromread via another object that overlaps in any way the storage of the first object, then the overlap shall be exact and the two objects shall have the same type, otherwise the behavior is undefined. [Note:...
[Moved to DR at the October, 2012 meeting.]
According to 5.13.3 [lex.ccon] paragraph 1,
A character literal that does not begin with u, U, or L is an ordinary character literal, also referred to as a narrow-character literal. An ordinary character literal that contains a single c-char has type char, with value equal to the numerical value of the encoding of the c-char in the execution character set.
However, the definition of c-char includes as one possibility a universal-character-name. The value of a universal-character-name cannot, in general, be represented as a char, so this specification is impossible to satisfy.
(See also issue 411 for related questions.)
Additional note (February, 2012):
See the discussion in issue 1422 for a possible interpretation of the existing text.
Proposed resolution (February, 2012):
Change 5.13.3 [lex.ccon] paragraph 1 as follows:
...A character literal that does not begin with u, U, or L is an ordinary character literal, also referred to as a narrow-character literal. An ordinary character literal that contains a single c-char representable in the execution character set has type char, with value equal to the numerical value of the encoding of the c-char in the execution character set. An ordinary character literal that contains more than one c-char is a multicharacter literal. A multicharacter literal, or an ordinary character literal containing a single c-char not representable in the execution character set, is conditionally-supported, has type int, and has an implementation-defined value.
This resolution also resolves issue 1024.
[Moved to DR at the October, 2012 meeting.]
There is no limit placed on the number of c-chars in a multicharacter literal or a wide-character literal containing multiple c-chars, either in 5.13.3 [lex.ccon] paragraphs 1-2 or in Annex Clause Annex B [implimits]. Presumably this means that an implementation must accept arbitrarily long literals.
An alternative approach might be to state that these literals are conditionally supported with implementation-defined semantics, allowing an implementation to impose a documented limit that makes sense for the particular architecture.
Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 912.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
In describing static data members initialized inside the class definition, 11.4.9.3 [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 6.3 [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 (7.7 [expr.const]) and the lvalue-to-rvalue conversion (7.3.2 [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 7.6.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 (August, 2011):
Divide 6.3 [basic.def.odr] paragraph 2 into two paragraphs and change as follows:
An expression is potentially evaluated unless it is an unevaluated operand (Clause 5) or a subexpression thereof. The set of potential results of an expression e is defined as:
if e is an id-expression (_N4567_.5.1.1 [expr.prim.general]), the set whose sole member is e,
if e is a class member access (7.6.1.5 [expr.ref]), the set of potential results of the object expression,
if e is a pointer-to-member expression (7.6.4 [expr.mptr.oper]) whose second operand is a constant expression, the set of potential results of the object expression,
if e has the form (e1), the set of potential results of e1,
if e is a glvalue conditional expression (7.6.16 [expr.cond]), the union of the sets of potential results of the second and third operands,
if e is a comma expression (7.6.20 [expr.comma]), the set of potential results of the right operand,
otherwise, the empty set.
A variable x whose name appears as a
potentially-evaluated expression ex is
odr-used unless it x is an
object that satisfies the requirements for appearing in a constant
expression (7.7 [expr.const]) and ex is an
element of the set of potential results of an expression e,
where either the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is immediately applied to e, or
e is a discarded-value expression (Clause 7 [expr]). this is odr-used...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The current wording of 6.3 [basic.def.odr] paragraph 2 uses the term “overloaded” differently from its definition in Clause 12 [over] paragraph 1. For example, names found by argument-dependent lookup are not “overloaded” if they are not declared in the same scope. The phrasing should be reconciled between the two sections.
Proposed resolution (August, 2011):
Change 6.3 [basic.def.odr] paragraph 2 as follows:
...Anon-overloadedfunction whose name appears as a potentially-evaluated expression is odr-used if it is the unique lookup result or it is the selectedor amember of a set ofcandidateoverloaded functions (6.5 [basic.lookup], 12.2 [over.match], 12.3 [over.over]), if selected by overload resolution when referred to from a potentially-evaluated expression, is odr-used, unless it is a pure virtual function and its name is not explicitly qualified...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The requirement in 6.3 [basic.def.odr] paragraph 4 that a type T must be complete if an expression is implicitly converted to a pointer to T or reference to T inadvertently applies to user-defined conversions, although it was intended only to refer to built-in conversions.
Proposed resolution (August, 2011):
Change the indicated bullet of 6.3 [basic.def.odr] paragraph 4 as follows:
an expression that is not a null pointer constant, and has
type other than cv void*, is converted to
the type pointer to T or reference to T using
an implicit a standard conversion (
7.3 [conv]), a dynamic_cast (7.6.1.7 [expr.dynamic.cast]) or a static_cast (7.6.1.9 [expr.static.cast]),
or
[Moved to DR at the April, 2013 meeting.]
We have a special case in 6.3 [basic.def.odr] paragraph 2 that variables which satisfy the requirements for appearing in a constant expression are not odr-used if the lvalue-to-rvalue conversion is immediately applied. This special case only applies to objects, and thus does not apply to variables of reference type. This inconsistency seems strange, and there is implementation divergence:
int n; void f() { constexpr int &r = n; [] { return r; }; // error: r is odr-used but not captured }
This code is accepted by g++ but rejected by clang. Should r be odr-used here?
Proposed resolution (October, 2012):
Change 6.3 [basic.def.odr] paragraph 3 as follows:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless xis an object thatsatisfies the requirements for appearing in a constant expression (7.7 [expr.const]) and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is applied to e, or e is a discarded-value expression ( Clause 7 [expr]). this is odr-used...
[Moved to DR at the April, 2013 meeting.]
One of the criteria in 6.3 [basic.def.odr] paragraph 6 for when a entity is allowed to have multiple definitions is:
in each definition of D, corresponding names, looked up according to 6.5 [basic.lookup], shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution (12.2 [over.match]) and after matching of partial template specialization (13.10.4 [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 (7.7 [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
This wording is possibly not sufficiently clear for an example like:
const volatile int n = 0; inline int get() { return n; }
Presumably this code could not appear in multiple translation units, because the requirement that n “has the same value in all definitions” cannot be satisfied (the value of a volatile variable can change “by means undetectable by the implementation,” per 9.2.9.2 [dcl.type.cv] paragraph 7, so the value of n might be different in each translation unit). However, it might be good to make it explicit that “a const object” is not intended to apply to a volatile-qualified object.
Other points that were raised during the discussion of this issue were that it would probably be better to rephrase “the value (but not the address) of the object is used” in terms of the odr-use of the object, as well as questioning why a const volatile variable implicitly has internal linkage.
Proposed resolution (October, 2012):
Change 6.3 [basic.def.odr] paragraph 6 as follows:
There can be more than one definition...
...
in each definition of D, corresponding names, looked up according to 6.5 [basic.lookup], shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution (12.2 [over.match]) and after matching of partial template specialization (13.10.4 [temp.over]), except that a name can refer to a non-volatile 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 (7.7 [expr.const]), and
the value (but not the address) ofthe object isusednot odr-used, and the object has the same value in all definitions of D; and...
Change 6.6 [basic.link] paragraph 3 as follows:
A name having namespace scope (6.4.6 [basic.scope.namespace]) has internal linkage if it is the name of
a variable, function or function template that is explicitly declared static; or,
a non-volatile variable that is explicitly declared const or constexpr and neither explicitly declared extern nor previously declared to have external linkage; or
a data member of an anonymous union.
[Moved to DR at the April, 2013 meeting.]
According to 6.4.2 [basic.scope.pdecl] paragraph 2,
The point of declaration for an enumeration is immediately after the identifier (if any) in either its enum-specifier (9.8.1 [dcl.enum]) or its first opaque-enum-declaration (9.8.1 [dcl.enum]), whichever comes first.
This would make the following example well-formed:
template<typename T> struct S { typedef char I; }; enum E: S<E>::I { e };
Presumably that would make E an incomplete enumeration type for the instantiation of S<E> (a concept not otherwise found in the Standard). However, some implementations reject this example, presumably making the point of declaration after the enum-base. We either need to make it ill-formed or describe incomplete enumeration types.
See also issue 977.
Proposed resolution (December, 2012):
Change 6.8 [basic.types] paragraph 5 as follows:
A class that has been declared but not defined, an enumeration type in certain contexts (9.8.1 [dcl.enum]), or an array of unknown size or of incomplete element type, is an incompletely-defined object type.43 Incompletely-defined object types...
Add the following as a new paragraph preceding 9.8.1 [dcl.enum] paragraph 6:
An enumeration whose underlying type is fixed is an incomplete type from its point of declaration (6.4.2 [basic.scope.pdecl]) to immediately after its enum-base (if any), at which point it becomes a complete type. An enumeration whose underlying type is not fixed is an incomplete type from its point of declaration to immediately after the closing } of its enum-specifier, at which point it becomes a complete type.
For an enumeration whose underlying type is not fixed...
This resolution also resolves issue 977.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The rules regarding class scope and when the class is considered to be complete (normally implemented by deferred parsing of portions of class member declarations) are inconsistent and need to be clarified.
Proposed resolution (August, 2011):
Change 6.4.7 [basic.scope.class] paragraph 1 as follows:
Change 6.5.3 [basic.lookup.unqual] paragraph 7 as follows:
A name used in the definition of a class X outside of a member function body, default argument, brace-or-equal-initializer of a non-static data member, or nested class definition29 shall be declared in one of the following ways:...
Change 6.5.3 [basic.lookup.unqual] paragraph 8 as follows:
AFor the members of a class X, a name used in a member function body, in a default argument, in the brace-or-equal-initializer of a non-static data member (11.4 [class.mem]), or in the definition of a class memberfunction (11.4.2 [class.mfct]) of class Xoutside of the definition of X, following thefunction'smember's declarator-id [Footnote: That is, an unqualified name that occurs, for instance, in a typeor default argumentin the parameter-declaration-clause or in thefunction bodyexception-specification. —end footnote],or in the brace-or-equal-initializer of a non-static data member (11.4 [class.mem]) of class Xshall be declared in one of the following ways:...
[Moved to DR at the April, 2013 meeting.]
In 6.5.5.2 [class.qual] paragraph 2,
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 11 [class]), or
in a using-declaration (9.10 [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.
it is not clear what constitutes “an acceptable lookup result.” For instance, is
struct S { } *sp = new S::S;
well-formed?
The intent of the wording was that S::S would refer to the constructor except in lookups that ignore the names of functions, e.g., in elaborated-type-specifiers and nested-name-specifiers. There doesn't seem to be a good reason to allow a qualified-id naming the injected-class-name. The alternative, i.e., only to find the constructor in a declarator, complicates parsing because the determination of whether the name is a type or a function would require lookahead.
Proposed resolution (August, 2012):
Change 6.5.5.2 [class.qual] paragraph 2 as follows:
In a lookup in which
the constructor is an acceptable lookup resultfunction names are not ignored [Footnote: Lookups in which function names are ignored include names appearing in a nested-name-specifier, an elaborated-type-specifier, or a base-specifier. —end footnote] 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 11 [class]), or
in a using-declaration (9.10 [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...
[Moved to DR at the October, 2012 meeting.]
There does not appear to be wording that prohibits a block-scope extern object declaration from being a definition.
Proposed resolution (February, 2012):
Add the following as a new paragraph following 9.5 [dcl.init] paragraph 4:
[Note: Default arguments are more restricted; see 9.3.4.7 [dcl.fct.default].
The order of initialization of variables with static storage duration is described in 6.9.3 [basic.start] and 8.9 [stmt.dcl]. —end note]
A declaration of a block-scope variable with external or internal linkage that has an initializer is ill-formed.
To zero-initialize an object...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
An lvalue referring to an out-of-lifetime non-POD class objects can be used in limited ways, subject to the restrictions in 6.7.4 [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 (7.3.12 [conv.ptr]) to a reference to a base class type, or
the lvalue is used as the operand of a static_cast (7.6.1.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 (7.6.1.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 9.5.4 [dcl.init.ref], not to 7.3.12 [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 6.7.4 [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 (7.3.12 [conv.ptr]) to a pointer to a virtual base class
type, orthe pointer is used as the operand of a static_cast (7.6.1.9 [expr.static.cast])
(except when the conversion is tovoid*, or to void* and subsequently to char*, or unsigned char*).pointer to cv void, or to pointer to cv void and subsequently to pointer to cv char or pointer to cv unsigned char, orthe pointer is used as the operand of a dynamic_cast (7.6.1.7 [expr.dynamic.cast])...
Change 6.7.4 [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 (7.3.12 [conv.ptr])bound to a reference to a virtual base classtype(9.5.4 [dcl.init.ref]), or
the lvalue is used as the operand of a static_cast (7.6.1.9 [expr.static.cast]) except when the conversion is ultimately to cv char& or cv unsigned char&, orthe lvalue is used as the operand of a dynamic_cast (7.6.1.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.]
[Moved to DR at the April, 2013 meeting.]
6.7.6.5.3 [basic.stc.dynamic.deallocation] paragraph 4 mentions that the effect of using an invalid pointer value is undefined. However, the standard never says what it means to 'use' a value.
There are a number of possible interpretations, but it appears that each of them leads to undesired conclusions:
int *x = new int(0); delete x; x = 0;into undefined behaviour. As this is a common idiom, this is clearly undesirable.
int *x = new int(0); delete x; x->~int();into undefined behaviour; according to _N4778_.7.6.1.4 [expr.pseudo], the variable x is 'evaluated' as part of evaluating the pseudo destructor call. This, in turn, would mean that all containers (Clause 23 [containers]) of pointers show undefined behaviour, e.g. 23.3.11.4 [list.modifiers] requires to invoke the destructor as part of the clear() method of the container.
If any other meaning was intended for 'using an expression', that meaning should be stated explicitly.
(See also issue 623.)
Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 616.
Any use of a pointer to deleted storage, even if the pointer is not dereferenced, produces undefined behavior (6.7.6.5.3 [basic.stc.dynamic.deallocation] paragraph 4) . The reason for this restriction is that, on some historical architectures, deallocating an object might free a memory segment, resulting in a hardware exception if a pointer referring to that segment were loaded into a pointer register, and on those architectures use of a pointer register for moving and comparing pointers was the most efficient mechanism for these operations.
It is not clear whether current or foreseeable architectures still require such a draconian restriction or whether it is feasible to relax it only to forbid a smaller range of operations. Of particular concern is the use of atomic pointers, which might be used in race conditions involving deallocation, where the loser of the race might encounter this undefined behavior.
(See also issue 312.)
Rationale (April, 2007):
The current specification is clear and was well-motivated. Analysis of whether this restriction is still needed should be done via a paper and discussed in the Evolution Working Group rather than being handled by CWG as an issue/defect.
Additional note, February, 2014:
This issue was resolved by the resolution of issue 616, which made use of a pointer to deleted storage implementation-defined behavior.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
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 6.7.7 [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 (7.6.20 [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 6.7.7 [class.temporary] unchanged and adding normative wording to 7.6.20 [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 7.6.20 [expr.comma] paragraph 1:
... The type and value of the result are the type and value of the right operand; the result is of the same value category as its right operand, and is a bit-field if its right operand is a glvalue and a bit-field. If the value of the right operand is a temporary (6.7.7 [class.temporary]), the result is that temporary.
[Moved to DR at the April, 2013 meeting.]
In 6.8 [basic.types] paragraph 10, the standard makes it quite clear that volatile qualified types are PODs:
Arithmetic types (6.8.2 [basic.fundamental]), enumeration types, pointer types, and pointer to member types (6.8.4 [basic.compound]), and cv-qualified versions of these types (6.8.5 [basic.type.qualifier]) are collectively called scalar types. Scalar types, POD-struct types, POD-union types (Clause 11 [class]), arrays of such types and cv-qualified versions of these types (6.8.5 [basic.type.qualifier]) are collectively called POD types.
However in 6.8 [basic.types] paragraph 3, the standard makes it clear that PODs can be copied “as if” they were a collection of bytes by memcpy:
For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the value of obj1 is copied into obj2, using the std::memcpy library function, obj2 shall subsequently hold the same value as obj1.
The problem with this is that a volatile qualified type may need to be copied in a specific way (by copying using only atomic operations on multithreaded platforms, for example) in order to avoid the “memory tearing” that may occur with a byte-by-byte copy.
I realise that the standard says very little about volatile qualified types, and nothing at all (yet) about multithreaded platforms, but nonetheless this is a real issue, for the following reason:
The forthcoming TR1 will define a series of traits that provide information about the properties of a type, including whether a type is a POD and/or has trivial construct/copy/assign operations. Libraries can use this information to optimise their code as appropriate, for example an array of type T might be copied with a memcpy rather than an element-by-element copy if T is a POD. This was one of the main motivations behind the type traits chapter of the TR1. However it's not clear how volatile types (or POD's which have a volatile type as a member) should be handled in these cases.
Notes from the April, 2005 meeting:
It is not clear whether the volatile qualifier actually guarantees atomicity in this way. Also, the work on the memory model for multithreading being done by the Evolution Working Group seems at this point likely to specify additional semantics for volatile data, and that work would need to be considered before resolving this issue.
(See also issue 1746.)
Proposed resolution, October, 2012:
Change 6.8 [basic.types] paragraph 9 as follows:
...Scalar types, trivially copyable class types (Clause 11 [class]), arrays of such types, andcv-qualifiednon-volatile const-qualified versions of these types (6.8.5 [basic.type.qualifier]) are collectively called trivially copyable types. Scalar types, trivial class types...
Change 9.2.9.2 [dcl.type.cv] paragraphs 6-7 as follows:
What constitutes an access to an object that has volatile-qualified type is implementation-defined. If an attempt is made to refer to an object defined with a volatile-qualified type through the use of a glvalue with a non-volatile-qualified type, the program behavior is undefined.
[Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. Furthermore, for some implementations, volatile might indicate that special hardware instructions are required to access the object. See 6.9.1 [intro.execution] for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. —end note]
Change 11.4.5.3 [class.copy.ctor] paragraph 12 as follows:
A copy/move constructor for class X is trivial if it is not user-provided, its declared parameter type is the same as if it had been implicitly declared, and if
class X has no virtual functions (11.7.3 [class.virtual]) and no virtual base classes (11.7.2 [class.mi]), and
class X has no non-static data members of volatile-qualified type, and
...
Change 11.4.5.3 [class.copy.ctor] paragraph 25 as follows:
A copy/move assignment operator for class X is trivial if it is not user-provided, its declared parameter type is the same as if it had been implicitly declared, and if
class X has no virtual functions (11.7.3 [class.virtual]) and no virtual base classes (11.7.2 [class.mi]), and
class X has no non-static data members of volatile-qualified type, and
...
Notes from the June, 2016 meeting:
The resolution of issue 2094 revert the changes above revert the changes of volatile qualification on trivial copyability.
The requirement in 6.7.4 [basic.life] paragraph 10 that
is mostly redundant with the constexpr constructor requirements in 9.2.6 [dcl.constexpr] paragraph 4 (although 11.9.3 [class.base.init] does not establish a strict equivalence between brace-or-equal-initializers and mem-initializers).
Proposed resolution (April, 2013):
This issue is resolved by the changes in N3652, adopted at the April, 2013 (Bristol) meeting.
[Moved to DR at the April, 2013 meeting.]
Currently, literal class types can have mutable members. It is not clear whether that poses any particular problems with constexpr objects and constant expressions, and if so, what should be done about it.
Proposed resolution (February, 2012):
Change 7.7 [expr.const] paragraph 2 as follows:
...
an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) unless it is applied to
a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or
a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable sub-object of such an object, or
a glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a constant expression;
...
[Moved to DR at the October, 2012 meeting.]
Can a literal class have a volatile member? For example,
struct S { constexpr S() : n(0) { } volatile int n; }; constexpr S s; // causes volatile write to S::n
Proposed resolution (February, 2012):
Change 6.8 [basic.types] paragraph 10 as follows:
A type is a literal type if it is:
...
a class type (Clause 11 [class]) that has all the following properties:
...
all of its non-static data members and base classes are of non-volatile literal types.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
There is no normative requirement on the ranges of the integral types, although the footnote in 6.8.2 [basic.fundamental] paragraph 2 indicates the intent (for int, at least) that they match the values given in the <climits> header. Should there be an explicit requirement of some sort?
(See also paper N1693.)
Proposed resolution (August, 2011):
Change 6.8.2 [basic.fundamental] paragraph 3 as follows:
...collectively called the extended integer types. The signed and unsigned integral types shall satisfy the constraints given in ISO C 5.2.4.2.1.
[Moved to DR at the October, 2012 meeting.]
The list of acceptable uses of an expression of type void in 6.8.2 [basic.fundamental] paragraph 9 does not, but should, include an operand of the noexcept operator.
Proposed resolution (August, 2011):
Change 6.8.2 [basic.fundamental] paragraph 9 as follows:
An expression of type void shall be used only as an expression statement (8.3 [stmt.expr]), as an operand of a comma expression (7.6.20 [expr.comma]), as a second or third operand of ?: (7.6.16 [expr.cond]), as the operand of typeid, noexcept, or decltype, as the expression in a return statement (8.7.4 [stmt.return]) for a function with the return type void, or as the operand of an explicit conversion to type cv void.
[Moved to DR at the April, 2013 meeting.]
According to 6.8.2 [basic.fundamental] paragraph 4,
Unsigned integers, declared unsigned, shall obey the laws of arithmetic modulo 2n where n is the number of bits in the value representation of that particular size of integer.
It is not clear whether this wording intentionally excludes types like char16_t and char32_t (and, possibly, types char and wchar_t, if those types are unsigned in a given implementation), since the unsigned keyword is not used in their declaration.
Proposed resolution (October, 2012):
Change 6.8.2 [basic.fundamental] paragraph 4 as follows:
Unsigned integers, declared unsigned,shall obey the laws of arithmetic modulo 2n where n is the number of bits in the value representation of that particular size of integer.46
[Moved to DR at the April, 2013 meeting.]
The term character type is used in the Standard without definition. It should be defined; however, the use of the term is divergent between the core and library clauses: in the former, it means narrow character types, while in the latter it includes wchar_t, char16_t, and char32_t, so care must be taken in ensuring that no inadvertent changes are implied.
Proposed resolution (October, 2012):
Change _N4885_6.7.5.5.4 [basic.stc.dynamic.safety] paragraph 1 as follows:
A traceable pointer object is
...
a sequence of elements in an array of narrow character type (6.8.2 [basic.fundamental]), where the size and alignment of the sequence match those of some object pointer type.
Change 6.8.2 [basic.fundamental] paragraph 1 as follows:
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, collectively called narrow character types. A char, a signed char, and an unsigned char occupy the same amount of storage and have the same alignment requirements (6.7.3 [basic.align]); that is, they have the same object representation. For narrow character types, all bits of the object representation participate in the value representation. For unsigned narrow 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 6.7.3 [basic.align] paragraph 6 as follows:
The alignment requirement of a complete type can be queried using an alignof expression (7.6.2.6 [expr.alignof]). Furthermore, thetypes char, signed char, and unsigned charnarrow character types (6.8.2 [basic.fundamental]) shall have the weakest alignment requirement. [Note: This enables the narrow character types to be used as the underlying type for an aligned memory area (9.13.2 [dcl.align]). —end note]
Change 9.5.3 [dcl.init.string] paragraph 1 as follows:
A char array (whether plain char, signed char, or unsigned char)An array of narrow character type (6.8.2 [basic.fundamental]), char16_t array, char32_t array, or wchar_t array can be initialized by a narrowcharacterstring literal, char16_t string literal, char32_t string literal, or wide string literal, respectively, or by an appropriately-typed string literal enclosed in braces (5.13.5 [lex.string]). Successive characters of the value of the string literal initialize the elements of the array. [Example:
[Moved to DR at the October, 2012 meeting.]
In spite of the resolution of issue 112, the exact relationship between cv-qualifiers and array types is not clear. There does not appear to be a definitive normative statement answering the question of whether an array with a const-qualified element type is itself const-qualified; the statement in 6.8.5 [basic.type.qualifier] paragraph 5,
Cv-qualifiers applied to an array type attach to the underlying element type, so the notation “cv T,” where T is an array type, refers to an array whose elements are so-qualified. Such array types can be said to be more (or less) cv-qualified than other types based on the cv-qualification of the underlying element types.
hints at an answer but is hardly decisive. For example, is the following example well-formed?
template <class T> struct is_const { static const bool value = false; }; template <class T> struct is_const<const T> { static const bool value = true; }; template <class T> void f(T &) { char checker[is_const<T>::value]; } int const arr[1] = {}; int main() { f(arr); }
Also, when 7.2.1 [basic.lval] paragraph 4 says,
Class prvalues can have cv-qualified types; non-class prvalues always have cv-unqualified types.
does this apply to array rvalues, as it appears? That is, given
struct S { const int arr[10]; };
is the array rvalue S().arr an array of int or an array of const int?
(The more general question is, when the Standard refers to non-class types, should it be considered to include array types? Or perhaps only arrays of non-class types?)
Proposed resolution (December, 2011):
Change 6.8.5 [basic.type.qualifier] paragraph 5 as follows:
...Cv-qualifiers applied to an array type attach to the underlying element type, so the notation “cv T,” where T is an array type, refers to an array whose elements are so-qualified.Such array types can be said to be more (or less) cv-qualified than other types based on the cv-qualification of the underlying element types.An array type whose elements are cv-qualified is also considered to have the same cv-qualification as its elements. [Example:typedef char CA[5]; typedef const char CC; CC arr1[5] = { 0 }; const CA arr2 = { 0 };The type of both arr1 and arr2 is “array of 5 const char,” and the array type is considered to be const-qualified. —end example]
Change 7.2.1 [basic.lval] paragraph 4 as follows:
Class and array prvalues can have cv-qualified types;non-classother prvalues always have cv-unqualified types. Unless otherwise indicated...
[Moved to DR at the October, 2012 meeting.]
The definition of “const object” in 6.8.5 [basic.type.qualifier] paragraph 1 is:
The presence of a const specifier in a decl-specifier-seq declares an object of const-qualified object type; such object is called a const object.
Because the type of an object created by a new-expression is given by a type-id or new-type-id rather than with a decl-specifier-seq, this definition gives the false impression that objects of dynamic storage duration cannot be const objects. The wording should be adjusted to make it clear that they can.
Proposed resolution (February, 2012):
Change 6.8.5 [basic.type.qualifier] paragraph 1 as follows:
The term object type (6.7.2 [intro.object]) includes the cv-qualifiers specified in the decl-specifier-seq (9.2 [dcl.spec]), declarator ( 9.3 [dcl.decl]), type-id (9.3.2 [dcl.name]), or new-type-id (7.6.2.8 [expr.new]) when the object is created.
The presence of a const specifier in a decl-specifier-seq declares an object of const-qualified object type; such object is called a const object. The presence of a volatile specifier in a decl-specifier-seq declares an object of volatile-qualified object type; such object is called a volatile object. The presence of both cv-qualifiers in a decl-specifier-seq declares an object of const-volatile-qualified object type; such object is called a const volatile object.
A const object is an object of type const T or a non-mutable subobject of such an object.
A volatile object is an object of type volatile T, a subobject of such an object, or a mutable subobject of a const volatile object.
A const volatile object is an object of type const volatile T, a non-mutable subobject of such an object, a const subobject of a volatile object, or a non-mutable volatile subobject of a const object.
The cv-qualified or cv-unqualified versions of a type are distinct types; however, they shall have the same representation and alignment requirements (6.8 [basic.types]).51
Change 6.8.5 [basic.type.qualifier] paragraph 3 as follows:
Each non-static, non-mutable, non-reference data member of a const-qualified class object is const-qualified, each non-static, non-reference data member of a volatile-qualified class object is volatile-qualified and similarly for members of a const-volatile class.See 9.3.4.6 [dcl.fct] and _N4868_.11.4.3.2 [class.this] regarding function types...
[Moved to DR at the April, 2013 meeting.]
Does the Standard require that an uninitialized auto variable have a stable (albeit indeterminate) value? That is, does the Standard require that the following function return true?
bool f() { unsigned char i; // not initialized unsigned char j = i; unsigned char k = i; return j == k; // true iff "i" is stable }6.8.2 [basic.fundamental] paragraph 1 requires that uninitialized unsigned char variables have a valid value, so the initializations of j and k are well-formed and required not to trap. The question here is whether the value of i is allowed to change between those initializations.
Mike Miller: 6.9.1 [intro.execution] paragraph 10 says,
An instance of each object with automatic storage duration (6.7.6.4 [basic.stc.auto] ) is associated with each entry into its block. Such an object exists and retains its last-stored value during the execution of the block and while the block is suspended...I think that the most reasonable way to read this is that the only thing that is allowed to change the value of an automatic (non-volatile?) value is a "store" operation in the abstract machine. There are no "store" operations to i between the initializations of j and k, so it must retain its original (indeterminate but valid) value, and the result of the program is well-defined.
The quibble, of course, is whether the wording "last-stored value" should be applied to a "never-stored" value. I think so, but others might differ.
Tom Plum: 9.2.9.2 [dcl.type.cv] paragraph 8 says,
[Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. See 6.9.1 [intro.execution] for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. ]>From this I would infer that non-volatile means "shall not be changed by means undetectable by an implementation"; that the compiler is entitled to safely cache accesses to non-volatile objects if it can prove that no "detectable" means can modify them; and that therefore i shall maintain the same value during the example above.
Nathan Myers: This also has practical code-generation consequences. If the uninitialized auto variable lives in a register, and its value is really unspecified, then until it is initialized that register can be used as a temporary. Each time it's "looked at" the variable has the value that last washed up in that register. After it's initialized it's "live" and cannot be used as a temporary any more, and your register pressure goes up a notch. Fixing the uninit'd value would make it "live" the first time it is (or might be) looked at, instead.
Mike Ball: I agree with this. I also believe that it was certainly never my intent that an uninitialized variable be stable, and I would have strongly argued against such a provision. Nathan has well stated the case. And I am quite certain that it would be disastrous for optimizers. To ensure it, the frontend would have to generate an initializer, because optimizers track not only the lifetimes of variables, but the lifetimes of values assigned to those variables. This would put C++ at a significant performance disadvantage compared to other languages. Not even Java went this route. Guaranteeing defined behavior for a very special case of a generally undefined operation seems unnecessary.
Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 616.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The specification of the forms of the definition of main that an impliementation is required to accept is clear in C99 that the parameter names and the exact syntactic form of the types can vary. Although it is reasonable to assume that a C++ implementation would accept a definition like
int main(int foo, char** bar) { /* ... */ }
instead of the canonical
int main(int argc, char* argv[]) { /* ... */ }
it might be a good idea to clarify the intent using wording similar to C99's.
Proposed resolution (August, 2011):
Change 6.9.3.1 [basic.start.main] paragraph 2 as follows:
...All implementations shall allow both
of the following definitions of main:int main() { /* ... */ }
andint main(int argc, char* argv[]) { /* ... */ }
function of () returning int and
function of (int, pointer to pointer to char) returning int
as the type of main (9.3.4.6 [dcl.fct]. In the latter form, for purposes of exposition, the first function parameter is called argc and the second function parameter is called argv, where argc shall be the number of arguments...
[Moved to DR at the April, 2013 meeting.]
According to 6.9.3.2 [basic.start.static] paragraph 2,
Constant initialization is performed:
...
if an object with static or thread storage duration is not initialized by a constructor call and if every full-expression that appears in its initializer is a constant expression.
Presumably this would include a value-initialization (i.e., with no expressions) such as
int a[1000]{};
However, we have recently clarified the degenerate cases of other similar rules referencing “every,” so it wouldn't hurt to be more explicit here.
Proposed resolution (October, 2012):
Change 6.9.3.2 [basic.start.static] paragraph 2 as follows:
Constant initialization is performed:
...
if an object with static or thread storage duration is not initialized by a constructor call and if either the object is value-initialized or every full-expression that appears in its initializer is a constant expression.
[Moved to DR at the October, 2012 meeting.]
Proposed resolution (December, 2011):
Change 7.2.1 [basic.lval] paragraph 4 as follows (supersedes the corresponding change in the resolution of issue 1059):
Class prvalues can have cv-qualified types; non-class prvalues always have cv-unqualified types.Unless otherwise indicated (7.6.1.3 [expr.call]), prvalues shall always have complete types or the void type; in addition to these types, glvalues can also have incomplete types. [Note: class and array prvalues can have cv-qualified types; other prvalues always have cv-unqualified types. See Clause 7 [expr]. —end note]
Add a new paragraph following Clause 7 [expr] paragraph 5:
If an expression initially has the type “reference to T”...
If a prvalue initially has the type “cv T,” where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.
Change 7.6.1.3 [expr.call] paragraph 3 as follows:
If the postfix-expression designates a destructor (11.4.7 [class.dtor]), the type of the function call expression is void; otherwise, the type of the function call expression is the return type of the statically chosen function (i.e., ignoring the virtual keyword), even if the type of the function actually called is different. This return type shall be an object type, a reference type orthe typecv void.
Change 7.6.1.4 [expr.type.conv] paragraph 2 as follows:
...[Note: if T is a non-class type that is cv-qualified, the cv-qualifiers areignoreddiscarded when determining the type of the resulting prvalue (7.2.1 [basic.lval]Clause 7 [expr]). —end note]
Change 7.6.3 [expr.cast] paragraph 1 as follows:
...[Note: if T is a non-class type that iscv-qualifiedcv-qualified, the cv-qualifiers areignoreddiscarded when determining the type of the resulting prvalue; see7.2.1 [basic.lval]Clause 7 [expr]. —end note]
[Moved to DR at the October, 2012 meeting.]
There are some points in the description discarded-value expressions that need clarification:
Does this require the lvalue-to-rvalue conversion in the listed cases or only prohibit it in all other cases (“only if” vs “if and only if”)?
Does this apply only to built-in operations or to overloaded operators?
Does this apply to non-POD types?
Suggested resolution:
In some contexts, an expression only appears for its side effects. Such an expression is called a discarded-value expression. The expression is evaluated and its value is discarded. The array-to-pointer (7.3.3 [conv.array]) and function-to-pointer (7.3.4 [conv.func]) standard conversions are not applied. The lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is applied if and only if the expression is an lvalue of volatile-qualified type and it has one of the following forms:
id-expression (_N4567_.5.1.1 [expr.prim.general]),
subscriptingsubscript operation (7.6.1.2 [expr.sub]),class member access (7.6.1.5 [expr.ref]),
indirection operation (7.6.2.2 [expr.unary.op]),
pointer-to-member operation (7.6.4 [expr.mptr.oper]),
conditional
expressionoperation (7.6.16 [expr.cond]) where both the second and the third operands are one ofthe abovethese forms, orcomma
expressionoperation (7.6.20 [expr.comma]) where the right operand is one ofthe abovethese forms.[Note: Expressions invoking user-defined operators are not the operations above. Discarded-value expressions apply to class types, which will be ill-formed if there is no volatile copy constructor with which to initialize the temporary. —end note]
Proposed resolution (February, 2012):
Change Clause 7 [expr] paragraph 10 as follows:
...The lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is applied if and only if the expression is an lvalue of volatile-qualified type and it
hasis one of the followingforms:
( expression ), where expression is one of these expressions,
id-expression (_N4567_.5.1.1 [expr.prim.general]),
...
conditional expression (7.6.16 [expr.cond]) where both the second and the third operands are one of
the abovethese expressions, orcomma expression (7.6.20 [expr.comma]) where the right operand is one of
the abovethese expressions.[Note: Using an overloaded operator causes a function call; the above covers only operators with built-in meaning. If the lvalue is of class type, it must have a volatile copy constructor to initialize the temporary that is the result of the lvalue-to-rvalue conversion. —end note]
Additional note (February, 2012):
A problem was discovered that was not addressed by the proposed resolution that was reviewed at the February, 2012 meeting, so the issue has been moved back to "review" status with revised wording.
[Moved to DR at the April, 2013 meeting.]
7.3.2 [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 6.8.2 [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 7.3.2 [conv.lval] paragraph 1. Instead, something like the following ought to be added to 6.8 [basic.types] paragraph 4 (which is where the concept of "value" is introduced):
Any use of an indeterminate value (7.6.2.8 [expr.new], 9.5 [dcl.init], 11.9.3 [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 6.8 [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 (7.6.2.8 [expr.new], 9.5 [dcl.init], 11.9.3 [class.base.init]) of a type other than unsigned char results in undefined behavior.
Change 7.3.2 [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 DR338. 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.
Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 616.
[Moved to DR at the April, 2013 meeting.]
According to 7.3.2 [conv.lval] paragraph 1, an lvalue-to-rvalue conversion on an uninitialized object produces undefined behavior. Since there is only one “value” of type std::nullptr_t, an lvalue-to-rvalue conversion on a std::nullptr_t glvalue does not need to fetch the value from storage. Is there any need for undefined behavior in this case?
Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 616.
[Moved to DR at the October, 2012 meeting.]
The resolution of issue 654 (found in paper N2656) enabled conversion of rvalues of type std::nullptr_t to bool. It appears that the use cases for this conversion are primarily or exclusively the “contextually converted to bool” cases, with some possibility for inadvertent misuse in other contexts. Paper N2656 mentioned the idea of limiting the conversions to the direct initialization contexts; that possibility should be reexamined.
Proposed resolution (February, 2012):
Change 7.3.14 [conv.fctptr] paragraph 1 as follows:
...any other value is converted to true.AFor direct-initialization (9.5 [dcl.init]), a prvalue of type std::nullptr_t can be converted to a prvalue of type bool; the resulting value is false.
[Moved to DR status at the April, 2013 meeting.]
There does not appear to be any technical difficulty that would require the restriction in 7.5.6 [expr.prim.lambda] paragraph 5 against default arguments in lambda-expressions.
Proposed resolution (August, 2011):
Delete the following sentence from 7.5.6 [expr.prim.lambda] paragraph 5:
Default arguments (9.3.4.7 [dcl.fct.default]) shall not be specified in the parameter-declaration-clause of a lambda-declarator.
Additional note (February, 2012):
EWG requested that the adoption of this proposed resolution be postponed to allow them to discuss it. The issue has thus been returned to "review" status pending EWG action.
[Moved to DR status at the April, 2013 meeting as part of paper N3638.]
There does not appear to be any technical difficulty that would require the current restriction that the return type of a lambda can be deduced only if the body of the lambda consists of a single return statement. In particular, multiple return statements could be permitted if they all return the same type.
Proposed resolution (August, 2011):
Change 7.5.6 [expr.prim.lambda] paragraph 4 as follows:
...If a lambda-expression does not include a trailing-return-type, it is as if the trailing-return-type denotes the following type:
if the compound-statement is of the form
{ attribute-specifier-seqopt return expression ;
the type of the returned expression after lvalue-to-rvalue conversion (7.3.2 [conv.lval]), array-to-pointer conversion (7.3.3 [conv.array]), and function-to-pointer conversion (7.3.4 [conv.func]);
otherwise, void.if there are no return statements in the compound-statement, or all return statements return either an expression of type void or no expression or braced-init-list, the type void;
otherwise, if all return statements return an expression and the types of the returned expressions after lvalue-to-rvalue conversion (7.3.2 [conv.lval]), array-to-pointer conversion (7.3.3 [conv.array]), and function-to-pointer conversion (7.3.4 [conv.func]) are the same, that common type;
otherwise, the program is ill-formed.
[Example:
auto x1 = [](int i){ return i; }; // OK: return type is int auto x2 = []{ return { 1, 2 }; }; // error: the return type is void (a // braced-init-list is not an expression) struct A { int fn1(); const int& fn2(); }; template <class T> void f () { [](T t, bool b){ if (b) return t.fn1(); else return t.fn2(); }; } template void f<A>(); // OK: lambda return type is int—end example]
Additional note (February, 2012):
EWG requested that the adoption of this proposed resolution be postponed to allow them to discuss it. The issue has thus been returned to "review" status pending EWG action.
[Moved to DR at the April, 2013 meeting as part of paper N3638.]
auto and lambda return types use slightly different rules for determining the result type from an expression. auto uses the rules in 13.10.3.2 [temp.deduct.call], which explicitly drops top-level cv-qualification in all cases, while the lambda return type is based on the lvalue-to-rvalue conversion, which drops cv-qualification only for non-class types. As a result:
struct A { }; const A f(); auto a = f(); // decltype(a) is A auto b = []{ return f(); }; // decltype(b()) is const A
This seems like an unnecessary inconsistency.
John Spicer:
The difference is intentional; auto is intended only to give a const type if you explicitly ask for it, while the lambda return type should generally be the type of the expression.
Daniel Krügler:
Another inconsistency: with auto, use of a braced-init-list can deduce a specialization of std::initializer_list; it would be helpful if the same could be done for a lambda return type.
Additional note, February, 2014:
EWG noted that g++ and clang differ in their treatment of this example and referred it back to CWG for resolution.
Proposed resolution, November, 2014:
This issue was actually resolved by paper N3638, adopted at the April, 2013 meeting. It is returned to "review" status to allow consideration of whether the resolution should be considered a change for C++14 or a retroactive change to C++11.
Notes from the November, 2014 meeting:
CWG agreed that the change embodied in paper N3638 should be considered to have been a DR against C++11.
[Moved to DR at the April, 2013 meeting.]
7.5.6 [expr.prim.lambda] paragraph 6 does not specify the language linkage of the function type of the closure type's conversion function.
Proposed resolution (October, 2012):
Change 7.5.6 [expr.prim.lambda] paragraph 6 as follows:
The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function with C++ language linkage (9.12 [dcl.link]). having the same parameter and return types as the closure type's function call operator. The value returned...
In the current specification of lambda expressions, a name appearing in a lambda-capture must refer to a local variable or reference with automatic storage duration (7.5.6 [expr.prim.lambda] paragraph 3). This restriction seems unnecessary and possibly confusing.
One possibility would be to extend the syntax of the lambda-capture to be something like
v = expr
with the meaning that the closure object would have a member named v initialized with the value expr. With this extension, the current syntax could be viewed as an abbreviation for
v = v
Rationale, March, 2009:
This idea was discussed and rejected by the EWG.
This issue was addressed by the adoption of N3648, adopted at the April, 2013 (Bristol) meeting.
The current wording of 7.5.6 [expr.prim.lambda] paragraphs 15 and 16 does not, but presumably should, indicate that this, if implicitly captured, is always captured by copy and not by reference.
Proposed resolution (February, 2016):
Change 7.5.6 [expr.prim.lambda] paragraph 15 as follows, dividing the existing text into a bulleted list:
An entity is captured by copy if
it is this,
it is implicitly captured and the capture-default is =, or
ifit is explicitly captured with a capture that is not of the form & identifier or & identifier initializer.For each entity captured by copy...
Notes from the February, 2016 meeting:
Paper P0018 is intended to change this area, so this issue is being returned to "review" status to accommodate those changes.
[Moved to DR status at the April, 2013 meeting.]
Because the subscripting operation is defined as indirection through a pointer value, the result of a subscript operator applied to an xvalue array is an lvalue, not an xvalue. This could be surprising to some.
Proposed resolution (December, 2012):
Change 7.6.1.2 [expr.sub] paragraph 1 as follows:
A postfix expression followed by an expression in square brackets is a postfix expression. One of the expressions shall have the type “array of T” or “pointer to T” and the other shall have unscoped enumeration or integral type. The result isan lvalueof type “T.” The type “T” shall be a completely-defined object type.62 The expression E1[E2] is identical (by definition) to *((E1)+(E2)) [Note: see 7.6.2 [expr.unary] and 7.6.6 [expr.add] for details of * and + and 9.3.4.5 [dcl.array] for details of arrays. —end note], except that in the case of an array operand, the result is an lvalue if that operand is an lvalue and an xvalue otherwise.
(See also issue 616.)
Additional note (January, 2013):
The preceding resolution differs from that discussed in the December, 2012 drafting review teleconference by the deletion of the words “an lvalue” in the second sentence. The issue has consequently been moved to "review" status instead of "tentatively ready."
[Moved to DR at the April, 2013 meeting.]
The terms “virtual function call” and “virtual call” are used in the Standard but are not defined.
Proposed resolution (August, 2012):
Change 7.6.1.3 [expr.call] paragraph 1 as follows:
If the selected function is non-virtual, or if the id-expression in the class member access expression is a qualified-id, that function is called. Otherwise, its final overrider (11.7.3 [class.virtual]) in the dynamic type of the object expression is called; such a call is referred to as a virtual function call.
[Moved to DR at the October, 2012 meeting.]
7.6.1.7 [expr.dynamic.cast] paragraph 2 allows an expression of any value category when the target type is an rvalue reference. However, paragraph 6 requires that the operand be an lvalue if the runtime check is to be applied. This requirement should presumably be relaxed to require only a glvalue when the target type is an rvalue reference.
Proposed resolution (August, 2011):
Change 7.6.1.7 [expr.dynamic.cast] paragraph 6 as follows:
Otherwise, v shall be a pointer to oran lvaluea glvalue of a polymorphic type (11.7.3 [class.virtual]).
Additional note, January, 2012:
An objection has been raised to the proposed resolution on the basis that it unnecessarily weakens the distinction between rvalues and lvalues, making it easier to create dangling references. Its status has therefore been changed back to "review" to allow further discussion.
[Moved to DR at the October, 2012 meeting.]
The requirement in 7.6.1.8 [expr.typeid] paragraph 5 that
The top-level cv-qualifiers of the glvalue expression or the type-id that is the operand of typeid are always ignored
could be misinterpreted as referring to cv-qualifiers in a function type, even though it is clear that a function type is never cv-qualified. A note emphasizing the fact that that is not the case would be helpful.
Proposed resolution (February, 2012):
Change 7.6.1.8 [expr.typeid] paragraph 5 as follows:
The top-level cv-qualifiers of the glvalue expression or the type-id that is the operand of typeid are always ignored.If the type of the expression or type-id is a cv-qualified type, the result of the typeid expression refers to a std::type_info object representing the cv-unqualified type. [Example:...
[Moved to DR at the April, 2013 meeting.]
The specification of static_cast (7.6.1.9 [expr.static.cast]) does not describe conversion of a scoped enumeration value to bool. Presumably it should be handled as for unscoped enumerations, with a zero value becoming false and a non-zero value becoming true.
Proposed resolution (August, 2012):
Change 7.6.1.9 [expr.static.cast] paragraph 9 as follows:
A value of a scoped enumeration type (9.8.1 [dcl.enum]) can be explicitly converted to an integral type.TheWhen that type is cv bool, the resulting value is false if the original value is zero and true for all other values. For the remaining integral types, the value is unchanged if the original value can be represented by the specified type. Otherwise, the resulting value is unspecified.
[Moved to DR at the April, 2013 meeting.]
The definedness of various pointer conversions (see 7.6.1.10 [expr.reinterpret.cast] paragraph 7, 11.4 [class.mem] paragraph 20, 7.6.1.9 [expr.static.cast] paragraph 13) relies on the properties of the types involved, such as whether they are standard-layout types and their intrinsic alignment. This creates contradictions and unnecessary unspecified behavior when the actual values of the pointer involved would actually permit the operations. Recasting the specification in terms of the memory model instead of the types of the objects provides a more coherent specification.
Proposed resolution (February, 2012):
Change 7.3.12 [conv.ptr] paragraph 2 as follows:
A prvalue of type “pointer to cv T,” where T is an object type, can be converted to a prvalue of type “pointer to cv void”. The result of converting a“pointer to cv T”non-null pointer value of a pointer to object type to a “pointer to cv void”points to the start of the storage location where the object of type T resides, as if the object is a most derived object (6.7.2 [intro.object]) of type T (that is, not a base class subobject)represents the address of the same byte in memory as the original pointer value. The null pointer value is converted to the null pointer value of the destination type.
Change 7.6.1.9 [expr.static.cast] paragraph 13 as follows:
A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T,” where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. The null pointer value is converted to the null pointer value of the destination type. If the original pointer value represents the address A of a byte in memory and A satisfies the alignment requirement of T, then the resulting pointer value represents the same address as the original pointer value, that is, A. The result of any other such pointer conversion is unspecified. A value of type pointer to object converted to “pointer to cv void” and back, possibly with different cv-qualification, shall have its original value...
Change 7.6.1.10 [expr.reinterpret.cast] paragraph 7 as follows:
An object pointer can be explicitly converted to an object pointer of a different type.70 When a prvalue v of object pointer type“pointer to T1”is converted to the object pointer type “pointer to cv T2”, the result is static_cast<cv T2*>(static_cast<cv void*>(v))if both T1 and T2 are standard-layout types (6.8 [basic.types]) and the alignment requirements of T2 are no stricter than those of T1, or if either type is void. Converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types 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 any other such pointer conversion is unspecified.
[Moved to DR at the October, 2012 meeting.]
According to 7.6.1.9 [expr.static.cast] paragraph 3,
A glvalue of type “cv1 T1” can be cast to type “rvalue reference to cv2 T2,” if “cv2 T2” is reference-compatible with “cv1 T1” (9.5.4 [dcl.init.ref]). The result refers to the object or the specified base class subobject thereof.
This specification fails to allow for a bit-field lvalue operand, since the reference cannot refer to a bit-field. Presumably a temporary should be formed and the reference be bound to it.
Proposed resolution (February, 2012):
Change 7.6.1.9 [expr.static.cast] paragraphs 3-4 as follows:
A glvalue of type “cv1 T1” can be cast to type “rvalue reference to cv2 T2” if “cv2 T2” is reference-compatible with “cv1 T1” (9.5.4 [dcl.init.ref]).
TheIf the glvalue is not a bit-field, the result refers to the object or the specified base class subobject thereof; otherwise, the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is applied to the bit-field and the resulting prvalue is used as the expression of the static_cast for the remainder of this section. If T2 is an inaccessible (11.8 [class.access]) or ambiguous (6.5.2 [class.member.lookup]) base class of T1, a program that necessitates such a cast is ill-formed.
Otherwise, anAn expression e can be explicitly converted...
[Moved to DR at the October, 2012 meeting.]
7.6.1.10 [expr.reinterpret.cast] paragraph 11, dealing with casting to reference types, only allows an lvalue operand. Presumably it should allow a glvalue operand when the target is an rvalue reference type.
Proposed resolution (August, 2011):
Change 7.6.1.10 [expr.reinterpret.cast] paragraph 11:
An lvalueA glvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result refers to the same object as the source glvalue, but with the specified type. [Note: That is, for lvalues, a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators (and similarly for reinterpret_cast<T&&>(x)). —end note]The result refers to the same object as the source lvalue, but with a different type. The result is an lvalue for an lvalue reference type or an rvalue reference to function type and an xvalue for an rvalue reference to object type.No temporary is created,...
Additional note, January, 2012:
An objection has been raised to the proposed resolution on the basis that it unnecessarily weakens the distinction between rvalues and lvalues, making it easier to create dangling references. Its status has therefore been changed back to "review" to allow further discussion.
[Moved to DR at the October, 2012 meeting.]
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 7.6.2.2 [expr.unary.op] paragraph 1 as follows:
The unary * operatorperforms indirectiondereferences a pointer value: the expression to which it is applied shall be a pointer...
Change 9.3.4.5 [dcl.array] paragraph 8 as follows:
Theresults are added and indirection appliedvalues 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 9.3.4.6 [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 thenusing indirection throughdereferencing the (pointer) result to yield an integer. In the declarator (*pif)(const char*, const char*), the extra parentheses are necessary to indicate thatindirection throughdereferencing 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: 29.6.9 [template.indirect.array] requires no change. Many more places in the current wording use “dereferencing” than “indirection.”]
Notes from the August, 2011 meeting:
CWG prefers use of the term “indirection” instead of “dereferencing.” This would be consistent with the usage in the C Standard and would avoid entanglement with the C++ concept of a reference type.
Proposed resolution (February, 2012):
Change 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 2 as follows:
...The effect ofdereferencingindirecting through a pointer returned as a request for zero size is undefined.
Change _N4885_6.7.5.5.4 [basic.stc.dynamic.safety] paragraph 2 as follows:
the value returned by a call to the C ++ standard library implementation of ::operator new(std:: size_t); [Footnote: This section does not impose restrictions on
dereferencingindirection through pointers to memory not allocated by ::operator new. This maintains the ability of many C++ implementations to use binary libraries and components written in other languages. In particular, this applies to C binaries, becausedereferencingindirection through pointers to memory allocated by std::malloc is not restricted. —end footnote]the result of taking the address of an object (or one of its subobjects) designated by an lvalue resulting from
dereferencingindirection through a safely-derived pointer value;...
Change 6.7.4 [basic.life] paragraph 5 as follows:
...SuchIndirection through such a pointermay be dereferencedis permitted but the resulting lvalue may only be used in limited ways...
Change 7.3.13 [conv.mem] paragraph 2 as follows:
...Since the result has type “pointer to member of D of type cv T”,it can be dereferencedindirection through it with a D object is valid. The result is the same as if indirecting through the pointer to member of Bwere dereferencedwith the B subobject of D. The null member pointer value...
Change 7.6.1.9 [expr.static.cast] paragraph 12 as follows:
...[Note: although class B need not contain the original member, the dynamic type of the objectonwith which indirection through the pointer to member isdereferencedperformed must contain the original member; see 7.6.4 [expr.mptr.oper]. —end note]
Change 7.6.2.2 [expr.unary.op] paragraph 1 as follows:
...[Note: indirection through a pointer to an incomplete type (other than cv void)can be dereferencedis valid. The lvalue thus obtained...
Change 7.6.10 [expr.eq] paragraph 2 as follows:
...Otherwise they compare equal if and only if they would refer to the same member of the same most derived object (6.7.2 [intro.object]) or the same subobject ifthey were dereferencedindirection with a hypothetical object of the associated class type were performed. [Example:...
Change 9.12 [dcl.link] paragraph 8:
[Note: Because the language linkage is part of a function type, when indirecting through a pointer to C function(for example) is dereferenced, the function to whichitthe resulting lvalue refers is considered a C function. —end note]
Change 9.3.4.3 [dcl.ref] paragraph 5 as follows:
...[Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained bydereferencingindirection through a null pointer, which causes undefined behavior. As described...
Change 16.4.4.6 [allocator.requirements] table 27:
Variable Definition ... c a dereferenceablepointer of type C* through which indirection is valid...
Change 20.2.3.3 [pointer.traits.functions] as follows:
Returns: The first member function returns adereferenceablepointer to r obtained by calling Ptr::pointer_to(r) through which indirection is valid; an instantiation of this function is ill-formed...
Change _N4885_.20.10.5 [util.dynamic.safety] paragraph 10 as follows:
Effects: The n bytes starting at p no longer contain traceable pointer locations, independent of their type. Hencepointersindirection through a pointer located theremay not be dereferencedis undefined if the objectthey pointit points to was created by global operator new and not previously declared reachable...
Change 26.11 [specialized.algorithms] paragraph 1 as follows:
...is required to have the property that no exceptions are thrown from increment, assignment, comparison, ordereference ofindirection through valid iterators...
Change 28.3.4.6.2.3 [locale.time.get.virtuals] paragraph 11 as follows:
Requires: t shallbe dereferenceablepoint to an object.
Change 23.4.3.2 [map.cons] paragraph 3 as follows:
Requires: If the iterator'sdereferenceindirection operator returns an lvalue or a const rvalue pair<key_type, mapped_type>, then both key_type and mapped_type shall be CopyConstructible.
Change 23.4.4.2 [multimap.cons] paragraph 3 as follows:
Requires: If the iterator'sdereferenceindirection operator returns an lvalue or a const rvalue pair<key_type, mapped_type>, then both key_type and mapped_type shall be CopyConstructible.
Change 23.4.6.2 [set.cons] paragraph 4 as follows:
Requires: If the iterator'sdereferenceindirection operator returns an lvalue or a non-const rvalue, then Key shall be CopyConstructible.
Change 23.4.7.2 [multiset.cons] paragraph 3 as follows:
Requires: If the iterator'sdereferenceindirection operator returns an lvalue or a const rvalue, then Key shall be CopyConstructible.
Change 24.5.4 [move.iterators] paragraph 1 as follows:
Class template move_iterator is an iterator adaptor with the same behavior as the underlying iterator except that itsdereferenceindirection operator implicitly converts the value returned by the underlying iterator'sdereferenceindirection operator to an rvalue reference...
Change the title of 28.6.11.1.4 [re.regiter.deref] as follows:
regex_iteratordereferenceindirection
Change the title of 28.6.11.2.4 [re.tokiter.deref] as follows:
regex_token_iteratordereferenceindirection
[Moved to DR at the October, 2012 meeting.]
According to 7.6.2.2 [expr.unary.op] paragraph 5,
The address of an object of incomplete type can be taken, but if the complete type of that object is a class type that declares operator&() as a member function, then the behavior is undefined (and no diagnostic is required).
This should actually be “ill-formed, no diagnostic required” instead of undefined behavior, since the problem could be detected by whole-program analysis. Also, it's not clear what this means for constant expressions.
Proposed resolution (February, 2012):
Change 7.6.2.2 [expr.unary.op] paragraph 5 as follows:
The address of an object of incomplete type can be taken, but if the complete type of that object is a class type that declares operator&() as a member function, then the behavior is undefined (and no diagnostic is required).If & is applied to an lvalue of incomplete class type and the complete type declares operator&(), it is unspecified whether the operator has the built-in meaning or the operator function is called. The operand of & shall not be a bit-field.
[Moved to DR at the April, 2013 meeting.]
According to 7.6.2.5 [expr.sizeof] paragraph 1,
The sizeof operator shall not be applied... to an lvalue that designates a bit-field.
Xvalues can also designate bit-fields and thus should presumably be addressed here as well.
Proposed resolution (October, 2012):
Change 7.6.2.5 [expr.sizeof] paragraph 1 as follows:
The sizeof operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is an unevaluated operand (Clause 7 [expr]), or a parenthesized type-id. The sizeof operator shall not be applied to an expression that has function or incomplete type, to an enumeration type whose underlying type is not fixed before all its enumerators have been declared, to the parenthesized name of such types, or toan lvaluea glvalue that designates a bit-field. sizeof(char)...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to 7.6.2.6 [expr.alignof] paragraph 1,
An alignof expression yields the alignment requirement of its operand type. The operand shall be a type-id representing a complete object type or an array thereof or a reference to a complete object type.
This (presumably unintentionally) excludes a reference to an array with an unknown bound but a complete element type; the bound is not needed to determine the alignment of the array.
Proposed resolution (August, 2011):
Change 7.6.2.6 [expr.alignof] paragraph 1 as follows:
An alignof expression yields the alignment requirement of its operand type. The operand shall be a type-id representing a complete object type or an array thereof or a reference toa complete object typeone of those types.
[Moved to DR at the October, 2012 meeting.]
The result of the noexcept operator does not consider possible exceptions thrown by the destructors for temporaries created in the operand expression.
Proposed resolution (February, 2012):
Change 6.9.1 [intro.execution] paragraph 10 as follows:
A full-expression is an expression that is not a subexpression of another expression. [Note: in some contexts such as unevaluated operands, a syntactic subexpression is considered a full-expression (Clause 7 [expr]). —end note] If a language construct...
Change Clause 7 [expr] paragraph 7 as follows:
...An unevaluated operand is not evaluated. An unevaluated operand is considered a full-expression. [Note:...
[Drafting note: This uniformly handles sizeof(A()), noexcept(A()), typeid(A()), and decltype(A()) with regard to the semantic requirements on ~A (accessible and not deleted), which might be checked via SFINAE. A programmer can use decltype(new A) to avoid considering the destructor. If this is undesired, an alternative change just addresses the noexecept issue:]
[Editing note: all the occurrences of “potentially evaluated” in 7.6.2.7 [expr.unary.noexcept] paragraph 3 should be hyphenated.]
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to the C++ Standard section 7.6.2.8 [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, 7.6.2.8 [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 7.6.2.8 [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 6.7.6.5.3 [basic.stc.dynamic.deallocation] paragraph 4.
Suggested resolution:
Change the first sentence of 7.6.2.8 [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 7.6.2.8 [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...
[Accepted at the April, 2013 meeting.]
Currently, 7.6.2.8 [expr.new] paragraph 7 requires that an attempt to allocate an array with a negative length be diagnosed:
If the value of that expression is less than zero or such that the size of the allocated object would exceed the implementation-defined limit, or if the new-initializer is a braced-init-list for which the number of initializer-clauses exceeds the number of elements to initialize, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (14.4 [except.handle]) of type std::bad_array_new_length (17.6.4.2 [new.badlength]).
Checking for a negative bound will be lost, however, upon the adoption of paper N3323, as the expression will be converted to std::size_t, an unsigned type. Although the result of this conversion will likely also cause the check to fail (and will always do so when scaled by an element size larger than 1), it is not inconceivable that an implementation could provide a heap that capable of providing more than half the addressable range of std::size_t, and a request for a character array (with an element size of 1) with a negative bound close to LONG_MIN (assuming std::size_t is unsigned long) might actually succeed.
The wording of 7.6.2.8 [expr.new] paragraph 7 should be changed so that the test for a negative bound is applied to the value before conversion to std::size_t, or some other mechanism should be invented to preserve the check for a negative bound.
Additional note (August, 2012):
The goal for addressing this issue should be that an attempt to use an invalid bound (negative, greater than the maximum allowed, or more than the number implied by the initializer) will be ill-formed when the bound is a compile-time constant and will result in an exception otherwise.
Proposed resolution (October, 2012):
Change 6.8.4 [basic.compound] paragraph 2 as follows:
These methods of constructing types can be applied recursively; restrictions are mentioned in 9.3.4.2 [dcl.ptr], 9.3.4.5 [dcl.array], 9.3.4.6 [dcl.fct], and 9.3.4.3 [dcl.ref]. Constructing a type such that the number of bytes in its object representation exceeds the maximum value representable in the type std::size_t (17.2 [support.types]) is ill-formed.
Change 7.6.2.8 [expr.new] paragraph 7 as follows:
The expression in a noptr-new-declarator is erroneous if:
the expression is of non-class type and its value before converting to std::size_t is less than zero;
the expression is of class type and its value before application of the second standard conversion (12.2.4.2.3 [over.ics.user]) [Footnote: If the conversion function returns a signed integer type, the second standard conversion converts to the unsigned type std::size_t and thus thwarts any attempt to detect a negative value afterwards. —end footnote] is less than zero;
its value is such that the size of the allocated object would exceed the implementation-defined limit (annex Clause Annex B [implimits]); or
the new-initializer is a braced-init-list and the number of array elements for which initializers are provided (including the terminating '\0' in a string literal (5.13.5 [lex.string])) exceeds the number of elements to initialize.
If the expression, after converting to std::size_t, is a core constant expression and the expression is erroneous, the program is ill-formed. Otherwise, a new-expression with an erroneous expression does not call an allocation function and terminates by throwing an exception of a type that would match a handler (14.4 [except.handle]) of type std::bad_array_new_length (17.6.4.2 [new.badlength]). When the value of the expression
in a noptr-new-declaratoris zero, the allocation function is called to allocate an array with no elements.If the value of that expression is less than zero or such that the size of the allocated object would exceed the implementation-defined limit, or if the new-initializer is a braced-init-list for which the number of initializer-clauses exceeds the number of elements to initialize, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (14.4 [except.handle]) of type std::bad_array_new_length (17.6.4.2 [new.badlength]).
(This resolution also resolves issue 1559.)
[Moved to DR at the April, 2013 meeting.]
According to 7.6.2.8 [expr.new] paragraph 7,
if the new-initializer is a braced-init-list for which the number of initializer-clauses exceeds the number of elements to initialize, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (14.4 [except.handle]) of type std::bad_array_new_length (17.6.4.2 [new.badlength]).
This wording does not, but presumably should, require an exception to be thrown in a case like
void f() { int x = 3; new char[x]{"abc"}; }
(See also issue 1464.)
Proposed resolution (October, 2012):
This issue is resolved by the resolution of issue 1464.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Both the .* and ->* operators (7.6.4 [expr.mptr.oper]) require that the class of the second operand be a complete object type. Current implementations do not enforce this requirement, and it is not clear that there is a need for it.
Proposed resolution (August, 2011):
Change 7.6.4 [expr.mptr.oper] paragraph 2 as follows:
The binary operator .* binds its second operand, which shall be of type “pointer to member of T”(where T is a completely-defined class type)to its first operand...
Change 7.6.4 [expr.mptr.oper] paragraph 3 as follows:
The binary operator ->* binds its second operand, which shall be of type “pointer to member of T”(where T is a completely-defined class type)to its first operand...
[Moved to DR at the October, 2012 meeting.]
Issue 614 adopted the corresponding C99 wording for 7.6.5 [expr.mul] paragraph 4,
...if the quotient a/b is representable in the type of the result, (a/b)*b + a%b is equal to a.
in an attempt to ensure that INT_MAX % -1 produces undefined behavior (because the result is not specified by the Standard). However, the new C draft makes the undefined behavior explicit:
If the quotient a/b is representable, the expression (a/b) * b + a%b shall equal a; otherwise, the behavior of both a/b and a%b is undefined.
Should C++ adopt similar wording?
Proposed resolution (February, 2012):
Change 7.6.5 [expr.mul] paragraph 4 as follows:
...If the second operand of / or % is zero the behavior is undefined. For integral operands the / operator yields the algebraic quotient with any fractional part discarded;81 if the quotient a/b is representable in the type of the result, (a/b)*b + a%b is equal to a; otherwise, the behavior of both a/b and a%b is undefined.
[Moved to DR at the April, 2013 meeting.]
The current wording is not sufficiently clear that a pointer to a base class subobject of an array element cannot be used in pointer arithmetic.
Proposed resolution (October, 2012):
Add the following as a new paragraph before 7.6.6 [expr.add] paragraph 7:
For addition or subtraction, if the expressions P or Q have type “pointer to cv T", where T is different from the cv-unqualified array element type, the behavior is undefined. [Note: In particular, a pointer to a base class cannot be used for pointer arithmetic when the array contains objects of a derived class type. —end note]
If the value 0 is added to or subtracted...
[Moved to DR at the October, 2012 meeting.]
The current wording of 7.6.7 [expr.shift] paragraph 2 makes it undefined behavior to create the most-negative integer of a given type by left-shifting a (signed) 1 into the sign bit, even though this is not uncommonly done and works correctly on the majority of (twos-complement) architectures:
...if E1 has a signed type and non-negative value, and E1 ⨯ 2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
As a result, this technique cannot be used in a constant expression, which will break a significant amount of code.
Proposed resolution (February, 2012):
Change 7.6.7 [expr.shift] paragraph 2 as follows:
...if E1 has a signed type and non-negative value, and E1 ⨯ 2E2 is representable in the corresponding unsigned type of the result type, then that value, converted to the result type, is the resulting value; otherwise, the behavior is undefined.
[Moved to DR status at the April, 2013 meeting as paper N3624.]
In C, this is ill-formed (cf C99 6.5.8):
void f(char* s) { if (s < 0) { } }
...but in C++, it's not. Why? Who would ever need to write (s > 0) when they could just as well write (s != 0)?
This has been in the language since the ARM (and possibly earlier); apparently it's because the pointer conversions (7.3.12 [conv.ptr]) need to be performed on both operands whenever one of the operands is of pointer type. So it looks like the "null-ptr-to-real-pointer-type" conversion is hitching a ride with the other pointer conversions.
Proposed resolution (April, 2013):
This issue is resolved by the resolution of issue 1512.
[Moved to DR status at the April, 2013 meeting as paper N3624.]
According to 7.6.9 [expr.rel] paragraph 2, describing pointer comparisons,
Pointer conversions (7.3.12 [conv.ptr]) and qualification conversions (7.3.6 [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.
This would appear to make the following example ill-formed,
bool foo(int** x, const int** y) {
return x < y; // valid ?
}
because int** cannot be converted to const int**, according to the rules of 7.3.6 [conv.qual] paragraph 4. This seems too strict for pointer comparison, and current implementations accept the example.
Proposed resolution (November, 2012):
The proposed wording is found in document N3478.
(This resolution also resolves issue 583.)
[Moved to DR at the April, 2013 meeting.]
The current wording of 7.6.16 [expr.cond] paragraph 2 says,
If either the second or the third operand has type void, then the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the second and third operands, and one of the following shall hold:
The second or the third operand (but not both) is a throw-expression (14.2 [except.throw]); the result is of the type of the other and is a prvalue.
Both the second and the third operands have type void; the result is of type void and is a prvalue. [Note: This includes the case where both operands are throw-expressions. —end note]
A parenthesized throw-expression is a primary-expression, not a throw-expression. Should a parenthesized throw-expression be considered a throw-expression for this purpose?
Proposed resolution (October, 2012):
Change 7.6.16 [expr.cond] paragraph 2 as follows:
If either the second or the third operand has type void,
then the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the second and third operands, andone of the following shall hold:
The second or the third operand (but not both) is a (possibly parenthesized) throw-expression (14.2 [except.throw]); the result is of the type and value category of the other
and is a prvalue....
(This resolution also resolves issue 1560.)
[Moved to DR at the April, 2013 meeting.]
A glvalue appearing as one operand of a conditional-expression in which the other operand is a throw-expression is converted to a prvalue, regardless of how the conditional-expression is used:
If either the second or the third operand has type void, then the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the second and third operands, and one of the following shall hold:
The second or the third operand (but not both) is a throw-expression (14.2 [except.throw]); the result is of the type of the other and is a prvalue.
This seems to be gratuitous and surprising.
Proposed resolution (October, 2012):
This issue is resolved by the resolution of issue 1550.
[Moved to DR at the April, 2013 meeting.]
According to 7.6.19 [expr.assign] paragraph 9,
A braced-init-list may appear on the right-hand side of
an assignment to a scalar...
an assignment defined by a user-defined assignment operator, in which case the initializer list is passed as the argument to the operator function.
Presumably the phrase “user-defined” is not intended to forbid an example like
struct A { A(); A ( std::initializer_list<int> ) ; }; void f() { A a; a = {37}; }
which relies on an implicitly-declared assignment operator.
Proposed resolution (August, 2012):
Change 7.6.19 [expr.assign] paragraph 9 as follows:
A braced-init-list may appear on the right-hand side of
an assignment to a scalar, in which case the initializer list shall have at most a single element. The meaning of x={v}, where T is the scalar type of the expression x, is that of x=T(v) except that no narrowing conversion (8.5.4) is allowed. The meaning of x={} is x=T().
an assignment
defined by a user-defined assignment operatorto an object of class type, in which case the initializer list is passed as the argument to the assignment operator function selected by overload resolution (12.4.3.2 [over.assign], 12.2 [over.match]).
[Moved to DR at the April, 2013 meeting.]
According to 7.6.19 [expr.assign] paragraph 9,
The meaning of x={v}, where T is the scalar type of the expression x, is that of x=T(v) except that no narrowing conversion (9.5.5 [dcl.init.list]) is allowed. The meaning of x={} is x=T().
This definition adds a gratuitous C-style cast to the right-hand operand, inadvertently allowing such things as base-to-derived conversions and circumvention of access checking.
Proposed resolution (October, 2012):
Change 7.6.19 [expr.assign] paragraph 9 as follows:
The meaning of x={v}, where T is the scalar type of the expression x, is that ofx=T(v) except that no narrowing conversion (9.5.5 [dcl.init.list]) is allowedx=T{v}. The meaning of x={} isx=T()x=T{}.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Proposed resolution (August, 2011):
This issue is resolved by the resolution of issue 1369.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
It is not clear whether a string literal can be used in a constant expression.
Proposed resolution (August, 2011):
Change 7.7 [expr.const] paragraph 2 as follows:
an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) unless it is applied to
a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression [Note: a string literal (5.13.5 [lex.string]) corresponds to an array of such objects. —end note], or
...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The current wording of 7.7 [expr.const] paragraph 2 does not, but should, prohibit use of volatile glvalues in constant expressions.
Proposed resolution (August, 2011):
Change 7.7 [expr.const] paragraph 2 as follows:
an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) unless it is applied to
a non-volatile glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or
a non-volatile glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a sub-object of such an object, or
a non-volatile glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a constant expression;
[Moved to DR at the October, 2012 meeting.]
Although a reinterpret_cast is prohibited in a constant expression, casting to and from void* can achieve the same effect.
Proposed resolution (August, 2011):
Change 7.7 [expr.const] paragraph 2 as follows:
an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) unless it is applied to...
an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) that is applied to a glvalue of type cv1 T that refers to an object of type cv2 U, where T and U are neither the same type nor similar types (7.3.6 [conv.qual]);
an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) that is applied to a glvalue that refers to a non-active member...
Note, January, 2012:
Additional discussion has occurred, so this issue has been returned to "review" status to allow further consideration.
Proposed resolution (February, 2012):
Change 7.7 [expr.const] paragraph 2 as follows:
...
an id-expression that refers to a variable or data member of reference type unless...
a conversion from type cv void * to a pointer-to-object type;
a dynamic cast (7.6.1.7 [expr.dynamic.cast]);
...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The requirements for constant expressions do not currently, but should, exclude expressions that have undefined behavior, such as pointer arithmetic when the pointers do not point to elements of the same array.
Proposed resolution (August, 2011):
Change 7.7 [expr.const] paragraph 2 as follows:
...
a result that is not mathematically defined or not in the
range of representable values for its type;
an operation that would have undefined behavior [Note: including, for example, signed integer overflow ( Clause 7 [expr]), certain pointer arithmetic (7.6.6 [expr.add]), division by zero (7.6.5 [expr.mul]), or certain shift operations (7.6.7 [expr.shift]) —end note];
...
a subtraction (7.6.6 [expr.add]) where both
operands are pointers;
...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Use of a parameter in a constexpr function appears to be ill-formed, because the lvalue-to-rvalue conversion on the parameter is not one of those permitted in a constant expression.
Proposed resolution (August, 2011):
Change the indicated bullet of 7.7 [expr.const] paragraph 2 as follows:
an invocation of a constexpr constructor with arguments that, when substituted by function invocation substitution (9.2.6 [dcl.constexpr]), do not produce all constant expressions for the constructor calls and full-expressions in the mem-initializers (including conversions); [Example:...
Delete the final bullet of 9.2.6 [dcl.constexpr] paragraph 3 and move the deleted "." to the preceding sub-bullet:
every constructor call and implicit conversion used in
initializing the return value (8.7.4 [stmt.return],
9.5 [dcl.init]) shall be one of those allowed in a constant
expression (7.7 [expr.const]).
Delete the final bullet of 9.2.6 [dcl.constexpr] paragraph 4 and change the preceding bullet as follows:
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;
and.
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.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The current wording appears to allow calling a constexpr function that is never defined within the body of a constexpr function. (The wording was intended to allow mutually-recursive constexpr functions but require that the not-yet-defined function be defined before it would be needed in an actual constant expression.)
Proposed resolution (August, 2011):
Change the indicated bullet of 7.7 [expr.const] paragraph 2 as follows:
an invocation of an undefined constexpr function
or an undefined constexpr constructor outside the definition
of a constexpr function or a constexpr
constructor;
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The provisions allowing the use of this in a constant expression appear to be unnecessary, as any uses of this in a constant expression that are valid will be replaced by function invocation substitution.
Proposed resolution (August, 2011):
This issue is resolved by the resolution of issue 1369.
[Moved to DR at the October, 2012 meeting.]
The current wording incorrectly appears to make the following example ill-formed:
constexpr const int &f(const int &n) { return n; } constexpr int k = f(0); // ill-formed
Proposed resolution (February, 2012):
Change 7.7 [expr.const] paragraph 2 as follows:
A conditional-expression is a core constant expression unless it involves one of the following...
...
an invocation of a constexpr function with arguments that, when substituted by function invocation substitution (9.2.6 [dcl.constexpr]), do not produce a core constant expression; [Example:...
an invocation of a constexpr constructor with arguments that, when substituted by function invocation substitution (9.2.6 [dcl.constexpr]), do not produce all core constant expressions for the constructor calls and full-expressions in the mem-initializers; [Example:...
...
an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) unless it is applied to
...
a glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a core constant expression;
...
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization
, initialized with a constant expression;and either
it is initialized with a constant expression or
it is a non-static data member of a temporary object whose lifetime has not ended and is initialized with a core constant expression;
...
Change 7.7 [expr.const] paragraph 3 as follows, dividing it into two paragraphs:
A literal constant expression is a prvalue core constant expression of literal type, but not pointer type.An integral constant expression isa literal constantan expression of integral or unscoped enumeration type, implicitly converted to a prvalue, where the converted expression is a core constant expression. [Note: Such expressions may be used as array bounds (9.3.4.5 [dcl.array], 7.6.2.8 [expr.new]), as bit-field lengths (11.4.10 [class.bit]), as enumerator initializers if the underlying type is not fixed (9.8.1 [dcl.enum]), as null pointer constants (7.3.12 [conv.ptr]), and as alignments (9.13.2 [dcl.align]). —end note] A converted constant expression of type T isa literal constantan expression, implicitly converted to a prvalue of type T, where theimplicit conversion (if any) is permitted in a literalconverted expression is a core constant expression and the implicit conversion sequence contains only user-defined conversions, lvalue-to-rvalue conversions (7.3.2 [conv.lval]), integral promotions (7.3.7 [conv.prom]), and integral conversions (7.3.9 [conv.integral]) other than narrowing conversions (9.5.5 [dcl.init.list]). [Note: such expressions may be used as case expressions (8.5.3 [stmt.switch]), as enumerator initializers if the underlying type is fixed (9.8.1 [dcl.enum]), and as integral or enumeration non-type template arguments (13.4 [temp.arg]). —end note]A literal constant expression is a prvalue core constant expression of literal type, but not pointer type (after conversions as required by the context). For a literal constant expression of array or class type, each subobject of its value shall have been initialized by a constant expression. A reference constant expression is an lvalue core constant expression that designates an object with static storage duration or a function. An address constant expression is a prvalue core constant expression (after conversions as required by the context) of type std::nullptr_t or of pointer type that evaluates to the address of an object with static storage duration, to the address of a function, or to a null pointer value
, or a prvalue core constant expression of type std::nullptr_t. Collectively, literal constant expressions, reference constant expressions, and address constant expressions are called constant expressions.
Change the second example 9.2.6 [dcl.constexpr] paragraph 5 as follows:
constexpr int f(bool b) { return b ? throw 0 : 0; } // OK constexpr int f() {throw 0return f(true); } // ill-formed, no diagnostic required ...
This resolution also resolves issue 1455.
[Moved to DR at the October, 2012 meeting.]
A "converted constant expression" must be a literal constant expression, which is a prvalue, and thus can't be an lvalue. This is unintended; the lvalue-to-rvalue conversion should be applied as necessary.
Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 1454.
[Moved to DR at the April, 2013 meeting.]
Currently an address constant expression cannot designate the address one past the end of an array. This seems unfortunate.
Proposed resolution (August, 2012):
Change 7.7 [expr.const] paragraph 3 as follows:
...An address constant expression is a prvalue core constant expression of pointer type that evaluates to the address of an object with static storage duration, to the address one past the last element of an array with static storage duration, to the address of a function, or to a null pointer value, or a prvalue core constant expression of type std::nullptr_t...
The following initializations appear to be well-formed:
struct C { int m; constexpr C(int m ) : m{m} {}; constexpr int get() { return m; }; }; C&& rr = C{1}; constexpr int k1 = rr.get(); const C& cl = C{1}; constexpr int k2 = cl.get();
They appear to fall under the bullet of 7.7 [expr.const] paragraph 2,
an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) unless it is applied to
...
a non-volatile glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a constant expression;
The problem in this example is that the referenced temporary object is not a constant, so it would be well-defined for intervening code to modify its value before it was used in the later initialization.
Additional note (February, 2013):
The intent is that non-const references to temporaries should be allowed within constant expressions, e.g., across constexpr function calls, but not as the result of a constant expression.
Proposed resolution (April, 2013):
This issue was rendered moot by paper N3652, adopted at the April, 2013 meeting.
[Moved to DR at the April, 2013 meeting.]
One of the criteria in 7.7 [expr.const] paragraph 2 for disqualifying an expression from being a constant expression is:
a typeid expression (7.6.1.8 [expr.typeid]) whose operand is of a polymorphic class type;
on the basis that a runtime test for the dynamic type is inconsistent with a constant expression. However, it is only glvalues of polymorphic type that require a runtime test; type-ids and prvalues with a polymorphic type could (and should) be permitted in constant expressions.
Proposed resolution (October, 2012):
Change 7.7 [expr.const] paragraph 2 as follows:
...
a typeid expression (7.6.1.8 [expr.typeid]) whose operand is a glvalue of a polymorphic class type;
[Moved to DR at the April, 2013 meeting.]
According to the note in 7.7 [expr.const] paragraph 4,
[Note: Although in some contexts constant expressions must be evaluated during program translation, others may be evaluated during program execution. Since this International Standard imposes no restrictions on the accuracy of floating-point operations, it is unspecified whether the evaluation of a floating-point expression during translation yields the same result as the evaluation of the same expression (or the same operations on the same values) during program execution.
With the advent of narrowing rules, which require the compiler to evaluate constant expressions in more contexts than was the case in C++03 in order to determine whether an expression is well formed or not, this wording is not sufficiently clear in stating that even in cases where the computation must be done at compile time, the implementation is free to use the result of a runtime calculation rather than preserving the one computed at compile time.
Proposed resolution (October, 2012):
Change 7.7 [expr.const] paragraph 4 as follows:
[Note:Although in some contexts constant expressions must be evaluated during program translation, others may be evaluated during program execution.Since this International Standard imposes no restrictions on the accuracy of floating-point operations, it is unspecified whether the evaluation of a floating-point expression during translation yields the same result as the evaluation of the same expression (or the same operations on the same values) during program execution. [Footnote: Nonetheless, implementations are encouraged to provide consistent results, irrespective of whether the evaluation wasactuallyperformed during translation and/or during program execution. —end footnote] [Example:...
[Moved to DR at the October, 2012 meeting.]
8.5.2 [stmt.if] is silent about whether the else clause of an if statement is executed if the condition is not evaluated. (This could occur via a goto or a longjmp.) C99 covers the goto case with the following provision:
If the first substatement is reached via a label, the second substatement is not executed.
It should probably also be stated that the condition is not evaluated when the “then” clause is entered directly.
Proposed resolution (February, 2012):
Change 8.5.2 [stmt.if] paragraph 1 as follows:
If the condition (8.5 [stmt.select]) yields true the first substatement is executed. If the else part of the selection statement is present and the condition yields false, the second substatement is executed. If the first substatement is reached via a label, the condition is not evaluated and the second substatement is not executed. In the second form...
[Moved to DR at the April, 2013 meeting.]
It is not clear whether the reference to argument-dependent lookup in 8.6.5 [stmt.ranged] bullet 1.3 should be “pure” argument-dependent lookup (with no unqualified name lookup component) or the usual lookup that is invoked when argument-dependent lookup is done, i.e., unqualified lookup, potentially augmented by the associated namespaces.
Proposed resolution (October, 2012):
Change 8.6.5 [stmt.ranged] bullet 1.3 as follows:
otherwise, begin-expr and end-expr are
begin(__range) and end(__range), respectively, where
begin and end are looked up with
argument-dependent lookup in the associated
namespaces (6.5.4 [basic.lookup.argdep]). For the purposes
of this name lookup, namespace std is an associated
namespace. [Note: Ordinary unqualified lookup
(6.5.3 [basic.lookup.unqual]) is not performed. —end
note]
[Moved to DR at the April, 2013 meeting.]
According to 8.7.4 [stmt.return] paragraph 3,
A return statement with neither an expression nor a braced-init-list can be used only in functions that do not return a value, that is, a function with the return type void, a constructor (11.4.5 [class.ctor]), or a destructor (11.4.7 [class.dtor]).
However, paragraph 3 allows a return type of cv void in cases where the expression in the return statement has type void. Should paragraph 2 follow suit?
Proposed resolution (October, 2012):
Change 8.7.4 [stmt.return] paragraph 2 as follows:
A return statement with neither an expression nor a braced-init-list can be used only in functions that do not return a value, that is, a function with the return type cv void, a constructor (11.4.5 [class.ctor]), or a destructor (11.4.7 [class.dtor]). A return statement with an expression of non-void type...
[Moved to DR at the April, 2013 meeting.]
There is a contradiction in the specification of the linkage of members of the unnamed namespace, for example
namespace { int x; }
According to 6.6 [basic.link] paragraph 4,
An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage. All other namespaces have external linkage. A name having namespace scope that has not been given internal linkage above has the same linkage as the enclosing namespace if it is the name of
a variable; or
...
which would give x internal linkage. However, 9.2.2 [dcl.stc] paragraph 7 says,
A name declared in a namespace scope without a storage-class-specifier has external linkage unless it has internal linkage because of a previous declaration and provided it is not declared const.
This would give x external linkage. (This appears to have been a missed edit from the resolution of issue 1113.)
Proposed resolution (October, 2012):
Delete 9.2.2 [dcl.stc] paragraph 7:
A name declared in a namespace scope without a storage-class-specifier has external linkage unless it has internal linkage because of a previous declaration and provided it is not declared const. Objects declared const and not explicitly declared extern have internal linkage.
[Moved to DR at the April, 2013 meeting.]
Consider the following:
struct S { int i; }; using A alignas(alignof(long long)) = S;
9.13.2 [dcl.align] paragraph 1 allows an alignment-specifier to be applied to the declaration of a class or enumeration type, which A arguably is. The specification should be clarified to indicate that such a usage is not permitted, however.
Proposed resolution (October, 2012):
Change 9.13.2 [dcl.align] paragraph 1 as follows:
An alignment-specifier may be applied to a variable or to a class data member, but it shall not be applied to a bit-field, a function parameter,the formal parameter of a catch clausean exception-declaration (14.4 [except.handle]), or a variable declared with the register storage class specifier. An alignment-specifier may also be applied to the declarationof a class or enumeration typeor definition of a class (in an elaborated-type-specifier (9.2.9.5 [dcl.type.elab]) or class-head ( Clause 11 [class]), respectively) and to the declaration or definition of an enumeration (in an opaque-enum-declaration or enum-head, respectively (9.8.1 [dcl.enum])). An alignment-specifier with an ellipsis is a pack expansion (13.7.4 [temp.variadic]).
[Moved to DR status at the April, 2013 meeting.]
The permission granted implementations in 9.2.6 [dcl.constexpr] paragraph 5 to diagnose definitions of constexpr functions that can never be used in a constant expression should not apply to an instantiated constexpr function template.
Notes from the August, 2011 meeting:
int f(); // not constexpr
struct A {
int m;
constexpr A(int i = f()) : m(i) { }
};
struct B {
A a;
} b;
This is ill-formed, no diagnostic required, because the defaulted default constructor of B will be declared constexpr but can never be invoked in a constant expression. See issue 1360.
Proposed resolution (February, 2012):
Change 9.2.6 [dcl.constexpr] paragraph 5 as follows:
... —end example]
For a non-template, non-defaulted constexpr function, if no function argument values exist such that the function invocation substitution would produce a constant expression (7.7 [expr.const]), the program is ill-formed; no diagnostic required. For a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that after function invocation substitution, every constructor call and full-expression in the mem-initializers would be a constant expression (including conversions), the program is ill-formed; no diagnostic required. For a constexpr function template or member function of a class template, if no instantiation would be well-formed when considered as a non-template constexpr function, the program is ill-formed; no diagnostic required. [Example:...
Delete 9.2.6 [dcl.constexpr] paragraph 6:
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, 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. —end note] If no specialization of the template would yield a constexpr function or constexpr constructor, the program is ill-formed; no diagnostic required.
Additional notes, February, 2012:
The proposed resolution inadvertently removes the provision allowing specializations of constexpr templates to violate the requirements of 9.2.6 [dcl.constexpr]. It is being retained in "drafting" status pending additional work.
Proposed resolution (October, 2012):
Change 9.2.6 [dcl.constexpr] paragraphs 5-6 as follows:
...For a non-template, non-defaulted constexpr function, if no function argument values exist such that the function invocation substitution would produce a constant expression (7.7 [expr.const]), the program is ill-formed; no diagnostic required. For a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that after function invocation substitution, every constructor call and full-expression in the mem-initializers would be a constant expression (including conversions), the program is ill-formed; no diagnostic required. [Example: ... —end example]
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, that specialization is
notstill a constexpr function or constexpr constructor, even though a call to such a function cannot appear in a constant expression.[Note: If the function is a member function it will still be const as described below. —end note]If no specialization of the template wouldyieldsatisfy the requirements for a constexpr function or constexpr constructor when considered as a non-template function or constructor, theprogramtemplate is ill-formed; no diagnostic required.
Additional note (January, 2013):
Questions arose in the discussion of issue 1581 as to whether this approach — making the specialization of a constexpr function template or member function of a class template still constexpr but unable to be invoked in a constant context — is correct. The implication is that class types might be categorized as literal but not be able to be instantiated at compile time. This issue is therefore returned to "review" status to allow further consideration of this question.
[Moved to DR at the October, 2012 meeting.]
A constexpr constructor is required to initialize all non-static data members (9.2.6 [dcl.constexpr] paragraph 4), which conflicts with the requirement that a constructor for a union is permitted to initialize only a single non-static data member (11.9.3 [class.base.init] paragraph 8).
Proposed resolution (February, 2012):
Change 9.2.6 [dcl.constexpr] paragraph 4 as follows:
In a definition of a constexpr constructor, each of the parameter types shall be a literal type. In addition, either its function-body shall be = delete or = default or it shall satisfy the following constraints:
...
every non-variant non-static data member and base class sub-object shall be initialized (11.9.3 [class.base.init]);
if the class is a non-empty union, or for each non-empty anonymous union member of a non-union class, exactly one non-static data member shall be initialized;
...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The requirement that a class with a constexpr constructor cannot have a virtual base only applies to constructors with non-deleted and non-defaulted function-bodys. This seems like an oversight.
Proposed resolution (August, 2011):
Change 9.2.6 [dcl.constexpr] paragraph 4 as follows:
In aThe definition of a constexpr constructor, each of the parameter types shall be a literal type. In addition, either its function-body shall be = delete or = default or itshall satisfy the following constraints:
the class shall not have any virtual base classes;
each of the parameter types shall be a literal type;
its function-body shall not be a function-try-block;
In addition, either its function-body shall be = delete or it shall satisfy the following constraints:
either its function-body shall be = default or the compound-statement of its function-body shall contain only...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Function invocation substitution (9.2.6 [dcl.constexpr] paragraph 5) seems underspecified with respect to this.
Proposed resolution (August, 2011):
Change the indicated bullet of 7.7 [expr.const] paragraph 2 as follows:
this (7.5 [expr.prim]
_N4567_.5.1.1 [expr.prim.general]) unless it appears as the
postfix-expression in a class member access expression,
including the result of the implicit transformation in the body of a
non-static member function (11.4.3 [class.mfct.non.static])
[Note: when evaluating a constant expression, function
invocation substitution (9.2.6 [dcl.constexpr]) replaces each
occurrence of this in a constexpr member function
with a pointer to the class object. —end
note];
Change 9.2.6 [dcl.constexpr] paragraph 5 as follows (converting the running text into a bulleted list):
Function invocation substitution for a call of a constexpr function or of a constexpr constructor means:
implicitly converting each argument to the corresponding parameter type as if by copy-initialization,91
substituting that converted expression for each use of the corresponding parameter in the function-body,
in a member function, substituting for each use of this (_N4868_.11.4.3.2 [class.this]) a prvalue pointer whose value is the address of the object for which the member function is called, and
forin a constexpr functions, implicitly converting the resulting returned expression or braced-init-list to the return type of the function as if by copy-initialization.Such substitution...
This resolution also resolves issues 1264 and 1367.
[Addressed by the adoption of paper N3652 at the April, 2013 meeting.]
One of the examples in 9.2.6 [dcl.constexpr] paragraph 3 reads,
constexpr int prev(int x)
{ return --x; } // error: use of decrement
According to paragraph 5, this ill-formed, no diagnostic required:
For a constexpr function, if no function argument values exist such that the function invocation substitution would produce a constant expression (7.7 [expr.const]), the program is ill-formed; no diagnostic required.
However, the surrounding errors in the example have required diagnostics, potentially leading the reader to the mistaken conclusion that this error must be diagnosed as well. The example should be removed or the comment updated to reflect its true status.
Proposed resolution (June, 2013):
This issue is no longer relevant after the adoption of the changes to constexpr in paper N3652.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The constraints on type-specifiers given in 9.2.9 [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 7.6.2.8 [expr.new], 8.7 [stmt.jump], 9.3.2 [dcl.name], and 11.4.8.3 [class.conv.fct].
Proposed resolution (August, 2011):
Change 9.2.9 [dcl.type] paragraph 3 as follows:
AtExcept in a declaration of a constructor, destructor, or conversion function, at least one type-specifier that is not a cv-qualifieris required in a declaration unless it declares a constructor, destructor or conversion functionshall appear in a complete type-specifier-seq or a complete decl-specifier-seq.92 A type-specifier-seq shall not define...
(Note: paper N2546, voted into the Working Draft in February, 2008, addresses part of this issue.)
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The current wording of 9.2.9.7 [dcl.spec.auto] does not appear to forbid using the auto specifier for both a function declaration with a trailing return type and a variable definition in the same declaration, e.g.,
auto f() -> int, i = 0;
(See also issue 1347.)
Proposed resolution (August, 2011):
Change 9.2.9.7 [dcl.spec.auto] paragraph 7 as follows:
If the list of declarators contains more than one declarator, they shall all form declarations of variables. Thethetype of each declared variable is determined as described above. If, and if the type deduced for the template parameter U is not the same in each deduction, the program is ill-formed. [Example:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
9.2.9.7 [dcl.spec.auto] does not address the case when the initializer for an auto variable is a parenthesized expression-list.
Proposed resolution (August, 2011):
Change 9.2.9.7 [dcl.spec.auto] paragraph 3 as follows:
...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. In an initializer of the form
( expression-list )
the expression-list shall be a single assignment-expression. [Example:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The intent of 9.2.9.7 [dcl.spec.auto] paragraph 7 appears to have been that the type represented by auto should be the same for each declarator in the declaration. However, the current wording does not achieve that goal. For example, in
auto a = 0, b = { 1, 2, 2 };
the auto specifier represents int in the first declarator and std::initializer_list<int> in the second. (See also issue 1265.)
Proposed resolution (August, 2011):
Move the example in 9.2.9.7 [dcl.spec.auto] paragraph 7 into that of paragraph 6 and change paragraph 7 as follows:
...[Example:
auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int> auto x2 = { 1, 2.0 }; // error: cannot deduce element type const auto &i = expr;The type of i is the deduced type of the parameter u in the call f(expr) of the following invented function template:
template <class U> void f(const U& u);—end example]
If the
list of declaratorsinit-declarator-list contains more than onedeclaratorinit-declarator, the type of each declared variable is determined as described above. If the typededuced for the template parameter Uthat replaces the occurrence of auto is not the same in each deduction, the program is ill-formed.[Example:
const auto &i = expr;
The type of i is the deduced type of the parameter u in the call f(expr) of the following invented function template:template <class U> void f(const U& u);auto x = 5, *y = &x; // OK: auto is int auto a = 5, b = { 1, 2 }; // error: different types for auto—end example]
In an example like
const auto x = 3;
the intent, clearly, is to make const int the type of x. It is not clear, however, that the current wording accomplishes this. Because the deduction is based on that of function calls, and because top-level cv-qualifiers are ignored in such deduction, it appears that 9.2.9.7 [dcl.spec.auto] paragraph 6,
The type deduced for the variable d is then the deduced A determined using the rules of template argument deduction from a function call (13.10.3.2 [temp.deduct.call]), where P is a function template parameter type and the initializer for d is the corresponding argument.
incorrectly gives x the type int.
Proposed resolution (April, 2013):
This issue is resolved by the wording changes in N3638, adopted at the April, 2013 (Bristol) meeting.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
There is a contradiction between the grammar of 9.3 [dcl.decl] paragraph 4 and that of 9.3.4.6 [dcl.fct] paragraphs 1 and 2 and 9.6.1 [dcl.fct.def.general] paragraph 2 regarding the placement of the optional exception-specification: in the former, it immediately follows the parameter-declaration-clause, while in the latter it follows the exception-specification.
Proposed resolution (August, 2011):
Change the grammar in 9.3 [dcl.decl] paragraph 4 as follows:
Change the grammar snippet in 12.2.2.2.3 [over.call.object] paragraph 2 as follows:
[Moved to DR at the October, 2012 meeting.]
Issue 147 changed the name lookup rules so that a lookup that would have found the injected-class-name of a class will refer to the constructor. However, there still appear to be vestiges of the earlier specification that were not removed by the resolution. For example, the grammar in 9.3 [dcl.decl] paragraph 4 contains,
It would seem that there is no longer any need for the second line, since a lookup for a declarator-id will not produce a class-name. Similarly, _N4567_.5.1.1 [expr.prim.general] paragraph 8 still contains the sentence,
Where class-name :: class-name is used, and the two class-names refer to the same class, this notation names the constructor (11.4.5 [class.ctor]).
Proposed resolution (February, 2012):
Change 9.3 [dcl.decl] paragraph 4 as follows:
...
declarator-id:...opt id-expression
nested-name-specifieropt class-name
A class-name has special meaning in a declaration of the class of that name and when qualified by that name using the scope resolution operator :: (5.1 [lex.separate], 11.4.5 [class.ctor], 11.4.7 [class.dtor]).
Change _N4567_.5.1.1 [expr.prim.general] paragraph 8 as follows:
...[Note: a class member can be referred to using a qualified-id at any point in its potential scope (6.4.7 [basic.scope.class]). —end note]Where class-name :: class-name is used, and the two class-names refer to the same class, this notation names the constructor (11.4.5 [class.ctor]).Where class-name ::~ class-name is used...
[Moved to DR at the April, 2013 meeting.]
There is no restriction against repeated cv-qualifiers in declarators, although there is (in 9.2.9 [dcl.type] paragraph 2) for those appearing in a decl-specifier-seq. However, most or all current implementations reject such code. Is a change needed?
Proposed resolution (October, 2012):
Change 9.2.9.2 [dcl.type.cv] paragraph 1 as follows:
There are two cv-qualifiers, const and volatile. Each cv-qualifier shall appear at most once in a cv-qualifier-seq. If a cv-qualifier appears in a decl-specifier-seq, the init-declarator-list of the declaration shall not be empty. [Note:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to 9.3.4 [dcl.meaning] paragraph 1,
A declarator-id shall not be qualified except for the definition of a member function (11.4.2 [class.mfct]) or static data member (11.4.9 [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.8.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 9.3.4 [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 (11.4.8 [class.conv], 11.4.7 [class.dtor], 12.4 [over.oper]) and for the declaration of template specializations or partial specializations (13.9 [temp.spec]).A declarator-id shall not be qualified except for the definition of a member function (11.4.2 [class.mfct]) or static data member (11.4.9 [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.8.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 11.4 [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.]
[Moved to DR at the April, 2013 meeting.]
The status of a declaration like the following is unclear:
template<typename T> struct A { A<T>(); };
9.3.4 [dcl.meaning] paragraph 1 appears to say that it is not allowed, but it is not clear.
Proposed resolution (October, 2012):
Change 9.3.4 [dcl.meaning] paragraph 1 as follows:
A list of declarators appears after an optional (9.1 [dcl.pre]) decl-specifier-seq (9.2 [dcl.spec]). Each declarator contains exactly one declarator-id; it names the identifier that is declared. An unqualified-id occurring in a declarator-id shall be a simple identifier except for the declaration of some special functions (11.4.5 [class.ctor], 11.4.8 [class.conv], 11.4.7 [class.dtor], 12.4 [over.oper]) and for the declaration of template specializations or partial specializations (13.9 [temp.spec]). A declarator-id shall not...
Change 11.4.5 [class.ctor] paragraph 1 as follows:
Constructors do not have names.
A special declarator syntax is used to declare or define the constructor. The syntax uses:
an optional 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.A declaration of a constructor uses a function declarator (9.3.4.6 [dcl.fct]) of the formptr-declarator ( parameter-declaration-clause ) exception-specificationopt attribute-specifier-seqopt
where the ptr-declarator consists solely of an id-expression, an optional attribute-specifier-seq, and optional surrounding parentheses, and the id-expression has one of the following forms:
in a member-declaration that belongs to the member-specification of a class but is not a friend declaration (11.8.4 [class.friend]), the id-expression is the injected-class-name (Clause 11 [class]) of the immediately-enclosing class;
in a member-declaration that belongs to the member-specification of a class template but is not a friend declaration, the id-expression is a class-name that names the current instantiation (13.8.3.2 [temp.dep.type]) of the immediately-enclosing class template; or
in a declaration at namespace scope or in a friend declaration, the id-expression is a qualified-id that names a constructor (6.5.5.2 [class.qual]).
The class-name shall not be a typedef-name. In a constructor declaration, each decl-specifier in the optional decl-specifier-seq shall be friend, inline, explicit, or constexpr. [Example:...
Delete 11.4.5 [class.ctor] paragraph 3:
A typedef-name shall not be used as the class-name in the declarator-id for a constructor declaration.
Change 11.4.5 [class.ctor] paragraph 4 as follows:
A constructor shall not be virtual (11.7.3 [class.virtual]) or static (11.4.9 [class.static]).A constructor can be invoked for a const, volatile or const volatile object.A constructor shall not be declared const, volatile, or const volatile (_N4868_.11.4.3.2 [class.this]).const and volatile semantics (9.2.9.2 [dcl.type.cv]) are not applied on an object under construction. They come into effect when the constructor for the most derived object (6.7.2 [intro.object]) ends.A constructor shall not be declared with a ref-qualifier.
Change 11.4.5 [class.ctor] paragraph 9 as follows:
No return type (not even void) shall be specified for a constructor.A return statement in the body of a constructor shall not specify a return value. The address of a constructor shall not be taken.
Change 11.4.7 [class.dtor] paragraphs 1-2 as follows:
A special declarator syntax using an optional function-specifier (9.2.3 [dcl.fct.spec]) followed by ~ followed by the destructor's class name followed by an empty parameter list is used to declare the destructor in a class definition. In such a declaration, the ~ followed by the destructor's class name can be enclosed in optional parentheses; such parentheses are ignored. A typedef-name shall not be used as the class-name following the ~ in the declarator for a destructor declaration.A declaration of a destructor uses a function declarator (9.3.4.6 [dcl.fct]) of the formptr-declarator ( parameter-declaration-clause ) exception-specificationopt attribute-specifier-seqopt
where the ptr-declarator consists solely of an id-expression, an optional attribute-specifier-seq, and optional surrounding parentheses, and the id-expression has one of the following forms:
in a member-declaration that belongs to the member-specification of a class but is not a friend declaration (11.8.4 [class.friend]), the id-expression is ~class-name and the class-name is the injected-class-name (Clause 11 [class]) of the immediately-enclosing class;
in a member-declaration that belongs to the member-specification of a class template but is not a friend declaration, the id-expression is ~class-name and the class-name names the current instantiation (13.8.3.2 [temp.dep.type]) of the immediately-enclosing class template; or
in a declaration at namespace scope or in a friend declaration, the id-expression is nested-name-specifier ~class-name and the class-name names the same class as the nested-name-specifier.
The class-name shall not be a typedef-name. A destructor shall take no arguments (9.3.4.6 [dcl.fct]). In a destructor declaration, each decl-specifier of the optional decl-specifier-seq shall be friend, inline, or virtual.
A destructor is used to destroy objects of its class type.
A destructor takes no parameters, and no return type can be specified for it (not even void).The address of a destructor shall not be taken.A destructor shall not be static.A destructor can be invoked for a const, volatile or const volatile object.A destructor shall not be declared const, volatile or const volatile (_N4868_.11.4.3.2 [class.this]).const and volatile semantics (9.2.9.2 [dcl.type.cv]) are not applied on an object under destruction. They stop being in effect when the destructor for the most derived object (6.7.2 [intro.object]) starts.A destructor shall not be declared with a ref-qualifier.
This resolution also resolves issue 344.
[Moved to DR at the April, 2013 meeting.]
According to 9.3.4.3 [dcl.ref] paragraph 1,
Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of a typedef (9.2.4 [dcl.typedef]) or of a template type argument (13.4 [temp.arg]), in which case the cv-qualifiers are ignored.
There does not appear to be a good reason not to extend this to apply to apply to decltype, as well.
Proposed resolution (October, 2012):
Change 9.3.4.3 [dcl.ref] paragraph 1 as follows:
...Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of atypedeftypedef-name (9.2.4 [dcl.typedef], 13.2 [temp.param]) orof a template type argument (13.4 [temp.arg])decltype-specifier (9.2.9.3 [dcl.type.simple]), in which case the cv-qualifiers are ignored. [Example:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
9.3.4.6 [dcl.fct]/2 restricts the use of void as parameter type, but does not mention CV qualified versions. Since void f(volatile void) isn't a callable function anyway, 9.3.4.6 [dcl.fct] should also ban cv-qualified versions. (BTW, this follows C)
Suggested resolution:
A possible resolution would be to add (cv-qualified) before void in
The parameter list (void) is equivalent to the empty parameter list. Except for this special case, (cv-qualified) void shall not be a parameter type (though types derived from void, such as void*, can).
Proposed resolution (August, 2011):
This issue is resolved by the resolution of issue 577.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
9.3.4.6 [dcl.fct] paragraph 2 says,
The parameter list (void) is equivalent to the empty parameter list.
This special case is intended for C compatibility, but C99 describes it differently (6.7.5.3 paragraph 10):
The special case of an unnamed parameter of type void as the only item in the list specifies that the function has no parameters.
The C99 formulation allows typedefs for void, while C++ (and C90) accept only the keyword itself in this role. Should the C99 approach be adopted?
Notes from the October, 2006 meeting:
The CWG did not take a formal position on this issue; however, there was some concern expressed over the treatment of function templates and member functions of class templates if the C++ rule were changed: for a template parameter T, would a function taking a single parameter of type T become a no-parameter function if it were instantiated with T = void?
Proposed resolution (August, 2011):
Change 9.3.4.6 [dcl.fct] paragraph 4 as follows:
...If the parameter-declaration-clause is empty, the function takes no arguments.The parameter list (void)A parameter list consisting of a single unnamed parameter of non-dependent type void is equivalent tothean empty parameter list. Except for this special case, a parameter shall not have type cv voidshall not be a parameter type (though types derived from void, such as void*, can). If the parameter-declaration-clause terminates...
This resolution also resolves issue 332.
[Moved to DR at the October, 2012 meeting.]
Although 9.3.4.6 [dcl.fct] paragraph 9 forbids defining a type in a parameter declaration, and a template parameter declaration is syntactically a parameter-declaration, the context in 9.3.4.6 [dcl.fct] function declarators. It's therefore not completely clear that that prohibition applies to template parameter declarations as well. This should be clarified.
Proposed resolution (February, 2012):
Change 13.2 [temp.param] paragraph 2 as follows:
...A storage class shall not be specified in a template-parameter declaration. Types shall not be defined in a template-parameter declaration. [Note:...
[Moved to DR at the October, 2012 meeting.]
Currently, 9.3.4.6 [dcl.fct] paragraph 9 requires that
The type of a parameter or the return type for a function definition shall not be an incomplete class type (possibly cv-qualified) unless the function definition is nested within the member-specification for that class (including definitions in nested classes defined within the class).
There is no reason for this requirement for a function with a deleted definition, and it would be useful to relax this prohibition in such cases.
Proposed resolution (February, 2012):
Change 9.3.4.6 [dcl.fct] paragraph 9 as follows:
Types shall not be defined in return or parameter types. The type of a parameter or the return type for a function definition shall not be an incomplete class type (possibly cv-qualified) unless the function is deleted (9.6.3 [dcl.fct.def.delete]) or the definition is nested within the member-specification for that class (including definitions in nested classes defined within the class).
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to the new wording of 9.3.4.7 [dcl.fct.default] paragraph 5,
A default argument is implicitly converted (7.3 [conv]) to the parameter type.
This is incorrect when the default argument is a braced-init-list. That sentence doesn't seem to be necessary, but if it is kept, it should be recast in terms of initialization rather than conversion.
Proposed resolution (August, 2011):
Delete the first sentence of 9.3.4.7 [dcl.fct.default] paragraph 5:
A default argument is implicitly converted (7.3 [conv]) to the parameter type.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
9.5 [dcl.init] paragraph 7 only describes how to initialize objects:
To value-initialize an object of type T means:
However, 7.6.1.4 [expr.type.conv] paragraph 2 calls for value-initializing prvalues, which in the case of scalar types are not objects:
The expression T(), where T is a simple-type-specifier or typename-specifier for a non-array complete object type or the (possibly cv-qualified) void type, creates a prvalue of the specified type, which is value-initialized (9.5 [dcl.init]; no initialization is done for the void() case).
Proposed resolution (August, 2011):
Change 7.6.1.4 [expr.type.conv] paragraph 2 as follows:
The expression T(), where T is a simple-type-specifier or typename-specifier for a non-array complete object type or the (possibly cv-qualified) void type, creates a prvalue of the specifiedtype,which is value-initialized (9.5 [dcl.init]type, whose value is that produced by value-initializing (9.5 [dcl.init]) an object of type T; no initialization is done for the void() case). [Note:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to 9.5 [dcl.init] paragraph 7,
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type (Clause 11 [class]) with a user-provided constructor (11.4.5 [class.ctor]), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T's implicitly-declared default constructor is non-trivial, that constructor is called.
if T is an array type, then each element is value-initialized;
otherwise, the object is zero-initialized.
This suggests that for
struct A { A() = delete; }; union B { A a }; int main() { B(); }
a B temporary is created and zero-initialized, even though its default constructor is deleted. We should strike "non-union" and also the "if...non-trivial" condition, since we can have a trivial deleted constructor.
Proposed resolution (August, 2011):
Change 9.5 [dcl.init] paragraph 7 as follows:
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type (Clause 11 [class]) with either no default constructor (11.4.5 [class.ctor]) or a default constructor that is user-provided or deleted
constructor (11.4.5 [class.ctor]), then the object is default-initializeddefault constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);if T is a (possibly cv-qualified) non-union class type without a user-provided or deleted default constructor, then the object is zero-initialized and, if
T's implicitly-declared default constructor isT has a non-trivial default constructor,that constructor is called.default-initialized;...
Change 9.5.5 [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, ifIf T is an aggregate, aggregate initialization is performed (9.5.2 [dcl.init.aggr]). [Example:...Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
...
This resolution also resolves issues 1324 and 1368.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
One would expect that an example like
struct B { B(const B&) = default; }; B b{};
would invoke value-initialization, but (because it does not have a default constructor), the logic ladder of 9.5.5 [dcl.init.list] paragraph 3 makes it aggregate initialization instead:
If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
Otherwise, if T is an aggregate, aggregate initialization is performed (9.5.2 [dcl.init.aggr]).
Proposed resolution (August, 2011):
This issue is resolved by the resolution of issue 1301.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to the current rules of 9.5 [dcl.init], given a class like
struct A { int i; A() = default; A(int i): i(i) { } };
value-initialization leaves A::i uninitialized. This seems like an oversight.
Proposed resolution (August, 2011):
This issue is resolved by the resolution of issue 1301.
[Moved to DR at the April, 2013 meeting.]
According to 9.5 [dcl.init] paragraph 7,
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type ( Clause 11 [class]) with either no default constructor (11.4.5 [class.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
if T is a (possibly cv-qualified) non-union class type without a user-provided or deleted default constructor, then the object is zero-initialized and, if T has a non-trivial default constructor, default-initialized;
if T is an array type, then each element is value-initialized;
otherwise, the object is zero-initialized.
In an example like
union U { int a = 5; }; int main() { return U().a; }
this means that the value returned is 0, because none of the first three bullets apply. Should the “non-union” restriction be dropped from the second bullet?
Proposed resolution (October, 2012):
Change 9.5 [dcl.init] paragraph 7 as follows:
To value-initialize an object of type T means:
...
if T is a (possibly cv-qualified)
non-unionclass type without a user-provided or deleted default constructor, then the object is zero-initialized and, if T has a non-trivial default constructor, default-initialized;...
[Moved to DR at the April, 2013 meeting.]
According to 9.5 [dcl.init] paragraph 7, a trivial default constructor is not used in value initialization, so the following example would appear to be well-formed:
struct A { private: A() = default; }; int main() { A(); }
Proposed resolution (February, 2013):
Change 9.5 [dcl.init] paragraph 7 as follows:
To default-initialize an object of type T means:
if T is a (possibly cv-qualified) class type ( Clause 11 [class]), the default constructor (11.4.5 [class.ctor]) for T is called (and the initialization is ill-formed if T has no
accessible default constructordefault constructor or overload resolution (12.2 [over.match]) results in an ambiguity or in a function that is deleted or inaccessible from the context of the initialization);...
Change 9.5 [dcl.init] paragraph 8 as follows:
To value-initialize an object of type T means:
...
if T is a (possibly cv-qualified) non-union class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Consider the following example:
struct X { unsigned bitfield : 4; }; int main() { X x = { 1 }; unsigned const &ref = static_cast<X &&>(x).bitfield; }
According to 9.5.4 [dcl.init.ref] paragraph 5, ref is bound to the bit-field xvalue.
Proposed resolution (August, 2011):
Change the indicated sub-bullet of 9.5.4 [dcl.init.ref] paragraph 5 as follows:
is an xvalue (but is not a bit-field), class prvalue, array prvalue or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
[Moved to DR at the April, 2013 meeting.]
According to 12.2.2.7 [over.match.ref] paragraph 1, the determination of the candidate functions is based on whether 9.5.4 [dcl.init.ref] requires an lvalue result or an rvalue result. It is not sufficiently clear exactly what this means, particularly with respect to function lvalues and rvalues.
Proposed resolution (August, 2011):
Change 12.2.2.7 [over.match.ref] paragraph 1 as follows:
The conversion functions of S and its base classes
are considered, except that for copy-initialization, only the
non-explicit conversion functions are considered. Those that are not
hidden within S and yield type “lvalue reference to
cv2 T2” (when 9.5.4 [dcl.init.ref]
requires an lvalue result initializing an lvalue reference
or an rvalue reference to function) or “cv2
T2” or “rvalue reference to cv2
T2” (when 9.5.4 [dcl.init.ref] requires an
rvalue result initializing an rvalue reference or an lvalue
reference to function), where “cv1
T” is reference-compatible (9.5.4 [dcl.init.ref])
with “cv2 T2”, are candidate
functions.
Change 12.2.4 [over.match.best] paragraph 1 as follows:
...
the context is an initialization by user-defined conversion... or if not that,
the context is an initialization by conversion function for direct reference binding (12.2.2.7 [over.match.ref]) of a reference to function type, the return type of F1 is the same kind of reference (i.e. lvalue or rvalue) as the reference being initialized, and the return type of F2 is not [Example:
template <class T> struct A { operator T&(); // #1 operator T&&(); // #2 }; typedef int Fn(); A<Fn> a; Fn& lf = a; // calls #1 Fn&& rf = a; // calls #2
—end example] or, if not that,
F1 is a non-template function...
[Moved to DR at the October, 2012 meeting.]
The definition of reference-compatible types in 9.5.4 [dcl.init.ref] paragraph 4 allows the types to differ in top-level cv-qualification, but it does not encompass the deeper added cv-qualification permitted for “similar types” (7.3.6 [conv.qual]). This seems surprising and could lead to errors resulting from the fact that the reference will be bound to a temporary and not to the original object in the initializer.
Proposed resolution (February, 2012):
Change 9.5.4 [dcl.init.ref] paragraph 4 as follows:
Given types “cv1 T1” and “cv2 T2,” “cv1 T1” is reference-related to “cv2 T2” if T1 is the same type as T2, or T1 is a base class of T2. “cv1 T1” is reference-compatible with “cv2 T2” if T1 is reference-related to T2 and cv1 is the same cv-qualification as, or greater cv-qualification than, cv2.For purposes of overload resolution, cases for which cv1 is greater cv-qualification than cv2 are identified as reference-compatible with added qualification (see 12.2.4.3 [over.ics.rank]).In all cases...
Delete 12.2.4.2.5 [over.ics.ref] paragraph 5:
The binding of a reference to an expression that is reference-compatible with added qualification influences the rank of a standard conversion; see 12.2.4.3 [over.ics.rank] and 9.5.4 [dcl.init.ref].
[Drafting note: CWG decided not to make a substantive change for this issue, but the investigation discovered that the term defined by these two citations is not actually used and could be removed.]
[Moved to DR at the October, 2012 meeting.]
Issue 1232 extended the language to allow creation of array temporaries using initializer lists. However, such initializer lists must be “completely braced;” the elision of braces described in 9.5.2 [dcl.init.aggr] paragraph 11 applies only
In a declaration of the form
T x = { a };
This restriction prevents plausible uses like
array<int, 3> f() { return { 1, 2, 3 }; }
Proposed resolution (February, 2012):
Change 9.5.2 [dcl.init.aggr] paragraph 11 as follows:
In a declaration of the form
T x = { a };
bracesBraces can be elided in an initializer-list as follows.[Footnote: Braces cannot be elided in other uses of list-initialization. —end footnote]
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
One might expect that in an example like
int i; int & ir{i};
ir would bind directly to i. However, according to 9.5.5 [dcl.init.list] paragraph 3, this example creates a temporary of type int and binds the reference to that temporary:
...
Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary...
Otherwise, if the initializer list has a single element, the object or reference is initialized from that element...
Also, the “or reference” in the last bullet is dead code, as a reference initialization is always handled by the preceding bullet.
Proposed resolution (August, 2011):
Change 9.5.5 [dcl.init.list] paragraph 3 as follows:
...
Otherwise, if T is a class type, constructors are considered...
Otherwise, if T is a reference type, a prvalue
temporary of the type referenced by T is list-initialized,
and the reference is bound to that temporary. [Note: As usual,
the binding will fail and the program is ill-formed if the reference
type is an lvalue reference to a non-const type. —end
note] [Example: ... —end
example]
Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed. [Example:...
Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary. [Note: As usual, the binding will fail and the program is ill-formed if the reference type is an lvalue reference to a non-const type. —end note] [Example: ... —end example]
Otherwise, if the initializer list has no elements...
...
[Moved to DR at the October, 2012 meeting.]
A question has arisen over expected behavior when an initializer_list is a non-static data member of a class. Initialization of an initializer_list is defined in terms of construction from an implicitly allocated array whose lifetime "is the same as that of the initializer_list object". That would mean that the array needs to live as long as the initializer_list does, which would on the face of it appear to require the array to be stored in something like a std::unique_ptr<T[]> within the same class (if the member is initialized in this manner).
It would be surprising if that was the intent, but it would make initializer_list usable in this context.
It would also be reasonable if this behaved similarly to binding temporaries to reference members (i.e., "temporary bound to a reference member in a constructor's ctor-initializer (11.9.3 [class.base.init]) persists until the constructor exits."), though this approach would probably prevent use of an initializer_list member in that context.
Proposed resolution (February, 2012):
Change 9.5.5 [dcl.init.list] paragraphs 5-6 as follows:
An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated
ana temporary array of N elements of type E, where...
The lifetime of the array is the same as that of the initializer_list object.The array has the same lifetime as any other temporary object (6.7.7 [class.temporary]), except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary. [Example:typedef std::complex<double> cmplx; std::vector<cmplx> v1 = { 1, 2, 3 }; void f() { std::vector<cmplx> v2{ 1, 2, 3 }; std::initializer_list<int> i3 = { 1, 2, 3 }; } struct A { std::initializer_list<int> i4; A(): i4{1,2,3} { } // creates an A with a dangling reference };For v1 and v2, the initializer_list object is a parameter in a function call, so the
andarray created for { 1, 2, 3 }havehas full-expression lifetime. For i3, the initializer_list object is a variable, so theandarrayhave automaticpersists for the lifetime of the variable. For i4, the initializer_list object is initialized in a constructor's ctor-initializer, so the array persists only until the constructor exits, and so any use of the elements of i4 after the constructor exits produces undefined behavior. —end example] [Note: The implementation is free to allocate the array in read-only memory if an explicit array with the same initializer could be so allocated. —end note]
Change 6.7.7 [class.temporary] paragraph 5 as follows:
The second context is when a reference is bound to a temporary. [Footnote: The same rules apply to initialization of an initializer_list object (9.5.5 [dcl.init.list]) with its underlying temporary array. —end footnote] The temporary to which...
[Moved to DR at the October, 2012 meeting.]
According to 9.5.5 [dcl.init.list] paragraph 5, the elements of the backing array for an object of type std::initializer_list<E> are of type E. This is contradicted by the wording of 17.11 [support.initlist] paragraph 2.
Proposed resolution (February, 2012):
Change 9.5.5 [dcl.init.list] paragraph 5 as follows:
An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated an array of N elements of type const E, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array. If a narrowing conversion is required to initialize any of the elements, the program is ill-formed. [Example:
struct X { X(std::initializer_list<double> v); }; X x{ 1,2,3 };The initialization will be implemented in a way roughly equivalent to this:
const double __a[3] = {double{1}, double{2}, double{3}}; X x(std::initializer_list<double>(__a, __a+3));assuming that the implementation can construct an initializer_list object with a pair of pointers. —end example]
[Moved to DR at the October, 2012 meeting.]
According to 9.5.5 [dcl.init.list] paragraph 7, an implicit conversion
from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type.
As is made plain in the examples in that paragraph, a conversion of a negative value to an unsigned type is intended to be a narrowing conversion; however, the phrase “actual value after conversion” makes this intent unclear, especially since the round-trip conversion between signed and unsigned types might well yield the original value.
Proposed resolution (February, 2012):
Change 9.5.5 [dcl.init.list] paragraph 7 as follows:
A narrowing conversion is an implicit conversion
...
from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression
and the actual value after conversionwhose value after integral promotions will fit into the target typeand will produce the original value when converted back to the original type.[Note:...
[Moved to DR at the April, 2013 meeting.]
One of the bullets in 9.5.5 [dcl.init.list] paragraph 3 reads,
Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary.
This does not say whether the initialization of the temporary is direct-list-initialization, copy-list-initialization, or the same kind of list-initialization as the top-level initialization.
Proposed resolution (December, 2012):
Change 9.5.5 [dcl.init.list] paragraph 3 as follows:
...
Otherwise, if T is a reference type, a prvalue
temporary of the type referenced by T is
list-initialized copy-list-initialized or
direct-list-initialized, depending on the kind of initialization for
the reference, and the reference is bound to that
temporary. [Note: As usual...
...
[Moved to DR at the April, 2013 meeting.]
One of the bullets in 9.5.5 [dcl.init.list] paragraph 3 says,
Otherwise, if T is a specialization of std::initializer_list<E>, an initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (9.5 [dcl.init]).
This does not, but should, say whether the initializer_list object is treated as an lvalue or prvalue for the purpose of the 9.5 [dcl.init] initialization.
Proposed resolution (October, 2012):
Change 9.5.5 [dcl.init.list] paragraph 3 as follows:
List-initialization of an object or reference of type T is defined as follows:
...
Otherwise, if T is a specialization of std::initializer_list<E>,
ana prvalue initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (9.5 [dcl.init])....
[Moved to DR at the April, 2013 meeting.]
In constructing an initializer_list object from an initializer list, 9.5.5 [dcl.init.list] paragraph 5 says of the underlying array,
Each element of that array is copy-initialized with the corresponding element of the initializer list
It would probably be good to mention that the copy/move constructor for this copy must be accessible in the context in which the initialization occurs.
Proposed resolution (October, 2012):
Change 9.5.5 [dcl.init.list] paragraph 4 as follows:
...Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array. [Note: A constructor or conversion function selected for the copy shall be accessible (11.8 [class.access]) in the context of the initializer list. —end note] If a narrowing conversion is required...
The resolution of issue 1696 appears to have removed the wording that makes the initialization of A::i4 in 9.5.5 [dcl.init.list] paragraph 6 create a dangling reference:
The array has the same lifetime as any other temporary object (6.7.7 [class.temporary]), except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary. [Example:
typedef std::complex<double> cmplx; std::vector<cmplx> v1 = { 1, 2, 3 }; void f() { std::vector<cmplx> v2{ 1, 2, 3 }; std::initializer_list<int> i3 = { 1, 2, 3 }; } struct A { std::initializer_list<int> i4; A() : i4{ 1, 2, 3 } {} // creates an A with a dangling reference };For v1 and v2, the initializer_list object is a parameter in a function call, so the array created for { 1, 2, 3 } has full-expression lifetime. For i3, the initializer_list object is a variable, so the array persists for the lifetime of the variable. For i4, the initializer_list object is initialized in a constructor's ctor-initializer, so the array persists only until the constructor exits, and so any use of the elements of i4 after the constructor exits produces undefined behavior. —end example]
Binding a reference to a temporary in a mem-initializer or default member initializer is now ill-formed, per 11.9.3 [class.base.init] paragraphs 8 and 11, which undercuts the description here.
Notes from the October, 2015 meeting:
The example is incorrect and will be fixed editorially. The issue is left in "review" status to check that the editorial change has been made.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The grammar for defaulted and deleted functions in 9.6.2 [dcl.fct.def.default] and 9.6.3 [dcl.fct.def.delete] does not provide for virt-specifiers. Is there a reason for this omission, or was it inadvertent?
Proposed resolution (August, 2011):
Change 9.6.2 [dcl.fct.def.default] paragraph 1 as follows:
A function definition of the form:
attribute-specifier-seqopt decl-specifier-seqopt declarator virt-specifier-seqopt = default ;
is called an explicitly-defaulted definition...
Change 9.6.3 [dcl.fct.def.delete] paragraph 1 as follows:
A function definition of the form:
attribute-specifier-seqopt decl-specifier-seqopt declarator virt-specifier-seqopt = delete ;
is called a deleted definition...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Paragraph 1 of 9.6.2 [dcl.fct.def.default] allows an explicitly-defaulted copy constructor or copy assignment operator to have a parameter type that is a reference to non-const, even if the corresponding implicitly-declared function would have a reference to const. However, paragraph 2 says that a copy constructor or copy assignment operator that is defaulted on its first declaration, the parameter type must be exactly the same. Is there a good reason for the stricter rule for a function that is defaulted on its first declaration?
Proposed resolution (August, 2011):
Change 9.6.2 [dcl.fct.def.default] paragraph 2 as follows:
...If a function is explicitly defaulted on its first declaration,
...
in the case of a copy constructor, move constructor, copy assignment operator, or move assignment operator, it shall have the same parameter type as if it had been implicitly declared.
Change 11.4.5.3 [class.copy.ctor] paragraph 12 as follows:
A copy/move constructor for class X is trivial if it is not user-provided, its declared parameter type is the same as if it had been implicitly declared, and if...
Change 11.4.5.3 [class.copy.ctor] paragraph 25 as follows:
A copy/move assignment operator for class X is trivial if it is not user-provided, its declared parameter type is the same as if it had been implicitly declared, and if...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The definition of “user-provided” given in 9.6.2 [dcl.fct.def.default] paragraph 4 applies only to special member functions, while the definition of an aggregate in 9.5.2 [dcl.init.aggr] paragraph 1 relies on that term in identifying constructors that make a class a non-aggregate. As a result, a class with a non-special constructor is considered an aggregate.
Proposed resolution (August, 2011):
Change 9.6.2 [dcl.fct.def.default] paragraph 4 as follows:
Aspecial memberfunction is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration...
[Drafting note: This makes a class with only a deleted initializer-list constructor an aggregate.]
[Moved to DR at the April, 2013 meeting.]
There is disagreement among implementations as to when an enumeration type is complete. For example,
enum E { e = E() };
is rejected by some and accepted by another. The Standard does not appear to resolve this question definitively.
See also issue 1482.
Proposed resolution (December, 2012):
This issue is resolved by the resolution of issue 1482.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The Standard does not appear to specify what happens for code like the following:
namespace one { template<typename T> void fun(T); } using one::fun; template<typename T> void fun(T);
9.10 [namespace.udecl] paragraph 13 does not appear to apply because it deals only with functions, not function templates:
If a function declaration in namespace scope or block scope has the same name and the same parameter types as a function introduced by a using-declaration, and the declarations do not declare the same function, the program is ill-formed.
John Spicer: For function templates I believe the rule should be that if they have the same function type (parameter types and return type) and have identical template parameter lists, the program is ill-formed.
Proposed resolution (August, 2011):
Change 9.10 [namespace.udecl] paragraph 14 as follows:
If a function declaration in namespace scope or block scope has the same name and the sameparameter typesparameter-type-list (9.3.4.6 [dcl.fct]) as a function introduced by a using-declaration, and the declarations do not declare the same function, the program is ill-formed. If a function template declaration in namespace scope has the same name, parameter-type-list, return type, and template parameter list as a function template introduced by a using-declaration, the program is ill-formed. [Note: Two using-declarations may introduce functions with the same name and the sameparameter typesparameter-type-list. If, for a call to an unqualified function name, function overload resolution selects the functions introduced by such using-declarations, the function call is ill-formed. [Example:...
[Moved to DR at the April, 2013 meeting.]
The ambiguity in an example like
struct A final { };
is resolved by 5.11 [lex.name] paragraph 2 to be the declaration of a variable named final:
any ambiguity as to whether a given identifier has a special meaning is resolved to interpret the token as a regular identifier.
Similarly, in an example like
struct C { constexpr operator int() { return 5; } }; struct A { struct B final : C{}; };
final is taken as the name of a bit-field member rather than as the name of a nested class.
Proposed resolution (October, 2012):
Change 5.11 [lex.name] paragraph 2 as follows:
The identifiers in Table 3 have a special meaning when appearing in a certain context. When referred to in the grammar, these identifiers are used explicitly rather than using the identifier grammar production. Unless otherwise specified, any ambiguity as to whether a given identifier has a special meaning is resolved to interpret the token as a regular identifier.
Change Clause 11 [class] paragraph 3 as follows:
If a class is marked with the class-virt-specifier final and it appears as a base-type-specifier in a base-clause (11.7 [class.derived]), the program is ill-formed. Whenever a class-key is followed by a class-head-name, the identifier final, and a colon or left brace, final is interpreted as a class-virt-specifier. [Example:
struct A; struct A final {}; // OK: definition of struct A, // not value-initialization of variable final struct X { struct C { constexpr operator int() { return 5; } }; struct B final : C{}; // OK: definition of nested class B, // not declaration of a bit-field member final };—end example]
[Moved to DR at the October, 2012 meeting.]
The requirements for a trivial class include having “a trivial default constructor” (Clause 11 [class] paragraph 6). However, with an explicitly-defaulted default constructor and other constructors with default arguments, it is possible to have multiple default constructors. Such a class cannot be default-initialized and thus should probably be considered non-trivial.
Proposed resolution (February, 2012):
Change Clause 11 [class] paragraph 6 as follows:
...A trivial class is a class that has atrivialdefault constructor (11.4.5 [class.ctor]), has no non-trivial default constructors, and is trivially copyable...
[Moved to DR at the April, 2013 meeting.]
The resolution of issue 355 failed to update the grammar for class-head-name in Clause 11 [class] paragraph 1 and removed the optional :: from class-or-decltype in 11.7 [class.derived] paragraph 1, with the result that it is still not possible to globally-qualify a class name in a class definition, and the ability to globally-qualify a base class name has been lost.
Proposed resolution (February, 2012):
Change the grammar in _N4567_.5.1.1 [expr.prim.general] paragraph 8 as follows:
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to 11.4 [class.mem] paragraph 2,
A class is considered a completely-defined object type (6.8 [basic.types]) (or complete type) at the closing } of the class-specifier. Within the class member-specification, the class is regarded as complete within function bodies, default arguments, exception-specifications, and brace-or-equal-initializers for non-static data members (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification.
With the advent of the noexcept operator, treating the class type as complete in exception-specifications is obviously not possible, e.g.,
struct X { // should X be considered as complete here? static void create() noexcept(noexcept(X())); X() noexcept(!noexcept(X::create())); };
Proposed resolution (August, 2011):
Change 11.4 [class.mem] paragraph 2 as follows:
A class is considered a completely-defined object type (6.8 [basic.types]) (or complete type) at the closing } of the class-specifier. Within the class member-specification, the class is regarded as complete within function bodies, default arguments,exception-specifications,and brace-or-equal-initializers for non-static data members (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification.
Change 14.5 [except.spec] paragraph 2 as follows:
...A type denoted in an exception-specification shall not denote an incomplete type other than a class currently being defined. A type denoted in an exception-specification shall not denote a pointer or reference to an incomplete type, other than cv void*, const void*, volatile void*, or const volatile void*or a pointer or reference to a class currently being defined. 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.
Note:
This change was subsequently removed by the resolution of issue 1330.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The grammar allows a brace-or-equal-initializer for any class member with a member-declarator, including typedef members and member function declarations, and there is no semantic restriction forbidding those forms, either.
Proposed resolution (August, 2011):
In 11.4 [class.mem], delete paragraph 4 and change paragraph 5 as follows:
A member can be initialized using a constructor; see 11.4.5 [class.ctor]. [Note: See 11.4.4 [special] for a description of constructors and other special member functions. —end note]A
member can be initialized using abrace-or-equal-initializer shall appear only in the declaration of a data member. (For static data members, see 11.4.9.3 [class.static.data]; for non-static data members, see 11.9.3 [class.base.init]).
[Moved to DR at the April, 2013 meeting.]
According to 11.4 [class.mem] paragraph 20,
A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa.
Given a standard-layout struct in which the non-static data members are in a base class (and hence the derived class is empty), it is not clear what the “initial member” is. Presumably the intent behind allowing such standard-layout classes was to treat the base class object and its first non-static data member as the initial member of the derived class, but this does not appear to be spelled out anywhere.
Proposed resolution (February, 2012):
Change 11.4 [class.mem] paragraph 20 as follows:
A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa.If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member. Otherwise, its address is the same as the address of its first base class subobject (if any). [Note:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Footnote 112 (11.4.5.3 [class.copy.ctor] 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, 11.4.5.3 [class.copy.ctor] 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 (11.8 [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 (August, 2011):
Change 6.3 [basic.def.odr] paragraph 2 as follows:
...[Note: This covers calls to named functions (7.6.1.3 [expr.call]), operator overloading (Clause 12 [over]), user-defined conversions (11.4.8.3 [class.conv.fct]), allocation function for placement new (7.6.2.8 [expr.new]), as well as non-default initialization (9.5 [dcl.init]). Acopy constructor or moveconstructor selected to copy or move an object of class type is odr-used even if the call is actually elided by the implementation (11.4.5.3 [class.copy.ctor]). —end note] ...A copy-assignment function for a classAn assignment operator function in a class is odr-used by an implicitly-defined copy-assignment or move-assignment function for another class as specified in 11.4.5.3 [class.copy.ctor].A move-assignment function for a class is odr-used by an implicitly-defined move-assignment function for another class as specified in 11.4.5.3 [class.copy.ctor].A default constructor...
Delete 11.4.5 [class.ctor] paragraph 9:
A copy constructor (11.4.5.3 [class.copy.ctor]) is used to copy objects of class type. A move constructor (11.4.5.3 [class.copy.ctor]) is used to move the contents of objects of class type.
Change 6.7.7 [class.temporary] paragraph 1 as follows:
...[Note:even if there is no call to the destructor or copy/move constructor, all the semantic restrictions, such as accessibility (11.8 [class.access]) and whether the function is deleted (9.6.3 [dcl.fct.def.delete]), shall be satisfied.this includes accessibility (11.8 [class.access]) and whether it is deleted, for the constructor selected and for the destructor. However, in the special case of a function call...
Change 11.4.5.3 [class.copy.ctor] paragraph 13 as follows:
A copy/move constructor that is defaulted and not defined as deleted is implicitly defined if it is odr-used (6.3 [basic.def.odr])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 [Footnote: See 9.5 [dcl.init] for more details on direct and copy initialization. —end footnote]or when it is explicitly defaulted...
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if thecopy/moveconstructor selected for the copy/move operation and/or the destructor for the object have side effects...
Change 12.2.4.2.3 [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 acopy/moveconstructor (i.e., a user-defined conversion function) is called for those cases.
Change 14.2 [except.throw] paragraph 3 as follows:
A throw-expression copy-initializes (9.5 [dcl.init]) a temporary object, called the exception object...
Change 14.2 [except.throw] paragraph 5 as follows:
When the thrown object is a class object, thecopy/moveconstructor selected for the copy-initialization and the destructor shall be accessible, even if the copy/move operation is elided (11.4.5.3 [class.copy.ctor]).
[Drafting note: 7.6.19 [expr.assign] paragraph 4, Clause 11 [class] paragraph 4, 11.5 [class.union] paragraph 1, 6.7.7 [class.temporary] paragraph 2, 11.4.5.3 [class.copy.ctor] paragraphs 1-2, and 14.5 [except.spec] paragraph 14 do not require any changes.]
[Moved to DR status at the April, 2013 meeting as paper N3667.]
Paragraphs 11 and 23 of 11.4.5.3 [class.copy.ctor] make a defaulted move constructor and assignment operator, respectively, deleted if there is a subobject with no corresponding move function and for which the copy operation is non-trivial. This seems excessive and unnecessary. For example:
template<typename T> struct wrap { wrap() = default; #ifdef USE_DEFAULTED_MOVE wrap(wrap&&) = default; #else wrap(wrap&& w) : t(static_cast<T&&>(w.t)) { } #endif wrap(const wrap&) = default; T t; }; struct S { S(){} S(const S&){} S(S&&){} }; typedef wrap<const S> W; W get() { return W(); } // Error, if USE_DEFAULTED_MOVE is defined, else OK
In this example the defaulted move constructor of wrap is selected by overload resolution, but this move-constructor is deleted, because S has no trivial copy-constructor.
I think that we overshoot here with the delete rules: I see no problem for the defaulted move-constructor in this example. Our triviality-deduction rules already cover this case (11.4.5.3 [class.copy.ctor] paragraph 12: W::W(W&&) is not trivial) and our exception-specification rules (14.5 [except.spec] paragraph 14) already correctly deduce a noexcept(false) specification for W::W(W&&).
It would still be OK to prevent that a move-constructor would be generated for the following example where no user-declared defaulted copy/move members are present:
template<typename T>
struct wrap_2
{
wrap_2() = default;
T t;
};
typedef wrap_2<const S> W2;
W2 get() { return W2(); } // OK, selects copy constructor
if we want. This would mean that we add a new bullet to 11.4.5.3 [class.copy.ctor] paragraph 9 and paragraph 20.
Proposed resolution (February, 2012):
Change 11.4.5.3 [class.copy.ctor] paragraph 9 as follows:
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
...
X does not have a user-declared destructor,
andthe move constructor would not be implicitly defined as deleted
., andeach of X's non-static data members and direct or virtual base classes has a type that either has a move constructor or is trivially copyable.
[Note:...
Change 11.4.5.3 [class.copy.ctor] paragraph 11 as follows:
An implicitly-declared copy/move constructor is an inline public member of its class. A defaulted copy/move constructor for a class X is defined as deleted (9.6.3 [dcl.fct.def.delete]) if X has:
...
any direct or virtual base class or non-static data member of a type with a destructor that is deleted or inaccessible from the defaulted constructor, or
for the copy constructor, a non-static data member of rvalue reference type.
, or
for the move constructor, a non-static data member or direct or virtual base class with a type that does not have a move constructor and is not trivially copyable.
Change 11.4.5.3 [class.copy.ctor] paragraph 20 as follows:
If the definition of a class X does not explicitly declare a move assignment operator, one will be implicitly declared as defaulted if and only if
...
X does not have a user-declared destructor,
andthe move assignment operator would not be implicitly defined as deleted
.,X has no direct or indirect virtual base class with a non-trivial move assignment operator, and
each of X's non-static data members and direct or virtual base classes has a type that either has a move assignment operator or is trivially copyable.
Example:...
Change 11.4.5.3 [class.copy.ctor] paragraph 23 as follows:
A defaulted copy/move assignment operator for class X is defined as deleted if X has:
...
a direct or virtual base class B that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to B's corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator.
, or
for the move assignment operator, a non-static data member or direct base class with a type that does not have a move assignment operator and is not trivially copyable, or any direct or indirect virtual base class.
Additional notes (August, 2012):
The proposed resolution was extensively discussed and additional alternatives were suggested. A paper is being produced for the October, 2012 meeting describing the various options, so the issue has been returned to "review" status to wait for the outcome of that deliberation.
See also the discussion of issue 1491 for additional considerations.
Proposed resolution (December, 2012):
Change 11.4.5.3 [class.copy.ctor] paragraph 9 as follows:
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
X does not have a user-declared copy constructor,
X does not have a user-declared copy assignment operator,
X does not have a user-declared move assignment operator, and
X does not have a user-declared destructor.
, and
the move constructor would not be implicitly defined as deleted.[Note:...
Change 11.4.5.3 [class.copy.ctor] paragraph 11 as follows:
...A defaulted copy/move constructor for a class X is defined as deleted (9.6.3 [dcl.fct.def.delete]) if X has:
a variant member with a non-trivial corresponding constructor and X is a union-like class,
a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to M's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,
a direct or virtual base class B that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to B's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,
any direct or virtual base class or non-static data member of a type with a destructor that is deleted or inaccessible from the defaulted constructor, or
for the copy constructor, a non-static data member of rvalue reference type.
, or
for the move constructor, a non-static data member or direct or virtual base class with a type that does not have a move constructor and is not trivially copyable.A defaulted move constructor that is defined as deleted is ignored by overload resolution (12.2 [over.match]). [Note: A deleted move constructor would otherwise interfere with initialization from an rvalue which can use the copy constructor instead. —end note]
Change 11.4.5.3 [class.copy.ctor] paragraph 20 as follows:
If the definition of a class X does not explicitly declare a move assignment operator, one will be implicitly declared as defaulted if and only if
X does not have a user-declared copy constructor,
X does not have a user-declared move constructor,
X does not have a user-declared copy assignment operator, and
X does not have a user-declared destructor.
, and
the move assignment operator would not be implicitly defined as deleted.[Example:...
Change 11.4.5.3 [class.copy.ctor] paragraph 23 as follows:
A defaulted copy/move assignment operator for class X is defined as deleted if X has:
a variant member with a non-trivial corresponding assignment operator and X is a union-like class, or
a non-static data member of const non-class type (or array thereof), or
a non-static data member of reference type, or
a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to M's corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator, or
a direct or virtual base class B that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to B's corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator.
, or
for the move assignment operator, a non-static data member or direct base class with a type that does not have a move assignment operator and is not trivially copyable, or any direct or indirect virtual base class.A defaulted move assignment operator that is defined as deleted is ignored by overload resolution (12.2 [over.match], 12.3 [over.over]).
This resolution also resolves issue 1491.
According to 11.4.5.3 [class.copy.ctor] paragraph 11, the last bullet, a defaulted move constructor for a class is defined as deleted if
a non-static data member or direct or virtual base class with a type that does not have a move constructor and is not trivially copyable.
This means that an example like
struct S { S(); int &&r; } s{S()};
is ill-formed. This is probably not intended.
(Note that the February, 2012 proposed resolution for issue 1402 also makes this example ill-formed, because the move constructor is not declared and the copy constructor is defined as deleted because of the rvalue-reference member.)
Additional note, February, 2014:
This issue is resolved by the resolution of issue 1402, which removed the problematic sentence.
Note that destructors suffer from similar problems as those of constructors dealt with in issue 194 and in 263 (constructors as friends). Also, the wording in 11.4.7 [class.dtor], paragraph 1 does not permit a destructor to be defined outside of the memberlist.
Change 11.4.7 [class.dtor], paragraph 1 from
...A special declarator syntax using an optional function-specifier (9.2.3 [dcl.fct.spec]) followed by ~ followed by the destructor's class name followed by an empty parameter list is used to declare the destructor in a class definition. In such a declaration, the ~ followed by the destructor's class name can be enclosed in optional parentheses; such parentheses are ignored....
to
...A special declarator syntax using an optional sequence of function-specifiers (9.2.3 [dcl.fct.spec]), an optional friend keyword, an optional sequence of function-specifiers (9.2.3 [dcl.fct.spec]) followed by an optional :: scope-resolution-operator followed by an optional nested-name-specifier followed by ~ followed by the destructor's class name followed by an empty parameter list is used to declare the destructor. The optional nested-name-specifier shall not be specified in the declaration of a destructor within the member-list of the class of which the destructor is a member. In such a declaration, the optional :: scope-resolution-operator followed by an optional nested-name-specifier followed by ~ followed by the destructor's class name can be enclosed in optional parentheses; such parentheses are ignored....
Proposed resolution:
This issue is resolved by the resolution of issue 1435.
[Moved to DR at the April, 2013 meeting.]
According to 11.4.7 [class.dtor] paragraph 13,
The invocation of a destructor is subject to the usual rules for member functions (11.4.2 [class.mfct]), that is, if the object is not of the destructor's class type and not of a class derived from the destructor's class type, the program has undefined behavior (except that invoking delete on a null pointer has no effect).
While true, the final parenthetical comment concerns a delete-expression, not an explicit destructor call. Its presence here could mislead a careless reader to think that invoking a destructor with a null pointer is harmless. It should be deleted.
Proposed resolution (February, 2013):
Change 11.4.7 [class.dtor] paragraph 13 as follows:
In an explicit destructor call, the destructor name appears as a ~ followed by a type-name or decltype-specifier that denotes the destructor's class type. The invocation of a destructor is subject to the usual rules for member functions (11.4.2 [class.mfct]),; that is, if the object is not of the destructor's class type and not of a class derived from the destructor's class type (including when the destructor is invoked via a null pointer value), the program has undefined behavior(except that invoking delete on a null pointer has no effect). [Note: invoking delete on a null pointer does not call the destructor; see 7.6.2.9 [expr.delete]. —end note] [Example:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Although the normative wording of 11.4.8.2 [class.conv.ctor] paragraph 1 defining a converting constructor says
A constructor declared without the function-specifier explicit specifies a conversion from the types of its parameters to the type of its class.
implying that a constructor with multiple parameters can be a converting constructor, it would be helpful if the example contained such a constructor.
Proposed resolution (August, 2011):
Change the example in 11.4.8.2 [class.conv.ctor] paragraph 1 as follows:
struct X { X(int); X(const char*, int =0); X(int, int); }; void f(X arg) { X a = 1; // a = X(1) X b = "Jessie"; // b = X("Jessie",0) a = 2; // a = X(2) f(3); // f(X(3)) f({1, 2}); // f(X(1,2)) }
Change the example in 11.4.8.2 [class.conv.ctor] paragraph 2 as follows:
struct Z { explicit Z(); explicit Z(int); explicit Z(int, int); }; Z a; // OK: default-initialization performed Z a1 = 1; // error: no implicit conversion Z a3 = Z(1); // OK: direct initialization syntax used Z a2(1); // OK: direct initialization syntax used Z* p = new Z(1); // OK: direct initialization syntax used Z a4 = (Z)1; // OK: explicit cast used Z a5 = static_cast<Z>(1); // OK: explicit cast used Z a6 = { 3, 4 }; // error: no implicit conversion
[Moved to DR at the October, 2012 meeting.]
Is the signedness of x in the following example implementation-defined?
template <typename T> struct A { T x : 7; }; template struct A<long>;
A similar example could be created with a typedef.
Lawrence Crowl: According to 11.4.10 [class.bit] paragraph 3,
It is implementation-defined whether a plain (neither explicitly signed nor unsigned) char, short, int or long bit-field is signed or unsigned.
This clause is conspicuously silent on typedefs and template parameters.
Clark Nelson: At least in C, the intention is that the presence or absence of this redundant keyword is supposed to be remembered through typedef declarations. I don't remember discussing it in C++, but I would certainly hope that we don't want to do something different. And presumably, we would want template type parameters to work the same way.
So going back to the original example, in an instantiation of A<long>, the signedness of the bit-field is implementation-defined, but in an instantiation of A<signed long>, the bit-field is definitely signed.
Peter Dimov: How can this work? Aren't A<long> and A<signed long> the same type?
(See also issue 739.)Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 739.[Moved to DR at the October, 2012 meeting.]
11.4.10 [class.bit] paragraph 3 says,
It is implementation-defined whether a plain (neither explicitly signed nor unsigned) char, short, int or long bit-field is signed or unsigned.
The implications of this permission for an implementation that chooses to treat plain bit-fields as unsigned are not clear. Does this mean that the type of such a bit-field is adjusted to the unsigned variant or simply that sign-extension is not performed when the value is fetched? C99 is explicit in specifying the former (6.7.2 paragraph 5: “for bit-fields, it is implementation-defined whether the specifier int designates the same type as signed int or the same type as unsigned int”), while C90 takes the latter approach (6.5.2.1: “Whether the high-order bit position of a (possibly qualified) 'plain' int bit-field is treated as a sign bit is implementation-defined”).
(See also issue 675 and issue 741.)Additional note, May, 2009:
As an example of the implications of this question, consider the following declaration:
struct S { int i: 2; signed int si: 2; unsigned int ui: 2; } s;
Is it implementation-defined which expression, cond?s.i:s.si or cond?s.i:s.ui, is an lvalue (the lvalueness of the result depends on the second and third operands having the same type, per 7.6.16 [expr.cond] paragraph 4)?
Proposed resolution (August, 2011):
Change 11.4.10 [class.bit] paragraph 3 as follows:
A bit-field shall not be a static member. A bit-field shall have integral or enumeration type (6.8.2 [basic.fundamental]).
It is implementation-defined whether a plain (neither explicitly signed nor unsigned) char, short, int, long, or long long bit-field is signed or unsigned.For a bit-field with a non-dependent type (13.8.3.2 [temp.dep.type]) that is specified to be plain (neither explicitly signed nor unsigned) short, int, long, or long long or a typename-name that is so defined (possibly through multiple levels of typedefs), it is implementation-defined whether the type of the bit-field is the corresponding signed or unsigned type. [Example:struct B { long x : 3; typedef signed int si; si y : 1; typedef int i; i z : 1; }; template<class T> struct A { T x : 7; };It is implementation-defined whether B::x has type signed long or unsigned long. B::y has type signed int. It is implementation-defined whether B::z has type signed int or unsigned int. A<int>::x and A<signed int>::x designate the same entity of type signed int. A<unsigned int>::x has type unsigned int. —end example]
A bool value...
This resolution also resolves issue 675.
Note, January, 2012:
Additional questions have been raised about the proposed resolution, so the status was returned to "review" to allow further discussion.
Proposed resolution (February, 2012):
Change 11.4.10 [class.bit] paragraph 3 as follows:
A bit-field shall not be a static member. A bit-field shall have integral or enumeration type (6.8.2 [basic.fundamental]).It is implementation-defined whether a plain (neither explicitly signed nor unsigned) char, short, int, long, or long long bit-field is signed or unsigned.A bool value can successfully be stored...
Add the following as a new section in C.7.7 [diff.class]:
11.4.10 [class.bit]
Change: Bit-fields of type plain int are signed.
Rationale: Leaving the choice of signedness to implementations could lead to inconsistent definitions of template specializations. For consistency, the implementation freedom was eliminated for non-dependent types, too.
Effect on original feature: The choice is implementation-defined in C, but not so in C++.
Difficulty of converting: Syntactic transformation.
How widely used: Seldom.
This resolution also resolves issue 675.
[Moved to DR at the October, 2012 meeting.]
According to 11.5 [class.union] paragraph 7,
A union for which objects or pointers are declared is not an anonymous union.
This should also apply to references, which are now possible because decltype allows writing an initializer for an unnamed union:
char buf[100]; union { int i; } &r = (decltype(r)) buf;
Proposed resolution (February, 2012):
Change 11.5 [class.union] paragraph 7:
A union for which objects,
orpointers, or references are declared is not an anonymous union. [Example:void f() { union { int aa; char* p; } obj, *ptr = &obj; aa = 1; // error ptr->aa = 1; // OK }
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to 11.7.3 [class.virtual] paragraph 8,
If the return type of D::f differs from the return type of B::f, the class type in the return type of D::f shall be complete at the point of declaration of D::f or shall be the class type D.
This provision was intended to deal with covariant return types but inadvertently affects types that vary only in cv-qualification:
struct A; struct B { virtual const A* f(); }; struct D : B { A* f(); // ill-formed };
Proposed resolution (August, 2011):
Change 11.7.3 [class.virtual] paragraph 8 as follows:
If the class type in the covariant return type of D::f differs fromthe return typethat of B::f, the class type in the return type of D::f shall be complete at the point of declaration of D::f or shall be the class type D. When the overriding function...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
11.9.3 [class.base.init] paragraph 8 appears to indicate that a class member that is an anonymous union is to be default initilized.
Proposed resolution (August, 2011):
Change the indicated bullet of 11.9.3 [class.base.init] paragraph 8 as follows:
[Moved to DR at the October, 2012 meeting.]
In 12.2.2.2.3 [over.call.object] paragraph 2, the non-explicit conversion functions considered for producing surrogate call functions are those of the form
This (presumably inadvertently) excludes conversion functions with a ref-qualifier and/or an exception-specification.
Proposed resolution (February, 2012):
Change 12.2.2.2.3 [over.call.object] paragraph 2 as follows:
In addition, for each non-explicit conversion function declared in T of the form
operator conversion-type-id ()
attribute-specifier-seqoptcv-qualifierref-qualifieropt exception-specificationopt attribute-specifier-seqopt ;
where cv-qualifier is the same...
[Moved to DR at the April, 2013 meeting.]
Issue 899 added wording to the second bullet of 12.2.2.5 [over.match.copy] paragraph 1 permitting use of explicit conversion functions when calling a copy constructor in a direct-initialization context. Issue 1087 addressed the problem in the earlier resolution that the phrase “copy constructor” did not include move constructors and template constructors. However, the term “copy constructor” implicitly (and correctly) restricted the constructor parameter to be the constructor's class, i.e., the class of the object being directly initialized. The new phrasing, “a constructor that takes a reference to possibly cv-qualified T as its first argument,” incorrectly assumed that T referred to the type of the object being initialized; however, that is not the case, and a converting constructor from T to the type of the object being initialized is inadvertently permitted. The wording needs a further tweak to restore the intended context.
Proposed resolution (October, 2012):
Change the second bullet of 12.2.2.5 [over.match.copy] paragraph 1 as follows:
...Assuming that “cv1 T” is the type of the object being initialized, with T a class type, the candidate functions are selected as follows:
The converting constructors (11.4.8.2 [class.conv.ctor]) of T are candidate functions.
When the type of the initializer expression is a class type “cv S”, the non-explicit conversion functions of S and its base classes are considered. When initializing a temporary to be bound to the first parameter of a constructor that takes a reference to possibly cv-qualified T as its first argument, called with a single argument in the context of direct-initialization of an object of type “cv2 T”, explicit conversion functions are also considered. Those that are not hidden within S and yield a type whose cv-unqualified version is the same type as T or is a derived class thereof are candidate functions. Conversion functions that return “reference to X” return lvalues or xvalues, depending on the type of reference, of type X and are therefore considered to yield X for this process of selecting candidate functions.
[Moved to DR at the October, 2012 meeting.]
In 12.2.2.6 [over.match.conv], dealing with non-reference initialization, direct initialization considers as candidate functions only those that
yield type T or a type that can be converted to type T with a qualification conversion
By contrast, 12.2.2.7 [over.match.ref], dealing with reference binding, requires only that the type returned be reference-compatible with the target, permitting both qualification conversions and derived-to-base conversions. This discrepancy is presumably unintentional.
Proposed resolution (February, 2012):
Change 12.2.2.7 [over.match.ref] paragraph 1 as follows:
...the candidate functions are selected as follows:
The conversion functions of S and its base classes
are considered, except that for copy-initialization, only the
non-explicit conversion functions are considered. Those
non-explicit conversion functions that are not hidden
within S and yield type “lvalue reference to
cv2 T2” (when 9.5.4 [dcl.init.ref]
requires an lvalue result) or “cv2 T2” or
“rvalue reference to cv2 T2” (when
9.5.4 [dcl.init.ref] requires an rvalue result), where
“cv1 T” is reference-compatible
(9.5.4 [dcl.init.ref]) with “cv2
T2”, are candidate functions. For
direct-initialization, those explicit conversion functions that are not
hidden within S and yield type “lvalue reference to
cv2 T2,” or “cv2 T2” or
“rvalue reference to cv2 T2,”
respectively, where T2 is the same type as T or can
be converted to type T with a qualification conversion
(7.3.6 [conv.qual]), are also candidate
functions.
[Moved to DR at the October, 2012 meeting.]
Both paragraphs 3 and 4 (for non-aggregate and aggregate types, respectively) of 12.2.4.2.6 [over.ics.list] say that the implicit conversion sequence is a user-defined conversion sequence, but neither specifies that the second standard conversion sequence is the identity conversion, as is presumably intended. This makes ranking by 12.2.4.3 [over.ics.rank] bullet 3.2 unncessarily unclear.
Proposed resolution (February, 2012):
Change 12.2.4.2.6 [over.ics.list] paragraphs 3-4 as follows:
Otherwise, if the parameter is a non-aggregate class X and overload resolution per 12.2.2.8 [over.match.list] chooses a single best constructor of X to perform the initialization of an object of type X from the argument initializer list, the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion sequence an identity conversion. If multiple constructors are viable but none is better than the others, the implicit conversion sequence is the ambiguous conversion sequence...
Otherwise, if the parameter has an aggregate type which can be initialized from the initializer list according to the rules for aggregate initialization (9.5.2 [dcl.init.aggr]), the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion sequence an identity conversion. [Example:...
[Moved to DR at the April, 2013 meeting.]
According to 12.2.4.2.6 [over.ics.list] paragraph 6, when passing an initializer-list argument to a non-class parameter,
if the initializer list has no elements, the implicit conversion sequence is the identity conversion.
However, there is no similar provision for an empty initializer list passed to a specialization of std::initializer_list or an array, as described in paragraph 2:
If the parameter type is std::initializer_list<X> or “array of X”134 and all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X.
It is not clear what the result should be for a list with no elements. For example, given
void f(int) { printf("int\n"); } void f(std::initializer_list<int>) { printf("init list\n"); } int main() { f({}); }
current implementations result in init list being printed, presumably on the basis of the last bullet of 12.2.4.3 [over.ics.rank] paragraph 3:
List-initialization sequence L1 is a better conversion sequence than list-initialization sequence L2 if L1 converts to std::initializer_list<X> for some X\and L2 does not.
That would imply that both conversion sequences are the identity conversion, which is reasonable, but it should be stated clearly if that is the intent.
Proposed resolution (October, 2012):
Change 12.2.4.2.6 [over.ics.list] paragraph 2 as follows:
If the parameter type is std::initializer_list<X> or “array of X”134 and all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X, or if the initializer list has no elements, the identity conversion. This conversion can be a user-defined conversion even in the context of a call to an initializer-list constructor. [Example:
void f(std::initializer_list<int>); f( {} ); // OK: f(initializer_list<int>) identity conversion f( {1,2,3} ); // OK: f(initializer_list<int>) identity conversion f( {'a','b'} ); // OK: f(initializer_list<int>) integral promotion f( {1.0} ); // error: narrowing ...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The following example appears in 12.2.4.3 [over.ics.rank] paragraph 3:
template<class T> int f(T&);
template<class T> int f(T&&);
void g();
int i1 = f(g); // calls f(T&)
This is not correct. Because of the special deduction rule for rvalue reference parameters in 13.10.3.2 [temp.deduct.call] paragraph 3 and the reference-collapsing rules of 9.3.4.3 [dcl.ref] paragraph 6, the parameter type for both will be void(&)().
Proposed resolution (August, 2011):
Change the example in 12.2.4.3 [over.ics.rank] paragraph 3 bullet 1 sub-bullet 5 as follows:
template<class T> int f(T&); template<class T> int f(T&&);int f(void(&)()); // #1 int f(void(&&)()); // #2 void g(); int i1 = f(g); // callsf(T&)#1
[Moved to DR at the April, 2013 meeting.]
The rule in 12.2.4.3 [over.ics.rank] paragraph 3 for ranking based on a difference in qualification conversion applies only if they "differ only in their qualification conversion".
It is unclear as to whether the property of being a reference binding is a factor in determining if there is a difference between conversion sequences. Notice that 12.2.4.2.5 [over.ics.ref] maps reference bindings to other forms of implicit conversion sequences, but does not state that the property of being a reference binding is preserved; however, 12.2.4.3 [over.ics.rank] has cases which depend on whether certain standard conversion sequences are reference bindings or not and on the specifics of the bindings.
In the following, picking T2 && would bind an rvalue to an rvalue reference. Picking T1 & would bind an rvalue to an lvalue reference, but the qualification conversion to T1 is "better". Which is better?
typedef int * * *const *const T1; typedef int *const *const *const *const T2; void foo(T1 &); void foo(T2 &&) { } int main() { foo((int ****)0); return 0; }
Notes from the February, 2012 meeting:
The CWG agreed that bullets 3 and 4 should be reversed, to check the reference binding first and then for qualification conversion.
Proposed resolution (February, 2012):
Move 12.2.4.3 [over.ics.rank] paragraph 3, first bullet, third sub-bullet, after the current fifth sub-bullet, as follows:
Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of the following rules applies:
Standard conversion sequence S1 is a better conversion sequence...
S1 is a proper subsequence of S2...
the rank of S1 is better...
S1 and S2 differ only in their qualification conversion... —end example] or if not that,S1 and S2 are reference bindings (9.5.4 [dcl.init.ref]) and neither refers... or if not that,
S1 and S2 are reference bindings (9.5.4 [dcl.init.ref]) and S1 binds... —end example] or if not that,
S1 and S2 differ only in their qualification conversion... —end example] or if not that,
S1 and S2 are reference bindings (9.5.4 [dcl.init.ref]), and the types to which the references refer...
- User-defined conversion sequence U1...
[Moved to DR at the October, 2012 meeting.]
Bullet 2 of 12.2.4.3 [over.ics.rank] paragraph 3 reads,
User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 if they contain the same user-defined conversion function or constructor or aggregate initialization and the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2.
It is not clear what “the same aggregate initialization” means — does this require that the same aggregate type is the target type?
Proposed resolution (February, 2012):
Change 12.2.4.3 [over.ics.rank] bullet 3.2 as follows:
Standard conversion sequence S1 is a better conversion sequence...
User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 if they contain the same user-defined conversion function or constructor or they initialize the same class in an aggregate initialization and in either case the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2. [Example:...
[Moved to DR at the October, 2012 meeting.]
In bullet 3 of paragraph 4 of 12.2.4.3 [over.ics.rank] are two sub-bullets dealing with overload tiebreakers:
binding of an expression of type C to a reference of type B& is better than binding an expression of type C to a reference of type A&,
...
binding of an expression of type B to a reference of type A& is better than binding an expression of type C to a reference of type A&,
Presumably both of these tiebreakers should apply to rvalue references as well as lvalue references.
Proposed resolution (February, 2012):
Change 12.2.4.3 [over.ics.rank] bullet 4.3 as follows:
If class B is derived directly or indirectly from class A and class C is derived directly or indirectly from B,
conversion of C* to B* is better...
binding of an expression of type C to a reference
of to type B& is better
than binding an expression of type C to a reference of
to type A&,
...
binding of an expression of type B to a reference
of to type A& is better
than binding an expression of type C to a reference
of to type A&,
...
[Moved to DR at the April, 2013 meeting.]
Presumably, 12.3 [over.over] paragraph 1,
A use of an overloaded function name without arguments is resolved in certain contexts to a function, a pointer to function or a pointer to member function for a specific function from the overload set. A function template name is considered to name a set of overloaded functions in such contexts. The function selected is the one whose type is identical to the function type of the target type required in the context. [Note: That is, the class of which the function is a member is ignored when matching a pointer-to-member-function type. —end note] The target can be
an object or reference being initialized (9.5 [dcl.init], 9.5.4 [dcl.init.ref]),
...
should apply to an example like
double bar(double) { return 0.0; } float bar(float) { return 0.0f; } using fun = double(double); fun &foo{bar};
However, there is implementation variance in whether the use of bar is accepted or not, and the omission of a cross-reference to 9.5.5 [dcl.init.list] might be read as indicating that list-initialization is not included.
Proposed resolution (February, 2013):
Change 12.3 [over.over] paragraph 1 as follows:
...The target can be
an object or reference being initialized (9.5 [dcl.init], 9.5.4 [dcl.init.ref], 9.5.5 [dcl.init.list]),
...
[Moved to DR at the April, 2013 meeting.]
The phrase in 12.4.7 [over.inc] paragraph 1,
a non-member function with one parameter of class or enumeration type
inadvertently excludes reference parameters, and in fact, no mention of the type is necessary in light of 12.4 [over.oper] paragraph 6:
An operator function shall either be a non-static member function or be a non-member function and have at least one parameter whose type is a class, a reference to a class, an enumeration, or a reference to an enumeration.
Proposed resolution (August, 2012):
Change 12.4.7 [over.inc] paragraph 1 as follows:
The user-defined function called operator++ implements the prefix and postfix ++ operator. If this function is a member function with no parameters, or a non-member function with one parameterof class or enumeration type, it defines the prefix increment operator ++ for objects of that type. If the function...
[Moved to DR at the April, 2013 meeting.]
The current grammar requires that there be no whitespace between the literal and the ud-suffix, e.g., ""_abc, but that there be whitespace between the "" and the identifier in a literal-operator-id, e.g., operator "" _abc. This seems unfortunate. Would it be possible to provide an alternate production,
with the requirement that the string-literal be empty?
The current wording is also unclear regarding interactions with phases of translation. We have the following production:
As this is after translation phase 6, would something like
operator "" "" _foo
be accepted?
Proposed resolution (October, 2012):
Change 12.6 [over.literal] as follows:
Change 12.6 [over.literal] paragraph 1 as follows:
The string-literal or user-defined-string-literal in a literal-operator-id shall have no encoding-prefix and shall contain no characters other than the implicit terminating '\0'. The ud-suffix of the user-defined-string-literal or the identifier in a literal-operator-id is called a literal suffix identifier. [Note: some literal suffix identifiers are reserved for future standardization; see 16.4.5.3.6 [usrlit.suffix]. —end note]
Change the example in 12.6 [over.literal] paragraph 8 as follows:
void operator "" _km(long double); // OK string operator "" _i18n(const char*, std::size_t); // OK template <char...> int operator "" \u03C0(); // OK: UCN for lowercase pi float operator ""E(const char*); //error: ""E (with no intervening space) // is a single tokenOK float operator " " B(const char*); // error: non-adjacent quotesempty string-literal string operator "" 5X(const char*, std::size_t); // error: invalid literal suffix identifier double operator "" _miles(double); // error: invalid parameter-declaration-clause template <char...> int operator "" _j(const char*); // error: invalid parameter-declaration-clause
[Moved to DR at the April, 2013 meeting.]
It appears that an example like
int operator"" _a (const char *, std::size_t = 0); int operator"" _a (const char *); int i = 123_a;
is ambiguous: although only the second declaration is a raw literal operator, the corresponding call
operator"" _a ("123")
could match either declaration. Should default arguments for literal operators be prohibited?
Proposed resolution (October, 2012):
Change 12.6 [over.literal] paragraph 3 as follows:
The declaration of a literal operator shall have a parameter-declaration-clause equivalent to one of the following:
...
If a parameter has a default argument (9.3.4.7 [dcl.fct.default]), the program is ill-formed.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The following example from 13.2 [temp.param] paragraph 11 is incorrect:
// U cannot be deduced or specified template<class... T, class... U> void f() { } template<class... T, class U> void g() { }
In fact, U can be deduced to an empty sequence by 13.10.2 [temp.arg.explicit] paragraph 3:
A trailing template parameter pack (13.7.4 [temp.variadic]) not otherwise deduced will be deduced to an empty sequence of template arguments.
Proposed resolution (August, 2011):
Change 13.2 [temp.param] paragraph 11 as follows:
...A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list of the function template or has a default argument (13.10.3 [temp.deduct]). [Example:
template<class T1 = int, class T2> class B; // error // U cannot be deduced from the parameter-type-list or specified template<class... T, class... U> void f() { } // error template<class... T, class U> void g() { } // error—end example]
[Moved to DR at the October, 2012 meeting.]
Although 13.2 [temp.param] paragraph 4 explicitly allows non-type template parameters of type std::nullptr_t, they cannot actually be used: 13.4.3 [temp.arg.nontype] paragraph 1 does not allow a template-argument of type std::nullptr_t, and paragraph 5 does not describe any conversions from other types to std::nullptr_t.
Proposed resolution (February, 2012):
Add the following new bullet in 13.4.3 [temp.arg.nontype] paragraph 1:
...
a pointer to member expressed as described in 7.6.2.2 [expr.unary.op].
an address constant expression of type std::nullptr_t.
[Moved to DR at the April, 2013 meeting.]
The list of pack expansion contexts in 13.7.4 [temp.variadic] paragraph 4 includes a mem-initializer-list, with no restriction on whether the mem-initializer corresponds to a base class or a member. However, it appears from 11.9.3 [class.base.init] paragraph 15 that such a pack expansion is intended for bases:
A mem-initializer followed by an ellipsis is a pack expansion (13.7.4 [temp.variadic]) that initializes the base classes specified by a pack expansion in the base-specifier-list for the class.
This is not conclusive, however, and use of a pack expansion with a mem-initializer for a member could be used with packs containing zero or one element:
class S { }; template<typename... T> class X { public: X(T ...args) : data_(args)... { } private: S data_; }; int main() { S s; X<> x1; X<S> x2(s); }
The Standard should be clarified as to whether such a usage is permitted or not.
Proposed resolution (October, 2012):
Change 13.7.4 [temp.variadic] paragraph 4 as follows:
...Pack expansions can occur in the following contexts:
...
In a mem-initializer-list (11.9.3 [class.base.init]) for a mem-initializer whose mem-initializer-id denotes a base class; the pattern is
athe mem-initializer....
[Moved to DR at the April, 2013 meeting.]
Consider an example like
template <int B, typename Type1, typename... Types> struct A; template<typename... Types> struct A<0, Types...> { }; A<0,int,int> t;
In this case, the partial specialization seems well-formed by the rules in 13.7.6 [temp.spec.partial], but it is not more specialized than the primary template. However, 13.7.6.2 [temp.spec.partial.match] says that if exactly one matching specialization is found, it is used, which suggests that the testcase is well-formed. That seems undesirable; I think a partial specialization that is not more specialized than the primary template should be ill-formed.
If the example is rewritten so that both versions are partial specializations, i.e.,
template <int B, typename... Types> struct A; template <int B, typename Type1, typename... Types> struct A<B, Type1, Types...> { } template<typename... Types> struct A<0, Types...> { }; A<0,int,int> t;
There is implementation variance, with gcc and clang reporting an ambiguity and EDG choosing the second specialization.
Proposed resolution (October, 2012):
Add the following as a new bullet in 13.7.6.1 [temp.spec.partial.general] paragraph 9:
...
The argument list of the specialization shall not be identical to the implicit argument list of the primary template.
The specialization shall be more specialized than the primary template (13.7.6.3 [temp.spec.partial.order]).
...
The example in 13.7.6.2 [temp.spec.partial.match] paragraph 2 reads,
A<int, int, 1> a1; // uses #1 A<int, int*, 1> a2; // uses #2, T is int, I is 1 A<int, char*, 5> a3; // uses #4, T is char A<int, char*, 1> a4; // uses #5, T1 is int, T2 is char, I is 1 A<int*, int*, 2> a5; // ambiguous: matches #3 and #5
However, the referenced numbers are defined two pages earlier, in 13.7.6.1 [temp.spec.partial.general] paragraph 3. This is confusing and should be changed.
Notes from the May, 2015 meeting:
This will be handled editorially; the status has been set to "review" to check that the editorial change has been made.
[Moved to DR at the October, 2012 meeting.]
Consider the following example:
int g(int); template <class T> decltype(g(T())) f(); int g(); template <class T> decltype(g(T())) f() { return g(T()); } int i = f<int>();
Do the two fs declare the same function template? According to 13.7.7.2 [temp.over.link] paragraph 5,
Two expressions involving template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one definition rule (6.3 [basic.def.odr]), except that the tokens used to name the template parameters may differ as long as a token used to name a template parameter in one expression is replaced by another token that names the same template parameter in the other expression.
The relevant portion of 6.3 [basic.def.odr] paragraph 5 says,
in each definition of D, corresponding names, looked up according to 6.5 [basic.lookup], shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution (12.2 [over.match]) and after matching of partial template specialization (13.10.4 [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 (7.7 [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
This could be read either way, since overload resolution isn't done at this point. Either we consider the result of the unqualified name lookup and say that the expressions aren't equivalent or we need a new rule for equivalence and merging of dependent calls.
Proposed resolution (December, 2011):
Change 13.7.7.2 [temp.over.link] paragraph 5 as follows:
Two expressions involving template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one definition rule (6.3 [basic.def.odr]), except that the tokens used to name the template parameters may differ as long as a token used to name a template parameter in one expression is replaced by another token that names the same template parameter in the other expression. For determining whether two dependent names (13.8.3 [temp.dep]) are equivalent, only the name itself is considered, not the result of name lookup in the context of the template. If multiple declarations of the same function template differ in the result of this name lookup, the result for the first declaration is used. [Example:
template <int I, int J> void f(A<I+J>); // #1 template <int K, int L> void f(A<K+L>); // same as #1 template <class T> decltype(g(T())) h(); int g(int); template <class T> decltype(g(T())) h() // redeclaration of h() uses the earlier lookup { return g(T()); } // ...although the lookup here does find g(int) int i = h<int>(); // template argument substitution fails; g(int) // was not in scope at the first declaration of h()—end example] Two expressions...
Change 13.8.3 [temp.dep] paragraph 1 as follows:
...In an expression of the form:
postfix-expression ( expression-listopt )
where the postfix-expression is an
id-expressionunqualified-id, theid-expressionunqualified-id denotes a dependent name if
any of the expressions in the expression-list is a pack expansion (13.7.4 [temp.variadic]),
any of the expressions in the expression-list is a type-dependent expression (13.8.3.3 [temp.dep.expr]), or
if the unqualified-id
of the id-expressionis a template-id in which any of the template arguments depends on a template parameter.if an operand...
Change 13.8.4.2 [temp.dep.candidate] paragraph 1 as follows:
For a function call
that depends on a template parameterwhere the postfix-expression is a dependent name, the candidate functions are found using the usual lookup rules (6.5.3 [basic.lookup.unqual], 6.5.4 [basic.lookup.argdep], 6.5.5 [basic.lookup.qual]) except that:
For the part of the lookup using unqualified name lookup (6.5.3 [basic.lookup.unqual])
or qualified name lookup (6.5.5 [basic.lookup.qual]), only function declarations from the template definition context are found.For the part of the lookup using associated namespaces (6.5.4 [basic.lookup.argdep]), only function declarations found in either the template definition context or the template instantiation context are found.
If
the function name is an unqualified-id andthe call would be ill-formed or would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced in those namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts, then the program has undefined behavior.
[Moved to DR at the October, 2012 meeting.]
In describing the partial ordering of function templates, 13.7.7.3 [temp.func.order] paragraph 3 says,
If only one of the function templates is a non-static member, that function template is considered to have a new first parameter inserted in its function parameter list. The new parameter is of type “reference to cv A,” where cv are the cv-qualifiers of the function template (if any) and A is the class of which the function template is a member. [Note: This allows a non-static member to be ordered with respect to a nonmember function and for the results to be equivalent to the ordering of two equivalent nonmembers. —end note]
The Standard appears to be silent as to whether the reference is an lvalue or rvalue reference; presumably that should be determined by the ref-qualifier of the member function, if any.
Proposed resolution (February, 2012):
Change 13.7.7.3 [temp.func.order] paragraph 3 as follows:
To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs (13.7.4 [temp.variadic]) thereof) synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template. If only one of the function templates is a non-static member of some class A, that function template is considered to have a new first parameter inserted in its function parameter list.TheGiven cv as the cv-qualifiers of the function template (if any), the new parameter is of type “rvalue reference to cv A,” if the optional ref-qualifier of the function template is &&, or of type “lvalue reference to cv A” otherwisewhere cv are the cv-qualifiers of the function template (if any) and A is the class of which the function template is a member. [Note:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to 13.8 [temp.res] paragraph 8,
Knowing which names are type names allows the syntax of every template definition to be checked. No diagnostic shall be issued for a template definition for which a valid specialization can be generated. If no valid specialization can be generated for a template definition, and that template is not instantiated, the template definition is ill-formed, no diagnostic required. If every valid specialization of a variadic template requires an empty template parameter pack, the template definition is ill-formed, no diagnostic required. If a type used in a non-dependent name...
It seems that these points could and should apply to template declarations that are not definitions, as well.
Proposed resolution (August, 2011):
Change 13.8 [temp.res] paragraph 8 as follows:
Knowing which names are type names allows the syntax of every templatedefinitionto be checked. No diagnostic shall be issued for a templatedefinitionfor which a valid specialization can be generated. If no valid specialization can be generated for a templatedefinition, and that template is not instantiated, the templatedefinitionis ill-formed, no diagnostic required. If every valid specialization of a variadic template requires an empty template parameter pack, the templatedefinitionis ill-formed, no diagnostic required. If a type used...
[Moved to DR at the April, 2013 meeting.]
Even though A::C is a nested type and member of the current instantiation, and thus dependent by the rules of 13.8.3.2 [temp.dep.type] paragraph 8, there does not seem to be a good reason for making it so:
struct B { struct C { }; }; template<typename T> struct A : B { A::C c; };
Proposed resolution (October, 2012):
[Some existing uses of the term “member of the current instantiation” are consistent with the definition in 13.8.3.2 [temp.dep.type] paragraph 4; others are intended to refer to members of the “current instantiation,” as defined in paragraph 1. The following resolution changes the latter to use the phrase “member of a class that is the current instantiation.”]
Change 13.8.3.2 [temp.dep.type] paragraph 4 as follows:
A name is a member of the current instantiation if it is
An unqualified name that, when looked up, refers to at least one member of a class that is the current instantiation or a non-dependent base class thereof. [Note: This can only occur when looking up a name in a scope enclosed by the definition of a class template. —end note]
A qualified-id in which the nested-name-specifier refers to the current instantiation and that, when looked up, refers to at least one member of a class that is the current instantiation or a non-dependent base class thereof. [Note: if no such member is found, and the current instantiation has any dependent base classes, then the qualified-id is a member of an unknown specialization; see below. —end note]
An id-expression denoting the member in a class member access expression (7.6.1.5 [expr.ref]) for which the type of the object expression is the current instantiation, and the id-expression, when looked up (_N4868_.6.5.6 [basic.lookup.classref]), refers to at least one member of a class that is the current instantiation or a non-dependent base class thereof. [Note: if no such member is found, and the current instantiation has any dependent base classes, then the id-expression is a member of an unknown specialization; see below. —end note]
[Example: ... —end example]
A name is a dependent member of the current instantiation if it is a member of the current instantiation which, when looked up, refers to at least one member of a class that is the current instantiation.
Change 13.8.3.2 [temp.dep.type] paragraph 5 as follows:
A name is a member of an unknown specialization if it is
A qualified-id in which the nested-name-specifier names a dependent type that is not the current instantiation.
A qualified-id in which the nested-name-specifier refers to the current instantiation, the current instantiation has at least one dependent base class, and name lookup of the qualified-id does not find any member of a class that is the current instantiation or a non-dependent base class thereof.
An id-expression denoting the member in a class member access expression (7.6.1.5 [expr.ref]) in which either
the type of the object expression is the current instantiation, the current instantiation has at least one dependent base class, and name lookup of the id-expression does not find a member of a class that is the current instantiation or a non-dependent base class thereof; or
the type of the object expression is dependent and is not the current instantiation.
Change 13.8.3.2 [temp.dep.type] paragraph 8 as follows:
A type is dependent if it is
...
a nested class or enumeration that is a dependent member of the current instantiation,
...
Change 13.8.3.3 [temp.dep.expr] paragraph 3 as follows:
An id-expression is type-dependent if it contains
...
or if it names a
static datadependent member of the current instantiation thathasis a static data member of type “array of unknown bound of T” for some T (13.7.2.5 [temp.static]). Expressions of the following forms...
Change 13.8.3.4 [temp.dep.constexpr] paragraph 2 as follows (assumes the base text is as modified by issue 1413):
An id-expression is value-dependent if:
it is a name declared with a dependent type,
it is the name of a non-type template parameter,
it names a member of an unknown specialization,
it names a static data member
of the current instantiationthat is a dependent member of the current instantiation and is not initialized in a member-declarator,it names a static member function that is a dependent member of the current instantiation, or
it is a constant with literal type and is initialized with an expression that is value-dependent.
Expressions of the following form...
Change 13.8.3.4 [temp.dep.constexpr] paragraph 5 as follows (assumes the base text is as modified by issue 1413):
An expression of the form &qualified-id where the qualified-id's nested-name-specifiernames a dependent member of the current instantiation is value-dependent.
[Moved to DR status at the April, 2013 meeting.]
Consider the following example:
void f(int*); void f(...); template <int N> void g() { f(N); } int main() { g<0>(); g<1>(); }
The call to f in g is not type-dependent, so the overload resolution must be done at definition time rather than at instantiation time. As a result, both of the calls to g will result in calls to f(...), i.e., N will not be a null pointer constant, even if the value of N is 0.
It would be most consistent to adopt a rule that a value-dependent expression can never be a null pointer constant, even in cases like
template <int N> void g() { int* p = N; }
This would always be ill-formed, even when N is 0.
John Spicer: It's clear that this treatment is required for overload resolution, but it seems too expansive given that there are other cases in which the value of a template parameter can affect the validity of the program, and an implementation is forbidden to issue a diagnostic on a template definition unless there are no possible valid specializations.
Notes from the July, 2009 meeting:
There was a strong consensus among the CWG that only the literal 0 should be considered a null pointer constant, not any arbitrary zero-valued constant expression as is currently specified.
Proposed resolution (October, 2012):
Change 7.3.12 [conv.ptr] paragraph 1 as follows:
A null pointer constant is anintegral constant expression (7.7 [expr.const]) prvalue of integer type that evaluates tointeger literal (5.13.2 [lex.icon]) with value zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted...
Change 7.7 [expr.const] paragraph 3 as follows:
...[Note: Such expressions may be used as array bounds (9.3.4.5 [dcl.array], 7.6.2.8 [expr.new]), as bit-field lengths (11.4.10 [class.bit]), as enumerator initializers if the underlying type is not fixed (9.8.1 [dcl.enum]),as null pointer constants (7.3.12 [conv.ptr]),and as alignments (9.13.2 [dcl.align]). —end note]...
Change 9.5 [dcl.init] paragraph 5 as follows:
To zero-initialize an object or reference of type T means:
if T is a scalar type (6.8 [basic.types]), the object is
set to the value 0 (zero), taken as an integral constant expression, convertedinitialized to the value obtained by converting integer literal 0 (zero) to T; [Footnote: As specified in 7.3.12 [conv.ptr], converting anintegral constant expressioninteger literal whose value is 0 to a pointer type results in a null pointer value. —end footnote]...
Change 13.4.3 [temp.arg.nontype] paragraph 5 as follows:
...
for a non-type template-parameter of type pointer to
object, qualification conversions (7.3.6 [conv.qual]) and the
array-to-pointer conversion (7.3.3 [conv.array]) are applied; if
the template-argument is of type std::nullptr_t, the
null pointer conversion (7.3.12 [conv.ptr]) is applied.
[Note: In particular, neither the null pointer conversion for a
zero-valued integral constant expression integer
literal (7.3.12 [conv.ptr]) nor the derived-to-base
conversion (7.3.12 [conv.ptr]) are applied. Although 0
is...
...
Change 14.4 [except.handle] paragraph 3 as follows:
...[Note: A throw-expression whose operand is anintegral constant expression of integer type that evaluates tointeger literal with value zero does not match a handler of pointer or pointer to member type. —end note]. [Example: ...
Add a new section to C.6 [diff.cpp03] as follows:
C.2.x Clause 4: standard conversions [diff.cpp03.conv] 7.3.12 [conv.ptr]
Change: Only literals are integer null pointer constants
Rationale: Removing surprising interactions with templates and constant expressions
Effect on original feature: Valid C++ 2003 code may fail to compile or produce different results in this International Standard, as the following example illustrates:
void f(void *); // #1 void f(...); // #2 template<int N> void g() { f(0*N); // calls #2; used to call #1 }
Additional note (January, 2013):
Concerns were raised at the Portland (October, 2012) meeting that the value false has been used in existing code as a null pointer constant, and such code would be broken by this change. This issue has been returned to "review" status to allow discussion of whether to accommodate such code or not.
[Moved to DR at the April, 2013 meeting.]
The list of cases in 13.8.3.4 [temp.dep.constexpr] paragraph 2 in which an identifier is value-dependent should also include:
an entity with reference type and is initialized with an expression that is value-dependent
a member function or a static data member of the current instantiation
Proposed resolution (October, 2012):
Change 13.8.3.4 [temp.dep.constexpr] paragraph 2 as follows and move the text following the bulleted list into a new paragraph:
An
identifierid-expression is value-dependent ifit is:
it is a name declared with a dependent type,
it is the name of a non-type template parameter,
it names a member of an unknown specialization,
it names a static data member of the current instantiation that is not initialized in a member-declarator,
it names a static member function that is a member of the current instantiation, or
it is a constant with literal type and is initialized with an expression that is value-dependent.
Expressions of the following form...
Change 13.8.3.4 [temp.dep.constexpr] paragraph 5 as follows:
An id-expression is value-dependent if it names a member of an unknown specialization.An expression of the form &qualified-id where the qualified-id's nested-name-specifier names the current instantiation is value-dependent.
[Moved to DR at the April, 2013 meeting.]
According to 13.9.3 [temp.explicit] paragraph 8,
An explicit instantiation that names a class template specialization is also an explicit instantiation 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.
This could be read as an indication that member class templates and member function templates are instantiated (as templates) when their containing class template is instantiated. For example,
template<typename T> struct A { template<typename U> void f() { T t; t.f(); } }; template struct A<int>;
In this view, the result would be a member function template definition in class A<int> equivalent to
template<typename U> void f() { int t; t.f(); }
Such a template could never be validly instantiated and thus would presumably fall under the rule in 13.8 [temp.res] paragraph 8,
If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required.
The wording of 13.9.3 [temp.explicit] paragraph 1 appears not to allow member templates to be instantiated as templates, however, mentioning only a “member template specialization” as a possibility:
A class, a function or member template specialization can be explicitly instantiated from its template. A member function, member class or static data member of a class template can be explicitly instantiated from the member definition associated with its class template.
This appears to be a contradiction, and although a diagnostic for a member template such as the example above would be helpful, most or all current implementations do not do so. Either the wording of paragraph 1 should be changed to allow explicit instantiation of a member template as a template, analogous to explicitly specializing a member template as a template, or paragraph 8 should be clarified to exclude member templates from the members explicitly instantiated when the containing class template is explicitly instantiated.
Proposed resolution (October, 2012):
Change 13.9.3 [temp.explicit] paragraph 8 as follows:
An explicit instantiation that names a class template specialization is also an explicit instantiation of the same kind (declaration or definition) of each of its members (not including members inherited from base classes and members that are templates) 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]
[Moved to DR at the October, 2012 meeting.]
Consider the following example:
template <int> struct X { typedef int type; }; template <class T> struct Y { }; template <class T> struct Z { static int const value = Y<T>::value; }; template <class T> typename X<Y<T>::value + Z<T>::value>::type f(T); int f(...); int main() { sizeof f(0); }
The problem here is that there is a combination of an invalid expression in the immediate context (Y<T>::value) and in the non-immediate context (within Z<T> when evaluating Z<T>::value). The Standard does not appear to state clearly whether this program is well-formed (because the error in the immediate context causes deduction failure) or ill-formed (because of the error in the non-immediate context).
Notes from the March, 2011 meeting:
Some members expressed a desire to allow implementations latitude in whether examples like this should be deduction failure or a diagnosable error, just as the order of evaluation of arithmetic operands is largely unconstrained. Others felt that specifying something like a depth-first left-to-right traversal of the expression or declaration would be better. Another possibility suggested was to enforce ordering only at comma operators. No consensus was achieved.
CWG agreed that the arguments should be processed in left-to-right order. Some popular existing code (e.g., Boost) depends on this ordering.
Proposed resolution (February, 2012):
Change 13.10.3 [temp.deduct] paragraph 7 as follows:
The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions. The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered. [Note: The equivalent substitution in exception specifications is done only when the function is instantiated, at which point a program is ill-formed if the substitution results in an invalid type or expression. —end note] [Example:
template <class T> struct A { using X = typename T::X; }; template <class T> typename T::X f(typename A<T>::X); template <class T> void f(...) { } template <class T> auto g(typename A<T>::X) -> typename T::X; template <class T> void g(...) { } void h() { f<int>(0); // OK, substituting return type causes deduction to fail g<int>(0); // error, substituting parameter type instantiates A<int> }—end example]
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Proposed resolution (August, 2011):
Change 13.10.3 [temp.deduct] paragraph 5 as follows:
The resulting substituted and adjusted function type is used as the type of the function template for template argument deduction. If a template argument has not been deduced, its default template argument, if any, is usedand its corresponding template parameter has a default argument, the template argument is determined by substituting the template arguments determined for preceding template parameters into the default argument. If the substitution results in an invalid type, as described above, type deduction fails. [Example:...
[Moved to DR at the April, 2013 meeting.]
The use of noexcept specifiers can cause instantiation of classes and functions that are not actually needed in the program, just to be able to complete the declaration. The actual value of the expression in the noexcept-specification is only needed if the function is defined (i.e., instantiated) or called, so it would significantly reduce the number of instantiations (and avoid certain kinds of errors when the value is currently required before a class is complete) if exception-specifications were treated like default arguments and only instantiated when they are actually needed.
Proposed resolution (February, 2012):
Change 13.7 [temp.decls] paragraph 2 as follows:
For purposes of name lookup and instantiation, default arguments and exception-specifications of function templates and default arguments and exception-specifications of member functions of class templates are considered definitions; each default argument or exception-specification is a separate definition which is unrelated to the function template definition or to any other default arguments or exception-specifications.
Change 13.8 [temp.res] paragraph 11 as follows:
[Note: For purposes of name lookup, default arguments and exception-specifications of function templates and fdefault arguments and exception-specifications of member functions of class templates are considered definitions (14.5). —end note]
Add a new paragraph following 13.8.4.1 [temp.point] paragraph 2:
If a function template or member function of a class template is called in a way which uses the definition of a default argument of that function template or member function, the point of instantiation of the default argument is the point of instantiation of the function template or member function specialization.
For an exception-specification of a function template specialization or specialization of a member function of a class template, if the exception-specification is implicitly instantiated because it is needed by another template specialization and the context that requires it depends on a template parameter, the point of instantiation of the exception-specification is the point of instantiation of the specialization that requires it. Otherwise, the point of instantiation for such an exception-specification immediately follows the namespace scope declaration or definition that requires the exception-specification.
Change 13.9.2 [temp.inst] paragraph 1 as follows:
...The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions,ordefault arguments, or exception-specifications, of the class member functions, member classes...
Add a new paragraph following 13.9.2 [temp.inst] paragraph 13:
Each default argument is instantiated independently. [Example: ... —end example]
If the exception-specification of a specialization of a function template or member function of a class template has not yet been instantiated, but is needed (e.g., to instantiate the function definition, to evaluate a noexcept-expression (7.6.2.7 [expr.unary.noexcept]), or to compare against the exception-specification of another declaration), the dependent names are looked up, the semantic constraints are checked, and the instantiation of any template used in the exception-specification is done as if it were being done as part of instantiating the declaration of the specialization. An exception-specification is not instantiated in order to calculate the exception-specification of a defaulted function in a derived class until the exception-specification of the derived member function becomes necessary.
Change the note 13.10.3 [temp.deduct] paragraph 7 as follows:
...[Note: The equivalent substitution in exception specifications is done only when thefunctionexception-specification is instantiated, at which point a program is ill-formed if the substitution results in an invalid type or expression. —end note]
Add a new paragraph following 14.5 [except.spec] paragraph 15:
A deallocation function (6.7.6.5.3 [basic.stc.dynamic.deallocation]) with no explicit exception-specification is treated as if it were specified with noexcept(true).
The exception-specification of a function template specialization is not instantiated along with the function declaration; it is instantiated when needed (13.9.2 [temp.inst]). The exception-specification of an implicitly-declared special member function is also evaluated as needed. [Note: Therefore, an implicit declaration of a member function of a derived class does not require the exception-specification of a base member function to be instantiated. —end note]
Notes from the February, 2012 meeting:
There should be a specific definition of when an exception-specification is needed and must thus be instantiated.
Additional discussion (September, 2012):
Daveed Vandevoorde brought up two additional points. First, the rule should be crafted so that an example like the following is ill-formed:
template<class T> T f() noexcept(sizeof(T) < 4); int main() { decltype(f<void>()) *p; }
Even though the exception-specification is not needed here, it should be instantiated (because of the unevaluated reference to f) in order to catch the ill-formed sizeof(T).
In addition, the proposed change creates an asymmetry between class templates and ordinary classes:
struct S {
void f() noexcept(sizeof(g()) < 8); // Invalid forward reference.
static int g();
};
but
template<typename> struct X {
void f() noexcept(sizeof(g()) < 8); // Okay.
static int g();
};
If the proposed change is adopted, the rules for exception-specifications in ordinary classes should be revised to make the parallel usage well-formed.
Proposed resolution (October, 2012):
Change 6.4.7 [basic.scope.class] paragraph 1 #1 as follows:
The potential scope of a name declared in a class consists not only of the declarative region following the name's point of declaration, but also of all function bodies, default arguments, exception-specifications, and brace-or-equal-initializers of non-static data members in that class (including such things in nested classes).
Change 6.5.3 [basic.lookup.unqual] paragraph 7 as follows:
A name used in the definition of a class X outside of a member function body, default argument, exception-specification, brace-or-equal-initializer of a non-static data member, or nested class definition29 shall be declared in one of the following ways:...
Change 6.5.3 [basic.lookup.unqual] paragraph 8 as follows:
For the members of a class X, a name used in a member function body, in a default argument, in an exception-specification, in the brace-or-equal-initializer of a non-static data member (11.4 [class.mem]), or in the definition of a class member outside of the definition of X, following the member's declarator-id31 , shall be declared in one of the following ways:...
Change 11.4 [class.mem] paragraph 2 as follows:
A class is considered a completely-defined object type (6.8 [basic.types]) (or complete type) at the closing } of the class-specifier. Within the class member-specification, the class is regarded as complete within function bodies, default arguments, exception-specifications, and brace-or-equal-initializers for non-static data members (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification.
Change 13.7 [temp.decls] paragraph 2 as follows:
For purposes of name lookup and instantiation, default arguments and exception-specifications of function templates and default arguments and exception-specifications of member functions of class templates are considered definitions; each default argument or exception-specification is a separate definition which is unrelated to the function template definition or to any other default arguments or exception-specifications.
Change 13.8 [temp.res] paragraph 11 as follows:
[Note: For purposes of name lookup, default arguments and exception-specifications of function templates and default arguments and exception-specifications of member functions of class templates are considered definitions (13.7 [temp.decls]). —end note]
Add the following as a new paragraph after 13.8.4.1 [temp.point] paragraph 2:
If a function template or member function of a class template is called in a way which uses the definition of a default argument of that function template or member function, the point of instantiation of the default argument is the point of instantiation of the function template or member function specialization.
For an exception-specification of a function template specialization or specialization of a member function of a class template, if the exception-specification is implicitly instantiated because it is needed by another template specialization and the context that requires it depends on a template parameter, the point of instantiation of the exception-specification is the point of instantiation of the specialization that requires it. Otherwise, the point of instantiation for such an exception-specification immediately follows the namespace scope declaration or definition that requires the exception-specification.
Change 13.9.2 [temp.inst] paragraph 1 as follows:
Unless a class template specialization has been explicitly instantiated (13.9.3 [temp.explicit]) or explicitly specialized (13.9.4 [temp.expl.spec]), the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program. The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions,ordefault arguments, or exception-specifications of the class member functions, member classes, scoped member enumerations, static data members and member templates; and it causes the implicit instantiation of the definitions of unscoped member enumerations and member anonymous unions. However, for the purpose...
Insert the following as a new paragraph immediately preceding 13.9.2 [temp.inst] paragraph 14:
The exception-specification of a function template specialization is not instantiated along with the function declaration; it is instantiated when needed (14.5 [except.spec]). If such an exception-specification is needed but has not yet been instantiated, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the exception-specification is done as if it were being done as part of instantiating the declaration of the specialization at that point.
[Note: 13.8.4.1 [temp.point] defines the point of instantiation of a template specialization. —end note]
Change 13.10.3 [temp.deduct] paragraph 7 as follows:
The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions. [Note: The equivalent substitution in exception specifications is done only when thefunctionexception-specification is instantiated, at which point a program is ill-formed if the substitution results in an invalid type or expression. —end note]
Change 14.5 [except.spec] paragraph 2 as follows:
...A type denoted in an exception-specification shall not denote an incomplete typeother than a class currently being defined. A type denoted in an exception-specification shall not denote a pointer or reference to an incomplete type, other than cv void*or a pointer or reference to a class currently being defined. 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.
Add the following as a new paragraph following 14.5 [except.spec] paragraph 15:
A deallocation function (6.7.6.5.3 [basic.stc.dynamic.deallocation]) with no explicit exception-specification is treated as if it were specified with noexcept(true).
An exception-specification is considered to be needed when:
in an expression, the function is the unique lookup result or the selected member of a set of overloaded functions (6.5 [basic.lookup], 12.2 [over.match], 12.3 [over.over]);
the function is odr-used (6.3 [basic.def.odr]) or, if it appears in an unevaluated operand, would be odr-used if the expression were potentially-evaluated;
the exception-specification is compared to that of another declaration (e.g. an explicit specialization or an overriding virtual function);
the function is defined; or
the exception-specification is needed for a defaulted special member function that calls the function. [Note: A defaulted declaration does not require the exception-specification of a base member function to be evaluated until the implicit exception-specification of the derived function is needed, but an explicit exception-specification needs the implicit exception-specification to compare against. —end note]
The exception-specification of a defaulted special member function is evaluated as described above only when needed; similarly, the exception-specification of a specialization of a function template or member function of a class template is instantiated only when needed.
[Note: this resolution reverses the decision in issue 1308.]
[Moved to DR at the April, 2013 meeting.]
The relationship between errors that render a program ill-formed but for which no diagnostic is required and things that cause deduction failure is not clearly specified. Presumably failures that need not be diagnosed cannot be the basis for SFINAE, lest different implementations perform deduction differently depending on how thoroughly they handle such cases. This should be spelled out explicitly.
Proposed resolution (October, 2012):
Change 13.10.3 [temp.deduct] paragraph 8 as follows:
If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [Note: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. —end note] Only invalid types and expressions...
[Moved to DR at the October, 2012 meeting.]
Presumably 13.10.3.6 [temp.deduct.type] paragraph 5 should include a bullet for a function parameter or function parameter pack that follows a function parameter pack.
Proposed resolution (February, 2012):
Change 13.10.3.2 [temp.deduct.call] paragraph 1 as follows:
...
For a function parameter pack that does not occur at the end of the parameter-declaration-list, the type of the parameter pack is a non-deduced context.When a function parameter pack appears in a non-deduced context (13.10.3.6 [temp.deduct.type]), the type of that parameter pack is never deduced. [Example:template<class ... Types> void f(Types& ...); template<class T1, class ... Types> void g(T1, Types ...); template<class T1, class ... Types> void g1(Types ..., T1); void h(int x, float& y) { const int z = x; f(x, y, z); // Types is deduced to int, float, const int g(x, y, z); // T1 is deduced to int; Types is deduced to float, int g1(x, y, z); // error: Types is not deduced g1<int, int, int>(x, y, z); // OK, no deduction occurs }—end example]
This resolution also resolves issue 1399.
[Moved to DR at the October, 2012 meeting.]
Consider:
template <class... T> void f(T..., int, T...) { } int main() { f(0); // OK f<int>(0,0,0); // OK f(0,0,0); // error }
It seems clear that the third call is ill-formed because by the time we get to the second function parameter pack we've already assumed that T is empty, so deducing anything for T would be nonsensical. But I don't think this is expressed anywhere in the standard.
One way to handle this would be to say that a template parameter pack is not deducible if it is used in a function parameter pack not at the end of the parameter list.
Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 1388.
[Moved to DR at the October, 2012 meeting.]
According to 13.10.3.4 [temp.deduct.conv] paragraph 1,
Template argument deduction is done by comparing the return type of the conversion function template (call it P; see 9.5 [dcl.init], 12.2.2.6 [over.match.conv], and 12.2.2.7 [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 13.10.3.6 [temp.deduct.type].
It would seem that the cross-references should apply to the determination of the type “required as the result of the conversion” (i.e., A) instead of the return type of the conversion function.
Proposed resolution (February, 2012):
Change 13.10.3.4 [temp.deduct.conv] paragraph 1 as follows:
Template argument deduction is done by comparing the return type of the conversion function template (call it P; see 9.5 [dcl.init], 12.2.2.6 [over.match.conv], and 12.2.2.7 [over.match.ref] for the determination of that type) with the type that is required as the result of the conversion (call it A; see 9.5 [dcl.init], 12.2.2.6 [over.match.conv], and 12.2.2.7 [over.match.ref] for the determination of that type) as described in 13.10.3.6 [temp.deduct.type].
[Moved to DR at the October, 2012 meeting.]
Presumably 13.10.3.6 [temp.deduct.type] paragraph 5 should include a bullet for a decltype-specifier whose expression references a template parameter.
Proposed resolution (February, 2012):
Change 13.10.3.6 [temp.deduct.type] paragraph 5 as follows:
The non-deduced contexts are:
The nested-name-specifier of a type that was specified using a qualified-id.
The expression of a decltype-specifier.
A non-type template argument...
[Moved to DR at the October, 2012 meeting.]
There are a number of places in the Standard that appear to assume that exceptions are only thrown by throw-expressions. Various other constructs, such as dynamic_casts, typeid, new-expressions, etc., can also throw exceptions, so a more general term should be coined and applied in place of throw-expression wherever necessary.
Proposed resolution (February, 2012):
Change 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 3 as follows:
...Any other allocation function that fails to allocate storage shall indicate failure only by throwing an exception (14.2 [except.throw]) of a type that would match a handler (14.4 [except.handle]) of type std::bad_alloc (17.6.4.1 [bad.alloc]).
Change 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 4 as follows:
...[Note: In particular, a global allocation function is not called to allocate storage for objects with static storage duration (6.7.6.2 [basic.stc.static]), for objects or references with thread storage duration (6.7.6.3 [basic.stc.thread]), for objects of type std::type_info (7.6.1.8 [expr.typeid]), or forthe copy of an object thrown by a throw expressionan exception object (14.2 [except.throw]). —end note]
Change 7.6.1.7 [expr.dynamic.cast] paragraph 9 as follows:
The value of a failed cast to pointer type is the null pointer value of the required result type. A failed cast to reference type throws an exception (14.2 [except.throw]) of a type that would match a handler (14.4 [except.handle]) of type std::bad_cast (17.7.4 [bad.cast]).
Change 7.6.1.8 [expr.typeid] paragraph 2 as follows:
...If the glvalue expression is obtained by applying the unary * operator to a pointer68 and the pointer is a null pointer value (7.3.12 [conv.ptr]), the typeid expression throwsthean exception (14.2 [except.throw]) of a type that would match a handler of type std::bad_typeid exception (17.7.5 [bad.typeid]).
Change 7.6.2.8 [expr.new] paragraph 7 as follows:
When the value of the expression in a noptr-new-declarator is zero, the allocation function is called to allocate an array with no elements. If the value of that expression is less than zero or such that the size of the allocated object would exceed the implementation-defined limit, or if the new-initializer is a braced-init-list for which the number of initializer-clauses exceeds the number of elements to initialize, no storage is obtained and the new-expressionterminates by throwingthrows an exception (14.2 [except.throw]) of a type that would match a handler (14.4 [except.handle]) of type std::bad_array_new_length (17.6.4.2 [new.badlength]).
Change Clause 14 [except] paragraph 1 as follows:
...A handler will be invoked only bya throw-expression invokedthrowing an exception in code executed in the handler's try block or in functions called from the handler's try block...
Change Clause 14 [except] paragraph 2 as follows:
A try-block is a statement (Clause 8 [stmt]). A throw-expression is of type void.Code that executes a throw-expression is said to “throw an exception;” code that subsequently gets control is called a “handler.”[Note:...
Change 14.2 [except.throw] paragraph 1 as follows:
Throwing an exception transfers control to a handler. [Note: An exception can be thrown from one of the following contexts: throw-expression (see below), allocation functions (6.7.6.5.2 [basic.stc.dynamic.allocation]), dynamic_cast (7.6.1.7 [expr.dynamic.cast]), typeid (7.6.1.8 [expr.typeid]), new-expression (7.6.2.8 [expr.new]), and standard library functions (16.3.2.4 [structure.specifications]). —end note] An object is passed and the type of that object determines which handlers can catch it. [Example:...
Change 14.2 [except.throw] paragraph 3 as follows:
A throw-expressionThrowing an exception copy-initializes (9.5 [dcl.init], 11.4.5.3 [class.copy.ctor]) a temporary object, called the exception object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T”, respectively. The temporary is an lvalue and is used to initialize the variable named in the matching handler (14.4 [except.handle]). If the type of the exception object would be an incomplete type or a pointer to an incomplete type other than (possibly cv-qualified) void the program is ill-formed.Except for these restrictions and the restrictions on type matching mentioned in 14.4 [except.handle], the operand of throw is treated exactly as a function argument in a call (7.6.1.3 [expr.call]) or the operand of a return statement.Evaluating a throw-expression with an operand throws an exception; the type of the exception object is determined by removing any top-level cv-qualifiers from the static type of the operand and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T,” respectively.
Change 14.2 [except.throw] paragraph 4 as follows:
...[Note:ana thrown exceptionthrown by a throw-expressiondoes not propagate to other threads unless caught, stored, and rethrown using appropriate library functions; see 17.9.7 [propagation] and 32.10 [futures]. —end note]
Change 14.2 [except.throw] paragraph 8 as follows:
A throw-expression with no operand rethrows the currently handled exception (14.4 [except.handle]). The exception is reactivated with the existingtemporaryexception object; no newtemporaryexception object is created. The exception is no longer considered to be caught; therefore, the value of std::uncaught_exception() will again be true. [Example:...
Change 14.3 [except.ctor] paragraph 1 as follows:
As control passes froma throw-expressionthe point where an exception is thrown to a handler, destructors are invoked for all automatic objects constructed since the try block was entered...
Change 14.3 [except.ctor] paragraph 3 as follows:
The process of calling destructors for automatic objects constructed on the path from a try block toa throw-expressionthe point where an exception is thrown is called “stack unwinding.” If a destructor...
Change 14.4 [except.handle] paragraph 17 as follows:
When the handler declaresa non-constantan object, any changes to that object will not affect thetemporary object that was initialized by execution of the throw-expressionexception object. When the handler declares a reference toa non-constantan object, any changes to the referenced object are changes to thetemporary object initialized when the throw-expression was executedexception object and will have effect should that object be rethrown.
Change 17.9.5.4 [terminate] paragraph 1 as follows:
Remarks: Called by the implementation when exception handling must be abandoned for any of several reasons (14.6.2 [except.terminate]), in effect immediately afterevaluating the throw-expression (17.9.5.1 [terminate.handler])throwing the exception. May also be called directly by the program.
[Moved to DR at the April, 2013 meeting.]
According to 14.2 [except.throw] paragraph 7,
If the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught, calls a function that exits via an exception, std::terminate is called (14.6.2 [except.terminate]).
This wording was overlooked in the resolution for issue 475 and should be changed, along with the following example, to indicate that std::terminate will be called for an uncaught exception only after initialization of the exception object is complete.
Proposed resolution (August, 2012):
Change 14.2 [except.throw] paragraph 7 as follows:
If the exception handling mechanism, after completing
evaluation of the expression to be thrownthe initialization of the exception object but before theexception is caughtactivation of a handler for the exception, calls a function that exits via an exception, std::terminate is called (14.6.2 [except.terminate]). [Example:struct C { C() { } C(const C&) {throw 0; }if (std::uncaught_exception()) { throw 0; // throw during copy to handler's exception-declaration object (14.4 [except.handle]) } } }; int main() { try { throw C(); // calls std::terminate() if construction of the handler's // exception-declaration object is not elided (11.4.5.3 [class.copy.ctor]) } catch(C) { } }—end example]
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
I have a question about exception handling with respect to derived to base conversions of pointers caught by reference.
What should the result of this program be?
struct S {}; struct SS : public S {}; int main() { SS ss; int result = 0; try { throw &ss; // throw object has type SS* // (pointer to derived class) } catch (S*& rs) // (reference to pointer to base class) { result = 1; } catch (...) { result = 2; } return result; }
The wording of 14.4 [except.handle] paragraph 3 would seem to say that the catch of S*& does not match and so the catch ... would be taken.
All of the compilers I tried (EDG, g++, Sun, and Microsoft) used the catch of S*& though.
What do we think is the desired behavior for such cases?
My initial reaction is that this is a bug in all of these compilers, but the fact that they all do the same thing gives me pause.
On a related front, if the handler changes the parameter using the reference, what is caught by a subsequent handler?
extern "C" int printf(const char *, ...); struct S {}; struct SS : public S {}; SS ss; int f() { try { throw &ss; } catch (S*& rs) // (reference to pointer to base class) { rs = 0; throw; } catch (...) { } return 0; } int main() { try { f(); } catch (S*& rs) { printf("rs=%p, &ss=%p\n", rs, &ss); } }
EDG, g++, and Sun all catch the original (unmodified) value. Microsoft catches the modified value. In some sense the EDG/g++/Sun behavior makes sense because the later catch could catch the derived class instead of the base class, which would be difficult to do if you let the catch clause update the value to be used by a subsequent catch.
But on this non-pointer case, all of the compilers later catch the modified value:
extern "C" int printf(const char *, ...); int f() { try { throw 1; } catch (int& i) { i = 0; throw; } catch (...) { } return 0; } int main() { try { f(); } catch (int& i) { printf("i=%p\n", i); } }
To summarize:
(See also issue 729.)
Notes from the October, 2009 meeting:
The consensus of the CWG was that it should not be possible to catch a pointer to a derived class using a reference to a base class pointer, and that a handler that takes a reference to non-const pointer should allow the pointer to be modified by the handler.
Proposed resolution (March, 2010):
Change 14.4 [except.handle] paragraph 3 as follows:
A handler is a match for an exception object of type E if
The handler is of type cv T or cv T& and E and T are the same type (ignoring the top-level cv-qualifiers), or
the handler is of type cv T or cv T& and T is an unambiguous public base class of E, or
the handler is of type cv
1T*cv2or const T& where T is a pointer type and E is a pointer type that can be converted tothe type of the handlerT by either or both of
a standard pointer conversion (7.3.12 [conv.ptr]) not involving conversions to pointers to private or protected or ambiguous classes
a qualification conversion
the handler is of type cv T or const T& where T is a pointer or pointer to member type and E is std::nullptr_t.
(This resolution also resolves issue 729.)
Notes from the March, 2011 meeting:
This resolution would require an ABI change and was thus deferred for further consideration.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Given the following example:
int f() { try { /* ... */ } catch(const int*&) { return 1; } catch(int*&) { return 2; } return 3; }
can f() return 2? That is, does an int* exception object match a const int*& handler?
According to 14.4 [except.handle] paragraph 3, it does not:
A handler is a match for an exception object of type E if
The handler is of type cv T or cv T& and E and T are the same type (ignoring the top-level cv-qualifiers), or
the handler is of type cv T or cv T& and T is an unambiguous public base class of E, or
the handler is of type cv1 T* cv2 and E is a pointer type that can be converted to the type of the handler by either or both of
a standard pointer conversion (7.3.12 [conv.ptr]) not involving conversions to pointers to private or protected or ambiguous classes
a qualification conversion
the handler is a pointer or pointer to member type and E is std::nullptr_t.
Only the third bullet allows qualification conversions, but only the first bullet applies to a handler of reference-to-pointer type. This is consistent with how other reference bindings work; for example, the following is ill-formed:
int* p; const int*& r = p;
(The consistency is not complete; the reference binding would be permitted if r had type const int* const &, but a handler of that type would still not match an int* exception object.)
However, implementation practice seems to be in the other direction; both EDG and g++ do match an int* with a const int*&, and the Microsoft compiler issues an error for the presumed hidden handler in the code above. Should the Standard be changed to reflect existing practice?
(See also issue 388.)
Notes from the October, 2009 meeting:
The CWG agreed that matching the exception object with a handler should, to the extent possible, mimic ordinary reference binding in cases like this.
Proposed resolution (February, 2010):
This issue is resolved by the resolution of issue 388.
[Moved to DR at the October, 2012 meeting.]
The types that may appear in an exception-specification (14.5 [except.spec] paragraph 2) include rvalue reference types, although they are excluded as handler types (14.4 [except.handle] paragraph 1) . This appears to have been an oversight.
Proposed resolution (February, 2012):
Change 14.5 [except.spec] paragraph 2 as follows:
...A type denoted in an exception-specification shall not denote an incomplete type or an rvalue reference type. A type denoted in an exception-specification shall not denote a pointer or reference...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
It is not clear whether the unexpected handler will be invoked in the following example:
#include <iostream> #include <exception> struct A { ~A() throw() { } }; struct B { ~B() noexcept { } }; struct C : A, B { ~C() { throw 0; } }; void unexpected_observer() { std::cerr << "unexpected called" << std::endl; std::terminate(); } int main() { std::set_unexpected(unexpected_observer); C c; }
The problem is 14.5 [except.spec] paragraph 14 only says that the exception-specification of C::~C “shall allow no exceptions,” which could mean either throw() or noexcept(true).
Proposed resolution (August, 2011):
Change 14.5 [except.spec] paragraph 14 as follows:
An implicitly declared special member function (11.4.4 [special])
shall havehas an exception-specification. If f is an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f's implicit definition; fshall allowallows all exceptions if any function it directly invokes allows all exceptions, and fshall allow no exceptionshas the exception-specification noexcept(true) if every function it directly invokes allows no exceptions. [Example:struct A { A(); A(const A&) throw(); A(A&&) throw(); ~A() throw(X); }; struct B { B() throw(); B(const B&) throw(); B(B&&) throw(Y); ~B() throw(Y); }; struct D : public A, public B { // Implicit declaration of D::D(); // Implicit declaration of D::D(const D&)throw()noexcept(true); // Implicit declaration of D::D(D&&) throw(Y); // Implicit declaration of D::~D() throw(X, Y); };Furthermore, if...
[Moved to DR at the October, 2012 meeting.]
According to 14.5 [except.spec] paragraph 14,
An implicitly declared special member function (11.4.4 [special]) shall have an exception-specification. If f is an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f's implicit definition; f shall allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions if every function it directly invokes allows no exceptions.
It would be clearer if this description made explicit the intent that special member functions that invoke no other functions are to allow no exceptions.
Proposed resolution (February, 2012):
Change 14.5 [except.spec] paragraph 14 as follows:
...and f has the exception-specification noexcept(true) if every function it directly invokes allows no exceptions. [Note: It follows that f has the exception-specification noexcept(true) if it invokes no other functions. —end note] [Example:
struct A { A(); A(const A&) throw(); A(A&&) throw(); ~A() throw(X); }; struct B { B() throw(); B(const B&)throw()= default; // Declaration of B::B(const B&) noexcept(true) B(B&&) throw(Y); ~B() throw(Y); }; ...
[Moved to DR at the October, 2012 meeting.]
15.7 [cpp.replace] paragraph 12 says,
If there is a ... in the identifier-list in the macro definition...
However, an identifier-list cannot contain an ellipsis according to the grammar in Clause 15 [cpp] paragraph 1.
Proposed resolution (February, 2012):
Change 15.7 [cpp.replace] paragraph 12 as follows:
If there is a ...in the identifier-listimmediately preceding the ) in the function-like macro definition, then the trailing arguments, including any separating comma preprocessing tokens, are merged to form a single item: thevariable argumentsvariable arguments. The number of arguments so combined is such that, following merger, the number of arguments is one more than the number of parameters in the macro definition (excluding the ...).
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
It is not clear whether the implementation limit on recursion in template instantiation applies only to instantiation itself or also to recursion that occurs during template argument deduction.
Proposed resolution (August, 2011):
Change Clause Annex B [implimits] as follows:
Recursively nested template instantiations, including substitution during template argument deduction (13.10.3 [temp.deduct]) [1 024].
[Moved to DR at the April, 2013 meeting.]
During the discussion of issues 167 and 174, it became apparent that there was no consensus on the meaning of deprecation. Some thought that deprecating a feature reflected an intent to remove it from the language. Others viewed it more as an encouragement to programmers not to use certain constructs, even though they might be supported in perpetuity.
There is a formal-sounding definition of deprecation in Annex Clause Annex D [depr] paragraph 2:
deprecated is defined as: Normative for the current edition of the Standard, but not guaranteed to be part of the Standard in future revisions.However, this definition would appear to say that any non-deprecated feature is "guaranteed to be part of the Standard in future revisions." It's not clear that that implication was intended, so this definition may need to be amended.
This issue is intended to provide an avenue for discussing and resolving those questions, after which the original issues may be reopened if that is deemed desirable.
Proposed resolution (August, 2012):
Change Clause Annex D [depr] paragraph 2 as follows:
These are deprecated features, where deprecated is defined as: Normative for the current edition of the Standard, butnot guaranteed to be part of the Standard inhaving been identified as a candidate for removal from future revisions.
[Moved to DR at the November, 2014 meeting.]
The section of the Standard reserving names that begin with two underscores or an underscore and a capital letter, _N4140_.17.6.4.3.2 [global.names], applies only to “programs that use the facilities of the C++ standard library” (16.4.5.1 [constraints.overview]). However, implementations rely on this restriction for mangling, even when no standard library facilities are used. Should this requirement be moved to the core language section?
(There is a related issue with user-defined literal suffixes, 16.4.5.3.6 [usrlit.suffix]. However, these are already mentioned normatively in the core language section, so it could be argued that the question of library usage does not apply.)
Proposed resolution (October, 2014):
Change 5.11 [lex.name] paragraph 3 as follows:
In addition, some identifiers are reserved for use by C++ implementations
and standard libraries (_N4140_.17.6.4.3.2 [global.names])and shall not be used otherwise; no diagnostic is required.
Each identifier that contains a double understore __ or begins with an underscore followed by an uppercase letter is reserved to the implementation for any use.
Each identifier that begins with an underscore is reserved to the implementation for use as a name in the global namespace.
Change the footnote in 9.6.1 [dcl.fct.def.general] paragraph 8 as follows:
[Footnote: Implementations are permitted to provide additional predefined variables with names that are reserved to the implementation (_N4140_.17.6.4.3.2 [global.names]5.11 [lex.name]). If a predefined variable is not odr-used (6.3 [basic.def.odr]), its string value need not be present in the program image. —end footnote]
Change the example in 12.6 [over.literal] paragraph 8 as follows:
double operator""_Bq(double); // OK: does not use the reservednameidentifier _Bq (_N4140_.17.6.4.3.2 [global.names]5.11 [lex.name]) double operator"" _Bq(double); // uses the reservednameidentifier _Bq (_N4140_.17.6.4.3.2 [global.names]5.11 [lex.name])
Delete _N4140_.17.6.4.3.2 [global.names]:
Certain sets of names and function signatures are always reserved to the implementation:
Each name that contains a double underscore __ or begins with an underscore followed by an uppercase letter (5.12 [lex.key]) is reserved to the implementation for any use.
Each name that begins with an underscore is reserved to the implementation for use as a name in the global namespace.
[Moved to DR at the November, 2014 meeting.]
Issue 1350 clarified that the exception-specification for an inheriting constructor is determined like defaulted functions, but we should also say something similar for deleted, and perhaps constexpr.
Also, the description of the semantics of inheriting constructors don't seem to allow for C-style variadic functions, so the text should be clearer that such constructors are only inherited without their ellipsis.
Proposed resolution (February, 2014):
Change _N4527_.12.9 [class.inhctor] paragraph 1 as follows:
A using-declaration (9.10 [namespace.udecl]) that names a constructor implicitly declares a set of inheriting constructors. The candidate set of inherited constructors from the class X named in the using-declaration consists of actual constructors and notional constructors that result from the transformation of defaulted parameters and ellipsis parameter specifications as follows:
all non-template constructorsfor each non-template constructor of X, the constructor that results from omitting any ellipsis parameter specification, andfor each non-template constructor of X that has at least one parameter with a default argument, the set of constructors that results from omitting any ellipsis parameter specification and successively omitting parameters with a default argument from the end of the parameter-type-list, and
all constructor templatesfor each constructor template of X, the constructor template that results from omitting any ellipsis parameter specification, andfor each constructor template of X that has at least one parameter with a default argument, the set of constructor templates that results from omitting any ellipsis parameter specification and successively omitting parameters with a default argument from the end of the parameter-type-list.
Change _N4527_.12.9 [class.inhctor] paragraph 2 as follows:
The constructor characteristics of a constructor or constructor template are
the template parameter list (13.2 [temp.param]), if any,
the
parameter-type-listparameter-type-list (9.3.4.6 [dcl.fct]), andabsence or presence of explicit (11.4.8.2 [class.conv.ctor])
, and.
absence or presence of constexpr (9.2.6 [dcl.constexpr]).
Change _N4527_.12.9 [class.inhctor] paragraph 4 as follows:
A constructor so declared has the same access as the corresponding constructor in X. It is constexpr if the user-written constructor (see below) would satisfy the requirements of a constexpr constructor (9.2.6 [dcl.constexpr]). It is deleted if the corresponding constructor in X is deleted (9.6 [dcl.fct.def]9.6.3 [dcl.fct.def.delete]) or if a defaulted default constructor (11.4.5 [class.ctor]) would be deleted, except that the construction of the direct base class X is not considered in the determination. An inheriting constructor shall not be explicitly instantiated (13.9.3 [temp.explicit]) or explicitly specialized (13.9.4 [temp.expl.spec]).
[Adopted at the October, 2015 meeting as P0136R1.]
For an example like
struct A { constexpr A(int, float = 0); explicit A(int, int = 0); A(int, int, int = 0) = delete; }; struct B : A { using A::A; };
it is not clear from _N4527_.12.9 [class.inhctor] what should happen: is B::B(int) constexpr and/or explicit? Is B::B(int, int) explicit and/or deleted? Although the rationale given in the note in paragraph 7,
If two using-declarations declare inheriting constructors with the same signatures, the program is ill-formed (11.4 [class.mem], _N4868_.12.2 [over.load]), because an implicitly-declared constructor introduced by the first using-declaration is not a user-declared constructor and thus does not preclude another declaration of a constructor with the same signature by a subsequent using-declaration.
might be thought to apply, paragraph 1 talks about a set of candidate constructors based on their parameter types, so presumably such a set would contain only a single declaration of A::A(int) and one for A::A(int, int). The constructor characteristics of that declaration, however, are not specified.
One possibility might be to declare such a constructor, resulting from the transformation of more than one base class constrctor, to be deleted, so there would be an error only if it were used.
Notes from the April, 2013 meeting:
CWG agreed with the direction of defining such constructors as deleted.
Additional note, June, 2014:
See issue 1941 for an alternative approach to this problem.
[Adopted at the October, 2015 meeting as P0136R1.]
Consider the following example:
template<class T> struct S { private: typedef int X; friend struct B; }; struct B { template<class T> B(T, typename T::X); }; struct D: B { using B::B; }; S<int> s; B b(s, 2); // Okay, thanks to friendship. D d(s, 2); // Error: friendship is not inherited.
My understanding is that the construction of d fails because typename T::X expands to S<int>::X in this case, and that is not accessible from D.
However, I'm not sure that makes sense from a usability perspective. The user of D just wanted to be able to wrap class B, and the fact that friendship was granted to B to enable its constructor parameter seems like just an implementation detail that D shouldn't have to cope with.
Would it perhaps be better to suspend access checking during the instantiation of inheriting member function template declarations (not definitions), since real access problems (e.g., the selection of a private constructor) would presumably be revealed when doing the full instantiation?
Proposed resolution (February, 2014):
Change _N4527_.12.9 [class.inhctor] paragraph 4 as follows:
A constructor so declared has the same access as the corresponding constructor in X. It is deleted if the corresponding constructor in X is deleted (
9.6 [dcl.fct.def]9.6.3 [dcl.fct.def.delete]). While performing template argument substitution (13.10.3 [temp.deduct]) for constructor templates so declared, name lookup, overload resolution, and access checking are performed in the context of the corresponding constructor template of X. [Example:struct B { template<class T> B(T, typename T::Q); }; class S { using Q = int; template<class T> friend B::B(T, typename T::Q); }; struct D : B { using B::B; }; B b(S(), 1); // OK: B::B is a friend of S D d(S(), 2); // OK: access control is in the context of B::B—end example] An inheriting constructor shall not be explicitly instantiated (13.9.3 [temp.explicit]) or explicitly specialized (13.9.4 [temp.expl.spec]).
Additional note (June, 2014):
This issue is being returned to "review" status in light of a suggestion for an alternative approach to the problem; see issue 1941.
[Adopted at the October, 2015 meeting as P0136R1.]
A local class cannot, according to 13.7.3 [temp.mem] paragraph 2, have member templates. Presumably, then, an example like the following is ill-formed:
struct S { template<class T> S(T) { struct L: S { using S::S; }; } };
It is accepted by current implementations, however. Does something need to be said about this case in _N4527_.12.9 [class.inhctor], either to explicitly allow or forbid it, or is the restriction in 13.7.3 [temp.mem] sufficient?
[Adopted at the October, 2015 meeting as P0136R1.]
Default arguments are a common mechanism for applying SFINAE to constructors. However, default arguments are not carried over when base class constructors are inherited; instead, an overload set of constructors with various numbers of arguments is created in the derived class. This seems problematic.
One possibility would be to change the mechanism for how constructors are inherited; a using-declaration might actually introduce the base class constructors into the derived class, as other using-declarations do, and if a base class constructor were selected, the remaining derived class members would be default-initialized.
This approach would also address issues 1645, as duplicated constructors would simply fail during overload resolution, and 1715, since there would be no synthesized constructors for which access checking would be needed.
The effect of such an approach on ABIs, including mangling, would need to be considered.
See also issue 1959.
[Adopted at the October, 2015 meeting as P0136R1.]
Consider the following example:
struct a { a() = default; a( a const & ) { std::cout << "copy\n"; } template< typename t > a( t ) { std::cout << "convert\n"; } }; struct b : a { using a::a; }; a x; b y = x;
The copy constructor is invoked by the inherited constructor template, making it effectively inherited, contrary to the intent of _N4527_.12.9 [class.inhctor] paragraph 3. std::function is affected by this issue.
A kernel of a resolution might be to inherit the copy and move constructors as deleted. Then they will be more specialized than any template, and the user won't get conversion-from-base behavior unless they explicitly declare it. However, reference binding in overload resolution is a potential gap. Something like b::b( a & ) = delete with a non-const parameter would not add safety if it's not chosen.
See also issue 1941.
[Adopted at the October, 2015 meeting as P0136R1.]
The creation of inheriting constructors does not, but should, consider the default arguments of constructors in the inheriting class. For example,
struct A { A(int, int); }; struct B : A { using A::A; B(int, int, int = 0); // does not suppress creation of B(int, int) from A(int, int) };
[Moved to DR at the May, 2015 meeting.]
It is not clear whether the template keyword should be accepted in an example like
template<typename> struct s {};
::template s<void> q; // innocuous disambiguation?
Although it is accepted by the grammar, the verbiage in _N4567_.5.1.1 [expr.prim.general] paragraph 10 does not mention the possibility, while the preceding paragraph dealing with class qualification calls it out explicitly.
Notes from the June, 2014 meeting:
CWG agreed that this usage should be accepted.
Proposed resolution (November, 2014):
Change _N4567_.5.1.1 [expr.prim.general] paragraph 10 as follows (the base wording is as modified by issue 1887):
The nested-name-specifier :: names the global namespace. A nested-name-specifier that names a namespace (9.9 [basic.namespace]), optionally followed by the keyword template (13.3 [temp.names]), and then followed by the name of a member of that namespace (or the name of a member of a namespace made visible by a using-directive), is a qualified-id...
[Moved to DR at the May, 2015 meeting.]
An example like
typedef int T; typedef const T CT; void blah2(T *a) { a->CT::~T(); }
is ill-formed, because _N4778_.7.6.1.4 [expr.pseudo] paragraph 2 requires that the two type-names in the qualified-id be the same type. The corresponding case for a real destructor, however, is allowed because of the provision in 11.3 [class.name] paragraph 5 ignoring cv-qualifiers in a typedef-name referring to a class type. The specification for pseudo-destructors should be adjusted accordingly.
Proposed resolution (November, 2014):
Change _N4778_.7.6.1.4 [expr.pseudo] paragraph 2 as follows:
...The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type. Furthermore, the two type-names in a pseudo-destructor-name of the form
nested-name-specifieropt type-name :: ~ type-name
shall designate the same scalar type (ignoring cv-qualification).
[Adopted at the February, 2016 meeting.]
The type/nontype hiding rules (“struct stat hack”) do not apply in class scope. This is a C compatibility issue:
struct A { struct B { int x; } b; int B; // Permitted in C };
Since the type/nontype hiding rules exist for C compatibility, should this example be supported?
Proposed resolution (September, 2015):
Change _N4868_.6.4.1 [basic.scope.declarative] paragraph 4 as follows:
Given a set of declarations in a single declarative region, each of which specifies the same unqualified name,
they shall all refer to the same entity, or all refer to functions and function templates; or
exactly one declaration shall declare a class name or enumeration name that is not a typedef name and the other declarations shall all refer to the same variable, non-static data member, or enumerator, or all refer to functions and function templates; in this case the class name or enumeration name is hidden (_N4868_.6.4.10 [basic.scope.hiding]). [Note: A namespace name or a class template name must be unique in its declarative region (9.9.3 [namespace.alias], Clause 13 [temp]). —end note]
[Moved to DR at the November, 2014 meeting.]
According to _N4868_.9.8.2.3 [namespace.memdef] paragraphs 1 and 2 read,
Members (including explicit specializations of templates (13.9.4 [temp.expl.spec])) of a namespace can be defined within that namespace.
Members of a named namespace can also be defined outside that namespace by explicit qualification (6.5.5.3 [namespace.qual]) of the name being defined, provided that the entity being defined was already declared in the namespace and the definition appears after the point of declaration in a namespace that encloses the declaration's namespace.
It is not clear what these specifications mean for the following pair of examples:
namespace N { struct A; } using N::A; struct A { };
Although this does not satisfy the “by explicit qualification” requirement, it is accepted by major implementations.
struct S; namespace A { using ::S; struct S { }; }
Is this a definition “within that namespace,” or should that wording be interpreted as “directly within” the namespace?
See also issue 1838.
Proposed Resolution (July, 2014):
This issue is resolved by the resolution of issue 1838.
[Moved to DR at the November, 2014 meeting.]
The Standard is not clear about what happens when an entity is declared but not defined in an inner namespace and declared via a using-declaration in an outer namespace, and a definition of an entity with that name as an unqualified-id appears in the outer namespace. Is this a legitimate definition of the inner-namespace entity, as it would be if the definition used a qualified-id, or is the definition a member of the outer namespace and thus in conflict with the using-declaration? There is implementation divergence on the treatment of such definitions.
See also issues 1708 and 1021.
Notes from the February, 2014 meeting:
CWG agreed that the definition in such cases is a member of the outer namespace, not a redeclaration of the name introduced in that namespace by the using-declaration.
Proposed Resolution (July, 2014):
Change _N4868_.9.8.2.3 [namespace.memdef] paragraph 1 as follows:
Members (including explicit specializations of templates (13.9.4 [temp.expl.spec])) of a namespace can be defined within that namespace.A declaration in a namespace N (excluding declarations in nested scopes) whose declarator-id is an unqualified-id declares (or redeclares) a member of N, and may be a definition. [Note: An explicit instantiation (13.9.3 [temp.explicit]) or explicit specialization (13.9.4 [temp.expl.spec]) of a template does not introduce a name and thus may be declared using an unqualified-id in a member of the enclosing namespace set, if the primary template is declared in an inline namespace. —end note] [Example:namespace X { void f() { /* ... */ } // OK: introduces X::f() namespace M { void g(); // OK: introduces X::M::g() } using M::g; void g(); // error: conflicts with X::M::g() }—end example]
Change _N4868_.9.8.2.3 [namespace.memdef] paragraph 3 as follows:
Every name first declared in a namespace is a member of that namespace.If a friend declaration...
This resolution also resolves issues 1021 and 987.
[Adopted at the February, 2016 meeting.]
The current specification of std::uncaught_exceptions() (_N5001_.14.6.3 [except.uncaught] paragraph 1) does not, but should, state that it is the number of uncaught exceptions in the current thread.
Proposed resolution (September, 2015):
Change _N5001_.14.6.3 [except.uncaught] paragraph 1 as follows:
...The function std::uncaught_exceptions() (17.9.6 [uncaught.exceptions]) returns the number of uncaught exceptions in the current thread.
[Adopted at the February, 2016 meeting.]
According to 3.57 [defns.signature.member.templ], the signature of a class member function template includes:
name, parameter type list (9.3.4.6 [dcl.fct]), class of which the function is a member, cv-qualifiers (if any), ref-qualifier (if any), return type, and template parameter list
However, a constructor template does not have a return type. This may be relevant to friend declaration matching.
Proposed resolution (October, 2015):
Change 3.57 [defns.signature.member.templ] as follows:
signature
<class member function template> name, parameter type list (9.3.4.6 [dcl.fct]), class of which the function is a member, cv-qualifiers (if any), ref-qualifier (if any), return type (if any), and template parameter list
[Moved to DR at the May, 2015 meeting.]
According to 5.2 [lex.phases] paragraph 1, first phase,
Any source file character not in the basic source character set (5.3.1 [lex.charset]) is replaced by the universal-character-name that designates that character. (An implementation may use any internal encoding, so long as an actual extended character encountered in the source file, and the same extended character expressed in the source file as a universal-character-name (i.e., using the \uXXXX notation), are handled equivalently except where this replacement is reverted in a raw string literal.)
This wording is obviously not intended to exclude the use of characters with code points larger than 0xffff, but the reference to “the \uXXXX notation” might suggest that the \Uxxxxxxxx form is not allowed.
Proposed resolution (April, 2015):
Change 5.2 [lex.phases] paragraph 1 number 1 as follows:
...(An implementation may use any internal encoding, so long as an actual extended character encountered in the source file, and the same extended character expressed in the source file as a universal-character-name (i.e.e.g., using the \uXXXX notation), are handled equivalently except where this replacement is reverted in a raw string literal.)
[Moved to DR at the November, 2014 meeting.]
According to 5.3.1 [lex.charset] paragraph 3,
The basic execution character set and the basic execution wide-character set shall each contain all the members of the basic source character set, plus control characters representing alert, backspace, and carriage return, plus a null character (respectively, null wide character), whose representation has all zero bits.
It is not clear that a portable program can examine the bits of the representation; instead, it would appear to be limited to examining the bits of the numbers corresponding to the value representation (6.8.2 [basic.fundamental] paragraph 1). It might be more appropriate to require that the null character value compare equal to 0 or '\0' rather than specifying the bit pattern of the representation.
There is a similar issue for the definition of shift, bitwise and, and bitwise or operators: are those specifications constraints on the bit pattern of the representation or on the values resulting from the interpretation of those patterns as numbers?
Proposed resolution (February, 2014):
Change 5.3.1 [lex.charset] paragraph 3 as follows:
The basic execution character set and the basic execution wide-character set shall each contain all the members of the basic source character set, plus control characters representing alert, backspace, and carriage return, plus a null character (respectively, null wide character), whoserepresentation has all zero bitsvalue is 0. For each basic execution character set...
[Moved to DR at the October, 2015 meeting.]
The “max munch” rule could be read to require the characters <int> in vector<int> to be parsed as a header-name rather than as three distinct tokens. 5.6 [lex.header] paragraph 1 says,
Header name preprocessing tokens shall only appear within a #include preprocessing directive (15.3 [cpp.include]).
However, that is not sufficiently clear that header-names are only to be recognized in that context.
Proposed resolution (May, 2015):
Change 5.5 [lex.pptoken] bullet 3.3 as follows:
Otherwise, 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, except that a header-name (5.6 [lex.header]) is only formed within a #include directive (15.3 [cpp.include]).
Change 5.6 [lex.header] paragraph 1 as follows:
[Note: Header name preprocessing tokensshallonly appear within a #include preprocessing directive (15.3 [cpp.include]see 5.5 [lex.pptoken]). —end note] The sequences in both forms...
[Moved to DR at the May, 2015 meeting.]
The grammar in 5.11 [lex.name] includes the production,
The rule for “other implementation-defined characters” is a holdover from before the introduction of universal-character-names, intended to allow the use of non-ASCII national characters in identifiers. However, since all characters outside the basic source character set are now conceptually mapped to universal-character-names in translation phase 1, there is no longer a need for such a provision and it should be removed.
Proposed resolution (April, 2015):
Change the grammar in 5.11 [lex.name] as follows:
[Moved to DR at the November, 2014 meeting.]
The intent of char16_t string literals, as evident from 5.13.5 [lex.string] paragraph 9, is that they be encoded in UTF-16, that is, including surrogate pairs to represent code points outside the basic multi-lingual plane:
A single c-char may produce more than one char16_t character in the form of surrogate pairs.
Paragraph 15, however, is inconsistent with this approach, saying,
Escape sequences and universal-character-names in non-raw string literals have the same meaning as in character literals (5.13.3 [lex.ccon]), except that the single quote ' is representable either by itself or by the escape sequence \', and the double quote " shall be preceded by a \.
The reason is that code points outside the basic multi-lingual plane are ill-formed in char16_t character literals:
A character literal that begins with the letter u, such as u'y', is a character literal of type char16_t. The value of a char16_t literal containing a single c-char is equal to its ISO 10646 code point value, provided that the code point is representable with a single 16-bit code unit. (That is, provided it is a basic multi-lingual plane code point.) If the value is not representable within 16 bits, the program is ill-formed.
It should be clarified that this restriction does not apply to char16_t string literals.
Proposed resolution (February, 2014):
Change 5.13.5 [lex.string] paragraph 16 as follows:
Escape sequences and universal-character-names in non-raw string literals have the same meaning as in character literals (5.13.3 [lex.ccon]), except that the single quote ' is representable either by itself or by the escape sequence \', and the double quote " shall be preceded by a \, and except that a universal-character-name in a char16_t string literal may yield a surrogate pair. In a narrow string literal...
[Moved to DR at the November, 2014 meeting.]
In explaining the relationship between preprocessing tokens and tokens, 5.5 [lex.pptoken] paragraph 4 contains the following example:
[Example: The program fragment 1Ex is parsed as a preprocessing number token (one that is not a valid floating or integer literal token), even though a parse as the pair of preprocessing tokens 1 and Ex might produce a valid expression (for example, if Ex were a macro defined as +1).
This analysis does not take into account the addition of user-defined literals. In fact, 1Ex matches the rule for a user-defined-integer-literal, which is then ill-formed because it uses a reserved ud-suffix (5.13.9 [lex.ext] paragraph 10), as well as (presumably) because of a lookup failure for a matching literal operator, raw literal operator, or literal operator template.
More generally, it might be preferable to eliminate the restriction on the use of a reserved ud-suffix and rely simply on the fact that it is ill-formed to declare a literal operator, raw literal operator, or literal operator template with a reserved literal suffix identifier (16.4.5.3.6 [usrlit.suffix], cf 12.6 [over.literal] paragraph 1).
Proposed resolution (June, 2014):
Change 5.5 [lex.pptoken] paragraph 4 as follows:
[Example: The program fragment1Ex0xe+foo is parsed as a preprocessing number token (one that is not a valid floating or integer literal token), even though a parse asthe pair ofthree preprocessing tokens10xe, +, andExfoo might produce a valid expression (for example, ifExfoo were a macro defined as+1). Similarly, the program fragment 1E1 is parsed as a preprocessing number (one that is a valid floating literal token), whether or not E is a macro name. —end example]
Delete 5.13.9 [lex.ext] paragraph 10:
Some identifiers appearing as ud-suffixes are reserved for future standardization (16.4.5.3.6 [usrlit.suffix]). A program containing such a ud-suffix is ill-formed, no diagnostic required.
Change 12.6 [over.literal] paragraph 1 as follows:
The string-literal or user-defined-string-literal in a literal-operator-id shall have no encoding-prefix and shall contain no characters other than the implicit terminating '\0'. The ud-suffix of the user-defined-string-literal or the identifier in a literal-operator-id is called a literal suffix identifier.[Note: someSome literal suffix identifiers are reserved for future standardization; see 16.4.5.3.6 [usrlit.suffix].—end note]A declaration whose literal-operator-id uses such a literal suffix identifier is ill-formed; no diagnostic required.
Change 16.4.5.3.6 [usrlit.suffix] paragraph 1 as follows:
Literal suffix identifiers (12.6 [over.literal]) that do not start with an underscore are reserved for future standardization.
Additional note, May, 2014:
It has been suggested that the change to 5.5 [lex.pptoken] paragraph 4 in the proposed resolution would be simpler and better if it did not venture into questions about user-defined literals but simply relied on a string that is a valid pp-number but not a valid floating-point number, as was the case before the introduction of user-defined literals, e.g., 1.2.3.4. The issue has been returned to "review" status for discussion of this suggestion.
[Moved to DR at the November, 2014 meeting.]
Sections 13.9.3 [temp.explicit] and 13.9.4 [temp.expl.spec] describe cases of explicit instantiation directives and explicit specializations, respectively, that are not definitions. However, the description in 6.2 [basic.def] does not include these distinctions, classifying all declarations other than those listed as definitions. These should be harmonized.
Proposed Resolution (July, 2014):
Change 6.2 [basic.def] paragraph 2 as follows:
A declaration is a definition unless it... an empty-declaration ( 9.1 [dcl.pre]),ora using-directive (9.9.4 [namespace.udir]), an explicit instantiation declaration (13.9.3 [temp.explicit]), or an explicit specialization (13.9.4 [temp.expl.spec]) whose declaration is not a definition.
[Moved to DR at the November, 2014 meeting.]
According to 6.3 [basic.def.odr] paragraph 3,
A function whose name appears as a potentially-evaluated expression is odr-used if it is the unique lookup result or the selected member of a set of overloaded functions (6.5 [basic.lookup], 12.2 [over.match], 12.3 [over.over]), unless it is a pure virtual function and its name is not explicitly qualified.
In the following example, consequently, S::f is odr-used but not defined, and (because it is an undefined odr-used inline function) a diagnostic is required:
namespace { struct S { inline virtual void f() = 0; }; void (S::*p) = &S::f; }
However, S::f cannot be called through such a pointer-to-member, so forming a pointer-to-member should not cause a pure virtual function to be odr-used. There is implementation divergence on this point.
Proposed resolution (April, 2013):
Change 6.3 [basic.def.odr] paragraph 3 as follows:
...A virtual member function is odr-used if it is not pure. A function whose name appears as a potentially-evaluated expression is odr-used if it is the unique lookup result or the selected member of a set of overloaded functions (6.5 [basic.lookup], 12.2 [over.match], 12.3 [over.over]), unless it is a pure virtual function and either its name is not explicitly qualified or the expression forms a pointer to member (5.3.1). [Note:...
[Moved to DR at the May, 2015 meeting.]
The definition of the potential results of an expression in 6.3 [basic.def.odr] paragraph 2 do not, but should, include the subscript operator.
Proposed resolution (November, 2014):
Change 6.3 [basic.def.odr] paragraph 2 as follows:
...The set of potential results of an expression e is defined as follows:
If e is an id-expression (_N4567_.5.1.1 [expr.prim.general]), the set contains only e.
If e is a subscripting operation (7.6.1.2 [expr.sub]) with an array operand, the set contains that operand.
If e is a class member access...
[Adopted at the February, 2016 meeting.]
The example in 6.3 [basic.def.odr] bullet 6.6 reads,
//translation unit 1: struct X { X(int); X(int, int); }; X::X(int = 0) { } class D: public X { }; D d2; // X(int) called by D() //translation unit 2: struct X { X(int); X(int, int); }; X::X(int = 0, int = 0) { } class D: public X { }; // X(int, int) called by D(); // D()'s implicit definition // violates the ODR
Creating a special member function via default arguments added in an out-of-class definition, as is done here, is no longer permitted, so at a minimum the example should be removed. It is not clear whether there remain any cases to which the normative wording of bullet 6.6 would apply:
if D is a class with an implicitly-declared constructor (11.4.5 [class.ctor]), it is as if the constructor was implicitly defined in every translation unit where it is odr-used, and the implicit definition in every translation unit shall call the same constructor for a base class or a class member of D.
If not, the entire bullet should be removed.
Proposed resolution (September, 2015):
Change 6.3 [basic.def.odr] bullet 6.6 as follows:
if D is a class with an implicitly-declared constructor
(11.4.5 [class.ctor]), it is as if the constructor was implicitly
defined in every translation unit where it is odr-used, and the implicit
definition in every translation unit shall call the same constructor for a
base class or a class member subobject
of D. [Example:
//translation unit 1: struct X { X(int, int); X(int, int, int); }; X::X(int, int = 0) { } class D: public X{ X x = 0; }; D d2; // X(int, int) called by D() //translation unit 2: struct X { X(int, int); X(int, int, int); }; X::X(int, int = 0, int = 0) { } class D: public X{ X x = 0; }; // X(int, int, int) called by D(); // D()'s implicit definition // violates the ODR
—end example]
[Adopted at the February, 2016 meeting.]
In an example like:
extern int i; namespace { constexpr int& r = i; } inline int f() { return r; }
use of f() in multiple translation units results in an ODR violation because of use of the internal-linkage reference r. It would be helpful if 6.3 [basic.def.odr] paragraph 6 could be amended to “look through” a constexpr reference in determining whether an inline function violates the ODR or not.
Proposed resolution (January, 2016):
Change 6.3 [basic.def.odr] bullet 6.2 as follows, dividing the running text into a bulleted list:
...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 6.5 [basic.lookup], shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution (12.2 [over.match]) and after matching of partial template specialization (13.10.4 [temp.over]), except that a name can refer to
a non-volatile const object with internal or no linkage if the object
has the same literal type in all definitions of D,
and
the objectis initialized with a constant expression (7.7 [expr.const]),and
the objectis not odr-used, and
the objecthas the same value in all definitions of D,or
a reference with internal or no linkage initialized with a constant expression such that the reference refers to the same entity in all definitions of D;
and
in each definition of D, corresponding entities...
[Moved to DR at the May, 2015 meeting.]
The rules for class scope in 6.4.7 [basic.scope.class] paragraph 1 include the following:
A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule.
If reordering member declarations in a class yields an alternate valid program under (1) and (2), the program is ill-formed, no diagnostic is required.
The need for rule #3 is not clear; it would seem that any otherwise-valid reordering would have to violate rule #2 in order to yield a different interpretation. Taken literally, rule #3 would also apply to simply reordering nonstatic data members with no name dependencies at all. Can it be simply removed?
Proposed resolution (June, 2014):
Delete the third item of 6.4.7 [basic.scope.class] paragraph 1 and renumber the succeeding items:
If reordering member declarations in a
class yields an alternate valid program under (1) and (2), the
program is ill-formed, no diagnostic is
required.
[Moved to DR at the November, 2014 meeting.]
One of the forms of pseudo-destructor-name is
Presumably the intent of this form is to allow the nested-name-specifier to designate a namespace; otherwise the
production would be used.
Since one of the forms of nested-name-specifier is
one can write something like p->decltype(x)::~Y(). However, the lookup rules in 6.5.5 [basic.lookup.qual] paragraph 6 are inappropriate for the decltype-specifier case:
If a pseudo-destructor-name (_N4778_.7.6.1.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.
Since this form appears to be useless (use of a decltype-specifier is permitted after a ~, but only with no nested-name-specifer — but see issue 1586), perhaps it should be made ill-formed.
Proposed resolution (February, 2014):
Change the grammar in 7.6.1 [expr.post] paragraph 1 as follows:
[Moved to DR at the November, 2014 meeting.]
In C++03, all namespace-scope names had external linkage unless explicitly declared otherwise (via static, const, or as a member of an anonymous union). C++11 now specifies that members of an unnamed namespace have internal linkage (see issue 1113). This change invalidated a number of assumptions scattered throughout the Standard that need to be adjusted:
6.6 [basic.link] paragraph 5 says,
a member function, static data member, a named class or enumeration of class scope, or an unnamed class or enumeration defined in a class-scope typedef declaration such that the class or enumeration has the typedef name for linkage purposes (9.2.4 [dcl.typedef]), has external linkage if the name of the class has external linkage.
There is no specification for the linkage of such members of a class with internal linkage. Formally, at least, that leads to the statement in paragraph 8 that such members have no linkage. This omission also contradicts the note in 11.4.2 [class.mfct] paragraph 3:
[Note: Member functions of a class in namespace scope have external linkage. Member functions of a local class (11.6 [class.local]) have no linkage. See 6.6 [basic.link]. —end note]
as well as the statement in 11.4.9.3 [class.static.data] paragraph 5,
Static data members of a class in namespace scope have external linkage (6.6 [basic.link]).
The footnote in 6.6 [basic.link] paragraph 8 says,
A class template always has external linkage, and the requirements of 13.4.2 [temp.arg.type] and 13.4.3 [temp.arg.nontype] ensure that the template arguments will also have appropriate linkage.
This is incorrect, since templates in unnamed namespaces now have internal linkage and template arguments are no longer required to have external linkage.
The statement in 9.2.2 [dcl.stc] paragraph 7 is now false:
A name declared in a namespace scope without a storage-class-specifier has external linkage unless it has internal linkage because of a previous declaration and provided it is not declared const.
The entire treatment of unique in 9.9.2.2 [namespace.unnamed] is no longer necessary, and the footnote is incorrect:
Although entities in an unnamed namespace might have external linkage, they are effectively qualified by a name unique to their translation unit and therefore can never be seen from any other translation unit.
Names in unnamed namespaces never have external linkage.
According to 11.8.4 [class.friend] paragraph 4,
A function first declared in a friend declaration has external linkage (6.6 [basic.link]).
This presumably is incorrect for a class that is a member of an unnamed namespace.
According to Clause 13 [temp] paragraph 4,
A non-member function template can have internal linkage; any other template name shall have external linkage.
Taken literally, this would mean that a template could not be a member of an unnamed namespace.
Proposed resolution (April, 2013):
Change 6.6 [basic.link] paragraph 5 as follows:
In addition, a member function, static data member, a named class or enumeration of class scope, or an unnamed class or enumeration defined in a class-scope typedef declaration such that the class or enumeration has the typedef name for linkage purposes (9.2.4 [dcl.typedef]), hasexternal linkage if the name of the class has external linkagethe same linkage, if any, as the name of the class of which it is a member.
Change the footnote in 6.6 [basic.link] paragraph 8 as follows:
33) A class templatealways has external linkage, and the requirements of 13.4.2 [temp.arg.type] and 13.4.3 [temp.arg.nontype] ensure that the template arguments will also have appropriate linkagehas the linkage of the innermost enclosing class or namespace in which it is declared.
Change 9.9.2.2 [namespace.unnamed] paragraph 1 as follows:
An unnamed-namespace-definition behaves as if it were replaced by
inlineopt namespace unique { /* empty body */ } using namespace unique ; namespace unique { namespace-body }where inline appears if and only if it appears in the unnamed-namespace-definition
,and all occurrences of unique in a translation unit are replaced by the same identifier, and this identifier differs from all other identifiers in theentire program. [Footnote: Although entities in an unnamed namespace might have external linkage, they are effectively qualified by a name unique to their translation unit and therefore can never be seen from any other translation unit. —end footnote]translation unit. [Example:...
Change the note in 11.4.2 [class.mfct] paragraph 3 as follows:
[Note: Member functions of a class in namespace scope haveexternal linkagethe linkage of that class. Member functions of a local class (11.6 [class.local]) have no linkage. See 6.6 [basic.link]. —end note]
Change 11.4.9.3 [class.static.data] paragraph 5 as follows:
Static data members of a class in namespace scope haveexternal linkagethe linkage of that class (6.6 [basic.link]).
Change 11.8.4 [class.friend] paragraph 4 as follows:
A function first declared in a friend declaration hasexternal linkagethe linkage of the namespace of which it is a member (6.6 [basic.link]). Otherwise, the function retains its previous linkage (9.2.2 [dcl.stc]).
Change Clause 13 [temp] paragraph 4 as follows:
A template name has linkage (6.6 [basic.link]).A non-member function template can have internal linkage; any other template name shall have external linkage.Specializations (explicit or implicit) of a template that has internal linkage are distinct from all specializations in other translation units...
[Moved to DR at the November, 2014 meeting.]
According to 6.6 [basic.link] paragraph 3,
A name having namespace scope (6.4.6 [basic.scope.namespace]) has internal linkage if it is the name of
...
a non-volatile variable that is explicitly declared const or constexpr and neither explicitly declared extern nor previously declared to have external linkage; or
...
It would be more precise and less confusing if the phrase “explicitly declared const” were replaced by saying that its type is const-qualified. This change would also allow removal of the reference to constexpr, which was added by issue 1112 because constexpr variables are implicitly const-qualified but not covered by the “explicitly declared” phrasing.
Proposed resolution (September, 2013):
Change the second bullet of 6.6 [basic.link] paragraph 3 as follows:
a non-volatile variable that is explicitly
declared const or constexpr and of
non-volatile const-qualified type that is neither explicitly
declared extern nor previously declared to have external linkage;
or
[Adopted at the June, 2016 meeting as document P0137R1.]
According to 6.7.2 [intro.object] paragraph 1,
An object is created by a definition (6.2 [basic.def]), by a new-expression (7.6.2.8 [expr.new]) or by the implementation (6.7.7 [class.temporary]) when needed.
This should probably also include a throw-expression, with a cross-reference to 14.2 [except.throw].
Notes from the October, 2015 meeting:
This issue is expected to be resolved by the resolution of issue 1776.
Proposed resolution (June, 2016):
This issues is resolved by the resolution of issue 1776.
[Adopted at the June, 2016 meeting as document P0137R1.]
N3092 comment US 27Related to issue 1027, consider:
int f() { union U { double d; } u1, u2; (int&)u1.d = 1; u2 = u1; return (int&)u2.d; }
Does this involve undefined behavior? 6.7.4 [basic.life] paragraph 4 seems to say that it's OK to clobber u1 with an int object. Then union assignment copies the object representation, possibly creating an int object in u2 and making the return statement well-defined. If this is well-defined, compilers are significantly limited in the assumptions they can make about type aliasing. On the other hand, the variant where U has an array of unsigned char member must be well-defined in order to support std::aligned_storage.
Suggested resolution: Clarify that this case is undefined, but that adding an array of unsigned char to union U would make it well-defined — if a storage location is allocated with a particular type, it should be undefined to create an object in that storage if it would be undefined to access the stored value of the object through the allocated type.
(See also issues 1027 and 1338.)
Proposed resolution (August, 2010):
Change 6.7.4 [basic.life] paragraph 1 as follows:
...The lifetime of an object of type T begins when storage with the proper alignment and size for type T is obtained, and either:
storage with the proper alignment and size for type T is obtained, andif the object has non-trivial initialization, its initialization is complete
., orif T is trivially copyable, the object representation of another T object is copied into the storage.
The lifetime of an object of type T ends...
Change 6.7.4 [basic.life] paragraph 4 as follows:
A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression (7.6.2.9 [expr.delete]) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior. If a program obtains storage for an object of a particular type A (e.g. with a variable definition or new-expression) and later reuses that storage for an object of another type B such that accessing the stored value of the B object through a glvalue of type A would have undefined behavior (7.2.1 [basic.lval]), the behavior is undefined. [Example:
int i; (double&)i = 1.0; // undefined behavior struct S { unsigned char alignas(double) ar[sizeof (double)]; } s; (double&)s = 1.0; // OK, can access stored double through s because it has an unsigned char subobject—end example]
Change 7.2.1 [basic.lval] paragraph 10 as follows:
If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined52:
the dynamic type of the object,
a cv-qualified version of the dynamic type of the object,
a type similar (as defined in 7.3.6 [conv.qual]) to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
a char or unsigned char type,
an aggregate or union type that includes one of the aforementioned types among its elements, bases, or non-static data members (including, recursively, an element, base, or non-static data member of a subaggregate, base, or contained union)
,.
a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
a char or unsigned char type.
This resolution also resolves issue 1027.
Additional note (August, 2012):
Concerns have been raised regarding the interaction of this change with facilities like std::aligned_storage and memory pools. Care must be taken to achieve the proper balance between supporting type-based optimization techniques and allowing practical storage management.
Additional note (January, 2013):
Several questions have been raised about the wording above . In particular:
Since aggregates and unions cannot have base classes, why are base classes mentioned?
Since unions can now have special member functions, is it still valid to assume that they alias all their member types?
Shouldn't standard-layout classes also be considered and not just aggregates?
Additional note, February, 2014:
According to 6.7.2 [intro.object] paragraph 1, an object (i.e., a “region of storage”) is created by one of only three means:
An object is created by a definition (6.2 [basic.def]), by a new-expression (7.6.2.8 [expr.new]) or by the implementation (6.7.7 [class.temporary]) when needed. The properties of an object are determined when the object is created.
This does not allow for obtaining the storage in other ways, such as via malloc, in determining the lifetime of an object with vacuous initialization (6.7.4 [basic.life] paragraph 1).
In addition, 6.7.4 [basic.life] paragraph 1 does not require the storage obtained for an object of type T to be accessed via an lvalue of type T in order to be considered an object of that type. The treatment of “effective type” by C may be helpful here.
Additional note, May, 2015:
We never say what the active member of a union is, how it can be changed, and so on. The Standard doesn't make clear whether the following is valid:
union U { int a; short b; } u = { 0 }; int x = u.a; // presumably this is OK, but we never say that a is the active member u.b = 0; // not clear whether this is valid
The closest we come to talking about this is the non-normative example in 11.5 [class.union] paragraph 4, which suggests that a placement new is needed.
It's also not clear whether a has two subobjects or only one (corresponding to the active member).
[Adopted at the February, 2016 meeting.]
The note in 6.7.4 [basic.life] paragraph 2 reads,
[Note: The lifetime of an array object starts as soon as storage with proper size and alignment is obtained, and its lifetime ends when the storage which the array occupies is reused or released. 11.9.3 [class.base.init] describes the lifetime of base and member subobjects. —end note]
This wording reflects an earlier version of paragraph 1 that deferred the start of an object's lifetime only for initialization of objects of class type. The note simply emphasized the implication that that the lifetime of a POD type or an array began immediately, even if lifetime of an array's elements began later.
The decomposition of POD types removed the mention of PODs, leaving only the array types, and when the normative text was changed to include aggregates whose members have non-trivial initialization, the note was overlooked.
It is not clear whether it would be better to update the note to emphasize the distinction between aggregates with non-trivial initialization and those without or to delete it entirely.
A possible related normative change to consider is whether the specification of paragraph 1 is sufficiently clear with respect to multidimensional arrays. The current definition of “non-trivial initialization” is:
An object is said to have non-trivial initialization if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial default constructor.
Presumably the top-level array of an N-dimensional array whose ultimate element type is a class type with non-trivial initialization would also have non-trivial initialization, but it's not clear that this wording says that.
A more radical change that came up in the discussion was whether the undefined behavior resulting from an lvalue-to-rvalue conversion of an uninitialized object in 7.3.2 [conv.lval] paragraph 1 would be better dealt with as a lifetime violation instead.
Proposed resolution (October, 2015):
Change 6.7.4 [basic.life] paragraphs 1 and 2 as follows:
The lifetime of an object is a runtime property of the object. An object is said to have non-vacuous initialization if it is of a class or aggregate type and it or one of its
memberssubobjects is initialized by a constructor other than a trivial default constructor. [Note: initialization...[Note:
The lifetime of an array object starts as soon as storage with proper size and alignment is obtained, and its lifetime ends when the storage which the array occupies is reused or released.11.9.3 [class.base.init] describes the lifetime of base and member subobjects. —end note]
[Moved to DR at the November, 2014 meeting.]
The description of is_trivially_constructible in 21.3.5.4 [meta.unary.prop] paragraph 3 says,
is_constructible<T, Args...>::value is true and the variable definition for is_constructible, as defined below, is known to call no operation that is not trivial ( 6.8 [basic.types], 11.4.4 [special]).
This risks confusion when compared with the wording in 6.7.4 [basic.life] paragraph 1,
An object is said to have non-trivial initialization if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial default constructor. [Note: initialization by a trivial copy/move constructor is non-trivial initialization. —end note]
The latter was written long before “trivial” became an important concept in its own right and uses the term differently from how it is used elsewhere in the Standard (as evidenced by the note referring to copy/move construction). The intent is to capture the idea that there is some actual initialization occurring; it should be rephrased to avoid the potential of confusion with the usage of “trivial” elsewhere.
Proposed resolution (February, 2014):
Change 6.7.4 [basic.life] paragraph 1 as follows:
The lifetime of an object is a runtime property of the object. An object is said to have
non-trivial initializationnon-vacuous initialization if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial default constructor. [Note: initialization by a trivial copy/move constructor isnon-trivialnon-vacuous initialization. —end note] The lifetime of an object of type T begins when:
storage with the proper alignment and size for type T is obtained, and
if the object has
non-trivialnon-vacuous initialization, its initialization is complete.The lifetime of an object...
[Adopted at the June, 2016 meeting as document P0137R1.]
N3690 comment FI 15The rules given in 6.7.4 [basic.life] paragraph 7 for when an object's lifetime can be ended and a new object created in its storage include the following restriction:
the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type
The intent of this restriction is to permit optimizers to rely upon the original values of const and reference members in their analysis of subsequent code. However, this makes it difficult to implement certain desirable functionality such as optional<T>; see this discussion for more details.
This rule should be reconsidered, at least as far as it applies to unions. If it is decided to keep the rule, acceptable programming techniques for writing safe code when replacing such objects should be outlined in a note.
(See also issue 1404, which will become moot if the restriction is lifted.)
Notes from the October, 2015 meeting:
See also paper P0137 and issue 2182.
Notes from the February, 2017 meeting.
The resolution of this issue also resolves issues 636 and 2151.
[Adopted at the February, 2016 meeting.]
According to 6.7.6 [basic.stc] paragraph 3,
The storage duration categories apply to references as well. The lifetime of a reference is its storage duration.
This is clearly not correct; references can have static storage duration but be dynamically initialized. Consider an example like:
extern int& r1; int& f(); int& r2 = r1; // #1 int& r1 = f(); int i = r2; // #2
r1 is not initialized until after its use at #1, so the initialization of r2 should produce undefined behavior, as should the use of r2 at #2.
The description of the lifetime of a reference should be deleted from 6.7.6 [basic.stc] and it should be described properly in 6.7.4 [basic.life].
Proposed resolution (September, 2015):
Change 6.7.6 [basic.stc] paragraph 3 as follows:
The storage duration categories apply to references as well.The lifetime of a reference is its storage duration.
Change 6.7.4 [basic.life] paragraph 1 as follows:
The lifetime of an object or reference is a runtime property of the object or reference. An object is said to have...
Add the following as a new paragraph following 6.7.6 [basic.stc] paragraph 2:
[Note: The lifetime of an array object starts as soon as storage with proper size and alignment is obtained, and its lifetime ends when the storage which the array occupies is reused or released. 11.9.3 [class.base.init] describes the lifetime of base and member subobjects. —end note]
The lifetime of a reference begins when its initialization is complete. The lifetime of a reference ends as if it were a scalar object.
Change 6.7.4 [basic.life] paragraph 3 as follows:
The properties ascribed to objects and references throughout this International Standard apply for a given object or reference only during its lifetime. [Note:...
Change Clause 7 [expr] paragraph 5 as follows:
If an expression initially has the type “reference to T” (9.3.4.3 [dcl.ref], 9.5.4 [dcl.init.ref]), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression. [Note: Before the lifetime of the reference has started or after it has ended, the behavior is undefined (see 6.7.4 [basic.life]). —end note]
Drafting note: there is no change to 6.7.4 [basic.life] paragraph 4:
A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type...
[Moved to DR at the October, 2015 meeting.]
According to 6.7.6.1 [basic.stc.general] paragraph 1,
The storage duration of member subobjects, base class subobjects and array elements is that of their complete object (6.7.2 [intro.object]).
This wording does not cover member references, which should also have the same storage duration as the object of which they are a member.
Proposed resolution (May, 2015):
Change 6.7.6.1 [basic.stc.general] paragraph 1 as follows:
The storage duration ofmembersubobjects, base class subobjectsandarray elementsreference members is that of their complete object (6.7.2 [intro.object]).
[Moved to DR at the May, 2015 meeting.]
According to 6.7.6.4 [basic.stc.auto] paragraph 3,
If a variable with automatic storage duration has initialization or a destructor with side effects, it shall not be destroyed before the end of its block, nor shall it be eliminated as an optimization even if it appears to be unused, except that a class object or its copy/move may be eliminated as specified in 11.4.5.3 [class.copy.ctor].
This is intended to be a requirement for the implementation, but it could be read as prohibiting the reuse of the storage of an automatic variable by the program using a placement new-expression.
Proposed resolution (November, 2014):
Change 6.7.6.4 [basic.stc.auto] paragraph 3 as follows:
If a variable with automatic storage duration has initialization or a destructor with side effects,itan implementation shall notbe destroyeddestroy it before the end of its block,norshall it be eliminatedeliminate it as an optimization, even if it appears to be unused, except that a class object or its copy/move may be eliminated as specified in 11.4.5.3 [class.copy.ctor].
[Moved to DR at the November, 2014 meeting.]
In 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 2, allocation functions are constrained to return a pointer that is different from any previously returned pointer that has not been passed to a deallocation function. This does not, for instance, prohibit returning a pointer to storage that is part of another object, for example, a pool of storage. The potential implications of this for aliasing should be spelled out.
(See also issues 1027 and 1116.)
Additional note (March, 2013):
One possibility to allow reasonable optimizations would be to require that allocation packages hide their storage in file-static variables, perhaps by adding wording such as:
Furthermore, p0 shall point to an object distinct from any other object that is accessible outside the implementation of the allocation and deallocation functions.
Additional note, April, 2013:
Concern was expressed that a pool class might provide an interface for iterating over all the pointers that were given out from the pool, and this usage should be supported.
Notes from the September, 2013 meeting:
CWG agreed that changes for this issue should apply only to non-placement forms.
Proposed resolution (February, 2014):
Change 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 2 as follows:
...If the request succeeds, the value returned shall be a non-null pointer value (7.3.12 [conv.ptr]) p0 different from any previously returned value p1, unless that value p1 was subsequently passed to an operator delete. Furthermore, for the library allocation functions in 17.6.3.2 [new.delete.single] and 17.6.3.3 [new.delete.array], p0 shall point to a block of storage disjoint from the storage for any other object accessible to the caller. The effect of indirecting through a pointer returned as a request for zero size is undefined.36
[Moved to DR at the November, 2014 meeting.]
Presumably a temporary bound to a reference in a non-static data member initializer should be treated analogously with what happens in a ctor-initializer, but the current wording of 6.7.7 [class.temporary] paragraph 5 is not clear on this point.
See also issue 1815 for similar questions regarding aggregate initialization.
Proposed resolution (June, 2014):
Add the following after 9.5.2 [dcl.init.aggr] paragraph 7:
If a reference member is initialized from its brace-or-equal-initializer and a potentially-evaluated subexpression thereof is an aggregate initialization that would use that brace-or-equal-initializer, the program is ill-formed. [Example:
struct A; extern A a; struct A { const A& a1 { A{a,a} }; // OK const A& a2 { A{} }; // error }; A a{a,a}; // OKIf an aggregate class C contains a subaggregate...
Delete the first bullet of 6.7.7 [class.temporary] paragraph 5:
The second context is when a reference is bound to a temporary.117 The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:
A temporary bound to a reference member in a constructor's ctor-initializer (11.9.3 [class.base.init]) persists until the constructor exits....
Insert the following as a new paragraph after 11.9.3 [class.base.init] paragraph 7:
A temporary expression bound to a reference member in a mem-initializer is ill-formed. [Example:
struct A { A() : v(42) { } // error const int& v; };
—end example]
In a non-delegating constructor, if a given potentially constructed subobject...
Insert the following as a new paragraph after 11.9.3 [class.base.init] paragraph 9:
A temporary expression bound to a reference member from a brace-or-equal-initializer is ill-formed. [Example:
struct A { A() = default; // OK A(int v) : v(v) { } // OK const int& v = 42; // OK }; A a1; // error: ill-formed binding of temporary to reference A a2(1); // OK, unfortunately—end example]
In a non-delegating constructor, the destructor for each potentially constructed subobject...
This resolution also resolves issue 1815.
[Adopted as paper P0135R1 at the June, 2016 meeting.]
In an example like,
struct S { ~S(); }; struct X { X(); X(const X&); }; struct T { S &&s; X x; }; void f(); void g() { T t = T{ {}, {} }; f(); }
it appears that the current wording allows two ways of handling this:
The copy to t in g is not elided. X(const X&) is called, then ~S() is called, then f() is called.
However, EDG and g++ produce a third behavior: they do not call X(const X&), but they destroy the S() temporary at the end of its full-expression. The current wording does not appear to permit this behavior, but it seems preferable that lifetime extension does not depend on whether copy elision is done.
[Adopted at the February, 2016 meeting.]
The lifetime of temporaries introduced for default arguments in array copying is not specified clearly. Presumably it should be treated like default arguments in default constructors (6.7.7 [class.temporary] paragraph 4), which deletes each element's set of default argument temporaries before construction of the next element.
Proposed resolution (September, 2015):
Change 6.7.7 [class.temporary] paragraphs 4-5 as follows:
There are
twothree contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array with no corresponding initializer (9.5 [dcl.init]). The second context is when a copy constructor is called to copy an element of an array while the entire array is copied (7.5.6 [expr.prim.lambda], 11.4.5.3 [class.copy.ctor]).IfIn either case, if the constructor has one or more default arguments, the destruction of every temporary created in a default argument is sequenced before the construction of the next array element, if any.The
secondthird context is when a reference is bound to a temporary.117 The temporary to which the reference is bound...
[Moved to DR at the May, 2015 meeting.]
6.8 [basic.types] paragraph 10 isn't clear whether a const-qualified class type can be a literal type, and the same for const void.
Proposed resolution (April, 2015):
Change 6.8 [basic.types] paragraph 10 as follows:
A type is a literal type if it is:
possibly cv-qualified void; or
a scalar type; or
a reference type; or
an array of literal type; or
a possibly cv-qualified class type ( Clause 11 [class]) that has all of the following properties:
...
[Adopted at the February, 2016 meeting.]
According to 6.8 [basic.types] bullet 10.5.3, all the members of a class type must be of non-volatile literal types. This seems overly constraining for unions; it would seem to be sufficient if at least one of its non-static members were of a literal type.
Proposed resolution (September, 2015):
Change 6.8 [basic.types] bullet 10.5 as follows:
A type is a literal type if it is:
...
a possibly cv-qualified class type (Clause 11 [class]) that has all of the following properties:
it has a trivial destructor,
it is an aggregate type (9.5.2 [dcl.init.aggr]) or has at least one constexpr constructor or constructor template that is not a copy or move constructor,
andif it is a union, at least one of its non-static data members is of non-volatile literal type, and
if it is not a union, all of its non-static data members and base classes are of non-volatile literal types.
[Moved to DR at the November, 2014 meeting.]
According to 6.8.2 [basic.fundamental] paragraph 1,
For unsigned narrow character types, all possible bit patterns of the value representation represent numbers.
Presumably the intent is that each distinct bit pattern represents a different number, but this should be made explicit.
Proposed resolution (February, 2014):
Change 6.8.2 [basic.fundamental] paragraph 1 as follows:
...For unsigned narrow character types,alleach possible bitpatternspattern of the value representationrepresent numbersrepresents a distinct number. These requirements...
[Moved to DR at the October, 2015 meeting.]
According to 6.8.4 [basic.compound] paragraph 3,
The type of a pointer to void or a pointer to an object type is called an object pointer type. [Note: A pointer to void does not have a pointer-to-object type, however, because void is not an object type. —end note]
This wording excludes cv-qualified void types. There are other references in the Standard to “void type” that are apparently intended to include cv-qualified versions as well.
Proposed resolution (May, 2015):
Change 6.8 [basic.types] paragraph 5 as follows:
...Incompletely-defined object types andthe void typescv void are incomplete types (6.8.2 [basic.fundamental])...
Change 6.8 [basic.types] paragraph 8 as follows:
An object type is a (possibly cv-qualified) type that is not a function type, not a reference type, and nota void typecv void.
Change 6.8.2 [basic.fundamental] paragraph 9 as follows:
The void type has an empty set of values. TheA type cv voidtypeis an incomplete type that cannot be completed; such a type has an empty set of values. It is used as the return type for functions that do not return a value. Any expression can be explicitly converted to type cv void (7.6.3 [expr.cast]). An expression of type cv void shall be used only as an expression statement (8.3 [stmt.expr]), as an operand of a comma expression (7.6.20 [expr.comma]), as a second or third operand of ?: (7.6.16 [expr.cond]), as the operand of typeid, noexcept, or decltype, as the expression in a return statement (8.7.4 [stmt.return]) for a function with the return type cv void, or as the operand of an explicit conversion to type cv void.
Change bullet 1.3 of 6.8.4 [basic.compound] as follows:
pointers to cv void or objects or functions (including static members of classes) of a given type, 9.3.4.2 [dcl.ptr];
Change 6.8.4 [basic.compound] paragraph 3 as follows:
The type of a pointer to cv void or a pointer to an object type is called an object pointer type. [Note:...
[Moved to DR at the October, 2015 meeting.]
The term “sequenced after” is used in both the core and library clauses instead of the more-correct “sequenced before.”
Proposed resolution (May, 2015):
Change 6.9.1 [intro.execution] paragraph 13 as follows:
Sequenced before is an asymmetric, transitive, pair-wise relation between evaluations executed by a single thread (6.9.2 [intro.multithread]), which induces a partial order among those evaluations. Given any two evaluations A and B, if A is sequenced before B (or, equivalently, B is sequenced after A), then the execution of A shall precede the execution of B. If A is not sequenced before B...
Change 6.9.2 [intro.multithread] paragraph 14 as follows:
An evaluation A happens before an evaluation B (or, equivalently, B happens after A) if:...
Change 6.9.1 [intro.execution] paragraph 15 as follows:
...Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called functionFor each function invocation F, for every evaluation A that occurs within F and every evaluation B that does not occur within F but is evaluated on the same thread and as part of the same signal handler (if any), either A is sequenced before B or B is sequenced before A.9 [Note: if A and B would not otherwise be sequenced then they are indeterminately sequenced. —end note] Several contexts...
Change 6.9.3.2 [basic.start.static] paragraph 4 as follows:
It is implementation-defined whether the dynamic initialization of a non-local variable with static storage durationis donehappens before the first statement of main. If the initialization is deferred tosome point in timehappen after the first statement of main, itshall occurhappens before the first odr-use (6.3 [basic.def.odr]) of any function or variable...
Change 6.9.3.2 [basic.start.static] paragraph 5 as follows:
It is implementation-defined whether the dynamic initialization of a non-local variable with static or thread storage duration isdonesequenced before the first statement of the initial function of the thread. If the initialization is deferred to some point in time sequenced after the first statement of the initial function of the thread, itshall occuris sequenced before the first odr-use (6.3 [basic.def.odr]) of any variable with thread storage duration defined in the same translation unit as the variable to be initialized.
Change 8.6.4 [stmt.for] paragraph 1 as follows:
...[Note: Thus the first statement specifies initialization for the loop; the condition (8.5 [stmt.select]) specifies a test,madesequenced before each iteration, such that the loop is exited when the condition becomes false; the expression often specifies incrementing that isdonesequenced after each iteration. —end note]
Add the following as a new paragraph at the end of Clause 14 [except]:
In this section, “before” and “after” refer to the “sequenced before” relation (6.9.1 [intro.execution]).
[Adopted at the February, 2016 meeting.]
According to 6.9.1 [intro.execution] paragraph 15,
If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, and they are not potentially concurrent (6.9.2 [intro.multithread]), the behavior is undefined.
Should this refer to “memory location,” which also encompasses contiguous bit-fields, as the definition of data races in 6.9.2 [intro.multithread] does? For example,
struct S { int x : 4; int y : 4; int z : 4; }; void f(int, int, int); int g(int, S&); int main(int argc, char ** argv) { S s = { argc, argc+1, argc+2 }; f(++s.x, g(++s.y, s), ++s.z); }
Proposed resolution (February, 2016):
Change 6.9.1 [intro.execution] paragraph 15 as follows:
...If a side effect on ascalar objectmemory location (6.7.1 [intro.memory]) is unsequenced relative to either another side effect on the samescalar objectmemory location or a value computation using the value of any object in the samescalar objectmemory location, and they are not potentially concurrent (6.9.2 [intro.multithread]), the behavior is undefined. [Note: The next section...
[Moved to DR at the October, 2015 meeting.]
According to 6.9.3.2 [basic.start.static] paragraph 2,
Variables with static storage duration (6.7.6.2 [basic.stc.static]) or thread storage duration (6.7.6.3 [basic.stc.thread]) shall be zero-initialized (9.5 [dcl.init]) before any other initialization takes place.
Does this apply to constant initialization as well? For example, should the following be well-formed, relying on the presumed zero-initialization preceding the constant initialization?
constexpr int i = i; struct s { constexpr s() : v(v) { } int v; }; constexpr s s1;
Notes from the November, 2014 meeting:
CWG agreed that constant initialization should be considered as happening instead of zero initialization in these cases, making the declarations ill-formed.
Proposed resolution (May, 2015):
Rename 6.9.3.2 [basic.start.static] and make the indicated changes, moving parts of its content to a new section immediately following, as indicated below:
3.6.2 IStatic initializationof non-local variables[basic.start .init.static]
There are two broad classes of named non-local variables: those with static storage duration (6.7.6.2 [basic.stc.static]) and those with thread storage duration (6.7.6.3 [basic.stc.thread]). Non-local variablesVariables with static storage duration are initialized as a consequence of program initiation.Non-local variablesVariables with thread storage duration are initialized as a consequence of thread execution. Within each of these phases of initiation, initialization occurs as follows.
Variables with static storage duration (6.7.6.2 [basic.stc.static]) or thread storage duration (6.7.6.3 [basic.stc.thread]) shall be zero-initialized (9.5 [dcl.init]) before any other initialization takes place.A constant initializer for an object o is an expression that is a constant expression, except that it may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types [Note: such a class may have a non-trivial destructor —end note]. Constant initialization is performed:
if each full-expression (including implicit conversions) that appears in the initializer of a reference with static or thread storage duration is a constant expression (7.7 [expr.const]) and the reference is bound to a glvalue designating an object with static storage duration, to a temporary object (see 6.7.7 [class.temporary]) or subobject thereof, or to a function;
if an object with static or thread storage duration is initialized by a constructor call, and if the initialization full-expression is a constant initializer for the object;
if an object with static or thread storage duration is not initialized by a constructor call and if either the object is value-initialized or every full-expression that appears in its initializer is a constant expression.
If constant initialization is not performed, a variable with static storage duration (6.7.6.2 [basic.stc.static]) or thread storage duration (6.7.6.3 [basic.stc.thread]) is zero-initialized (9.5 [dcl.init]). Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place.
Dynamic initialization of a non-local variable with static storage duration is unordered if the variable is an implicitly or explicitly instantiated specialization, and otherwise is ordered [Note: an explicitly specialized static data member or variable template specialization has ordered initialization. —end note]. Variables with ordered initialization defined within a single translation unit shall be initialized in the order of their definitions in the translation unit. If a program starts a thread (32.4 [thread.threads]), the subsequent initialization of a variable is unsequenced with respect to the initialization of a variable defined in a different translation unit. Otherwise, the initialization of a variable is indeterminately sequenced with respect to the initialization of a variable defined in a different translation unit. If a program starts a thread, the subsequent unordered initialization of a variable is unsequenced with respect to every other dynamic initialization. Otherwise, the unordered initialization of a variable is indeterminately sequenced with respect to every other dynamic initialization. [Note: This definition permits initialization of a sequence of ordered variables concurrently with another sequence. —end note][Note: The dynamic initialization of non-local variables is described in 3.6.3 [basic.start.dynamic]; that of local static variables is described in 8.9 [stmt.dcl]. —end note]An implementation is permitted to perform the initialization of a
non-localvariable with static or thread storage duration as a static initialization even if such initialization is not required to be done statically, provided that
the dynamic version of the initialization does not change the value of any other object of
namespace scopestatic or thread storage duration prior to its initialization, andthe static version of the initialization produces the same value in the initialized variable as would be produced by the dynamic initialization if all variables not required to be initialized statically were initialized dynamically.
[Note: As a consequence, if the initialization of an object obj1 refers to an object obj2 of namespace scope potentially requiring dynamic initialization and defined later in the same translation unit, it is unspecified whether the value of obj2 used will be the value of the fully initialized obj2 (because obj2 was statically initialized) or will be the value of obj2 merely zero-initialized. For example,
inline double fd() { return 1.0; } extern double d1; double d2 = d1; // unspecified: // may be statically initialized to 0.0 or // dynamically initialized to 0.0 if d1 is // dynamically initialized, or 1.0 otherwise double d1 = fd(); // may be initialized statically or dynamically to 1.0—end note]
Insert a new section after 6.9.3.2 [basic.start.static]:
3.6.3 Dynamic initialization of non-local variables [basic.start.dynamic]
Move part of 6.9.3.2 [basic.start.static] paragraph 2 as paragraph 1 of the new section:
Dynamic initialization of a non-local variable with static storage duration is unordered if the variable is an implicitly or explicitly instantiated specialization, and otherwise is ordered [Note: an explicitly specialized static data member or variable template specialization has ordered initialization. —end note]. Variables with ordered initialization defined within a single translation unit shall be initialized in the order of their definitions in the translation unit. If a program starts a thread (32.4 [thread.threads]), the subsequent initialization of a variable is unsequenced with respect to the initialization of a variable defined in a different translation unit. Otherwise, the initialization of a variable is indeterminately sequenced with respect to the initialization of a variable defined in a different translation unit. If a program starts a thread, the subsequent unordered initialization of a variable is unsequenced with respect to every other dynamic initialization. Otherwise, the unordered initialization of a variable is indeterminately sequenced with respect to every other dynamic initialization. [Note: This definition permits initialization of a sequence of ordered variables concurrently with another sequence. —end note]
Move paragraphs 4-6 of 6.9.3.2 [basic.start.static] as paragraphs 2-4 of the new section:
It is implementation-defined whether the dynamic initialization of a non-local variable with static storage duration is done before the first statement of main. If the initialization is deferred to some point in time after the first statement of main, it shall occur before the first odr-use (6.3 [basic.def.odr]) of any function or variable defined in the same translation unit as the variable to be initialized. [Footnote: A non-local variable with static storage duration having initialization with side-effects must be initialized even if it is not odr-used (6.3 [basic.def.odr], 6.7.6.2 [basic.stc.static]). —end footnote] [Example:
// - File 1 - #include "a.h" #include "b.h" B b; A::A(){ b.Use(); } // - File 2 - #include "a.h" A a; // - File 3 - #include "a.h" #include "b.h" extern A a; extern B b; int main() { a.Use(); b.Use(); }It is implementation-defined whether either a or b is initialized before main is entered or whether the initializations are delayed until a is first odr-used in main. In particular, if a is initialized before main is entered, it is not guaranteed that b will be initialized before it is odr-used by the initialization of a, that is, before A::A is called. If, however, a is initialized at some point after the first statement of main, b will be initialized prior to its use in A::A. —end example]
It is implementation-defined whether the dynamic initialization of a non-local variable with static or thread storage duration is done before the first statement of the initial function of the thread. If the initialization is deferred to some point in time after the first statement of the initial function of the thread, it shall occur before the first odr-use (6.3 [basic.def.odr]) of any variable with thread storage duration defined in the same translation unit as the variable to be initialized.
If the initialization of a non-local variable with static or thread storage duration exits via an exception, std::terminate is called (14.6.2 [except.terminate]).
The zero-initialization (9.5 [dcl.init]) of all block-scope variables with static storage duration (6.7.6.2 [basic.stc.static]) or thread storage duration (6.7.6.3 [basic.stc.thread]) is performed before any other initialization takes place. Constant initialization (6.9.3.2 [basic.start.static]) of a 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 block-scope variables with static or thread storage duration under the same conditions that an implementation is permitted to statically initialize a variable with static or thread storage duration in namespace scope (6.9.3.2 [basic.start.static]). Otherwise such a variable is initializedDynamic initialization of a block-scope variable with static storage duration (6.7.6.2 [basic.stc.static]) or thread storage duration (6.7.6.3 [basic.stc.thread]) is performed the first time control passes...
Editing note: all existing cross-references to 6.9.3.2 [basic.start.static] must be examined to determine which of the two current sections should be targeted.
[Moved to DR at the May, 2015 meeting.]
There does not appear to be any restriction on giving main() an explicit language linkage, but it should probably be either ill-formed or conditionally-supported.
Proposed resolution (November, 2014):
Change 6.9.3.1 [basic.start.main] paragraph 2 as follows:
An implementation shall not predefine the main function. This function shall not be overloaded.ItIts type shall have C++ language linkage and it shall have a declared return type of type int, but otherwise its type is implementation-defined. An implementation shall allow both...
Change 6.9.3.1 [basic.start.main] paragraph 3 as follows:
The function main shall not be used within a program. The linkage (6.6 [basic.link]) of main is implementation-defined. A program that defines main as deleted or that declares main to be inline, static, or constexpr is ill-formed. The main function shall not be declared with a linkage-specification (9.12 [dcl.link]). A program that declares a variable main at global scope or that declares the name main with C language linkage (in any namespace) is ill-formed. The name main is not otherwise reserved...
[Moved to DR at the November, 2014 meeting.]
According to 6.9.3.2 [basic.start.static] paragraph 2,
Definitions of explicitly specialized class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization.
This is not clear whether it is referring to static data members of explicit specializations of class templates or to explicit specializations of static data members of class template specializations. It also does not apply to static data member templates and non-member variable templates.
Proposed resolution (February, 2014):
Change 6.9.3.2 [basic.start.static] paragraph 2 as follows:
...Dynamic initialization of a non-local variable with static storage duration iseither ordered or unordered. Definitions of explicitly specialized class template static data members have ordered initializa-tion. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization. Other non-local variables with static storage duration have ordered initializationunordered if the variable is an implicitly or explicitly instantiated specialization, and otherwise is ordered [Note: an explicitly specialized static data member or variable template specialization has ordered initialization. —end note]. Variables with ordered initialization...
[Moved to DR at the November, 2014 meeting.]
According to 6.9.3.2 [basic.start.static] paragraph 2,
Constant initialization is performed:
if each full-expression (including implicit conversions) that appears in the initializer of a reference with static or thread storage duration is a constant expression (7.7 [expr.const]) and the reference is bound to an lvalue designating an object with static storage duration or to a temporary (see 6.7.7 [class.temporary]);
...
This wording should also permit the reference to be bound to an xvalue, e.g., a subobject of a temporary, and not just to a complete temporary.
Proposed resolution (February, 2014):
Change 6.9.3.2 [basic.start.static] paragraph 2 as follows (note that this resolution incorporates the overlapping change from the resolution of issue 1299)::
...Constant initialization is performed:
if each full-expression (including implicit conversions) that appears in the initializer of a reference with static or thread storage duration is a constant expression (7.7 [expr.const]) and the reference is bound to
an lvaluea glvalue designating an object with static storage duration, to a temporary object (see 6.7.7 [class.temporary]) or subobject thereof, or to a function;...
[Adopted at the February, 2016 meeting.]
It is not clear what constraints are placed on a floating point implementation by the wording of the Standard. For instance, is an implementation permitted to generate a "fused multiply-add" instruction if the result would be different from what would be obtained by performing the operations separately? To what extent does the "as-if" rule allow the kinds of optimizations (e.g., loop unrolling) performed by FORTRAN compilers?
Proposed resolution (September, 2015):
Change 6.8.2 [basic.fundamental] paragraph 8 as follows:
There are three floating point types: float, double, and long double. The type double provides at least as much precision as float, and the type long double provides at least as much precision as double. The set of values of the type float is a subset of the set of values of the type double; the set of values of the type double is a subset of the set of values of the type long double. The value representation of floating-point types is implementation-defined. [Note: This International Standard imposes no requirements on the accuracy of floating-point operations; see also 17.3 [support.limits]. —end note] Integral and floating types are collectively called arithmetic types. Specializations of the standard library template std::numeric_limits (17.3 [support.limits]) shall specify the maximum and minimum values of each arithmetic type for an implementation.
The aliasing rules given in 7.2.1 [basic.lval] paragraph 10 rely on the concept of “dynamic type.” The problem is that the dynamic type of an object often cannot be determined (or even sufficiently constrained) at the point at which an optimizer needs to be able to determine whether aliasing might occur or not. For example, consider the function
void foo(int* p, double* q) { *p = 42; *q = 3.14; }
An optimizer, on the basis of the existing aliasing rules, might decide that an int* and a double* cannot refer to the same object and reorder the assignments. This reordering, however, could result in undefined behavior if the function foo is called as follows:
void goo() { union { int i; double d; } t; t.i = 12; foo(&t.i, &t.d); cout << t.d << endl; };
Here, the reference to t.d after the call to foo will be valid only if the assignments in foo are executed in the order in which they were written; otherwise, the union will contain an int object rather than a double.
One possibility would be to require that if such aliasing occurs, it be done only via member names and not via pointers.
Notes from the July, 2007 meeting:
This is the same issue as C's DR236. The CWG expressed a desire to address the issue the same way C99 does. The issue also occurs in C++ when placement new is used to end the lifetime of one object and start the lifetime of a different object occupying the same storage.
Proposed resolution (March, 2017):
This issue is resolved by the resolution of issue 1776.
[Adopted at the February, 2016 meeting.]
According to 7.2.1 [basic.lval] paragraph 4,
Unless otherwise indicated (7.6.1.3 [expr.call]), prvalues shall always have complete types or the void type; in addition to these types, glvalues can also have incomplete types.
This wording inadvertently implies that glvalues can have type void, which is not correct.
Proposed resolution (January, 2016):
Change 7.2.1 [basic.lval] paragraph 4 as follows:
Unless otherwise indicated (7.6.1.3 [expr.call]),prvaluesa prvalue shall always have completetypestype or the void type; in addition to these types, glvalues can also have incomplete types. A glvalue shall not have type cv void. [Note:classA glvalue may have complete or incomplete non-void type. Class and array prvalues can have cv-qualified types; other prvalues always have cv-unqualified types. See Clause 7 [expr]. —end note]
[Moved to DR at the October, 2015 meeting.]
According to 7.3 [conv] paragraph 5,
Certain language constructs require conversion to a value having one of a specified set of types appropriate to the construct. An expression e of class type E appearing in such a context is said to be contextually implicitly converted to a specified type T and is well-formed if and only if e can be implicitly converted to a type T that is determined as follows: E is searched for conversion functions whose return type is cv T or reference to cv T such that T is allowed by the context. There shall be exactly one such T.
This description leaves open two questions: first, can explicit conversion functions be used for this conversion? Second, assuming that they cannot, is the restriction to “exactly one such T” enforced before or after exclusion of explicit conversion functions?
Notes from the November, 2014 meeting:
CWG felt that explicit conversion functions should be removed from consideration before determining the set of types for the conversion.
Proposed resolution (May, 2015):
Change 7.3 [conv] paragraph 5 as follows:
...An expression e of class type E appearing in such a context is said to be contextually implicitly converted to a specified type T and is well-formed if and only if e can be implicitly converted to a type T that is determined as follows: E is searched for non-explicit conversion functions whose return type is cv T or reference to cv T such that T is allowed by the context. There shall be exactly one such T.
[Adopted at the February, 2016 meeting.]
The current rules in 7.3.2 [conv.lval] paragraph 2 do not require fetching the value in memory of an object of type std::nullpt_t in order to produce its prvalue. This choice has implications that may not have been considered for questions like whether use of a std::nullptr_t that is an inactive member of a union results in undefined behavior or whether a volatile std::nullptr_t variable in a discarded-value expression produces a side effect.
Proposed resolution (January, 2016):
Change 7.3.2 [conv.lval] bullet 2.3 as follows:
...In all other cases, the result of the conversion is determined according to the following rules:
If T is (possibly cv-qualified) std::nullptr_t, the result is a null pointer constant (7.3.12 [conv.ptr]). [Note: Since no value is fetched from memory, there is no side effect for a volatile access (6.9.1 [intro.execution]), and an inactive member of a union (11.5 [class.union]) may be accessed. —end note]
...
[Moved to DR as N4261 at the November, 2014 meeting.]
Section 7.3.6 [conv.qual] covers the case of multi-level pointers, but does not appear to cover the case of pointers to arrays of pointers. The effect is that arrays are treated differently from simple scalar values.
Consider for example the following code: (from the thread "Pointer to array conversion question" begun in comp.lang.c++.moderated)
int main() { double *array2D[2][3]; double * (*array2DPtr1)[3] = array2D; // Legal double * const (*array2DPtr2)[3] = array2DPtr1; // Legal double const * const (*array2DPtr3)[3] = array2DPtr2; // Illegal }and compare this code with:-
int main() { double *array[2]; double * *ppd1 = array; // legal double * const *ppd2 = ppd1; // legal double const * const *ppd3 = ppd2; // certainly legal (4.4/4) }
The problem appears to be that the pointed to types in example 1 are unrelated since nothing in the relevant section of the standard covers it - 7.3.6 [conv.qual] does not mention conversions of the form "cv array of N pointer to T" into "cv array of N pointer to cv T"
It appears that reinterpret_cast is the only way to perform the conversion.
Suggested resolution:
Artem Livshits proposed a resolution :-
"I suppose if the definition of "similar" pointer types in 7.3.6 [conv.qual] paragraph 4 was rewritten like this:
it would address the problem.T1 is cv1,0 P0 cv1,1 P1 ... cv1,n-1 Pn-1 cv1,n T
and
T2 is cv1,0 P0 cv1,1 P1 ... cv1,n-1 Pn-1 cv1,n T
where Pi is either a "pointer to" or a "pointer to an array of Ni"; besides P0 may be also a "reference to" or a "reference to an array of N0" (in the case of P0 of T2 being a reference, P0 of T1 may be nothing).
In fact I guess Pi in this notation may be also a "pointer to member", so 7.3.6 [conv.qual]/{4,5,6,7} would be nicely wrapped in one paragraph."
Additional note, February, 2014:
Geoffrey Romer: LWG plans to resolve US 16/LWG 2118, which concerns qualification-conversion of unique_ptr for array types, by effectively punting the issue to core: unique_ptr<T[]> will be specified to be convertible to unique_ptr<U[]> only if T(*)[] is convertible to U(*)[]. LWG and LEWG have jointly decided to adopt the same approach for shared_ptr<T[]> and shared_ptr<T[N]> in the Fundamentals TS. This will probably substantially raise the visibility of core issue 330, which concerns the fact that array types support only top-level qualification conversion of the element type, so it'd be nice if CWG could bump up the priority of that issue.
See also issue 1865.
Proposed resolution (October, 2014):
The resolution is contained in paper N4261.
[Moved to DR at the November, 2014 meeting.]
7.3.9 [conv.integral] paragraph 3 says, regarding integral conversions,
If the destination type is signed, the value is unchanged if it can be represented in the destination type (and bit-field width); otherwise, the value is implementation-defined.
The values that can be represented in a bit-field are not well specified, except for the correspondence with the values of an enumeration in 9.8.1 [dcl.enum]. In particular, it is not clear whether a bit-field has a sign bit and whether bit-fields may have padding bits.
Another point to note in this wording: paragraph 1 describes the context as
A prvalue of an integer type can be converted to a prvalue of another integer type.
However, prvalues cannot be bit-fields, so the applicability of the mention of “bit-field width” in paragraph 3 is unclear.
Proposed resolution (February, 2014):
Change 7.3.9 [conv.integral] paragraph 3 as follows:
If the destination type is signed, the value is unchanged if it can be represented in the destination type(and bit-field width); otherwise, the value is implementation-defined.
Change 7.6.1.6 [expr.post.incr] paragraph 1 as follows:
...The result is a prvalue. The type of the result is the cv-unqualified version of the type of the operand. If the operand is a bit-field that cannot represent the incremented value, the resulting value of the bit-field is implementation-defined. See also 7.6.6 [expr.add] and 7.6.19 [expr.assign].
Change 7.6.19 [expr.assign] paragraph 6 as follows:
When the left operand of an assignment operatordenotes a reference to T, the operation assigns to the object of type T denoted by the referenceis a bit-field that cannot represent the value of the expression, the resulting value of the bit-field is implementation-defined..
Change the final bullet of 9.5 [dcl.init] paragraph 17 as follows:
The semantics of initializers are as follows...
...
...no user-defined conversions are considered. If the conversion cannot be done, the initialization is ill-formed. When initializing a bit-field with a value that it cannot represent, the resulting value of the bit-field is implementation-defined. [Note: An expression of type...
[Moved to DR at the May, 2015 meeting.]
According to 7.5.6 [expr.prim.lambda] paragraph 4,
If a lambda-expression does not include a lambda-declarator, it is as if the lambda-declarator were (). The lambda return type is auto, which is replaced by the trailing-return-type if provided...
trailing-return-type is a syntactic nonterminal that includes the -> and thus cannot be used directly to refer to the type. It should instead say something like, ...the type specified by the trailing-return-type.
The reference in 9.3.4.6 [dcl.fct] paragraph 2, “...returning trailing-return-type” should be similarly adjusted.
Proposed resolution (November, 2014):
Change 7.5.6 [expr.prim.lambda] paragraph 4 as follows:
If a lambda-expression does not include a lambda-declarator, it is as if the lambda-declarator were (). The lambda return type is auto, which is replaced by the type specified by the trailing-return-type if provided and/or deduced from return statements as described in 9.2.9.7 [dcl.spec.auto]. [Example:...
Change 9.3.4.6 [dcl.fct] paragraph 2 as follows:
The type of the declarator-id in D is “derived-declarator-type-list function of (parameter-declaration-clause) cv-qualifier-seqopt ref-qualifieropt returningtrailing-return-typeU”, where U is the type specified by the trailing-return-type. The optional attribute-specifier-seq...
[Moved to DR at the October, 2015 meeting.]
According to 7.5.6 [expr.prim.lambda] paragraph 6,
The closure type for a non-generic lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function with C++ language linkage (9.12 [dcl.link]) having the same parameter and return types as the closure type's function call operator.
This does not specify whether the conversion function is noexcept(true) or noexcept(false). It might be helpful to nail that down.
Proposed resolution (May, 2015):
Change 7.5.6 [expr.prim.lambda] paragraph 6 as follows:
The closure type for a non-generic lambda-expression with no lambda-capture has a
public non-virtual non-explicit constconversion function to pointer to function with C++ language linkage (9.12 [dcl.link]) having the same parameter and return types as the closure type's function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type's function call operator. For a generic lambda with no lambda-capture, the closure type has apublic non-virtual non-explicit constconversion function template to pointer to function. The conversion function template... [Example:auto GL = [](auto a) { std::cout << a; return a; }; int (*GL_int)(int) = GL; // OK: through conversion function template GL_int(3); // OK: same as GL(3)
—end example] The conversion function or conversion function template is public, non-virtual, non-explicit, const, and has a non-throwing exception specification (14.5 [except.spec]).
[Moved to DR at the November, 2014 meeting.]
Similarly to issue 1738, it is not clear whether it is permitted to explicitly instantiate or specialize the call operator of a polymorphic lambda (via decltype).
Proposed resolution (February, 2014):
Add the following as a new paragraph following 7.5.6 [expr.prim.lambda] paragraph 21:
The closure type associated with a lambda-expression has an implicitly-declared destructor (11.4.7 [class.dtor]).
A member of a closure type shall not be explicitly instantiated (13.9.2 [temp.inst]), explicitly specialized (13.9.3 [temp.explicit]), or named in a friend declaration (11.8.4 [class.friend]).
[Moved to DR at the November, 2014 meeting.]
According to 7.5.6 [expr.prim.lambda] paragraph 20,
The closure type associated with a lambda-expression has a deleted (9.6.3 [dcl.fct.def.delete]) default constructor and a deleted copy assignment operator. It has an implicitly-declared copy constructor (11.4.5.3 [class.copy.ctor]) and may have an implicitly-declared move constructor (11.4.5.3 [class.copy.ctor]).
However, according to 11.4.5.3 [class.copy.ctor] paragraph 9,
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
X does not have a user-declared copy constructor,
X does not have a user-declared copy assignment operator,
X does not have a user-declared move assignment operator, and
X does not have a user-declared destructor.
It is not clear how this applies to the closure class. Would it be better to state that the closure class has a defaulted move constructor and a defaulted move assignment operator? There is already wording that handles the case if they are ultimately defined as deleted.
Proposed resolution (October, 2014):
Change 7.5.6 [expr.prim.lambda] paragraph 20 as follows:
The closure type associated with a lambda-expression hasa deleted (9.6.3 [dcl.fct.def.delete])no default constructor and a deleted copy assignment operator. It hasan implicitly-declareda defaulted copy constructor(11.4.5.3 [class.copy.ctor]) and may have an implicitly-declaredand a defaulted move constructor (11.4.5.3 [class.copy.ctor]). [Note:The copy/move constructor is implicitly defined in the same way as any other implicitly declared copy/move constructor would be implicitly definedThese special member functions are implicitly defined as usual, and might therefore be defined as deleted. —end note]
[Adopted at the February, 2016 meeting.]
According to 7.5.6 [expr.prim.lambda] paragraph 15,
An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that is not of the form & identifier or & identifier initializer. For each entity captured by copy, an unnamed non-static data member is declared in the closure type. The declaration order of these members is unspecified. The type of such a data member is the type of the corresponding captured entity if the entity is not a reference to an object, or the referenced type otherwise.
It's not clear how to handle capture by copy when the entity is an rvalue reference to function. In particular, this appears to be a contradiction with 7.5.6 [expr.prim.lambda] paragraph 3,
An implementation shall not add members of rvalue reference type to the closure type.
Proposed resolution (September, 2015):
Change 7.5.6 [expr.prim.lambda] paragraph 15 as follows:
An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that is not of the form & identifier or & identifier initializer. For each entity captured by copy, an unnamed non-static data member is declared in the closure type. The declaration order of these members is unspecified. The type of such a data member is the referenced type if the entity is a reference to an object, an lvalue reference to the referenced function type if the entity is a reference to a function, or the type of the corresponding captured entityif the entity is not a reference to an object, or the referenced typeotherwise.[Note: If the captured entity is a reference to a function, the corresponding data member is also a reference to a function. —end note]A member of an anonymous union shall not be captured by copy.
[Adopted at the June, 2016 meeting as part of paper P0135R1.]
According to 7.6.1.3 [expr.call] paragraph 4,
The lifetime of a parameter ends when the function in which it is defined returns. The initialization and destruction of each parameter occurs within the context of the calling function.
This presumably means that the destruction of the parameter object occurs before the end of the full-expression, unlike temporaries. This is not what current implementations do, however. It is not clear that a change to treat parameter objects like temporaries, to match existing practice, would be an improvement, however, as it would result in ABI breakage for implementations that destroy parameters in the called function.
See also issue 1935 for a related question regarding the handling of arguments to a placement allocation function and placement deallocation function.
Notes from the June, 2014 meeting:
WG decided to make it unspecified whether parameter objects are destroyed immediately following the call or at the end of the full-expression to which the call belongs. This approach also resolves issue 1935.
[Moved to DR at the November, 2014 meeting.]
The intent is that a function call is a temporary expression whose result is a temporary, but that appears not to be said anywhere. It should also be clarified that a return statement in a function with a class return type copy-initializes the temporary that is the result. The sequencing of the initialization of the returned temporary, destruction of temporaries in the return expression, and destruction of automatic variables should be make explicit.
Proposed resolution (October, 2014):
Change 8.7.4 [stmt.return] paragraphs 2-3 as follows:
A return statement with neither an expression nor a braced-init-list can be used only in functions that do not return a value, that is,The expression or braced-init-list of a return statement is called its operand. A return statement with no operand shall be used only in a functionwith thewhose return type is cv void, a constructor (11.4.5 [class.ctor]), or a destructor (11.4.7 [class.dtor]). A return statement with an operand of type void shall be used only in a function whose return type is cv void. A return statement withan expression of non-void type can be used onlyany other operand shall be used only infunctions returning a value; the value of the expression is returned to the caller of the function. The value of the expression is implicitly converted to the return type of the function in which it appearsa function whose return type is not cv void; the return statement initializes the object or reference to be returned by copy-initialization (9.5 [dcl.init]) from the operand. [Note: A return statement can involve the construction and copy or move of a temporary object (6.7.7 [class.temporary]).[Note:A copy or move operation associated with a return statement may be elided or considered as an rvalue for the purpose of overload resolution in selecting a constructor (11.4.5.3 [class.copy.ctor]). —end note]A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (9.5.5 [dcl.init.list]) from the specified initializer list.[Example:std::pair<std::string,int> f(const char* p, int x) { return {p,x}; }—end example] Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function.
A return statement with an expression of type void can be used only in functions with a return type of cv void; the expression is evaluated just before the function returns to its caller.The copy-initialization of the returned entity is sequenced before the destruction of temporaries at the end of the full-expression established by the operand of the return statement, which, in turn, is sequenced before the destruction of local variables (8.7 [stmt.jump]) of the block enclosing the return statement.
(See also the related changes in the resolution of issue 1299.)
[Adopted at the February, 2016 meeting.]
Consider the following example:
#include <stdio.h> struct X { X() { puts("X()"); } X(const X&) { puts("X(const X&)"); } ~X() { puts("~X()"); } }; struct Y { ~Y() noexcept(false) { throw 0; } }; X f() { try { Y y; return {}; } catch (...) { } return {}; } int main() { f(); }
Current implementations print X() twice but ~X() only once. That is obviously wrong, but it is not clear that the current wording covers this case.
Proposed resolution (February, 2016):
Change 14.3 [except.ctor] paragraph 2 as follows:
The destructor is invoked for each automatic object of class type constructed, but not yet destroyed, since the try block was entered. If an exception is thrown during the destruction of temporaries or local variables for a return statement (8.7.4 [stmt.return]), the destructor for the returned object (if any) is also invoked. The
automaticobjects are destroyed in the reverse order of the completion of their construction. [Example:struct A { }; struct Y { ~Y() noexcept(false) { throw 0; } }; A f() { try { A a; Y y; A b; return {}; // #1 } catch (...) { } return {}; // #2 }At #1, the returned object of type A is constructed. Then, the local variable b is destroyed (8.7 [stmt.jump]). Next, the local variable y is destroyed, causing stack unwinding, resulting in the destruction of the returned object, followed by the destruction of the local variable a. Finally, the returned object is constructed again at #2. —end example]
[Moved to DR at the November, 2014 meeting.]
The specification of casting to an enumeration type in 7.6.1.9 [expr.static.cast] paragraph 10 does not require that the enumeration type be complete. Should it? (There is variation among implementations.)
Proposed resolution (February, 2014):
Change 7.6.1.9 [expr.static.cast] paragraph 10 as follows:
A value of integral or enumeration type can be explicitly converted toana complete enumeration type. The value is...
[Moved to DR at the November, 2014 meeting.]
According to 7.6.2.2 [expr.unary.op] paragraph 3,
The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id. 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 a prvalue designating C::m.
It is not clear whether this wording applies to variant members of C (i.e., members of nested anonymous unions) or only to its non-variant members. For example, given
struct A { union { int n; }; }; auto x = &A::n;
should the type of x be int A::* or int A::anon::*? Current implementations choose the former.
Proposed resolution (February, 2014):
Change 7.6.2.2 [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. If the operand is a qualified-id naming a non-static or variant member m of some class C with type T, the result has type “pointer to member of class C of type T” and is a prvalue designating C::m. Otherwise...
[Moved to DR at the May, 2015 meeting.]
There is language in 7.6.2.2 [expr.unary.op] paragraph 10 to disambiguate destructor references from references to operator~:
There is an ambiguity in the unary-expression ~X(), where X is a class-name or decltype-specifier. The ambiguity is resolved in favor of treating ~ as a unary complement rather than treating ~X as referring to a destructor.
However, it is not clear whether this is intended to apply to an example like,
struct X { X(int); operator int(); void foo() { ~X(0); } };
where the form of reference has an apparent argument.
Proposed resolution (November, 2014):
Change 7.6.2.2 [expr.unary.op] paragraph 10 as follows:
The operand of ~ shall have integral or unscoped enumeration type; the result is the one's complement of its operand. Integral promotions are performed. The type of the result is the type of the promoted operand. There is an ambiguityin the unary-expression ~X(), where X isin the grammar when ~ is followed by a class-name or decltype-specifier. The ambiguity is resolvedin favor ofby treating ~ asathe unary complement operator rather thantreating ~X as referring toas the start of an unqualified-id naming a destructor. [Note: Because the grammar does not permit an operator to follow the ., ->, or :: tokens, a ~ followed by a class-name or decltype-specifier in a member access expression or qualified-id is unambiguously parsed as a destructor name. —end note]
[Adopted at the October, 2015 meeting as P0002R1.]
The preincrement (7.6.2.3 [expr.pre.incr]) and postincrement (7.6.1.6 [expr.post.incr]) operators can be applied to operands of type bool, setting the operand to true, but this use is deprecated. Can it now be removed altogether?
[Moved to DR at the November, 2014 meeting.]
The list of causes for a false result of the noexcept operator does not include a new-expression with a non-constant array bound, which could result in an exception even if the allocation function that would be called is declared not to throw (see 7.6.2.8 [expr.new] paragraph 7).
Proposed resolution (June, 2012):
This issue is resolved by the resolution of issue 1351.
[Moved to DR at the November, 2014 meeting.]
According to 7.6.2.8 [expr.new] paragraph 15,
[Note: unless an allocation function is declared with a non-throwing exception-specification (14.5 [except.spec]), it indicates failure to allocate storage by throwing a std::bad_alloc exception (Clause 14 [except], 17.6.4.1 [bad.alloc]); it returns a non-null pointer otherwise. If the allocation function is declared with a non-throwing exception-specification, it returns null to indicate failure to allocate storage and a non-null pointer otherwise. —end note] If the allocation function returns null, initialization shall not be done, the deallocation function shall not be called, and the value of the new-expression shall be null.
This wording applies even to the non-replaceable placement forms defined in 17.6.3.4 [new.delete.placement] that simply return the supplied pointer as the result of the allocation function. Compilers are thus required to check for a null pointer and avoid the initialization if one is used. This test is unnecessary overhead; it should be the user's responsibility to ensure that a null pointer is not used in these forms of placement new, just as for other cases when a pointer is dereferenced.
Proposed resolution (February, 2014):
Change 7.6.2.8 [expr.new] paragraph 15 as follows:
[Note: unless an allocation function is declared with a non-throwing exception-specification (14.5 [except.spec]), it indicates failure to allocate storage by throwing a std::bad_alloc exception (Clause 14 [except], 17.6.4.1 [bad.alloc]); it returns a non-null pointer otherwise. If the allocation function is declared with a non-throwing exception-specification, it returns null to indicate failure to allocate storage and a non-null pointer otherwise. —end note] If the allocation function is a reserved placement allocation function (17.6.3.4 [new.delete.placement]) that returns null, the behavior is undefined. Otherwise, if the allocation function returns null, initialization shall not be done, the deallocation function shall not be called, and the value of the new-expression shall be null.
[Moved to DR at the November, 2014 meeting.]
9.2.9.7 [dcl.spec.auto] paragraph 5 says that a placeholder type (presumably including decltype(auto)) can appear in a new-expression. However, 7.6.2.8 [expr.new] mentions only auto, not decltype(auto).
Proposed resolution (February, 2014):
Change 7.6.2.8 [expr.new] paragraph 2 as follows:
If theauto type-specifiera placeholder type (9.2.9.7 [dcl.spec.auto]) appears in the type-specifier-seq of a new-type-id or type-id of a new-expression, the new-expression shall contain...
[Adopted at the February, 2016 meeting.]
According to 7.6.2.8 [expr.new] paragraph 7,
If the expression, after converting to std::size_t, is a core constant expression and the expression is erroneous, the program is ill-formed. Otherwise, a new-expression with an erroneous expression does not call an allocation function and terminates by throwing an exception of a type that would match a handler (14.4 [except.handle]) of type std::bad_array_new_length (17.6.4.2 [new.badlength]).This wording makes no provision for an expression like
new (std::nothrow) int[N]which most programmers would intuitively expect not to throw an exception under any condition.
Proposed resolution (May, 2015) [SUPERSEDED]:
Change the last part of 7.6.2.8 [expr.new] paragraph 7 as follows, converting the running text into bullets, and making the last sentence into a paragraph 8:
...If the expression
,is erroneous after converting to std::size_t,:
if the expression is a core constant expression
and the expression is erroneous, the program is ill-formed.;
Otherwiseotherwise,a new-expression with an erroneous expression does not callan allocation function is not called; instead
if the allocation function that would have been called is non-throwing (14.5 [except.spec]), the value of the new-expression is the null pointer value of the required result type;
andotherwise, the new-expression terminates by throwing an exception of a type that would match a handler (14.4 [except.handle]) of type std::bad_array_new_length (17.6.4.2 [new.badlength]).When the value of the expression is zero, the allocation function is called to allocate an array with no elements.
Notes from the October, 2015 meeting:
The text in 15.4 paragraph 15 should also be changed.
Proposed resolution (January, 2016):
Change 7.6.2.8 [expr.new] paragraph 7 as follows, dividing the running text into bullets and making the last sentence into a new paragraph:
The expression in a noptr-new-declarator is erroneous if:
...
If the expression
,is erroneous after converting to std::size_t,:
if the expression is a core constant expression
and the expression is erroneous, the program is ill-formed.;
Otherwiseotherwise,a new-expression with an erroneous expression does not callan allocation functionandis not called; instead
if the allocation function that would have been called has a non-throwing exception specification (14.5 [except.spec]), the value of the new-expression is the null pointer value of the required result type;
otherwise, the new-expression terminates by throwing an exception of a type that would match a handler (14.4 [except.handle]) of type std::bad_array_new_length (17.6.4.2 [new.badlength]).
When the value of the expression is zero, the allocation function is called to allocate an array with no elements.
Change 14.5 [except.spec] paragraph 14 as follows:
The set of potential exceptions of an expression e is empty if e is a core constant expression (7.7 [expr.const]). Otherwise, it is the union of the sets of potential exceptions of the immediate subexpressions of e, including default argument expressions used in a function call, combined with a set S defined by the form of e, as follows:
...
If e implicitly invokes
aone or more functions (such as an overloaded operator, an allocation function in a new-expression, or a destructor if e is a full-expression (6.9.1 [intro.execution])), S is theset of potential exceptions of the function.union of:
the sets of potential exceptions of all such functions, and
if e is a new-expression with a non-constant expression in the noptr-new-declarator (7.6.2.8 [expr.new]) and the allocation function selected for e has a non-empty set of potential exceptions, the set containing std::bad_array_new_length.
...
If e is a new-expression with a non-constant expression in the noptr-new-declarator (7.6.2.8 [expr.new]), S consists of the type std::bad_array_new_length.[Example:...
Change the example in 14.5 [except.spec] bullet 17.2 as follows:
struct A { A(int = (A(5), 0)) noexcept; A(const A&) throw(); A(A&&) throw(); ~A() throw(X); }; struct B { B() throw(); B(const B&) = default; // exception specification contains no types B(B&&, int = (throw Y(), 0)) noexcept; ~B() throw(Y); }; int n = 7; struct D : public A, public B { int * p = new (std::nothrow) int[n]; // exception specification of D::D() contains Xandstd::bad_array_new_length// exception specification of D::D(const D&) contains no types // exception specification of D::D(D&&) contains Y // exception specification of D::~D() contains X and Y }; struct exp : std::bad_alloc {}; void *operator new[](size_t) throws(exp); struct E : public A { int * p = new int[n]; // exception specification of E::E() contains X, exp, and std::bad_array_new_length };
[Adopted at the February, 2016 meeting.]
According to 7.6.2.8 [expr.new] paragraph 1,
It is implementation-defined whether over-aligned types are supported (6.7.3 [basic.align]).
However, there is no mechanism for informing an allocation function of the required alignment for over-aligned types. Nevertheless, 6.7.3 [basic.align] paragraph 9 says:
Additionally, a request for runtime allocation of dynamic storage for which the requested alignment cannot be honored shall be treated as an allocation failure.
This seems contradictory.
Proposed resolution (October, 2015):
Change 6.7.3 [basic.align] paragraph 9 as follows:
If a request for a specific extended alignment in a specific context is not supported by an implementation, the program is ill-formed.Additionally, a request for runtime allocation of dynamic storage for which the requested alignment cannot be honored shall be treated as an allocation failure.
[Adopted at the February, 2016 meeting.]
Consider the following example:
struct A { }; void foo() { new struct A { }; }
This could be either an elaborated-type-specifier followed by a braced-init-list or a class-specifier. There does not appear to be a disambiguation rule for this case.
One possibility for addressing this could be to use trailing-type-specifier-seq instead of type-specifier-seq in new-type-id. That could also be a purely syntactic alternative to the resolution of issue 686: change all uses of type-specifier-seq to trailing-type-specifier-seq and provide a new grammar production for use in alias-declaration.
Proposed resolution (February, 2016):
Change the grammar in 9.1 [dcl.pre] paragraph 1 as follows:
Change 9.1 [dcl.pre] paragraph 8 as follows:
Each init-declarator in the init-declarator-list contains exactly one declarator-id, which is the name declared by that init-declarator and hence one of the names declared by the declaration. The defining-type-specifiers (9.2.9 [dcl.type]) in the decl-specifier-seq and the recursive declarator structure of the init-declarator describe a type (9.3.4 [dcl.meaning]), which is then associated with the name being declared by the init-declarator.
Change the grammar in 9.2 [dcl.spec] paragraph 1 as follows:
Change 9.2 [dcl.spec] paragraph 3 as follows:
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 defining-type-specifier other than a cv-qualifier in the decl-specifier-seq. The sequence...
Change 9.2.4 [dcl.typedef] paragraph 1 as follows:
Declarations containing the decl-specifier typedef declare identifiers that can be used later for naming fundamental (6.8.2 [basic.fundamental]) or compound (6.8.4 [basic.compound]) types. The typedef specifier shall not be combined in a decl-specifier-seq with any other kind of specifier except a defining-type-specifier, and it shall not be used in the decl-specifier-seq of a parameter-declaration (9.3.4.6 [dcl.fct]) nor in the decl-specifier-seq of a function-definition (9.6 [dcl.fct.def]).
Change 9.2.4 [dcl.typedef] paragraph 2 as follows:
A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that typedef-name.ItThe defining-type-specifier-seq of the defining-type-id may define a class or enumeration only if the alias-declaration is not the declaration of a template-declaration. Such a typedef-name has the same semantics as if it were introduced by the typedef specifier. In particular, it does not define a new type. [Example:
Change 9.2.9 [dcl.type] paragraph 1 as follows:
The optional attribute-specifier-seq in a type-specifier-seq or a
trailingdefining-type-specifier-seq appertains to the type denoted by the preceding type-specifiers or defining-type-specifiers (9.3.4 [dcl.meaning]). The attribute-specifier-seq affects the type only for the declaration it appears in, not other declarations involving the same type.
Change 9.2.9.3 [dcl.type.simple] paragraph 2 as follows:
As a general rule, at most one defining-type-specifier is allowed in the complete decl-specifier-seq of a declaration or in a type-specifier-seq ortrailingdefining-type-specifier-seq. The only exceptions to this rule are the following:...
Change 9.2.9 [dcl.type] paragraph 3 as follows:
Except in a declaration of a constructor, destructor, or conversion function, at least one defining-type-specifier that is not a cv-qualifier shall appear in a complete type-specifier-seq or a complete decl-specifier-seq.95A type-specifier-seq shall not define a class or enumeration unless it appears in the type-id of an alias-declaration (9.2.4 [dcl.typedef]) that is not the declaration of a template-declaration.
Change the grammar in 9.3 [dcl.decl] paragraph 4 as follows:
Change the grammar in 9.3.2 [dcl.name] paragraph 1 as follows:
Change 11.4.8.3 [class.conv.fct] paragraph 1 as follows:
...A decl-specifier in the decl-specifier-seq of a conversion function (if any) shall be neither a defining-type-specifier nor static. Type of the conversion function
[Moved to DR at the November, 2014 meeting.]
The changes from N3778 require use of a sized deallocator for a case like
char *p = new char[32]; void f() { delete [] p; }
That is unimplementable under current ABIs, which do not store the array size for such allocations. It should instead be unspecified or implementation-defined whether the sized form of operator[] is used for a pointer to a type other than a class with a non-trivial destructor or array thereof.
Proposed resolution (February, 2014) [SUPERSEDED]:
Change 7.6.2.9 [expr.delete] paragraph 10 as follows:
If
the type is complete and ifdeallocation function lookup finds both a usual deallocation function with only a pointer parameter and a usual deallocation function with both a pointer parameter and a size parameter, then
- for the first alternative (delete object), if the type of the object to be deleted is complete, and for the second alternative (delete array), if the type of the object to be deleted is a complete class type with a non-trivial destructor, then the selected deallocation function shall be the one with two parameters;
otherwise, it is implementation-defined which deallocation function is selected.
Otherwise, the selected deallocation function shall be the function with one parameter.
Additional note, February, 2014:
It is not clear that this resolution accurately reflects the intent of the issue. In particular, it changes deletion of a pointer to incomplete type from requiring use of the single-parameter version to being implementation-defined. Also, the “type of the object to be deleted” in the array case is always an array type and thus cannot be “a complete class type with a non-trivial destructor.” The issue has consequently been returned to "review" status.
Proposed resolution (June, 2014):
Change 7.6.2.9 [expr.delete] paragraph 10 as follows:
Ifthe type is complete and ifdeallocation function lookup finds both a usual deallocation function with only a pointer parameter and a usual deallocation function with both a pointer parameter and a size parameter,then the selected deallocation function shall be the one with two parameters. Otherwise, the selected deallocation function shall be the function with one parameter.the function to be called is selected as follows:
If the type is complete and if, for the second alternative (delete array) only, the operand is a pointer to a class type with a non-trivial destructor or a (possibly multi-dimensional) array thereof, the function with two parameters is selected.
Otherwise, it is unspecified which of the two deallocation functions is selected.
[Adopted at the February, 2016 meeting.]
The meaning of an old-style cast is described in terms of const_cast, static_cast, and reinterpret_cast in 7.6.3 [expr.cast] paragraph 5. Ignoring const_cast for the moment, it basically says that if the conversion performed by a given old-style cast is one of those performed by static_cast, the conversion is interpreted as if it were a static_cast; otherwise, it's interpreted as if it were a reinterpret_cast, if possible. The following example is given in illustration:
struct A {}; struct I1 : A {}; struct I2 : A {}; struct D : I1, I2 {}; A *foo( D *p ) { return (A*)( p ); // ill-formed static_cast interpretation }
The obvious intent here is that a derived-to-base pointer conversion is one of the conversions that can be performed using static_cast, so (A*)(p) is equivalent to static_cast<A*>(p), which is ill-formed because of the ambiguity.
Unfortunately, the description of static_cast in 7.6.1.9 [expr.static.cast] does NOT support this interpretation. The problem is in the way 7.6.1.9 [expr.static.cast] lists the kinds of casts that can be performed using static_cast. Rather than saying something like "All standard conversions can be performed using static_cast," it says
An expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration "T t(e);" is well-formed, for some invented temporary variable t.
Given the declarations above, the hypothetical declaration
A* t(p);
is NOT well-formed, because of the ambiguity. Therefore the old-style cast (A*)(p) is NOT one of the conversions that can be performed using static_cast, and (A*)(p) is equivalent to reinterpret_cast<A*>(p), which is well-formed under 7.6.1.10 [expr.reinterpret.cast] paragraph 7.
Other situations besides ambiguity which might raise similar questions include access violations, casting from virtual base to derived, and casting pointers-to-members when virtual inheritance is involved.
Proposed resolution (October, 2015):
Change 7.6.1.9 [expr.static.cast] paragraph 2 as follows:
An lvalue of type “cv1 B”, where B is a class type, can be cast to type “reference to cv2 D”, where D is a class derived ( 11.7 [class.derived]) from B, ifa valid standard conversion from “pointer to D” to “pointer to B” exists (7.3.12 [conv.ptr]),cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and. If B isneithera virtual base class of Dnoror a base class of a virtual base class of D, or if no valid standard conversion from “pointer to D” to “pointer to B” exists (7.3.12 [conv.ptr]), the program is ill-formed.The result has type “cv2 D”.An xvalue of type “cv1 B”maycan be cast to type “rvalue reference to cv2 D” with the same constraints as for an lvalue of type “cv1 B”. If the object of type “cv1 B” is actually a subobject of an object of type D, the result refers to the enclosing object of type D. Otherwise, the behavior is undefined. [Example:...
Change 7.6.1.9 [expr.static.cast] paragraph 4 as follows:
An expression e can be explicitly converted to a type T
using a static_cast of the form static_cast<T>(e)ifthe declaration T t(e); is well-formed, for some invented temporary variable t (9.5 [dcl.init])there is an implicit conversion sequence (12.2.4.2 [over.best.ics]) from e to T, or if overload resolution for a direct-initialization (9.5 [dcl.init]) of an object or reference of type T from e would find at least one viable function (12.2.3 [over.match.viable]). The effect of such an explicit conversion is the same as performing the declaration and initializationT t(e);for some invented temporary variable t (9.5 [dcl.init]) and then using the temporary variable as the result of the conversion. [Note: The conversion is ill-formed when attempting to convert an expression of class type to an inaccessible or ambiguous base class. —end note] The expression e is used as a glvalue if and only if the initialization uses it as a glvalue.
Change 7.6.1.9 [expr.static.cast] paragraph 11 as follows:
A prvalue of type “pointer to cv1 B”, where B is a class type, can be converted to a prvalue of type “pointer to cv2 D”, where D is a class derived (11.7 [class.derived]) from B, ifa valid standard conversion from “pointer to D” to “pointer to B” exists (7.3.12 [conv.ptr]),cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and. If B isneithera virtual base class of Dnoror a base class of a virtual base class of D, or if no valid standard conversion from “pointer to D” to “pointer to B” exists (7.3.12 [conv.ptr]), the program is ill-formed. The null pointer value (7.3.12 [conv.ptr]) is converted to the null pointer value of the destination type. If the prvalue of type “pointer to cv1 B” points to a B that is actually a subobject of an object of type D, the resulting pointer points to the enclosing object of type D. Otherwise, the behavior is undefined.
Change 7.6.1.9 [expr.static.cast] paragraph 12 as follows:
A prvalue of type “pointer to member of D of type cv1 T” can be converted to a prvalue of type “pointer to member of B”of type cv2 T”, where B is a base class ( 11.7 [class.derived]) of D, ifa valid standard conversion from “pointer to member of B of type T” to “pointer to member of D of type T” exists (7.3.13 [conv.mem]), andcv2 is the same cv-qualification as, or greater cv-qualification than, cv1.70 If no valid standard conversion from “pointer to member of B of type T” to “pointer to member of D of type T” exists (7.3.13 [conv.mem]), the program is ill-formed. The null member pointer value (7.3.13 [conv.mem]) is converted to the null member pointer value of the destination type. If class B contains the original member, or is a base or derived class of the class containing the original member, the resulting pointer to member points to the original member. Otherwise, the behavior is undefined. [Note: although class B need not contain the original member, the dynamic type of the object with which indirection through the pointer to member is performed must contain the original member; see 7.6.4 [expr.mptr.oper]. —end note]
[Moved to DR at the November, 2014 meeting.]
The resolution of issue 1504 added 7.6.6 [expr.add] paragraph 7:
For addition or subtraction, if the expressions P or Q have type “pointer to cv T”, where T is different from the cv-unqualified array element type, the behavior is undefined.
This wording was intended to address derived-base conversion in pointer arithmetic, but it inadvertently categorized as undefined behavior previously well-defined pointer arithmetic on pointers that are the result of multi-level qualification conversions. For example:
void f() { int i = 0; int* arr[3] = {&i, &i, &i}; int const * const * aptr = arr; assert(aptr[2] == &i); }
This now has undefined behavior because the type of *aptr is “pointer to const int,” which is different from the cv-unqualified array element type, “pointer to int.”
See also issue 330.
Proposed Resolution (July, 2014):
Change 7.6.6 [expr.add] paragraph 7 as follows:
For addition or subtraction, if the expressions P or Q have type “pointer to cv T”, where Tis different from the cv-unqualifiedand the array element type are not similar (7.3.6 [conv.qual]), the behavior is undefined. [Note: In particular, a pointer to a base class cannot be used for pointer arithmetic when the array contains objects of a derived class type. —end note]
[Moved to DR at the November, 2014 meeting.]
The provision to treat non-array objects as if they were array objects with a bound of 1 is given only for pointer arithmetic in C++ (7.6.6 [expr.add] paragraph 4). C99 supplies similar wording for the relational and equality operators, explicitly allowing pointers resulting from such implicit-array treatment to be compared. C++ should follow suit.
Proposed resolution (August, 2013):
Change 7.6.2.2 [expr.unary.op] paragraph 3 as follows:
...Otherwise, if the type of the expression is T, the result has type “pointer to T” and is a prvalue that is the address of the designated object (6.7.1 [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] For purposes of pointer arithmetic (7.6.6 [expr.add]) and comparison (7.6.9 [expr.rel], 7.6.10 [expr.eq]), an object that is not an array element whose address is taken in this way is considered to belong to an array with one element of type T. [Example:
struct A { int i; }; struct B : A { }; ... &B::i ... // has type int A::* int a; int* p1 = &a; int* p2 = p1 + 1; // Defined behavior bool b = p2 > p1; // Defined behavior, with value true—end example] [Note: a pointer to member...
Delete 7.6.6 [expr.add] paragraph 4:
For the purposes of these operators, a pointer to a nonarray object behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.
Change 7.6.6 [expr.add] paragraph 5 as follows:
When an expression that has integral type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object [Footnote: An object that is not an array element is considered to belong to a single-element array for this purpose; see 7.6.2.2 [expr.unary.op] —end footnote], and the array is large enough, the result points to an element...
Change 7.6.9 [expr.rel] paragraph 3 as follows:
Comparing pointers to objects [Footnote: An object that is not an array element is considered to belong to a single-element array for this purpose; see 7.6.2.2 [expr.unary.op] —end footnote] is defined as follows:...
[Drafting note: No change is proposed for 7.6.10 [expr.eq], since the comparison is phrased in terms of “same address”, not in terms of array elements, so the handling of one-past-the-end addresses falls out of the specification of pointer arithmetic.]
[Moved to DR at the May, 2015 meeting.]
Pointer equality is defined by reference to the addresses of the objects designated by the pointer values, reflecting the implementation technique of most/all compilers. However, this definition is intrinsically a runtime property, and such a description is inappropriate with respect to constexpr expressions, which must deal with pointer comparisons without necessarily knowing the runtime layout of the objects involved. A better definition usable at compile time is needed.
Proposed resolution (November, 2014):
Change 7.6.10 [expr.eq] paragraph 2, converting the existing running text into bullets, as follows:
If at least one of the operands is a pointer, pointer conversions (7.3.12 [conv.ptr]) and qualification conversions (7.3.6 [conv.qual]) are performed on both operands to bring them to their composite pointer type (Clause 7 [expr]). Comparing pointers is defined as follows:
Two pointers compare equal
If one pointer represents the address of a complete object, and another pointer represents the address one past the last element of a different complete object [Footnote: An object that is not an array element is considered to belong to a single-element array for this purpose; see 7.6.2.2 [expr.unary.op]. —end footnote], the result of the comparison is unspecified.
Otherwise, if
theythe pointers are both null, both point to the same function, or both represent the same address (6.8.4 [basic.compound]), they compare equal.
otherwise theyOtherwise, the pointers compare unequal.
[Moved to DR at the November, 2014 meeting.]
Comparison of pointers to members is currently specified in 7.6.10 [expr.eq] paragraph 3 as,
two pointers to members compare equal if they would refer to the same member of the same most derived object (6.7.2 [intro.object]) or the same subobject if indirection with a hypothetical object of the associated class type were performed, otherwise they compare unequal.
The “same member” requirement could be interpreted as incorrect for union members. The wording should be clarified in this regard.
Proposed Resolution (July, 2014):
Insert the following before bullet 5 of 7.6.10 [expr.eq] paragraph 3:
...
If both refer to (possibly different) members of the same union (11.5 [class.union]), they compare equal.
Otherwise, two pointers to members compare equal if...
[Moved to DR at the November, 2014 meeting.]
The final bullet of 7.6.16 [expr.cond] paragraph 3, describing the attempt to convert the operands of the conditional operator to the other operand's type as part of determining the type of the result, says,
Otherwise (i.e., if E1 or E2 has a nonclass type, or if they both have class types but the underlying classes are not either the same or one a base class of the other): E1 can be converted to match E2 if E1 can be implicitly converted to the type that expression E2 would have if E2 were converted to a prvalue (or the type it has, if E2 is a prvalue).
The phrase “if E2 were converted to a prvalue” is problematic if E2 has an array type. For example,
struct S {
S(const char *s);
operator const char *();
};
S s;
const char *f(bool b) {
return b ? s : ""; // #1
}
One might expect that the expression in #1 would be ambiguous, since S can be converted both to and from const char*. However, the target type for the conversion of s is const char[1], not const char*, so that conversion fails and the result of the conditional-expression has type S.
It might be better to specify the target type for this trial conversion to be the type after the usual lvalue-to-rvalue, array-to-pointer, and function-to-pointer conversions instead of simply the result of converting “to a prvalue.”
Proposed resolution (February, 2014):
Change the final subbullet of 7.6.16 [expr.cond] paragraph 3 as follows:
[Editorial note: this wording was approved by CWG, but I'd suggest an editorial change to “...or if both have class types but the underlying classes are not the same and neither is a base class of the other.” —wmm]...The process for determining whether an operand expression E1 of type T1 can be converted to match an operand expression E2 of type T2 is defined as follows:
...
If E2 is a prvalue or if neither of the conversions above can be done and at least one of the operands has (possibly cv-qualified) class type:
if E1 and E2 have class type...
Otherwise (i.e., if E1 or E2 has a nonclass type, or if they both have class types but neither are the underlying classes
are not eitherthe sameornor is one a base class of the other): E1 can be converted to match E2 if E1 can be implicitly converted to the type thatexpressionE2 would haveif E2 were converted to a prvalue (or the type it has, if E2 is a prvalue)after applying the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions.
[Moved to DR at the November, 2014 meeting.]
Presumably the result of something like
b ? x : throw y
is a bit-field if x is, but the current wording does not say that.
Proposed resolution (February, 2014):
Change 7.6.16 [expr.cond] paragraph 2 as follows (this assumes the revised wording of the resolution of issue 1299 as the base text):
If either the second or the third operand has type void, one of the following shall hold:
The second or the third operand (but not both) is a (possibly parenthesized) throw-expression (14.2 [except.throw]); the result is of the type and value category of the other operand. The conditional-expression is a temporary expression if that operand is a temporary expression and is a bit-field if that operand is a bit-field.
...
[Adopted at the February, 2016 meeting.]
In an example like,
struct B; struct A { A(); A(B&) = delete; operator B&(); }; struct B : A {} b; B &c = true ? A() : b;
the rules of 7.6.16 [expr.cond] paragraph 3 make this ambiguous: A() can be implicitly converted to the type “lvalue reference to B,” and b satisfies the constraints to be converted to an A prvalue (it's of a type derived from A and the cv-qualifiers are okay). Bullet 3 bullet 1 is clear that we do not actually try to create an A temporary from b, so we don't notice that it invokes a deleted constructor and rule out that conversion.
If the deleted conversion is in the other sense, the result is unambiguous:
struct B; struct A { A(); A(B&); operator B&() = delete; }; struct B : A {} b; B &c = true ? A() : b;
A() can no longer be implicitly converted to the type “lvalue reference to B”: since the declaration B &t = A(); is not well formed (it invokes a deleted function), there is no implicit conversion. So we unambiguously convert the third operand to an A prvalue.
These should presumably either both be valid or both invalid. EDG and gcc call both ambiguous.
Notes from the June, 2014 meeting:
The wording should be changed to handle the convertibility test more like overload resolution: the conversion "exists" if the conversion function is declared, but is ill-formed if it would actually be used.
Proposed resolution (October, 2015):
Add the following as a new paragraph following 7.6.16 [expr.cond] paragraph 2:
Otherwise, if the second and third operand are glvalue bit-fields of the same value category and of types cv1 T and cv2 T, respectively, the operands are considered to be of type cv T for the remainder of this section, where cv is the union of cv1 and cv2.
Change 7.6.16 [expr.cond] paragraph 3 as follows:
Otherwise, if the second and third operand have different types and either has (possibly cv-qualified) class type, or if both are glvalues of the same value category and the same type except for cv-qualification, an attempt is made to
convertform an implicit conversion sequence (12.2.4.2 [over.best.ics]) from each of those operands to the type of the other. [Note: Properties such as access, whether an operand is a bit-field, or whether a conversion function is deleted are ignored for that determination. —end note]The process for determining whether an operand expression E1 of type T1 can be converted to match an operand expression E2 of type T2 is defined as follows:Attempts are made to form an implicit conversion sequence from an operand expression E1 of type T1 to a target type related to the type T2 of the operand expression E2 as follows:
If E2 is an lvalue
: E1 can be converted to match E2 if E1 can be implicitly converted (7.3 [conv]) to the type, the target type is “lvalue reference to T2”, subject to the constraint that in the conversion the reference must bind directly (9.5.4 [dcl.init.ref]) to an lvalue.If E2 is an xvalue
: E1 can be converted to match E2 if E1 can be implicitly converted to the type, the target type is “rvalue reference to T2”, subject to the constraint that the reference must bind directly.If E2 is a prvalue or if neither of the
conversionsconversion sequences above can bedoneformed and at least one of the operands has (possibly cv-qualified) class type:
if E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied, E1 is changed to a prvalue of type T2 by copy-initializing a temporary of type T2 from E1 and using that temporary as the converted operand.if T1 and T2 are the same class type (ignoring cv-qualification), or one is a base class of the other, and T2 is at least as cv-qualified as T1, the target type is T2,
Otherwise (if E1 or E2 has a non-class type, or if they both have class types but the underlying classes are not the same and neither is a base class of the other): E1 can be converted to match E2 if E1 can be implicitly converted tootherwise, the target type is the type that E2 would have after applying the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions.Using this process, it is determined whether an implicit conversion sequence can be formed from the second operand
can be converted to matchto the target type determined for the third operand, andwhether the third operand can be converted to match the second operandvice versa. If bothcan be convertedsequences can be formed, or onecan be converted but the conversion isformed, but it is the ambiguous conversion sequence, the program is ill-formed. Ifneither can be convertedno conversion sequence can be formed, the operands are left unchanged and further checking is performed as described below.If exactly one conversion is possible,Otherwise, if exactly one conversion sequence can be formed, that conversion is applied to the chosen operand and the converted operand is used in place of the original operand for the remainder of this section. [Note: The conversion might be ill-formed even if an implicit conversion sequence could be formed. —end note]
This resolution also resolves issue 1932.
[Adopted at the February, 2016 meeting.]
According to 7.6.16 [expr.cond] paragraph 3,
if the second and third operand have different types and either has (possibly cv-qualified) class type, or if both are glvalues of the same value category and the same type except for cv-qualification, an attempt is made to convert each of those operands to the type of the other. The process for determining whether an operand expression E1 of type T1 can be converted to match an operand expression E2 of type T2 is defined as follows:
If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted ( 7.3 [conv]) to the type “lvalue reference to T2”, subject to the constraint that in the conversion the reference must bind directly (9.5.4 [dcl.init.ref]) to an lvalue.
If two bit-field glvalues have exactly the same scalar type, paragraph 3 does not apply (two non-class operands must differ in at least cv-qualification). For an example like
struct S { int i:3; const int j:4; } s; int k = true ? s.i : s.j;
the condition is satisfied. The intent is that S::i can be converted to const int but S::j cannot be converted to int, so the result should be a bit-field lvalue of type const int. However, the test for convertibility is phrased in terms of direct reference binding, which is inapplicable to bit-fields, resulting in neither conversion succeeding, leading to categorizing the expression as ambiguous.
Proposed resolution (October, 2015):
This issue is resolved by the resolution of issue 1895.
[Moved to DR at the May, 2015 meeting.]
According to 7.6.20 [expr.comma] paragraph 1,
The type and value of the result are the type and value of the right operand; the result is of the same value category as its right operand, and is a bit-field if its right operand is a glvalue and a bit-field.
The description of a bit-field result seems to indicate that the operand might not be a glvalue but could still be a bit-field. There doesn't appear to be a normative prohibition against prvalue bit-fields, so one should presumably be added, and this wording should be adjusted to remove the suggestion that such a thing might exist.
Proposed resolution (November, 2014):
Change 7.2.1 [basic.lval] paragraph 2 as follows:
Whenever a glvalue appears in a context where a prvalue is expected, the glvalue is converted to a prvalue; see 7.3.2 [conv.lval], 7.3.3 [conv.array], and 7.3.4 [conv.func]. [Note: An attempt to bind an rvalue reference to an lvalue is not such a context; see 9.5.4 [dcl.init.ref]. —end note] [Note: There are no prvalue bit-fields; if a bit-field is converted to a prvalue (7.3.2 [conv.lval]), a prvalue of the type of the bit-field is created, which might then be promoted (7.3.7 [conv.prom]). —end note]
Change 7.6.20 [expr.comma] paragraph 1 as follows:
...The type and value of the result are the type and value of the right operand; the result is of the same value category as its right operand, and is a bit-field if its right operand isa glvalue anda bit-field. If the value...
The example in 7.7 [expr.const] paragraph 6,
struct A { constexpr A(int i) : val(i) { } constexpr operator int() { return val; } constexpr operator long() { return 43; } private: int val; }; template<int> struct X { }; constexpr A a = 42; X<a> x; // OK: unique conversion to int int ary[a]; // error: ambiguous conversion
is no longer correct now that constexpr does not imply const for member functions, since the conversion functions cannot be invoked for the constant a.
Notes from the September, 2013 meeting:
This issue is being handled editorially and is being placed in "review" status to ensure that the change has been made.
[Moved to DR at the November, 2014 meeting.]
We're missing a restriction on the value of a temporary which is bound to a static storage duration reference:
void f(int n) { static constexpr int *&&r = &n; }
This is currently valid, because &n is a core constant expression, and it is a constant expression because the reference binds to a temporary (of type int*) that has static storage duration (because it's lifetime-extended by the reference binding).
The value of r is constant here (it's a constant reference to a temporary with a non-constant initializer), but I don't think we should accept this. Generally, I think a temporary which is lifetime-extended by a constexpr variable should also be treated as if it were declared to be a constexpr object.
Proposed resolution (September, 2013) [SUPERSEDED]:
Change 7.7 [expr.const] paragraph 4 as follows:
A constant expression is either a glvalue core constant expression whose value refers to an
object with static storage duration or to a functionentity that is a permitted result of a constant expression, or a prvalue core constant expression whose value is an object where, for that object and its subobjects:
each non-static data member of reference type refers to an
object with static storage duration or to a functionentity that is a permitted result of a constant expression, andif the object or subobject is of pointer type, it contains the address of an object with static storage duration, the address past the end of such an object (7.6.6 [expr.add]), the address of a function, or a null pointer value.
An entity is a permitted result of a constant expression if it is an object with static storage duration that is either not a temporary or is a temporary whose value satisfies the above constraints, or it is a function.
Proposed resolution (February, 2014):
Change 7.7 [expr.const] paragraph 4 as follows:
A constant expression is either a glvalue core constant expression whose value refers to an
object with static storage duration or to a functionentity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value is an object where, for that object and its subobjects:
each non-static data member of reference type refers to an
object with static storage duration or to a functionentity that is a permitted result of a constant expression, andif the object or subobject is of pointer type, it contains the address of an object with static storage duration, the address past the end of such an object (5.7), the address of a function, or a null pointer value.
An entity is a permitted result of a constant expression if it is an object with static storage duration that is either not a temporary object or is a temporary object whose value satisfies the above constraints, or it is a function.
[Moved to DR at the November, 2014 meeting.]
The requirements for a constant expression in 7.7 [expr.const] permit an lvalue-to-rvalue conversion on
a non-volatile glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression
This does not exclude subobjects of objects that are not compile-time constants, for example:
int f(); struct S { S() : a(f()), b(5) {} int a, b; }; const S s; constexpr int k = s.b;
This rule is intended to provide backward compatibility with pre-constexpr C++, but it should be restricted to complete objects. Care should be taken in resolving this issue not to break the handling of string literals, since use of their elements in constant expressions depends on the current form of this rule.
Proposed resolution (February, 2014):
Change 7.7 [expr.const] bullet 2.7 as follows:
A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following expressions:
...
an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) unless it is applied to
a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression
[Note: a string literal (5.13.5 [lex.string]) corresponds to an array of such objects. —end note], ora non-volatile glvalue that refers to a subobject of a string literal (5.13.5 [lex.string]), or
a non-volatile glvalue that refers to a non-volatile object defined with constexpr...
[Moved to DR at the May, 2015 meeting.]
According to bullet 2.5 of 7.7 [expr.const],
A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following expressions:
...
an operation that would have undefined behavior [Note: including, for example, signed integer overflow (Clause 7 [expr]), certain pointer arithmetic (7.6.6 [expr.add]), division by zero (7.6.5 [expr.mul]), or certain shift operations (7.6.7 [expr.shift]) —end note];
...
The definition of “operation” is unclear. In particular, is it intended to include use of library components that are specified to produce undefined behavior, such as use of the offsetof macro when applied to a non-standard-layout class?
Proposed resolution (April, 2015):
Change 7.7 [expr.const] bullet 2.5 as follows:
an operation that would have undefined behavior as specified in Clauses Clause 4 [intro] through Clause 15 [cpp] of this International Standard [Note: including, for example, signed integer overflow ( Clause 7 [expr]), certain pointer arithmetic (7.6.6 [expr.add]), division by zero (7.6.5 [expr.mul]), or certain shift operations (7.6.7 [expr.shift]) —end note];
Add the following at the end of 7.7 [expr.const] paragraph 2:
If e satisfies the constraints of a core constant expression, but evaluation of e would evaluate an operation that has undefined behavior as specified in Clauses Clause 16 [library] through Clause 32 [thread] of this International Standard, it is unspecified whether e is a core constant expression.
[Moved to DR at the October, 2015 meeting.]
In an example like
union U { int a; mutable int b; };
constexpr U u1 = {1};
int k = (u1.b = 2);
constexpr U u2 = u1; // ok!!
The initialization of u2 is not disqualified by the current wording of the Standard because the copy is done via the object representation, not formally involving an lvalue-to-rvalue conversion. A restriction should be added to 7.7 [expr.const] forbidding the evaluation of a defaulted copy/move construction/assignment on a class type that has any variant mutable subobjects.
Proposed resolution (May, 2015):
Add the following bullet after bullet 3.1 of 9.2.6 [dcl.constexpr]:
The definition of a constexpr function shall satisfy the following constraints:
it shall not be virtual (11.7.3 [class.virtual]);
for a defaulted copy/move assignment, the class of which it is a member shall not have a mutable subobject that is a variant member;
...
Add the following bullet after bullet 4.1 of 9.2.6 [dcl.constexpr]
The definition of a constexpr constructor shall satisfy the following constraints:
the class shall not have any virtual base classes;
for a defaulted copy/move constructor, the class shall not have a mutable subobject that is a variant member;
...
[Adopted at the June, 2016 meeting.]
Consider the following example:
struct A { void *p; constexpr A(): p(this) {} }; constexpr A a; // well-formed constexpr A b = A(); // ?
The declaration of a seems well-formed because the address of a is constant.
The declaration of b, however, seems to depend on whether copy elision is performed. If it is, the declaration is the equivalent of a; if not, however, this creates a temporary and initializes p to the address of that temporary, making the initialization non-constant and the declaration ill-formed.
It does not seem desirable for the well-formedness of the program to depend on whether the implementation performs an optional copy elision.
Notes from the November, 2014 meeting:
CWG decided to leave it unspecified whether copy elision is performed in cases like this and to add a note to 11.4.5.3 [class.copy.ctor] to make clear that that outcome is intentional.
Notes from the May, 2015 meeting:
CWG agreed that copy elision should be mandatory in constant expressions.
Proposed resolution (April, 2016):
Change 7.7 [expr.const] paragraph 1 as follows:
...Expressions that satisfy these requirements, assuming that copy elision is performed, are called constant expressions. [Note:...
Change 9.2.6 [dcl.constexpr] paragraph 7 as follows, breaking the existing running text into a bulleted list:
A call to a constexpr function produces the same result as a call to an equivalent non-constexpr function in all respects except that
a call to a constexpr function can appear in a constant expression (7.7 [expr.const]) and
copy elision is mandatory in a constant expression (11.4.5.3 [class.copy.ctor]).
Change 11.4.5.3 [class.copy.ctor] paragraph 31 as follows:
...This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
...
Copy elision is required where an expression is evaluated in a context requiring a constant expression (7.7 [expr.const]) and in constant initialization (6.9.3.2 [basic.start.static]). [Note: Copy elision might not be performed if the same expression is evaluated in another context. —end note] [Example:
class Thing { public: Thing(); ~Thing(); Thing(const Thing&); }; Thing f() { Thing t; return t; } Thing t2 = f(); struct A { void *p; constexpr A(): p(this) {} }; constexpr A a; // well-formed, a.p points to a constexpr A b = A(); // well-formed, b.p points to b void g() { A c = A(); // well-formed, c.p may point to c or to an ephemeral temporary }Here the criteria for elision can be combined...
[Adopted at the February, 2016 meeting.]
According to 7.7 [expr.const] paragraph 5,
A constant expression is either a glvalue core constant expression whose value... or a prvalue core constant expression whose value is an object where...
Since an integer literal is prvalue that is not an object, this definition does not allow it to be a constant expression.
Proposed resolution (February, 2016):
Change 7.7 [expr.const] paragraph 5 as follows:
A constant expression is either a glvalue core constant expression whose value refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value
is an object where, for that object and its subobjectssatisfies the following constraints:
if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a constant expression,
andif the
object or subobjectvalue is of pointer type, it contains the address of an object with static storage duration, the address past the end of such an object (7.6.6 [expr.add]), the address of a function, or a null pointer value., andif the value is an object of class or array type, each subobject satisfies these constraints for the value.
An entity is a permitted result of a constant expression if...
[Adopted at the February, 2016 meeting.]
The current wording of 7.7 [expr.const] bullet 2.9 says:
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is initialized with a constant expression or
it is a non-static data member of an object whose lifetime began within the evaluation of e;
This incorrectly excludes non-member references whose lifetime began within the current evaluation.
Proposed resolution (February, 2016):
Change 7.7 [expr.const] bullet 2.9.2 as follows:
A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following expressions:
...
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is initialized with a constant expression or
it is a non-static data member of an object whoseits lifetime began within the evaluation of e;...
[Moved to DR at the October, 2015 meeting.]
It would be helpful to have a single grammar term for expression and braced-init-list, which often occur together in the text. In particular, 8.6.5 [stmt.ranged] paragraph 1 allows both, but the description of __RangeT refers only to the expression case; such errors would be less likely if the common term were available.
Proposed resolution (May, 2015):
Add a new production to the grammar in 9.5 [dcl.init] paragraph 1:
Change the grammar in 7.6.1 [expr.post] paragraph 1 as follows:
Change the grammar in 8.6 [stmt.iter] paragraph 1 as follows:
Change 8.6.5 [stmt.ranged] paragraph 1 as follows:
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 parentheses90
( expression )
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, aA range-based for statement is equivalent to{ auto && __range =range-initfor-range-initializer; for ( auto __begin = begin-expr, __end = end-expr; __begin != __end; ++__begin ) { for-range-declaration = *__begin; statement } }where
if the for-range-initializer is an expression, it is regarded as if it were surrounded by parentheses (so that a comma operator cannot be reinterpreted as delimiting two init-declarators);
__range, __begin, and __end are variables defined for exposition only
,; and
_RangeT is the type of the expression, andbegin-expr and end-expr are determined as follows:
if
_RangeTthe for-range-initializer is an expression of array type R, begin-expr and end-expr are __range and __range + __bound, respectively, where __bound is the array bound. If_RangeTR is an array of unknown size or an array of incomplete type, the program is ill-formed;if
_RangeTthe for-range-initializer isaan expression of class type C, the unqualified-ids begin and end are looked up in the scope ofclass _RangeTC as if by class member access lookup (_N4868_.6.5.6 [basic.lookup.classref]), and if either (or both) finds at least one declaration, begin-expr and end-expr are __range.begin() and __range.end(), respectively;otherwise, begin-expr and end-expr are begin(__range) and end(__range), respectively, where begin and end are looked up in the associated namespaces (6.5.4 [basic.lookup.argdep]). [Note: Ordinary unqualified lookup (6.5.3 [basic.lookup.unqual]) is not performed. —end note]
Change the grammar of 8.7 [stmt.jump] paragraph 1 as follows:
Change 8.7.4 [stmt.return] paragraph 2 as follows:
Theexpression or braced-init-listexpr-or-braced-init-list of a return statement is called its operand. A return statement...
Change 12.4.5 [over.sub] paragraph 1 as follows:
operator[] shall be a non-static member function with exactly one parameter. It implements the subscripting syntax
postfix-expression [
expressionexpr-or-braced-init-list ]
or
postfix-expression [ braced-init-list ]Thus, a subscripting expression...
[Adopted at the February, 2016 meeting.]
According to 8.7.4 [stmt.return] paragraph 2,
Flowing off the end of a function is equivalent to a return with no value...
This is not correct, since a return with no value is ill-formed in a value-returning function but flowing off the end results in undefined behavior.
Proposed resolution (May, 2015): [SUPERSEDED]
Change 8.7.4 [stmt.return] paragraph 2 as follows:
...Flowing off the end of a value-returning function is undefined behavior. Flowing off the end of any other function is equivalent to a return with no value; this results in undefined behavior in a value-returning function.
Additional notes, October, 2015:
There is similar wording in 14.4 [except.handle] paragraph 14. Also, it might be better to avoid the use of the word “value”, since it is currently not clearly defined.
Proposed resolution (October, 2015):
Change 6.9.3.1 [basic.start.main] paragraph 5 as follows:
A return statement in main has the effect of leaving the main function (destroying any objects with automatic storage duration) and calling std::exit with the return value as the argument. If control
reachesflows off the end of the compound-statement of mainwithout encountering a return statement, the effect isthat of executingequivalent to a return with operand 0 (see also 14.4 [except.handle]).return 0;
Change 8.7.4 [stmt.return] paragraph 2 as follows:
...Flowing off the end of a function with a void return type is equivalent to a return with novalue; this results in undefined behavior in a value-returning functionoperand. Otherwise, flowing off the end of a function other than main (6.9.3.1 [basic.start.main] results in undefined behavior.
Change 14.4 [except.handle] paragraph 14 as follows:
The currently handled exception is rethrown if control reaches the end of a handler of the function-try-block of a constructor or destructor. Otherwise,a function returns when control reaches the end of a handler for the function-try-block (8.7.4 [stmt.return]). Flowing off the end of a function-try-block is equivalent to a return with no value; this results in undefined behavior in a value-returning function (8.7.4 [stmt.return])flowing off the end of the compound-statement of a handler of a function-try-block is equivalent to flowing off the end of the compound-statement of that function (see 8.7.4 [stmt.return]).
[Moved to DR at the November, 2014 meeting.]
Although repeated type-specifiers such as const are forbidden, there is no such prohibition against repeated non-type specifiers like constexpr and virtual. Should there be?
On the “con” side, it's not clear that such a prohibition actually helps anyone; it could happen via macros, and a warning about non-macro use could be a QoI issue. Also, C99 moved in the opposite direction, removing the prohibition against repeated cv-qualifiers.
Proposed resolution (February, 2014):
Add the following as a new paragraph before 9.2 [dcl.spec] paragraph 2:
Each decl-specifier shall appear at most once in the complete decl-specifier-seq of a declaration, except that long may appear twice.
If a type-name is encountered...
[Moved to DR at the October, 2015 meeting.]
In an example like
void f() {
f(); // #1
}
The statement at #1 is ambiguous and can be parsed as either an expression or as a declaration. The problem is the fact that the decl-specifier-seq in a simple-declaration is optional.
Proposed resolution (May, 2015):
Change the grammar in 9.1 [dcl.pre] paragraph 1 as follows:
Change 9.1 [dcl.pre] paragraph 2 as follows:
TheA simple-declaration or nodeclspec-function-declaration of the formattribute-specifier-seqopt decl-specifier-seqopt init-declarator-listopt ;
is divided into three parts. Attributes are described in 9.13 [dcl.attr]. decl-specifiers, the principal components of a decl-specifier-seq, are described in 9.2 [dcl.spec]. declarators, the components of an init-declarator-list, are described in 9.3 [dcl.decl]. The attribute-specifier-seq
in a simple-declarationappertains to each of the entities declared by the declarators of the init-declarator-list. [Note:...
Change 9.1 [dcl.pre] paragraph 11 as follows:
Only in function declarations forA nodeclspec-function-declaration shall declare a constructors, destructors,and typeor conversionsfunctioncan the decl-specifier-seq be omitted.93 [Note: a nodeclspec-function-declaration can only be used in a template-declaration (Clause 13 [temp]), explicit-instantiation (13.9.3 [temp.explicit]), or explicit-specialization (13.9.4 [temp.expl.spec]). —end note]
Change 9.3.4 [dcl.meaning] paragraph 1 as follows:
A list of declarators appears after an optional ( 9.1 [dcl.pre]) decl-specifier-seq (9.2 [dcl.spec]). EachA declarator contains exactly one declarator-id; it names the identifier...
Change 11.4.5 [class.ctor] paragraph 1 as follows:
...In a constructor declaration, eachEach decl-specifier in theoptionaldecl-specifier-seq of a constructor declaration (if any) shall be friend, inline, explicit, or constexpr. [Example:...
Change 11.4.8.3 [class.conv.fct] paragraph 1 as follows:
...Such functions are called conversion functions.No return type can be specified.A decl-specifier in the decl-specifier-seq of a conversion function (if any) shall be neither a type-specifier nor static.If a conversion function is a member function, theThe type of the conversion function (9.3.4.6 [dcl.fct]) is...
Delete 11.4.8.3 [class.conv.fct] paragraph 6:
Conversion functions cannot be declared static.
Change 11.4.7 [class.dtor] paragraph 1 as follows:
...In a destructor declaration, eachEach decl-specifier of theoptionaldecl-specifier-seq of a destructor declaration (if any) shall be friend, inline, or virtual.
This resolution also resolves issue 2016.
[Moved to DR at the November, 2014 meeting.]
According to 9.2.2 [dcl.stc] paragraph 1,
...If thread_local appears in any declaration of a variable it shall be present in all declarations of that entity... A storage-class-specifier shall not be specified in an explicit specialization (13.9.4 [temp.expl.spec]) or an explicit instantiation (13.9.3 [temp.explicit]) directive.
These two requirements appear to be in conflict when an explicit instantiation or explicit specialization names a thread_local variable. For example,
template <class T> struct S { thread_local static int tlm; }; template <> int S<int>::tlm = 0; template <> thread_local int S<float>::tlm = 0;
which of the two explicit specializations is correct?
Proposed resolution (February, 2014):
Change 9.2.2 [dcl.stc] paragraph 1 as follows:
...A storage-class-specifier other than thread_local shall not be specified in an explicit specialization (13.9.4 [temp.expl.spec]) or an explicit instantiation (13.9.3 [temp.explicit]) directive.
[Moved to DR at the November, 2014 meeting.]
According to 9.2.2 [dcl.stc] paragraph 9,
The mutable specifier can be applied only to names of class data members (11.4 [class.mem]) and cannot be applied to names declared const or static, and cannot be applied to reference members.
This is similar to issue 1686 in that the restriction appears to apply only to declarations in which the const keyword appears directly. It should instead apply to members with const-qualified types, regardless of how the qualification was achieved.
Proposed resolution (January, 2014) [SUPERSEDED]:
Change 9.2.2 [dcl.stc] paragraph 9 as follows:
The mutable specifier can be applied only to names of non-static class data members (11.4 [class.mem])and cannot be applied to names declared const or static, and cannot be applied to reference memberswhose type is neither const-qualified nor a reference type. [Example:...
Proposed resolution (February, 2014):
Change 9.2.2 [dcl.stc] paragraph 9 as follows:
The mutable specifiercan be appliedshall appear onlyto namesin the declaration ofclassa non-static datamembersmember (11.4 [class.mem])and cannot be applied to names declared const or static, and cannot be applied to reference memberswhose type is neither const-qualified nor a reference type. [Example:...
[Adopted at the February, 2016 meeting.]
According to 9.2.2 [dcl.stc] paragraph 1,
If a storage-class-specifier appears in a decl-specifier-seq, there can be no typedef specifier in the same decl-specifier-seq and the init-declarator-list of the declaration shall not be empty...
This obviously should apply to mutable but does not because mutable applies to member-declarator-lists, not init-declarator-lists. Similarly, in 9.2.9.2 [dcl.type.cv] paragraph 1,
If a cv-qualifier appears in a decl-specifier-seq, the init-declarator-list of the declaration shall not be empty.
this should apply to member declarations as well.
Proposed resolution (October, 2015):
Change 9.2.2 [dcl.stc] paragraph 1 as follows:
...If a storage-class-specifier appears in a decl-specifier-seq, there can be no typedef specifier in the same decl-specifier-seq and the init-declarator-list or member-declarator-list of the declaration shall not be empty (except for an anonymous union declared in a named namespace or in the global namespace, which shall be declared static (11.5 [class.union])). The storage-class-specifier applies...
Change 9.2.9.2 [dcl.type.cv] paragraph 1 as follows:
...If a cv-qualifier appears in a decl-specifier-seq, the init-declarator-list or member-declarator-list of the declaration shall not be empty. [Note:...
Additional note, November, 2014:
The preceding resolution, which was advanced to "tentatively ready" status during the review session following the November, 2014 (Urbana) meeting, introduces an apparently unintentional conflict with 11.5 [class.union] paragraph 6 regarding the requirements for anonymous unions in unnamed namespaces and has been returned to "review" status to allow further discussion.
Notes from the October, 2015 meeting:
The proposed resolution was changed to address the preceding concern.
[Moved to DR at the November, 2014 meeting.]
According to 9.2.3 [dcl.fct.spec] paragraph 4,
A string literal in the body of an extern inline function is the same object in different translation units.
The Standard does not otherwise specify when string literals are required to be the same object, and this requirement is not widely implemented. Should it be removed?
Proposed resolution (February, 2014):
Change 5.13.5 [lex.string] paragraph 1 as follows:
Astring literalstring-literal is a sequence of characters...
Change 5.13.5 [lex.string] paragraph 2 as follows:
Astring literalstring-literal that has an R in the prefix...
Change 5.13.5 [lex.string] paragraph 6 as follows:
After translation phase 6, astring literalstring-literal that does not begin...
Change 5.13.5 [lex.string] paragraph 7 as follows:
Astring literalstring-literal that begins with u8...
Change 5.13.5 [lex.string] paragraph 10 as follows:
Astring literalstring-literal that begins with u, such as u"asdf", is a char16_t string literal. A char16_t string literal has type “array of n const char16_t”, where n is the size of the string as defined below; ithas static storage duration andis initialized with the given characters. A single c-char may produce more than one char16_t character in the form of surrogate pairs.
Change 5.13.5 [lex.string] paragraph 11 as follows:
Astring literalstring-literal that begins with U, such as U"asdf", is a char32_t string literal. A char32_t string literal has type “array of n const char32_t”, where n is the size of the string as defined below; ithas static storage duration andis initialized with the given characters.
Change 5.13.5 [lex.string] paragraph 12 as follows:
Astring literalstring-literal that begins with L, such as L"asdf", is a wide string literal. A wide string literal has type “array of n const wchar_t”, where n is the size of the string as defined below; ithas static storage duration andis initialized with the given characters.
Delete 5.13.5 [lex.string] paragraph 13:
Whether all string literals are distinct (that is, are stored in nonoverlapping objects) is implementation-defined. The effect of attempting to modify a string literal is undefined.
Change 5.13.5 [lex.string] paragraph 14 as follows:
In translation phase 6 (5.2 [lex.phases]), adjacentstring literalsstring-literals are concatenated. If bothstring literalsstring-literals have the same encoding-prefix, the resulting concatenated string literal has that encoding-prefix. If onestring literalstring-literal has no encoding-prefix, it is treated as astring literalstring-literal of the same encoding-prefix as the other operand. If a UTF-8 string literal token is adjacent to a wide string literal token, the program is ill-formed. Any other concatenations are conditionally-supported with implementation-defined behavior. [Note: This concatenation is an interpretation, not a conversion. Because the interpretation happens in translation phase 6 (after each character from a literal has been translated into a value from the appropriate character set), astring literalstring-literal's initial rawness has no effect on the interpretation or well-formedness of the concatenation. —end note] Table 8...
Add the following as a new paragraph at the end of 5.13.5 [lex.string]:
Evaluating a string-literal results in a string literal object with static storage duration, initialized from the given characters as specified above. Whether all string literals are distinct (that is, are stored in nonoverlapping objects) and whether successive evaluations of a string-literal yield the same or a different object is unspecified. [Note: The effect of attempting to modify a string literal is undefined. —end note]
Change 9.2.3 [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 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.
Additional note, February, 2014:
Two editorial changes have been made since CWG approved the proposed resolution:
The deletion of the requirement in 9.2.3 [dcl.fct.spec] paragraph 4 that string literals in inline functions be the same made the note following that requirement irrelevant, so the deletion has been extended to include the note as well.
The issue has been returned to "review" status to allow possible reconsideration of these editorial changes.
[Moved to DR at the May, 2015 meeting.]
With the resolution of issue 1044, there is no need to say that the name of the alias cannot appear in the type-id of the declaration.
Proposed resolution (April, 2015):
Change 9.2.4 [dcl.typedef] paragraph 2 as follows:
A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that typedef-name. It has the same semantics as if it were introduced by the typedef specifier. In particular, it does not define a new typeand it shall not appear in the type-id. [Example:...
[Adopted at the February, 2016 meeting.]
There should be a rule to prohibit the almost certainly erroneous declaration
typedef struct X { }; // Missing declarator
Proposed resolution (September, 2015):
Change 9.2.4 [dcl.typedef] paragraph 1 as follows:
Declarations containing the decl-specifier typedef declare identifiers that can be used later for naming fundamental (6.8.2 [basic.fundamental]) or compound (6.8.4 [basic.compound]) types. The typedef specifier shall not be combined in a decl-specifier-seq with any other kind of specifier except a type-specifier, and it shall not be used in the decl-specifier-seq of a parameter-declaration (9.3.4.6 [dcl.fct]) nor in the decl-specifier-seq of a function-definition (9.6 [dcl.fct.def]). If a typedef specifier appears in a declaration without a declarator, the program is ill-formed.
[Moved to DR at the November, 2014 meeting.]
According to 9.2.6 [dcl.constexpr] paragraph 1,
If any declaration of a function, function template, or variable template has a constexpr specifier, then all its declarations shall contain the constexpr specifier.
This requirement does not make sense applied to variable templates. The constexpr specifier requires that there be an initializer, and a variable template declaration with an initializer is a definition, so there cannot be more than one declaration of a variable template with the constexpr specifier.
Proposed resolution (February, 2014):
Change 9.2.6 [dcl.constexpr] paragraph 1 as follows:
...If any declaration of a function,or function template, or variable templatehas a constexpr specifier, then all its declarations shall contain the constexpr specifier. [Note:...
[Adopted at the February, 2016 meeting.]
According to 9.2.6 [dcl.constexpr] paragraph 6,
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, that specialization is still a constexpr function or constexpr constructor, even though a call to such a function cannot appear in a constant expression.
The restriction on appearing in a constant expression assumes the previous wording that made such a specialization non-constexpr, and a call to a non-constexpr function cannot appear in a constant expression. With the current wording, however, there is no normative restriction against calls to such specializations. 7.7 [expr.const] should be updated to include such a prohibition.
Proposed resolution (January, 2016):
Add the following bullet following 7.7 [expr.const] bulllet 2.3:
A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following expressions:
...
an invocation of an undefined constexpr function or an undefined constexpr constructor;
an invocation of an instantiated constexpr function or constexpr constructor that fails to satisfy the requirements for a constexpr function or constexpr constructor (9.2.6 [dcl.constexpr]);
...
[Moved to DR at the November, 2014 meeting.]
An example like
struct X { std::unique_ptr<int> p; constexpr X() { } };
is ill-formed because the X constructor cannot be used in a constant expression, because a constant expression cannot construct an object of a non-literal type like unique_ptr. This prevents use of something like
X x;
to guarantee constant-initialization.
Proposed resolution (June, 2014):
Change 9.2.6 [dcl.constexpr] paragraph 5 as follows:
For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression (7.7 [expr.const]), or, for a constructor, a constant initializer for some object (6.9.3.2 [basic.start.static]), the program is ill-formed; no diagnostic required.
[Adopted at the February, 2016 meeting.]
The requirements for constexpr functions do not, but presumably should, forbid the appearance of a label in the function body (gotos are prohibited).
Proposed resolution (January, 2016):
Add the following as an additional bullet following 9.2.6 [dcl.constexpr] bullet 3.5.2:
The definition of a constexpr function shall satisfy the following requirements:
...
its function-body shall be = delete, = default, or a compound-statement that does not contain
an asm-definition,
a goto statement,
an identifier label (8.2 [stmt.label]),
...
[Moved to DR at the November, 2014 meeting.]
The phrase “top-level cv-qualifier” is used numerous times in the Standard, but it is not defined. The phrase could be misunderstood to indicate that the const in something like const T& is at the “top level,” because where it appears is the highest level at which it is permitted: T& const is ill-formed.
Proposed resolution (February, 2014):
Change 6.8.5 [basic.type.qualifier] paragraph 5 as follows, splitting it into two paragraphs:
In this International Standard, the notation cv (or cv1, cv2, etc.), used in the description of types, represents an arbitrary set of cv-qualifiers, i.e., one of {const}, {volatile}, {const, volatile}, or the empty set. For a type cv T, the top-level cv-qualifiers of that type are those denoted by cv. [Example: The type corresponding to the type-id “const int&” has no top-level cv-qualifiers. The type corresponding to the type-id “volatile int * const” has the top-level cv-qualifier const. For a class type C, the type corresponding to the type-id “void (C::* volatile)(int) const” has the top-level cv-qualifier volatile. —end example]
Cv-qualifiers applied to an array type attach...
[Moved to DR at the November, 2014 meeting.]
The example in 9.2.9.3 [dcl.type.simple] paragraph 4 reads, in part,
const int&& foo();
int i;
decltype(foo()) x1 = i; // type is const int&&
The initialization is an ill-formed attempt to bind an rvalue reference to an lvalue.
Proposed resolution (April, 2013):
Change the example in 9.2.9.3 [dcl.type.simple] paragraph 4 as follows:
const int&& foo(); int i; struct A { double x; }; const A* a = new A(); decltype(foo()) x1 =i17; // type is const int&& decltype(i) x2; // type is int decltype(a->x) x3; // type is double decltype((a->x)) x4 = x3; // type is const double&
[Moved to DR at the November, 2014 meeting.]
According to 9.2.9.3 [dcl.type.simple] paragraph 2,
The auto specifier is a placeholder for a type to be deduced (9.2.9.7 [dcl.spec.auto]).
This is not true when auto appears in the decltype(auto) construct.
On a slightly related wording issue, 9.2.9.7 [dcl.spec.auto] paragraph 2 says,
The auto and decltype(auto) type-specifiers designate a placeholder type that will be replaced later, either by deduction from an initializer or by explicit specification with a trailing-return-type.
This could be read as implying that decltype(auto) can be used to introduce a function with a trailing-return-type, contradicting 9.3.4.6 [dcl.fct] paragraph 2, which requires that a function declarator with a trailing-return-type must have auto as the sole type specifier.
Proposed resolution (February, 2014):
Change 9.2.9.3 [dcl.type.simple] paragraph 2 as follows:
The simple-type-specifier autospecifieris a placeholder for a type to be deduced (9.2.9.7 [dcl.spec.auto]). The other simple-type-specifiers...
Change 9.2.9.7 [dcl.spec.auto] paragraph 1 as follows:
The auto and decltype(auto) type-specifiers are used to designate a placeholder type that will be replaced later
, eitherby deduction from an initializeror by explicit specification with a trailing-return-type. The auto type-specifier is also used to introduce a function type having a trailing-return-type or to signify that a lambda is a generic lambda.
[Adopted at the February, 2016 meeting.]
The resolution of issue 1966 does not apply to the case where the enumeration name is qualified, e.g.,
enum E : int; struct X { enum ::E : int(); };
Proposed resolution (October, 2015):
Change 9.8.1 [dcl.enum] paragraph 1 as follows:
...A : following “enum nested-name-specifieropt identifier” within the decl-specifier-seq of a member-declaration is parsed as part of an enum-base. [Note: This resolves a potential ambiguity...
[Moved to DR at the November, 2014 meeting.]
Return type deduction from a return statement with no expression is described in 9.2.9.7 [dcl.spec.auto] paragraph 7 as follows:
When a variable declared using a placeholder type is initialized, or a return statement occurs in a function declared with a return type that contains a placeholder type, the deduced return type or variable type is determined from the type of its initializer. In the case of a return with no operand, the initializer is considered to be void(). Let T be the declared type of the variable or return type of the function. If the placeholder is the auto type-specifier, the deduced type is determined using the rules for template argument deduction. If the deduction is for a return statement and the initializer is a braced-init-list (9.5.5 [dcl.init.list]), the program is ill-formed. Otherwise, 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, with std::initializer_list<U>. Deduce a value for U using the rules of template argument deduction from a function call (13.10.3.2 [temp.deduct.call]), where P is a function template parameter type and the initializer is the corresponding argument.
However, this does not work: the deduction for an argument of void() would give a parameter type of void and be ill-formed. It would be better simply to say that the deduced type in this case is void.
In a related example, consider
decltype(auto) f(void *p) { return *p; }
This is presumably an error because decltype(*p) would be void&, which is ill-formed. Perhaps this case should be mentioned explicitly.
Notes from the June, 2014 meeting:
The last part of the issue is not a defect, because the unary * operator requires its operand to be a pointer to an object or function type, and void is neither, so the expression is ill-formed and deduction does not occur for that case.
It was also observed during the discussion that the same deduction problem occurs when returning an expression of type void as when the expression is omitted, so the resolution should cover both cases.
Proposed resolution (June, 2014):
Change 9.2.9.7 [dcl.spec.auto] paragraph 7 as follows:
When a variable declared using a placeholder type is initialized, or a return statement occurs in a function declared with a return type that contains a placeholder type, the deduced return type or variable type is determined from the type of its initializer. In the case of a return with no operand or with an operand of type void, theinitializerdeclared return type shall be auto and the deduced return type is voidconsidered to be void().LetOtherwise, let T be the declared type...
[Moved to DR at the November, 2014 meeting.]
The Standard currently appears to allow something like
struct S { template<class T> operator auto() { return 42; } };
This is of very limited utility and presents difficulties for some implementations. It might be good to prohibit such constructs.
Proposed resolution (October, 2014):
Add the following as the last paragraph of 11.4.8.3 [class.conv.fct]:
A conversion function template shall not have a deduced return type (9.2.9.7 [dcl.spec.auto]).
[Moved to DR at the November, 2014 meeting.]
There appear to be no restrictions against using the auto specifier in examples like the following:
template<typename T> using X = T; X<auto()> f_with_deduced_return_type; // ok std::vector<auto(*)()> v; // ok?! void f(auto (*)()); // ok?!
Proposed resolution (June, 2014):
Change 9.2.9.7 [dcl.spec.auto] paragraph 2 as follows:
The placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such a declarator is valid. If the function declarator includes a trailing-return-type (9.3.4.6 [dcl.fct]), that specifies the declared return type of the function. Otherwise, the function declarator shall declare a function. If the declared return type of the function contains a placeholder type, the return type of the function is deduced from return statements in the body of the function, if any.
[Moved to DR at the May, 2015 meeting.]
According to 9.2.9.7 [dcl.spec.auto] paragraph 7,
If the placeholder is the decltype(auto) type-specifier, the declared type of the variable or return type of the function shall be the placeholder alone. The type deduced for the variable or return type is determined as described in 9.2.9.3 [dcl.type.simple], as though the initializer had been the operand of the decltype.
This is problematic when the parenthesized form of initializer is used, e.g.,
int i; decltype(auto) x(i);
the specification would deduce the type as decltype((i)), or int&. The wording should be clarified that the expression and not the entire initializer is considered to be the operand of decltype.
Proposed resolution (April, 2015):
Change 9.2.9.7 [dcl.spec.auto] paragraph 7 as follows:
...If the placeholder is the decltype(auto) type-specifier, the declared type of the variable or return type of the function shall be the placeholder alone. The type deduced for the variable or return type is determined as described in 9.2.9.3 [dcl.type.simple], as though theinitializerinitializer-clause or expression-list of the initializer or the expression of the return statement had been the operand of the decltype. [Example:int i; int&& f(); auto x2a(i); // decltype(x2a) is int decltype(auto) x2d(i); // decltype(x2d) is int auto x3a = i; // decltype(x3a) is int decltype(auto) x3d = i; // decltype(x3d) is int ...
[Adopted at the February, 2016 meeting.]
The resolution of issue 1877 does not correctly handle decltype(auto) return types with void return expressions:
T f(); decltype(auto) g() { return f(); }
fails when T is void.
Suggested resolution:
Change 9.2.9.7 [dcl.spec.auto] paragraph 7 as follows:
...In the case of a return with no operand or with an operand of type void, the declared return type shall be auto or decltype(auto) and the deduced return type is void. Otherwise...
Proposed resolution (September, 2015):
Change 9.2.9.7 [dcl.spec.auto] paragraph 7 as follows:
When a variable declared using a placeholder type is initialized, or a return statement occurs in a function declared with a return type that contains a placeholder type, the deduced return type or variable type is determined from the type of its initializer. In the case of a return with no operand or with an operand of type void
,:
if the declared return type is decltype(auto), then the deduced return type is void;
otherwise, the declared return type shall be cv auto and the deduced return type is cv void.
Otherwise, let T be...
[Adopted at the February, 2016 meeting.]
According to 9.3 [dcl.decl] paragraph 5,
The type-id in a trailing-return-type includes the longest possible sequence of abstract-declarators. [Note: This resolves the ambiguous binding of array and function declarators. [Example:
auto f()->int(*)[4]; // function returning a pointer to array[4] of int // not function returning array[4] of pointer to int—end example] —end note]
However, the grammar has changed since that rule and example were added; because trailing-return-type can only appear at the top level, there is no longer any potential ambiguity.
Proposed resolution (September, 2015):
Delete 9.3 [dcl.decl] paragraph 5:
The optional attribute-specifier-seq in a trailing-return-type appertains to the indicated return type. The type-id in a trailing-return-type includes the longest possible sequence of abstract-declarators. [Note: This resolves the ambiguous binding of array and function declarators. [Example:auto f()->int(*)[4]; // function returning a pointer to array[4] of int // not function returning array[4] of pointer to int
—end example] —end note]
[Adopted at the February, 2016 meeting.]
The declaration
operator int [[noreturn]] ();
is ambiguous with respect to the binding of the attribute. It could either be parsed (as apparently intended by the user) as part of the noptr-declarator (9.3 [dcl.decl] paragraph 4) or as part of the type-specifier-seq (9.2.9 [dcl.type] paragraph 1) of the conversion-type-id (11.4.8.3 [class.conv.fct] paragraph 1) . Current implementations disambiguate this declaration in favor of the latter interpretation, issuing an error for the declaration because the noreturn attribute cannot apply to a type.
Proposed resolution (February, 2016):
Change 11.4.8.3 [class.conv.fct] paragraph 3 as follows:
The conversion-type-id shall not represent a function type nor an array type. The conversion-type-id in a conversion-function-id is the longest
possiblesequence ofconversion-declaratorstokens that could possibly form a conversion-type-id. [Note: This prevents ambiguities between the declarator operator * and its expression counterparts. [Example:&ac.operator int*i; // syntax error: // parsed as: &(ac.operator int *)i // not as: &(ac.operator int)*iThe * is the pointer declarator and not the multiplication operator. —end example] This rule also prevents ambiguities for attributes. [Example:
operator int [[noreturn]] (); // error: noreturn attribute applied to a type—end example] —end note]
[Adopted at the February, 2016 meeting.]
According to 9.3.4 [dcl.meaning]
A static, thread_local, extern, register, mutable, friend, inline, virtual, or typedef specifier applies directly to each declarator-id in an init-declarator-list; the type specified for each declarator-id depends on both the decl-specifier-seq and its declarator.
This list is missing constexpr and explicit. Also, this should apply, but doesn't, to member-declarator-lists.
Proposed resolution (September, 2015):
Change 9.3.4 [dcl.meaning] paragraph 2 as follows:
A static, thread_local, extern, register, mutable, friend, inline, virtual, constexpr, explicit, or typedef specifier applies directly to each declarator-id in an init-declarator-list or member-declarator-list; the type specified for each declarator-id depends on both the decl-specifier-seq and its declarator.
[Adopted at the February, 2016 meeting.]
According to 9.3.4.5 [dcl.array] paragraph 3,
An array bound may also be omitted when the declarator is followed by an initializer (9.5 [dcl.init]). In this case the bound is calculated from the number of initial elements...
However, the grammar for member-declarator uses brace-or-equal-initializer, not initializer, so the following is ill-formed:
struct X { static constexpr int arr[] = { 1, 2, 3 }; };
Proposed resolution (October, 2015):
Change 9.3.4.5 [dcl.array] paragraph 3 as follows:
...An array bound may also be omitted when the declarator is followed by an initializer (9.5 [dcl.init]) or when a declarator for a static data member is followed by a brace-or-equal-initializer (11.4 [class.mem]). Inthis caseboth cases the bound is calculated from the number of initial elements...
[Moved to DR at the November, 2014 meeting.]
EDG rejects this code:
template <typename T> struct S {}; void f (S<int (*)[]>);G++ accepts it.
This is another case where the standard isn't very clear:
The language from 9.3.4.6 [dcl.fct] is:
If the type of a parameter includes a type of the form "pointer to array of unknown bound of T" or "reference to array of unknown bound of T," the program is ill-formed.Since "includes a type" is not a term defined in the standard, we're left to guess what this means. (It would be better if this were a recursive definition, the way a type theoretician would do it:
Notes from April 2003 meeting:
We agreed that the example should be allowed.
Additional note (January, 2013):
Additional discussion of this issue has arisen . For example, the following is permissible:
T (*p) [] = (U(*)[])0;
but the following is not:
template<class T> void sp_assert_convertible( T* ) {} sp_assert_convertible<T[]>( (U(*)[])0 );
Proposed resolution (February, 2014):
Change 9.3.4.6 [dcl.fct] paragraph 8 as follows, including deleting the footnote:
If the type of a parameter includes a type of the form “pointer to array of unknown bound of T” or “reference to array of unknown bound of T,” the program is ill-formed.101Functions shall not have a return type of type array or function, although...
[Moved to DR at the November, 2014 meeting.]
Consider the following example:
template<typename T> struct A { T t; }; struct S { A<S> f() { return A<S>(); } };
According to 9.3.4.6 [dcl.fct] paragraph 9,
The type of a parameter or the return type for a function definition shall not be an incomplete class type (possibly cv-qualified) unless the function is deleted (9.6.3 [dcl.fct.def.delete]) or the definition is nested within the member-specification for that class (including definitions in nested classes defined within the class).
Thus type A<S> must be a complete type. The requirement for a complete type triggers the instantiation of the template, which requires that its template argument be complete in order to use it as the type of a non-static data member.
According to 13.8.4.1 [temp.point] paragraph 4, the point of instantiation of A<S> is “immediately preced[ing] the namespace scope declaration or definition that refers to the specialization.” Thus the point of instantiation precedes the definition of S, making this example ill-formed. Most or all current implementations accept the example, however.
Perhaps the specification in 9.3.4.6 [dcl.fct] ought to say that the completeness of the type is checked from the context of the function body (at which S is a complete type)?
Proposed resolution (February, 2014):
Change 9.3.4.6 [dcl.fct] paragraph 9 as follows:
Types shall not be defined in return or parameter types. The type of a parameter or the return type for a function definition shall not be an incomplete class type (possibly cv-qualified) in the context of the function definition unless the function is deleted (9.6.3 [dcl.fct.def.delete])or the definition is nested within the member-specification for that class (including definitions in nested classes defined within the class).
[Moved to DR at the November, 2014 meeting.]
The resolution for issue 974 permitting default arguments in lambda-expressions overlooked 9.3.4.7 [dcl.fct.default] paragraph 3:
A default argument shall be specified only in the parameter-declaration-clause of a function declaration or in a template-parameter (13.2 [temp.param])...
Proposed resolution (February, 2014):
Change 9.3.4.7 [dcl.fct.default] paragraph 3 as follows:
A default argument shall be specified only in the parameter-declaration-clause of a function declaration or lambda-declarator or in a template-parameter (13.2 [temp.param]); in the latter case, the initializer-clause shall be an assignment-expression. A default argument shall not be specified for a parameter pack. If it is specified in a parameter-declaration-clause, it shall not occur within a declarator or abstract-declarator of a parameter-declaration.103
[Adopted at the February, 2016 meeting.]
According to 9.3.4.7 [dcl.fct.default] paragraph 9,
A default argument is evaluated each time the function is called with no argument for the corresponding parameter. The order of evaluation of function arguments is unspecified. Consequently, parameters of a function shall not be used in a default argument, even if they are not evaluated.
This prohibits use of parameters in unevaluated operands, e.g.,
void foo(int a = decltype(a){});
This wording predates the concept of “unevaluated operands” (the phrase “not evaluated” refers to calls to the function where an actual argument is supplied and thus the default argument is not used, not to unevaluated operands) and should not apply to such cases.
Proposed resolution (October, 2015):
Change 9.3.4.7 [dcl.fct.default] paragraph 7 as follows:
Local variablesA local variable shall notbe usedappear as a potentially-evaluated expression in a default argument. [Example:void f() { int i; extern void g(int x = i); // error extern void h(int x = sizeof(i)); // OK // ... }—end example]
Change 9.3.4.7 [dcl.fct.default] paragraph 8 as follows:
[Note: The keyword this
shallmay notbe usedappear in a default argument of a member function; see _N4567_.5.1.1 [expr.prim.general]. [Example:class A { void f(A* p = this) { } // error };
—end example] —end note]
Change 9.3.4.7 [dcl.fct.default] paragraph 9 as follows:
A default argument is evaluated each time the function is called with no argument for the corresponding parameter.
The order of evaluation of function arguments is unspecified. Consequently, parameters of a function shall not be used in a default argument, even if they are not evaluated.A parameter shall not appear as a potentially-evaluated expression in a default argument. Parameters of a function declared before a default argument are in scope and can hide namespace and class member names. [Example:int a; int f(int a, int b = a); // error: parameter a // used as default argument typedef int I; int g(float I, int b = I(2)); // error: parameter I found int h(int a, int b = sizeof(a)); //error, parameterausedOK, unevaluated operand// in default argument—end example]
Similarly, aA non-static member shall notbe usedappear in a default argument, even if it is not evaluated,unless it appears as the id-expression of a class member access expression (7.6.1.5 [expr.ref]) or unless it is used to form a pointer to member (7.6.2.2 [expr.unary.op]). [Example:...
[Accepted at the June, 2016 meeting as part of paper P0135R1.]
In this example:
struct A {}; struct B: A { B(int); B(B&); B(A); }; void foo(B); void bar() { foo(0); }
we are copy-initializing a B from 0. So by 12.2.2.5 [over.match.copy] we consider all the converting constructors of B, and choose B(int) to create a B. Then, by 9.5 [dcl.init] paragraph 15, we direct-initialize the parameter from that temporary B. By 12.2.2.4 [over.match.ctor] we consider all constructors. The copy constructor cannot be called with a temporary, but B(A) is callable.
As far as I can tell, the Standard says that this example is well-formed, and calls B(A). EDG and G++ have rejected this example with a message about the copy constructor not being callable, but I have been unsuccessful in finding anything in the Standard that says that we only consider the copy constructor in the second step of copy-initialization. I wouldn't mind such a rule, but it doesn't seem to be there. And implementing issue 391 causes G++ to start accepting the example.
This question came up before in a GCC bug report; in the discussion of that bug Nathan Sidwell said that some EDG folks explained to him why the testcase is ill-formed, but unfortunately didn't provide that explanation in the bug report.
I think the resolution of issue 391 makes this example well-formed; it was previously ill-formed because in order to bind the temporary B(0) to the argument of A(const A&) we needed to make another temporary B, and that's what made the example ill-formed. If we want this example to stay ill-formed, we need to change something else.
Steve Adamczyk:
I tracked down my response to Nathan at the time, and it related to my paper N1232 (on the auto_ptr problem). The change that came out of that paper is in 12.2.4.2 [over.best.ics] paragraph 4:
However, when considering the argument of a user-defined conversion function that is a candidate by 12.2.2.4 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, or by 12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv], or 12.2.2.7 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are allowed.
This is intended to prevent use of more than one implicit user- defined conversion in an initialization.
I told Nathan B(A) can't be called because its argument would require yet another user-defined conversion, but I was wrong. I saw the conversion from B to A and immediately thought “user-defined,” but in fact because B is a derived class of A the conversion according to 12.2.4.2 [over.best.ics] paragraph 6 is a derived-to-base Conversion (even though it will be implemented by calling a copy constructor).
So I agree with you: with the analysis above and the change for issue 391 this example is well-formed. We should discuss whether we want to make a change to keep it ill-formed.
[Moved to DR at the November, 2014 meeting.]
It is unclear whether code like the following is supposed to be supported or not:
#include <iostream> #include <type_traits> #define ENABLE_IF(...) \ typename std::enable_if<__VA_ARGS__, int>::type = 0 #define PRINT_VALUE(...) \ std::cout << #__VA_ARGS__ " = " << __VA_ARGS__ << std::endl struct undefined {}; template <class T> undefined special_default_value(T *); template <class T> struct has_special_default_value : std::integral_constant < bool, !std::is_same < decltype(special_default_value((T *)0)), undefined >{} > {}; template <class T> struct X { template <class U = T, ENABLE_IF(!has_special_default_value<U>{})> X() : value() {} template <class U = T, ENABLE_IF(has_special_default_value<U>{})> X() : value(special_default_value((T *)0)) {} T value; }; enum E { e1 = 1, e2 = 2 }; E special_default_value(E *) { return e1; } int main() { X<int> x_int; X<E> x_E; PRINT_VALUE(x_int.value); PRINT_VALUE(x_E.value); PRINT_VALUE(X<int>().value); PRINT_VALUE(X<E>().value); }
The intent is that X<int> should call the first default constructor and X<E> should call the second.
If this is intended to work, the rules for making it do so are not clear; current wording reads as if a class can have only a single default constructor, and there appears to be no mechanism for using overload resolution to choose between variants.
Proposed resolution (June, 2014):
Change 6.3 [basic.def.odr] paragraph 3 as follows:
...An assignment operator function in a class is odr-used by an implicitly-defined copy-assignment or move-assignment function for another class as specified in 11.4.5.3 [class.copy.ctor].A default constructor for a class is odr-used by default initialization or value initialization as specified in 9.5 [dcl.init].A constructor for a class is odr-used as specified in 9.5 [dcl.init]. A destructor for a class is odr-used if it is potentially invoked (11.4.7 [class.dtor]).
Change 9.5 [dcl.init] paragraph 7 as follows:
To default-initialize an object of type T means:
ifIf T is a (possibly cv-qualified) class type (Clause 11 [class]),the default constructor (11.4.5 [class.ctor]) for T is called (and the initialization is ill-formed if T has no default constructor or overload resolution (12.2 [over.match]) results in an ambiguity or in a function that is deleted or inaccessible from the context of the initialization);constructors are considered. The applicable constructors are enumerated (12.2.2.4 [over.match.ctor]), and the best one for the initializer () is chosen through overload resolution (12.2 [over.match]). The constructor thus selected is called, with an empty argument list, to initialize the object.
ifIf T is an array type, each element is default-initialized;.
otherwiseOtherwise, no initialization is performed.
Change 11.4.5 [class.ctor] paragraph 4 as follows:
A default constructor for a class X is a constructor of class X thatcan be called without an argumenteither has no parameters or else each parameter that is not a function parameter pack has a default argument. If there is no user-declared constructor...
Change 12.2 [over.match] bullet 2.4 as follows:
Overload resolution selects the function to call in seven distinct contexts within the language:
...
invocation of a constructor for default- or direct-initialization (9.5 [dcl.init]) of a class object (12.2.2.4 [over.match.ctor]);
...
Change 12.2.2.4 [over.match.ctor] paragraph 1 as follows:
When objects of class type are direct-initialized (9.5 [dcl.init]),orcopy-initialized from an expression of the same or a derived class type (9.5 [dcl.init]), or default-initialized, overload resolution selects the constructor. For direct-initialization or default-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization, the candidate functions are all the converting constructors (11.4.8.2 [class.conv.ctor]) of that class. The argument list is the expression-list or assignment-expression of the initializer.
[Moved to DR at the November, 2014 meeting.]
According to 9.5 [dcl.init] paragraph 16,
The initialization that occurs in the forms
T x(a); T x{a};as well as in new expressions (7.6.2.8 [expr.new]), static_cast expressions (7.6.1.9 [expr.static.cast]), functional notation type conversions (7.6.1.4 [expr.type.conv]), and base and member initializers (11.9.3 [class.base.init]) is called direct-initialization.
This wording was overlooked when brace-or-equal-initializers were added to the language, permitting copy-initialization of members by use of the = form.
Proposed resolution (April, 2013):
Change 9.5 [dcl.init] paragraphs 15-16 as follows, removing the example in paragraph 15 and making it a single running sentence:
The initialization that occurs in the = form of a brace-or-equal-initializer or condition (8.5 [stmt.select]),
T x = a;as well as in argument passing, function return, throwing an exception (14.2 [except.throw]), handling an exception (14.4 [except.handle]), and aggregate member initialization (9.5.2 [dcl.init.aggr]), is called copy-initialization. [Note: Copy-initialization may invoke a move (12.8). —end note]
The initialization that occurs in the forms
T x(a); T x{a};as well as in new expressions (7.6.2.8 [expr.new]), static_cast expressions (5.2.9), functional notation type conversions (7.6.1.4 [expr.type.conv]),
and base and member initializersmem-initializers (11.9.3 [class.base.init]), and the braced-init-list form of a condition is called direct-initialization.
[Moved to DR at the November, 2014 meeting.]
According to 9.5 [dcl.init] paragraph 14,
The form of initialization (using parentheses or =) is generally insignificant, but does matter when the initializer or the entity being initialized has a class type; see below.
This does not consider conversions from std::nullptr_t to bool, which are permitted only for direct-initialization (7.3.14 [conv.fctptr]).
Proposed resolution (February, 2014):
Change 9.5 [dcl.init] paragraph 14 as follows:
The form of initialization (using parentheses or =) is generally insignificant, but does matter when the initializer or the entity being initialized has a class type; see below.If the entity being initialized...
[Accepted at the February, 2016 meeting as part of paper P0017R1.]
The definition of an aggregate class 9.5.2 [dcl.init.aggr] was originally intended to include only C-like classes because proper C++ classes were expected to encapsulate data members and use constructors for initialization. Consequently, classes with bases were excluded from being aggregates.
With the inclusion of aggregate initialization in list-initialization, the consequence of this decision could be surprising, so it should be reexamined. For example,
struct A { int& val; }; struct B { }; struct C : B { int& val; }; int main() { int i = 0; A a { i } ; // #1 C c { i } ; // #2 return 0; }
it is not clear that there is a good rationale for #1 being well-formed but #2 being ill-formed.
Rationale (October, 2012):
CWG felt that this language design question would be better considered by EWG.
[Moved to DR at the November, 2014 meeting.]
With the recent addition of brace-or-equal-initializers to aggregates and the presumed resolution for issue 1696, it is not clear how lifetime extension of temporaries should work in aggregate initialization. For example:
struct A { }; struct B { A&& a { A{} } }; B b; // #1 B b{ A{} }; // #2 B b{}; // #3
#1 is default initialization, so (presumably) the lifetime of the temporary persists only until B's default constructor exits. #2 is aggregate initialization, which binds B::a to the temporary in the initializer for b and thus extends its lifetime to that of b. #3 is aggregate initialization, but it is not clear whether the lifetime of the temporary in the non-static data member initializer for B::a should be lifetime-extended like #2 or not, like #1.
One possibility might be to extend the lifetime in #3 but to give B a deleted default constructor since it would extend the lifetime of a temporary.
See also issue 1696.
Notes from the February, 2014 meeting:
CWG agreed with the suggested direction, which would treat #3 in the example like #2 and make the default constructor deleted, resulting in #1 being ill-formed.
Proposed resolution (June, 2014):
This issue is resolved by the resolution of issue 1696.
[Moved to DR at the November, 2014 meeting.]
In the case of indirect reference binding, 9.5.4 [dcl.init.ref] paragraph 5 only requires that the cv-qualification of the referred-to type be the same or greater than that of the initializer expression when the types are reference-related. This leads to the following anomaly:
class A { public: operator volatile int &(); }; A a; const int & ir1a = a.operator volatile int&(); // error! const int & ir2a = a; // allowed! ir = a.operator volatile int&();
Is this intended?
Notes from the April, 2013 meeting:
CWG felt that the declaration of ir2a should also be an error.
Proposed resolution (February, 2014):
Change 9.5.4 [dcl.init.ref] paragraph 5 as follows:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
If the reference is an lvalue reference...
Otherwise, the reference shall be an lvalue reference to a non-volatile const type...
If the initializer expression
is an xvalue (but not a bit-field), class prvalue, array prvalue or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an xvalue, class prvalue, or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (see 12.2.2.7 [over.match.ref]),
then the reference is bound to the value of the initializer expression in the first case and to the result of the conversion in the second case (or, in either case, to an appropriate base class subobject).
In the second case, if the reference is an rvalue reference and the second standard conversion sequence of the user-defined conversion sequence includes an lvalue-to-rvalue conversion, the program is ill-formed.[Example:struct A { }; struct B : A { } b; extern B f(); const A& rca2 = f(); // bound to the A subobject of the B rvalue. A&& rra = f(); // same as above struct X { operator B(); operator int&(); } x; const A& r = x; // bound to the A subobject of the result of the conversion int i2 = 42; int&& rri = static_cast<int&&>(i2); // bound directly to i2 B&& rrb = x; // bound directly to the result of operator Bint&& rri2 = X(); // error: lvalue-to-rvalue conversion applied to the // result of operator int&—end example]
Otherwise:
If T1 or T2 is a class type and T1 is not reference-related to T2, user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1 T1” by user-defined conversion (9.5 [dcl.init], 12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv]); the program is ill-formed if the corresponding non-reference copy-initialization would be ill-formed. The result of the call to the conversion function, as described for the non-reference copy-initialization, is then used to direct-initialize the reference.
The program is ill-formed if the direct-initialization does not result in a direct binding or if it involves a user-defined conversion.For this direct-initialization, user-defined conversions are not considered.
If T1 is a non-class typeOtherwise, a temporary of type “cv1 T1” is created and copy-initialized (9.5 [dcl.init]) from the initializer expression. The reference is then bound to the temporary.If T1 is reference-related to T2:
cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2; and
if the reference is an rvalue reference, the initializer expression shall not be an lvalue.
[Example:
struct Banana { }; struct Enigma { operator const Banana(); }; struct Alaska { operator Banana&(); }; void enigmatic() { typedef const Banana ConstBanana; Banana &&banana1 = ConstBanana(); // ill-formed Banana &&banana2 = Enigma(); // ill-formed Banana &&banana2 = Alaska(); // ill-formed } const double& rcd2 = 2; // rcd2 refers to temporary with value 2.0 double&& rrd = 2; // rrd refers to temporary with value 2.0 const volatile int cvi = 1; const int& r2 = cvi; // error: type qualifiers dropped struct A { operator volatile int&(); } a; const int& r3 = a; // error: type qualifiers dropped from result of conversion function double d2 = 1.0; double&& rrd2 = d2; // error:copyinginitializer is lvalue of related type struct X { operator int&(); }; int && rri2 = X(); // error: result of conversion function is lvalue of related type int i3 = 2; double&& rrd3 = i3; // rrd3 refers to temporary with value 2.0—end example]
This resolution also resolves issue 1572.
[Moved to DR at the November, 2014 meeting.]
The example just before the final bullet of 9.5.5 [dcl.init.list] paragraph 5 is incorrect. It reads, in part,
struct X { operator int&(); } x; int&& rri2 = X(); // error: lvalue-to-rvalue conversion applied to the // result of operator int&
In fact, according to 12.2.2.7 [over.match.ref] (as clarified by the proposed resolution of issue 1328, although the intent was arguably the same for the previous wording), X::operator int&() is not a candidate for the initialization of rri2, so the case falls into the last bullet, creating an int temporary.
It is not clear whether the lvalue-to-rvalue conversion whose prohibition is intended to be illustrated by that example could actually occur, given the specification of candidate functions in 12.2.2.7 [over.match.ref].
Proposed resolution (February, 2014):
This issue is resolved by the resolution of issue 1571.
[Moved to DR at the November, 2014 meeting.]
The current list-initialization rules do not provide for list-initialization of an aggregate from an object of the same type:
struct X { X() = default; X(const X&) = default; #ifdef OK X(int) { } #endif }; X x; X x2{x}; // error, {x} is not a valid aggregate initializer for X
Suggested resolution:
Change 9.5.5 [dcl.init.list] paragraph 3 as follows:
List-initialization of an object or reference of type T is defined as follows:
If T is a class type and the initializer list has a single element of type cv T or a class type derived from T, the object is initialized from that element.
IfOtherwise, if T is an aggregate...
Additional notes (September, 2012):
(See messages 22368, 22371 through 22373, 22388, and 22494.)
It appears that 12.2.4.2.6 [over.ics.list] will also need to be updated in parallel with this change. Alternatively, it may be better to change 9.5.2 [dcl.init.aggr] instead of 9.5.5 [dcl.init.list] and 12.2.4.2.6 [over.ics.list].
In a related note, given
struct NonAggregate {
NonAggregate() {}
};
struct WantsIt {
WantsIt(NonAggregate);
};
void f(NonAggregate n);
void f(WantsIt);
int main() {
NonAggregate n;
// ambiguous!
f({n});
}
12.2.4.2.6 [over.ics.list] paragraph 3 says that the call to f(NonAggregate) is a user-defined conversion, the same as the call to f(WantsIt) and thus ambiguous. Also,
NonAggregate n; // #1 (n -> NonAggregate = Identity conversion) NonAggregate m{n}; // #2 ({n} -> NonAggregate = User-defined conversion} // (copy-ctor not considered according to 12.2.4.2 [over.best.ics] paragraph 4) NonAggregate m{{n}};
Finally, the suggested resolution simply says “initialized from,” without specifying whether that means direct initialization or copy initialization. It should be explicit about which is intended, e.g., if it reflects the kind of list-initialization being done.
Proposed resolution (February, 2014) [SUPERSEDED]:
Change 9.5.5 [dcl.init.list] paragraph 3 as follows:
List-initialization of an object or reference of type T is defined as follows:
If T is a class type and the initializer list has a single element of type cv U, where U is T or a class derived from T, the object is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization).
Otherwise, if T is a character array and the initializer list has a single element that is an appropriately typed string literal (9.5.3 [dcl.init.string]), initialization is done as described in that section.
IfOtherwise, if T is an aggregate...
Delete the final bullet of 12.2.4.2 [over.best.ics] paragraph 4, as follows:
However, if the target is
the first parameter of a constructor or
the implicit object parameter of a user-defined conversion function
and the constructor or user-defined conversion function is a candidate by
12.2.2.4 [over.match.ctor], when the argument is the temporary in the second step of a class copy-initialization, or
12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv], or 12.2.2.7 [over.match.ref] (in all cases),
or
the second phase of 12.2.2.8 [over.match.list] when the initializer list has exactly one element, and the target is the first parameter of a constructor of class X, and the conversion is to X or reference to (possibly cv-qualified) X,user-defined conversion sequences are not considered. [Note:...
Insert the following two paragraphs between 12.2.4.2.6 [over.ics.list] paragraphs 1 and 2, moving the footnote from the current paragraph 3 to the second inserted paragraph:
When an argument is an initializer list (9.5.5 [dcl.init.list]), it is not an expression and special rules apply for converting it to a parameter type.
If the parameter type is a class C and the initializer list has a single element of type cv U, where U is C or a class derived from C, the implicit conversion sequence is the one required to convert the element to the parameter type.
Otherwise, if the parameter type is a character array [Footnote: Since there are no parameters of array type, this will only occur as the underlying type of a reference parameter. —end footnote] and the initializer list has a single element that is an appropriately typed string literal (9.5.3 [dcl.init.string]), the implicit conversion is the identity conversion.
IfOtherwise, if the parameter type is std::initializer_list<X> and...Otherwise, if the parameter type is “array of N X”
[Footnote: ... —end footnote], if the initializer list has...
Change 12.2.4.2.6 [over.ics.list] paragraph 7 as follows:
Otherwise, if the parameter type is not a class:
if the initializer list has one element that is not itself an initializer list, the implicit conversion sequence is the one required to convert the element to the parameter type; [Example:...
...
Change 12.2.4.3 [over.ics.rank] paragraph 3 as follows:
Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of the following rules applies:
...
List-initialization sequence L1 is a better conversion sequence than list-initialization sequence L2 if
L1 converts to std::initializer_list<X> for some X and L2 does not, or, if not that,
L1 converts to type “array of N1 T”, L2 converts to type “array of N2 T”, and N1 is smaller than N2
.,even if one of the above rules would otherwise apply. [Example:
void f1(int); // #1 void f1(std::initializer_list<long>); // #2 void g1() { f1({42}); } // chooses #2 void f2(std::pair<const char*, const char*>); // #3 void f2(std::initializer_list<std::string>); // #4 void g2() { f2({"foo","bar"}); } // chooses #4—end example]
This resolution also resolves issues 1490, 1589, and 1631.
Notes from the February, 2014 meeting:
The resolution above does not adequately address the related issue 1758. It appears that conversion functions and constructors must be handled separately.
Proposed resolution (June, 2014):
Change 9.5.5 [dcl.init.list] paragraph 3 as follows:
List-initialization of an object or reference of type T is defined as follows:
If T is a class type and the initializer list has a single element of type cv U, where U is T or a class derived from T, the object is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization).
Otherwise, if T is a character array and the initializer list has a single element that is an appropriately-typed string literal (9.5.3 [dcl.init.string]), initialization is performed as described in that section.
IfOtherwise, if T is an aggregate,Otherwise, if the initializer list has no elements...
Otherwise, if T is a specialization of std::initializer_list<E>...
Otherwise, if T is a class type...
Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization); if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed. [Example:...
Otherwise...
Change 12.2.2.8 [over.match.list] paragraph 1 as follows:
When objects of non-aggregate class type T are list-initialized(9.5.5 [dcl.init.list])such that 9.5.5 [dcl.init.list] specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor...
Change 12.2.4.2 [over.best.ics] paragraph 4 as follows:
...and the constructor or user-defined conversion function is a candidate by
12.2.2.4 [over.match.ctor], when the argument is the temporary in the second step of a class copy-initialization, or
12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv], or 12.2.2.7 [over.match.ref] (in all cases),
or
the second phase of 12.2.2.8 [over.match.list] when the initializer list has exactly one element, and the target is the first parameter of a constructor of class X, and the conversion is to X or reference to (possibly cv-qualified) X,user-defined conversion sequences are not considered.
Change 12.2.4.2.6 [over.ics.list] paragraphs 1-2 as follows, moving the footnote from paragraph 3:
When an argument is an initializer list (9.5.5 [dcl.init.list]), it is not an expression and special rules apply for converting it to a parameter type.
If the parameter type is a class X and the initializer list has a single element of type cv U, where U is X or a class derived from X, the implicit conversion sequence is the one required to convert the element to the parameter type.
Otherwise, if the parameter type is a character array [Footnote: Since there are no parameters of array type, this will only occur as the underlying type of a reference parameter. —end footnote] and the initializer list has a single element that is an appropriately-typed string literal (9.5.3 [dcl.init.string]), the implicit conversion sequence is the identity conversion.
IfOtherwise, if the parameter type is std::initializer_list<X> and...
Change 12.2.4.2.6 [over.ics.list] paragraph 7 as follows:
Otherwise, if the parameter type is not a class:
if the initializer list has one element that is not itself an initializer list, the implicit conversion sequence is the one required to convert the element to the parameter type; [Example:...
Move the final bullet of 12.2.4.3 [over.ics.rank] paragraph 3 to the beginning of the list and change it as follows:
List-initialization sequence L1 is a better conversion sequence than list-initialization sequence L2 if
L1 converts to std::initializer_list<X> for some X and L2 does not, or, if not that,
L1 converts to type “array of N1
T”, L2 converts to type “array
of N2 T”, and N1 is smaller
than N2.,
even if one of the other rules in this paragraph would otherwise apply. [Example:
void f1(int); // #1 void f1(std::initializer_list<long>); // #2 void g1() { f1({42}); } // chooses #2 void f2(std::pair<const char*, const char*>); // #3 void f2(std::initializer_list<std::string>); // #4 void g2() { f2({"foo","bar"}); } // chooses #4
—end example]
This resolution also resolves issues 1490, 1589, 1631, 1756, and 1758.
[Moved to DR at the November, 2014 meeting.]
Initialization of an array of characters from a string literal is handled by the third bullet of 9.5 [dcl.init] paragraph 16, branching off to 9.5.3 [dcl.init.string]. However, list initialization is handled by the first bullet, branching off to 9.5.5 [dcl.init.list], and there is no corresponding special case in 9.5.5 [dcl.init.list] paragraph 3 for an array of characters initialized by a brace-enclosed string literal. That is, an initialization like
char s[4]{"abc"};
is ill-formed, which could be surprising. Similarly,
std::initializer_list<char>{"abc"};
is plausible but also not permitted.
Notes from the October, 2012 meeting:
CWG agreed that the first example should be permitted, but not the second.
Proposed resolution (June, 2014):
This issue is resolved by the resolution of issue 1467.
[Adopted at the June, 2016 meeting as paper P0398R0.]
Consider the following example:
struct A { explicit A() = default; }; struct B : A { explicit B() = default; }; struct C { explicit C(); }; struct D : A { C c; explicit D() = default; }; template<typename T> void f() { T t = {}; } template<typename T> void g() { void x(T t); x({}); }
The question is whether f<B>, f<C>, f<D>, g<B>, g<C>, and g<D> are well-formed or ill-formed.
The crux here is whether 12.2.2.8 [over.match.list] is the governing law in each of these cases. If it is, the initialization is ill-formed, because copy-list-initialization has selected an explicit constructor. The standard seems clear that f<A> and g<A> are valid (because A is an aggregate, so 12.2.2.8 [over.match.list] is not reached nor applicable), f<B> is valid (because value-initialization does not call the default constructor, so 12.2.2.8 [over.match.list] is not reached), and that g<B>, g<C>, and g<D> are ill-formed (because 12.2.2.8 [over.match.list] is reached from 12.2.4.2.6 [over.ics.list] and selects an explicit constructor). The difference between f<B> and g<B> is troubling.
For f<C> and f<D>, it's not clear whether the default constructor call within value-initialization within list-initialization uses 12.2.2.8 [over.match.list] — but some form of overload resolution seems to be implied, since presumably we want to apply SFINAE to variadic constructor templates, diagnose classes which have multiple default constructors through the addition of default arguments, and the like.
It has been suggested that perhaps we are supposed to reach 12.2.2.8 [over.match.list] for an empty initializer list for a non-aggregate class with a default constructor only when we're coming from 12.2.4.2.6 [over.ics.list], and not when 9.5.5 [dcl.init.list] delegates to value-initialization. That would make all the fs valid, but g<B>, g<C>, and g<D> ill-formed.
11.4.8.2 [class.conv.ctor] paragraph 2 says explicit constructors are only used for direct-initialization or casts, which argues for at least f<C>, f<D>, g<C> and g<D> being ill-formed.
See also issue 2116.
Proposed resolution (May, 2015): [SUPERSEDED]
This issue is resolved by the resolution of issue 1630: default initialization now uses 12.2.2.4 [over.match.ctor], which now permits explicit constructors for default-initialization.
Additional note, October, 2015:
It has been suggested that the resolution of issue 1630 went too far in allowing use of explicit constructors for default initialization, and that default initialization should be considered to model copy initialization instead. The resolution of this issue would provide an opportunity to adjust that.
Proposed resolution (October, 2015):
Change 12.2.2.4 [over.match.ctor] paragraph 1 as follows:
When objects of class type are direct-initialized (9.5 [dcl.init]), copy-initialized from an expression of the same or a derived class type (9.5 [dcl.init]), or default-initialized (9.5 [dcl.init]), overload resolution selects the constructor. For direct-initializationor default-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization, the candidate functions are all the converting constructors (11.4.8.2 [class.conv.ctor]) of that class. The argument list is the expression-list or assignment-expression of the initializer.
[Adopted as paper P0135R1 at the June, 2016 meeting.]
The normative wording of 9.5.5 [dcl.init.list] regarding the lifetime of the array underlying an initializer_list object does not match the intent as specified in the example in paragraph 6 of that section, even after application of the resolution of issue 1290. That example contains the lines:
void f() { std::initializer_list<int> i3 = { 1, 2, 3 }; }
The commentary indicates that the lifetime of the array created for the initialization of i3 “persists for the lifetime of the variable.” However, that is not the effect of the normative wording. According to paragraph 3,
if T is a specialization of std::initializer_list<E>, an initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (9.5 [dcl.init]).
In other words, the underlying array for {1,2,3} in the example is associated with the temporary and shares its lifetime; its lifetime is not extended to that of the variable.
(See also issue 1565.)
Notes from the February, 2014 meeting:
The resolution of issue 1299, clarifying the relationship between temporary expressions and temporary objects, is relevant to this issue.
[Moved to DR at the November, 2014 meeting.]
The wording of 9.5.5 [dcl.init.list] paragraph 3,
if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element
does not specify whether the initialization is direct-initialization, copy-initialization, or the same kind of initialization that applied to the list-initialization. This matters when E is a class type with an explicit conversion function. (Note that aggregate initialization performs copy-initialization on its subobjects, but it's not clear whether that should be the pattern followed for this case.)
Proposed resolution (June, 2014):
This issue is resolved by the resolution of issue 1467.
[Adopted at the June, 2016 meeting.]
It is not clear in code like the following that selecting a copy/move constructor is the correct choice when an initializer list contains a single element of the type being initialized, as required by issue 1467:
#include <initializer_list> #include <iostream> struct Q { Q() { std::cout << "default\n"; } Q(Q const&) { std::cout << "copy\n"; } Q(Q&&) { std::cout << "move\n"; } Q(std::initializer_list<Q>) { std::cout << "initializer list\n"; } }; int main() { Q x = Q { Q() }; }
Here the intent is that Q objects can contain other Q objects, but this is broken by the resolution of issue 1467.
Perhaps the presence of an initializer-list constructor should change the outcome?
Proposed resolution (April, 2016):
Change 9.5.5 [dcl.init.list] bullet 3.1 as follows:
List-initialization of an object or reference of type T is defined as follows:
If T is
a class typean aggregate class and the initializer list has a single element of type cv U, where U is T or a class derived from T, the object is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization)....
Change 12.2.4.2.6 [over.ics.list] paragraph 2 as follows:
If the parameter type isaan aggregate class X and the initializer list has a single element of type cv U, where U is X or a class derived from X, the implicit conversion sequence is the one required to convert the element to the parameter type.
Change 12.2.4.2.6 [over.ics.list] paragraph 6 as follows, breaking the existing running text into a bulleted list:
Otherwise, if the parameter is a non-aggregate class X and overload resolution per 12.2.2.8 [over.match.list] chooses a single best constructor C of X to perform the initialization of an object of type X from the argument initializer
list,list:
If C is not an initializer-list constructor and the initializer list has a single element of type cv U, where U is X or a class derived from X, the implicit conversion sequence has Exact Match rank if U is X, or Conversion rank if U is derived from X
Otherwise, the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion sequence an identity conversion.
If multiple constructors are viable...
[Moved to DR at the November, 2014 meeting.]
Paragraph 5 of 9.6.1 [dcl.fct.def.general] says,
A cv-qualifier-seq or a ref-qualifier (or both) can be part of a non-static member function declaration, non-static member function definition, or pointer to member function only (9.3.4.6 [dcl.fct]); see _N4868_.11.4.3.2 [class.this].
This is redundant with the specification in 9.3.4.6 [dcl.fct] paragraph 6 and is factually incorrect, since the list there contains other permissible constructs. It should be at most a note or possibly removed altogether.
Proposed resolution (February, 2014):
Change 9.6.1 [dcl.fct.def.general] paragraph 5 as follows:
A cv-qualifier-seq or a ref-qualifier (or both) can be part of a non-static member function declaration, non-static member function definition, or pointer to member function only (9.3.4.6 [dcl.fct]); see _N4868_.11.4.3.2 [class.this].[Note: a cv-qualifier-seq affects the type of this in the body of a member function; see 9.3.4.3 [dcl.ref]. —end note]
[Adopted at the June, 2016 meeting.]
According to 9.6.1 [dcl.fct.def.general] paragraph 2,
The declarator in a function-definition shall have the form
D1 ( parameter-declaration-clause ) cv-qualifier-seqopt
ref-qualifieropt exception-specificationopt attribute-specifier-seqopt trailing-return-typeopt
However, in practice implementations accept a parenthesized declarator in a function definition.
Proposed resolution (April, 2016):
Change 9.6.1 [dcl.fct.def.general] paragraph 2 as follows:
The declarator inIn a function-definitionshall have the form,
D1 parameters-and-qualifiers trailing-return-typeopteither void declarator ; or declarator ; shall be a well-formed function declarator as described in 9.3.4.6 [dcl.fct]. A function shall be defined only in namespace or class scope.
[Moved to DR at the November, 2014 meeting.]
The current wording of 9.6.2 [dcl.fct.def.default] paragraph 2 has some surprising implications:
An explicitly-defaulted function may be declared constexpr only if it would have been implicitly declared as constexpr, and may have an explicit exception-specification only if it is compatible (14.5 [except.spec]) with the exception-specification on the implicit declaration.
In an example like
struct A { A& operator=(A&); }; A& A::operator=(A&) = default;
presumably the exception-specification of A::operator=(A&) is noexcept(false). However, attempting to make that exception-specification explicit,
A& A::operator=(A&) noexcept(false) = default;
is an error. Is this intentional?
Proposed resolution (February, 2014):
Change 14.5 [except.spec] paragraph 4 as follows:
...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 a compatible exception-specification. If a declaration of a function has an implicit exception-specification, other declarations of the function shall not specify an exception-specification. In an explicit instantiation...
(This resolution also resolves issue 1492.)
Additional note (January, 2013):
The resolution conflicts with the current specification of operator delete: in 6.7.6.5 [basic.stc.dynamic] paragraph 2, the two operator delete overloads are declared with an implicit exception specification, while in 17.6 [support.dynamic] paragraph 1, they are declared as noexcept.
Additional note (February, 2014):
The overloads cited in the preceding note have been independently changed in N3936 to include a noexcept specification, making the proposed resolution correct as it stands.
[Moved to DR at the November, 2014 meeting.]
According to 9.6.2 [dcl.fct.def.default] paragraph 2,
An explicitly-defaulted function may be declared constexpr only if it would have been implicitly declared as constexpr.
However, the rules for determining whether a function is constexpr and its exception-specification depend on the definition of function and do not apply to deleted functions. Can an explicitly-defaulted implicitly-deleted function be declared constexpr or have an exception-specification, and if so, how is its correctness to be determined?
Proposed resolution (February, 2014):
Change 9.6.2 [dcl.fct.def.default] paragraph 2 as follows:
An explicitly-defaulted function that is not defined as deleted may be declared constexpr only if it would have been implicitly declared as constexpr.
[Moved to DR at the October, 2015 meeting.]
It is not clear that the odr-use of a virtual function described in 6.3 [basic.def.odr] paragraph 3 is exempt from the prohibition against referring to a deleted function (9.6.3 [dcl.fct.def.delete] paragraph 2) .
Proposed resolution (May, 2015):
Change 9.6.3 [dcl.fct.def.delete] paragraph 2 as follows:
A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed. [Note: This includes calling the function implicitly or explicitly and forming a pointer or pointer-to-member to the function. It applies even for references in expressions that are not potentially-evaluated. If a function is overloaded, it is referenced only if the function is selected by overload resolution. The implicit odr-use (6.3 [basic.def.odr]) of a virtual function does not, by itself, constitute a reference. —end note]
[Adopted at the February, 2016 meeting.]
There is no syntax currently for declaring an explicit specialization of a member scoped enumeration. A declaration (not a definition) of such an explicit specialization most resembles an opaque-enum-declaration, but the grammar for that requires that the name be a simple identifier, which will not be the case for an explicit specialization of a member enumeration. This could be remedied by adding a nested-name-specifier to the grammar with a restriction that a nested-name-specifier only appear in an explicit specialization.
Proposed resolution (October, 2015):
Change the grammar in 9.8.1 [dcl.enum] paragraph 1 as follows:
Add the following at the end of 9.8.1 [dcl.enum] paragraph 1:
If an opaque-enum-declaration contains a nested-name-specifier, the declaration shall be an explicit specialization (13.9.4 [temp.expl.spec]).
[Moved to DR at the November, 2014 meeting.]
Although issue 1094 clarified that the value of an expression of enumeration type might not be within the range of the values of the enumeration after a conversion to the enumeration type (see 7.6.1.9 [expr.static.cast] paragraph 10), the result is simply an unspecified value. This should probably be strengthened to produce undefined behavior, in light of the fact that undefined behavior makes an expression non-constant. See also 11.4.10 [class.bit] paragraph 4.
Proposed resolution (February, 2014):
Change 7.6.1.9 [expr.static.cast] paragraph 10 as follows:
A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (9.8.1 [dcl.enum]). Otherwise, theresulting value is unspecified (and might not be in that range)behavior is undefined. A value of floating-point type...
[Moved to DR at the May, 2015 meeting.]
The resolution of issue 1514 was intended to apply whenever enum identifier appears; however, the fact that the disambiguation rule appears in the description of the enum-specifier makes it unclear that it is intended to apply in other contexts such as:
enum E { e1 }; void f() { false ? new enum E : int(); }
Proposed resolution (April, 2015):
Change 9.8.1 [dcl.enum] paragraph 1 as follows:
A : following “enum identifier” within the decl-specifier-seq of a member-declaration is parsed as part of an enum-base. [Note: This resolves a potential ambiguity between the declaration of an enumeration with an enum-base and the declaration of an unnamed bit-field of enumeration type...
[Adopted at the February, 2016 meeting.]
The description of enumeration declarations in 9.8.1 [dcl.enum] does not, but should, contain similar wording to that preventing a class definition from defining a class type named by a using-declaration:
If a class-head-name 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 (9.9.2 [namespace.def]) of that namespace (i.e., not merely inherited or introduced by a using-declaration), and the class-specifier shall appear in a namespace enclosing the previous declaration. In such cases, the nested-name-specifier of the class-head-name of the definition shall not begin with a decltype-specifier.
Proposed resolution (February, 2016):
Add the following as a new paragraph at the end of 9.8.1 [dcl.enum]:
If an enum-head contains a nested-name-specifier, the enum-specifier shall refer to an enumeration 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 (9.9.2 [namespace.def]) of that namespace (i.e., not merely inherited or introduced by a using-declaration), and the enum-specifier shall appear in a namespace enclosing the previous declaration. In such cases, the nested-name-specifier of the enum-head of the definition shall not begin with a decltype-specifier.
[Moved to DR at the November, 2014 meeting.]
According to 9.9 [basic.namespace] paragraph 1,
The name of a namespace can be used to access entities declared in that namespace; that is, the members of the namespace.
implying that all declarations in a namespace, including definitions of members of nested namespaces, explicit instantiations, and explicit specializations, introduce members of the containing namespace. _N4868_.9.8.2.3 [namespace.memdef] paragraph 3 clarifies the intent somewhat:
Every name first declared in a namespace is a member of that namespace.
However, current changes to clarify the behavior of deleted functions (which must be deleted on their “first declaration”) state that an explicit specialization of a function template is its first declaration.
Proposed resolution (November, 2014):
This issue is resolved by the resolution of issue 1838.
[Addressed by the adoption of paper N4266 at the November, 2104 meeting.]
During the discussion of paper N3394, it was observed that the grammar does not currently, but perhaps should, permit attributes to be specified for namespaces and enumerators.
[Moved to DR at the November, 2014 meeting.]
According to 9.9.2 [namespace.def] paragraph 2,
The identifier in an original-namespace-definition shall not have been previously defined in the declarative region in which the original-namespace-definition appears.
Apparently the intent of this requirement is to say that, given the declarations
namespace N { } namespace N { }
the second declaration is to be taken as an extension-namespace-definition and not an original-namespace-definition, since the general rules in _N4868_.6.4.1 [basic.scope.declarative] cover the case in which the identifier has been previously declared as something other than a namespace.
This use of “shall” for disambiguation is novel, however, and it would be better to replace it with a specific statement addressing disambiguation in paragraphs 2 and 3.
Proposed Resolution (July, 2014):
Change 6.4.6 [basic.scope.namespace] paragraph 1 as follows:
The declarative region of a namespace-definition is its namespace-body.The potential scope denoted by an original-namespace-name is the concatenation of the declarative regions established by each of the namespace-definitions in the same declarative region with that original-namespace-name.Entities declared in a namespace-body...
Change 9.9.2 [namespace.def] paragraphs 1-4 as follows:
The grammar for a namespace-definition is
namespace-name:
original-namespace-nameidentifier
namespace-alias
original-namespace-name:
identifiernamespace-definition:
named-namespace-definition
unnamed-namespace-definitionnamed-namespace-definition:
original-namespace-definition
extension-namespace-definition
original-namespace-definition:inlineopt namespace identifier { namespace-body }
extension-namespace-definition:
inlineopt namespace original-namespace-name { namespace-body }unnamed-namespace-definition:
inlineopt namespace { namespace-body }
namespace-body:
declaration-seqopt
The identifier in an original-namespace-definition shall not have been previously defined in the declarative region in which the original-namespace-definition appears. The identifier in an original-namespace-definition is the name of the namespace. Subsequently in that declarative region, it is treated as an original-namespace-name.
The original-namespace-name in an extension-namespace-definition shall have previously been defined in an original-namespace-definition in the same declarative region.Every namespace-definition shall appear in the global scope or in a namespace scope (6.4.6 [basic.scope.namespace]).
In a named-namespace-definition, the identifier is the name of the namespace. If the identifier, when looked up (6.5.3 [basic.lookup.unqual]), refers to a namespace-name (but not a namespace-alias) introduced in the declarative region in which the named-namespace-definition appears, the namespace-definition extends the previously-declared namespace. Otherwise, the identifier is introduced as a namespace-name into the declarative region in which the named-namespace-definition appears.
Change 9.9.2 [namespace.def] paragraph 7 as follows:
If the optional initial inline keyword appears in a namespace-definition for a particular namespace, that namespace is declared to be an inline namespace. The inline keyword may be used onan extension-namespace-definitiona namespace-definition that extends a namespace only if it was previously used on theoriginal-namespace-definitionnamespace-definition that initially declared the namespace-name for that namespace.
Delete 9.9.3 [namespace.alias] paragraph 4:
A namespace-name or namespace-alias shall not be declared as the name of any other entity in the same declarative region. A namespace-name defined at global scope shall not be declared as the name of any other entity in any global scope of the program. No diagnostic is required for a violation of this rule by declarations in different translation units.
Change 9.9.4 [namespace.udir] paragraph 5 as follows:
If a namespace is extendedby an extension-namespace-definitionafter a using-directive for that namespace is given, the additional members of the extended namespace and the members of namespaces nominated by using-directives in theextension-namespace-definitionextending namespace-definition can be used after theextension-namespace-definitionextending namespace-definition.
[Adopted at the February, 2016 meeting.]
After the resolution of issue 1795, 9.9.2 [namespace.def] paragraph 3 now says:
In a named-namespace-definition, the identifier is the name of the namespace. If the identifier, when looked up (6.5.3 [basic.lookup.unqual]), refers to a namespace-name (but not a namespace-alias) introduced in the declarative region in which the named-namespace-definition appears, the namespace-definition extends the previously-declared namespace. Otherwise, the identifier is introduced as a namespace-name into the declarative region in which the named-namespace-definition appears.
This appears to break code like the following:
namespace A { inline namespace b { namespace C { template<typename T> void f(); } } } namespace A { namespace C { template<> void f<int>() { } } }
because (by definition of “declarative region”) C cannot be used as an unqualified name to refer to A::b::C within A if its declarative region is A::b.
Proposed resolution (September, 2015):
Change 9.9.2 [namespace.def] paragraph 3 as follows:
In a named-namespace-definition, the identifier is the name of the namespace. If the identifier, when looked up (6.5.3 [basic.lookup.unqual]), refers to a namespace-name (but not a namespace-alias) that was introduced in thedeclarative regionnamespace in which the named-namespace-definition appears or that was introduced in a member of the inline namespace set of that namespace, the namespace-definition extends the previously-declared namespace. Otherwise, the identifier is introduced as a namespace-name into the declarative region in which the named-namespace-definition appears.
[Moved to DR at the November, 2014 meeting.]
Issue 1411 added :: as a production for nested-name-specifier. However, the grammar for using-declarations should have been updated but was overlooked:
In addition, there is some verbiage in 6.5.5.3 [namespace.qual] paragraph 1 and 9.10 [namespace.udecl] paragraph 9 that should probably be revised.
Proposed resolution (October, 2014):
Change the grammar in 9.10 [namespace.udecl] paragraph 1 as follows:
Change 6.5.5.3 [namespace.qual] paragraph 1 as follows:
If the nested-name-specifier of a qualified-id nominates a namespace (including the case where the nested-name-specifier is ::, i.e., nominating the global namespace), the name specified after the nested-name-specifier is looked up in the scope of the namespace.If a qualified-id starts with ::, the name after the :: is looked up in the global namespace. In either case, theThe names in a template-argument of a template-id are looked up in the context in which the entire postfix-expression occurs.
Change _N4567_.5.1.1 [expr.prim.general] paragraph 10 as follows:
A ::, or aThe nested-name-specifier :: names the global namespace. A nested-name-specifier that names a namespace (9.9 [basic.namespace]),in either casefollowed by the name of a member of that namespace (or the name of a member of a namespace made visible by a using-directive), is a qualified-id; 6.5.5.3 [namespace.qual] describes name lookup for namespace members that appear in qualified-ids. The result is...
Change 9.10 [namespace.udecl] paragraph 9 as follows:
Members declared by a using-declaration can be referred to by explicit qualification just like other member names (6.5.5.3 [namespace.qual]).In a using-declaration, a prefix :: refers to the global namespace.[Example:
[Adopted at the October, 2015 meeting as P0136R1.]
The set of declarations introduced by a using-declaration that is a member-declaration is specified by 9.10 [namespace.udecl] paragraph 3. However, there is no corresponding specification for a non-member using-declaration.
[Moved to DR at the November, 2014 meeting.]
According to 9.12 [dcl.link] paragraph 6,
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 entity; no diagnostic is required if the declarations appear in different translation units.
This restriction is too broad; it does not allow for the so-called stat hack, where a C-linkage function and a class are both declared in global scope, and it does not allow for function overloading, either. It should be revised to apply only to variables.
Additional note (February, 2014):
See also issue 1838 for an interaction with using-declarations.
Proposed resolution (February, 2014):
Change 9.12 [dcl.link] paragraph 6 as follows:
...An entity with C language linkage shall not be declared with the same name asan entitya variable in global scope, unless both declarations denote the same entity; no diagnostic is required if the declarations appear in different translation units...
Additional note, May, 2014:
It was observed that this resolution would allow a definition of main as a C-linkage variable in a namespace. The issue is being returned to "review" status for further discussion.
[Adopted at the February, 2016 meeting.]
According to 9.13.1 [dcl.attr.grammar] paragraph 6,
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]
In order to allow program fragments to appeae within attributes, this restriction should not apply within the balanced-token-seq of an attribute.
Proposed resolution (September, 2015):
Change 9.13.1 [dcl.attr.grammar] paragraph 6 as follows:
Two consecutive left square bracket tokens shall appear only when introducing an attribute-specifier or within the balanced-token-seq of an attribute-argument-clause. [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, y[5]; int(p[[x] { return x; }()]); // error: invalid attribute on a nested // declarator-id and not a function-style cast of // an element of p. y[[] { return 2; }()] = 2; // error even though attributes are not allowed // in this context. int i [[vendor::attr([[]])]]; // well-formed implementation-defined attribute. }—end example]
[Moved to DR at the November, 2014 meeting.]
According to 9.13.2 [dcl.align] paragraph 5,
The combined effect of all alignment-specifiers in a declaration shall not specify an alignment that is less strict than the alignment that would be required for the entity being declared if all alignment-specifiers were omitted (including those in other declarations).
Presumably the intent was “other declarations of the same entity,” but the wording as written could be read to make the following example well-formed (assuming alignof(int) is 4):
struct alignas(4) A { alignas(8) int n; }; struct alignas(8) B { char c; }; alignas(1) B b; struct alignas(1) C : B {}; enum alignas(8) E : int { k }; alignas(4) E e = k;
Proposed resolution (February, 2014):
Change 9.13.2 [dcl.align] paragraph 5 as follows:
...if all alignment-specifiers appertaining to that entity were omitted
(including those in other declarations). [Example:struct alignas(8) S {}; struct alignas(1) U { S s; }; // Error: U specifies an alignment that is less strict than // if the alignas(1) were omitted.—end example]
[Moved to DR at the October, 2015 meeting.]
The description of alignment-specifiers is unclear. For example, 9.13.2 [dcl.align] bullet 2.2 says,
if the constant expression evaluates to a fundamental alignment, the alignment requirement of the declared entity shall be the specified fundamental alignment
However, paragraph 4 says,
When multiple alignment-specifiers are specified for an entity, the alignment requirement shall be set to the strictest specified alignment.
meaning that a less-strict alignment will be ignored, rather than being the alignment of the entity, and presumably meaning that no diagnostic is required for an insufficiently-strict alignment if a more stringent requirement is also supplied.
Proposed resolution (May, 2015):
Change 9.13.2 [dcl.align] paragraph 2 as follows:
When the alignment-specifier is of the form alignas( constant-expression ):
the constant-expression shall be an integral constant expression;
if the constant expression evaluates to a fundamental alignment, the alignment requirement of the declared 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 entity shall be that alignmentif the constant expression does not evaluate to an alignment value (6.7.3 [basic.align]), or evaluates to an extended alignment and the implementation does not support that alignment in the context of the declaration, the program is ill-formed.
if the constant expression evaluates to zero, the alignment specifier shall have no effect
otherwise, the program is ill-formed.
Change 9.13.2 [dcl.align] paragraph 4 as follows:
When multiple alignment-specifiers are specified for an entity, theThe alignment requirementshall be set toof an entity is the strictestspecifiednon-zero alignment specified by its alignment-specifiers, if any; otherwise, the alignment-specifiers have no effect.
[Moved to DR at the November, 2014 meeting.]
One of the criteria for a standard-layout class in Clause 11 [class] paragraph 7 is:
either has no non-static data members in the most derived class and at most one base class with non-static data members, or has no base classes with non-static data members,
In an example like
struct B { int i; }; struct C : B { }; struct D : C { };
this could be read as indicating that D is not a standard-layout class, since it has two base classes, one direct and one indirect, that each have a non-static data member. The intent should be clarified.
See also issue 1881 for a related question about standard-layout classes.
Proposed resolution (June, 2014):
Change Clause 11 [class] paragraph 7 as follows:
A standard-layout class is a class that:
has no non-static data members of type non-standard-layout class (or array of such types) or reference,
has no virtual functions (11.7.3 [class.virtual]) and no virtual base classes (11.7.2 [class.mi]),
has the same access control ( 11.8 [class.access]) for all non-static data members,
has no non-standard-layout base classes,
has at most one base class subobject of any given type,
either has no non-static data members in the most derived class and at most one base class with non-static data members, or has no base classes with non-static data membershas all non-static data members and bit-fields in the class and its base classes first declared in the same class, andhas no base classes of the same type as the first non-static data member.109
[Example:
struct B { int i; }; // standard-layout class struct C : B { }; // standard-layout class struct D : C { }; // standard-layout class struct E : D { char : 4; }; // not a standard-layout class struct Q {}; struct S : Q { }; struct T : Q { }; struct U : S, T { }; // not a standard-layout class—end example]
This resolution also resolves issue 1881.
(See also the related changes in the resolution of issue 1672.)
[Moved to DR at the November, 2014 meeting.]
According to 11.4.10 [class.bit] paragraph 2,
Unnamed bit-fields are not members and cannot be initialized.
However, the rules defining standard-layout classes in Clause 11 [class] paragraph 7 do not account for the fact that a class containing an unnamed bit-field has associated storage.
See also issue 1813 for a related question about standard-layout classes.
Proposed resolution (June, 2014):
This issue is resolved by the resolution of issue 1813.
[Moved to DR at the October, 2015 meeting.]
The specification for determining whether a derived class is a standard-layout class, considering the types of its bases and those associated with its initial member, overlooked the case of a class whose first member is an array.
Proposed resolution (May, 2015):
Change Clause 11 [class] paragraph 7 as follows:
...M(X) is defined as follows:
If X is a non-union class type, the set M(X) is empty if X has no (possibly inherited ( 11.7 [class.derived])) non-static data members; otherwise, it consists of the type of the first non-static data member of X (where said member may be an anonymous union), X0, and the elements of M(X0).
If X is a union type, the set M(X) is the union of all M(Ui) and the set containing all Ui, where each Ui is the type of the ith non-static data member of X.
If X is an array type with element type Xe, the set M(X) consists of Xe and the elements of M(Xe).
If X is a non-class, non-array type, the set M(X) is empty.
[Note: M(X) is the set of the types of all non-base-class subobjects that are guaranteed in a standard-layout class to be at a zero offset in X. —end note]
[Adopted at the February, 2016 meeting.]
A default constructor that is defined as deleted is trivial, according to 11.4.5 [class.ctor] paragraph 5. This means that, according to Clause 11 [class] paragraph 6, such a class can be trivial. If, however, the class has no default constructor because it has a user-declared constructor, the class is not trivial. Since both cases prevent default construction of the class, it is not clear why there is a difference in triviality between the cases.
(See also issue 1928.)
Notes from the October, 2012 meeting:
It was observed that this issue was related to issue 1344, as the current specification allows adding a default constructor by adding default arguments to the definition of a constructor. The resolution of that issue should also resolve this one.
Notes from the September, 2013 meeting:
It was decided to resolve issue 1344 separately from this issue, so this issue now requires its own resolution.
Proposed resolution (October, 2015):
Change Clause 11 [class] paragraph 6 as follows:
A trivial class is a class thathas a default constructor (11.4.5 [class.ctor]), has no non-trivial default constructors, andis trivially copyable and has one or more default constructors (11.4.5 [class.ctor]), all of which are either trivial or deleted and at least one of which is not deleted. [Note: In particular, a trivially copyable or trivial class does not have virtual functions or virtual base classes. —end note]
[Moved to DR at the November, 2014 meeting.]
In Bloomington there was general agreement that given a class that uses non-static data member initializers, the exception-specification for the default constructor depends on whether those initializers are noexcept. However, according to 11.4 [class.mem] paragraph 2, the class is regarded as complete within the brace-or-equal-initializers for non-static data members.
This suggests that we need to finish processing the class before parsing the NSDMI, but our direction on issue 1351 suggests that we need to parse the NSDMI in order to finish processing the class. Can't have both...
Additional note (March, 2013):
A specific example:
struct A { void *p = A{}; operator void*() const { return nullptr; } };
Perhaps the best way of addressing this would be to make it ill-formed for a non-static data member initializer to use a defaulted constructor of its class.
See also issue 1360.
Notes from the September, 2013 meeting:
One approach that might be considered would be to parse deferred portions lazily, on demand, and then issue an error if this results in a cycle.
Proposed resolution (February, 2014):
Change 11.4 [class.mem] paragraph 4 as follows:
A brace-or-equal-initializer shall appear only in the declaration of a data member. (For static data members, see 11.4.9.3 [class.static.data]; for non-static data members, see 11.9.3 [class.base.init]). A brace-or-equal-initializer for a non-static data member shall not directly or indirectly cause the implicit definition of a defaulted default constructor for the enclosing class or the exception-specification of that constructor.
[Moved to DR at the November, 2014 meeting.]
The layout compatibility rules of 11.4 [class.mem] paragraph 16 are phrased only in terms of non-static data members, ignoring the existence of base classes:
Two standard-layout struct (Clause 11 [class]) types are layout-compatible if they have the same number of non-static data members and corresponding non-static data members (in declaration order) have layout-compatible types (6.8 [basic.types]).
However, this means that in an example like
struct empty {}; struct A { char a; }; struct also_empty : empty {}; struct C : empty, also_empty { char c; }; union U { struct X { A a1, a2; } x; struct Y { C c1, c2; } y; } u;
u.x.a2.a and u.y.c2.c must have the same address, even though sizeof(A) would typically be 1 and sizeof(B) would need to be at least 2 to give the empty subobjects different addresses.
Proposed resolution (October, 2014):
Change Clause 11 [class] paragraph 7 as indicated and add the following as a new paragraph:
A class S is a standard-layout class
is a class thatif it:
...
has no
base classes of the same type as the first non-static data memberelement of the set M(S) of types (defined below) as a base class.109M(X) is defined as follows:
If X is a non-union class type, the set M(X) is empty if X has no (possibly inherited (11.7 [class.derived])) non-static data members; otherwise, it consists of the type of the first non-static data member of X (where said member may be an anonymous union), X0, and the elements of M(X0).
If X is a union type, the set M(X), where each Ui is the type of the ith non-static data member of X, is the union of all M(Ui) and the set containing all Ui.
If X is a non-class type, the set M(X) is empty.
[Note: M(X) is the set of the types of all non-base-class subobjects that are guaranteed in a standard-layout class to be at a zero offset in X. —end note]
(See also the related changes in the resolution for issue 1813.)
[Moved to DR at the November, 2014 meeting.]
When the effect of cv-qualification on layout compatibility was previously discussed (see issue 1334), the question was resolved by reference to the historical origin of layout compatibility: it was a weakening of type correctness that was added for C compatibility, mimicking exactly the corresponding C specification of compatible types in this context and going no further. Because cv-qualified and cv-unqualified types are not compatible in C, they were not made layout-compatible in C++.
Because of specific use-cases involving std::pair and the like, however, and in consideration of the fact that cv-qualified and cv-unqualified versions of types are aliasable by the rules of 7.2.1 [basic.lval], the outcome of that question is worthy of reconsideration.
Proposed resolution (June, 2014):
Change 6.1 [basic.pre] paragraph 3 as follows:
An entity is a value, object, reference, function, enumerator, type, class member, bit-field, template, template specialization, namespace, parameter pack, or this.
Change 6.8 [basic.types] paragraph 11 as follows:
If two types T1 and T2 are the same type, then T1 and T2Two types cv1 T1 and cv2 T2 are layout-compatible types if T1 and T2 are the same type, layout-compatible enumerations (9.8.1 [dcl.enum]), or layout-compatible standard-layout class types (11.4 [class.mem]).[Note: Layout-compatible enumerations are described in 9.8.1 [dcl.enum]. Layout-compatible standard-layout structs and standard-layout unions are described in 11.4 [class.mem]. —end note]
Change 6.8.4 [basic.compound] paragraph 3 as follows:
...The value representation of pointer types is implementation-defined. Pointers tocv-qualified and cv-unqualified versions (6.8.5 [basic.type.qualifier]) oflayout-compatible types shall have the same value representation and alignment requirements (6.7.3 [basic.align]). [Note:...
Insert the following as a new paragraph before 11.4 [class.mem] paragraph 16 and change paragraphs 16 through 18 as follows:
The common initial sequence of two standard-layout struct (Clause 11 [class]) types is the longest sequence of non-static data members and bit-fields in declaration order, starting with the first such entity in each of the structs, such that corresponding entities have layout-compatible types and either neither entity is a bit-field or both are bit-fields with the same width. [Example:
struct A { int a; char b; }; struct B { const int b1; volatile char b2; }; struct C { int c; unsigned : 0; char b; }; struct D { int d; char b : 4; }; struct E { unsigned int e; char b; };The common initial sequence of A and B comprises all members of either class. The common initial sequence of A and C and of A and D comprises the first member in each case. The common initial sequence of A and E is empty. —end example]
Two standard-layout struct (Clause 11 [class]) types are layout-compatible if
they have the same number of non-static data members and corresponding non-static data members (in declaration order) have layout-compatible typestheir common initial sequence comprises all members and bit-fields of both classes (6.8 [basic.types]).Two standard-layout
union (Clause 11 [class]) typesunions are layout-compatible if they have the same number of non-static data members and corresponding non-static data members (in any order) have layout-compatible types (6.8 [basic.types]).
If a standard-layout union contains two or more standard-layout structs that share a common initial sequence, and if the standard-layout union object currently contains one of these standard-layout structs, it is permitted to inspect the common initial part of any of them. Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types and either neither member is a bit-field or both are bit-fields with the same width for a sequence of one or more initial members.In a standard-layout union with an active member (11.5 [class.union]) of struct type T1, it is permitted to read a non-static data member m of another union member of struct type T2 provided m is part of the common initial sequence of T1 and T2. [Note: Reading a volatile object through a non-volatile glvalue has undefined behavior (9.2.9.2 [dcl.type.cv]). —end note]
[Moved to DR at the November, 2014 meeting.]
The list in 11.4 [class.mem] paragraph 14 of the kinds of class members whose names must differ from that of the class does not include an entry for a member class template. Presumably it should.
Proposed resolution (October, 2014):
Change 11.4 [class.mem] paragraph 14 as follows:
If T is the name of a class, then each of the following shall have a name different from T:
...
every member of class T that is itself a type;
every member template of class T; ...
[Adopted at the February, 2016 meeting.]
The current wording does not appear to ban a pure-specifier from a friend declaration.
Proposed resolution (February, 2016):
Change 11.4 [class.mem] paragraph 6 as follows:
...A pure-specifier shall be used only in the declaration of a virtual function (11.7.3 [class.virtual]) that is not a friend declaration.
[Adopted at the February, 2016 meeting.]
There does not appear to be a rule to disambiguate a pure-specifier and a brace-or-equal-initializer in a member declarator.
Proposed resolution (February, 2016):
Add the following as a new paragraph following 11.4 [class.mem] paragraph 3:
[Note: A single name can denote several function members provided their types are sufficiently different (Clause 12 [over]). —end note]
In a member-declarator, an = immediately following the declarator is interpreted as introducing a pure-specifier if the declarator-id has function type, otherwise it is interpreted as introducing a brace-or-equal-initializer. [Example:
struct S { using T = void(); T * p = 0; // OK: brace-or-equal-initializer virtual T f = 0; // OK: pure-specifier };—end example]
[Moved to DR at the May, 2015 meeting.]
It used to be clear that an implicitly-declared default constructor is not explicit. That has been inadvertently lost due to other changes, so this specification should be added to 11.4.5 [class.ctor] in parallel with the similar statement in 11.4.5.3 [class.copy.ctor] paragraph 3.
Proposed resolution (November, 2014):
Change 11.4.5 [class.ctor] paragraph 4 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 non-explicit constructor having no parameters is implicitly declared as defaulted (9.6 [dcl.fct.def]). An implicitly-declared default constructor...
Change 11.4.8.2 [class.conv.ctor] paragraph 3 as follows:
A non-explicit copy/move constructor (11.4.5.3 [class.copy.ctor]) is a converting constructor. [Note: An implicitly-declared copy/move constructor is not an explicit constructor; it may be called for implicit type conversions. —end note]
Change 11.4.5.3 [class.copy.ctor] paragraph 7 as follows:
If the class definition does not explicitly declare a copy constructor, a non-explicit one is declared implicitly. If the class...
Change 11.4.5.3 [class.copy.ctor] paragraph 9 as follows:
If the definition of a class X does not explicitly declare a move constructor, a non-explicit one will be implicitly declared as defaulted if and only if...
[Adopted at the February, 2016 meeting.]
According to 11.4.5 [class.ctor] paragraph 4 says,
A 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,
...
This should make the following example ill-formed:
struct S { S(); }; union U { S s{}; } u;
because the default constructor of U is deleted. However, both clang and g++ accept this without error. Should the rule be relaxed for a union with an NSDMI?
Notes from the May, 2015 meeting:
An NSDMI is basically syntactic sugar for a mem-initializer, so the presence of one should be treated as if a user-declared default constructor were present.
Proposed resolution (October, 2015):
Change 11.4.5 [class.ctor] paragraph 4 as follows:
...A defaulted default constructor for class X is defined as deleted if:
X is a union that has a variant member with a non-trivial default constructor and no variant member of X has a default member initializer,
X is a
union-likenon-union class that has a variant member M with a non-trivial default constructor and no variant member of the anonymous union containing M has a default member initializer,...
[Adopted as paper P0135R1 at the June, 2016 meeting.]
Copy initialization in some cases uses constructors that are not copy/move constructors (e.g., a specialization of a constructor template might be selected by overload resolution, or in copy-list-initialization, any constructor could be selected). Some ABIs require that an object of certain class types be passed in a register (effectively using the trivial copy/move constructor), even if the class has a non-trivial constructor that would be selected to do the copy. The Standard should be changed to permit this usage.
Proposed resolution (April, 2013):
Add the following as a new paragraph following 11.4.5.3 [class.copy.ctor] paragraph 1:
When an object of class type X is passed to or returned from a function, if X has a trivial, accessible copy or move constructor that is not deleted, and X has no non-trivial copy constructors, move constructors, or destructors, implementations are permitted to perform an additional copy or move of the object using the trivial constructor (even if it would not be selected by overload resolution to perform a copy or move of the object). [Note: This latitude is granted to allow objects of class type to be passed to or returned from functions in registers. —end note]
See also issue 1928.
Additional note, May, 2014:
Questions have been raised regarding this resolution. In particular, the interaction of the “extra copy” with copy elision, lifetime, and access checking context are not specified. In addition, some concern has also been expressed regarding the requirement that the trivial copy/move constructor be accessible. The issue is being returned to "review" status for discussion of these points.
Notes from the June, 2014 meeting:
CWG felt that the requirements for accessibility should be removed, in line with the idea making all access public in a program should not change its semantics. Similarly, the prohibition of non-trivial functions was not desirable. The approach should be to recognize the extra copy as a temporary object and deal explicitly with its lifetime.
[Adopted at the February, 2016 meeting.]
The intent was for PODs in C++11 to be a superset of C++03 PODs. Consequently, in the following example, C should be a POD but isn't:
struct A { const int m; A& operator=(A const&) = default; // deleted and trivial, so A is a // POD, as it would be in 2003 // without this explicit op= decl }; static_assert(__is_trivially_copyable(A), ""); struct B { int i; B& operator=(B &) & = default; // non-trivial B& operator=(B const&) & = default; // trivial }; struct C { const B m; C& operator=(C const& r) = default; // deleted (apparently), but non-trivial (apparently) /* Notionally: C& operator=(C const& r) { (*this).m.operator=(r.m); return *this; } */ }; static_assert(!__is_trivially_copyable(C), "");
This is because of the following text from 11.4.5.3 [class.copy.ctor] paragraph 25:
for each non-static data member of X that is of class type (or array thereof), the assignment operator selected to copy/move that member is trivial;
In this case, overload resolution fails, so no assignment operator is selected, so C::operator=(const C&) is non-trivial.
(See also issue 1928.)
Additional note, November, 2014:
See paper N4148.
Additional note, October, 2015:
Moved from "extension" to "open" status, along with issue 1928, to allow reconsideration by CWG. It has been suggested that the triviality of a deleted function should be irrelevant, since it cannot be used in any event. A possible change to implement that, more conservative than the one proposed in N4148, would be:
A trivially copyable class is a class that:
has no non-trivial, non-deleted copy constructors (11.4.5.3 [class.copy.ctor]),
has no non-trivial, non-deleted move constructors (11.4.5.3 [class.copy.ctor]),
has no non-trivial, non-deleted copy assignment operators (12.4.3.2 [over.assign], 11.4.5.3 [class.copy.ctor]),
has no non-trivial, non-deleted move assignment operators (12.4.3.2 [over.assign], 11.4.5.3 [class.copy.ctor]),
has at least one non-deleted copy or move constructor or assignment operator, and
has a trivial, non-deleted destructor (11.4.7 [class.dtor]).
Proposed resolution (October, 2015):
Change Clause 11 [class] paragraph 6 as follows:
A trivially copyable class is a class
that:
has no non-trivial copy constructors (11.4.5.3 [class.copy.ctor]),
has no non-trivial move constructors (11.4.5.3 [class.copy.ctor]),
has no non-trivial copy assignment operators (12.4.3.2 [over.assign], 11.4.5.3 [class.copy.ctor]),
has no non-trivial move assignment operators (12.4.3.2 [over.assign], 11.4.5.3 [class.copy.ctor]), andwhere each copy constructor, move constructor, copy assignment operator, and move assignment operator (11.4.5.3 [class.copy.ctor], 12.4.3.2 [over.assign]) is either deleted or trivial,
that has at least one non-deleted copy constructor, move constructor, copy assignment operator, or move assignment operator, and
that has a trivial, non-deleted destructor (11.4.7 [class.dtor]).
[Moved to DR at the May, 2015 meeting.]
11.4.5.3 [class.copy.ctor] paragraph 31 uses the phrase, “same cv-unqualified type,” twice. This is ambiguous, potentially either requiring that the types not be cv-qualified or meaning that cv-qualification should be ignored. The latter meaning is intended and the phrase should be replaced accordingly.
Proposed resolution (November, 2014):
Change 11.4.5.3 [class.copy.ctor] paragraph 31 as follows:
...This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
in a return statement in a function with a class return type, when the
expressionexpression is the name of a non-volatile automatic object (other than a functionor catch-clauseparameter or a variable introduced by the exception-declaration of a handler (14.4 [except.handle])) with the samecv-unqualifiedtype (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function's return valuein a throw-expression...
when a temporary class object that has not been bound to a reference (6.7.7 [class.temporary]) would be copied/moved to a class object with the same
cv-unqualifiedtype (ignoring cv-qualification), the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/movewhen the exception-declaration...
[Adopted at the June, 2016 meeting.]
Issue 1333 says that a defaulted copy constructor with a less-const-qualified parameter type than the implicit declaration is non-trivial. This is inconsistent with the usual pattern that whether a special member function is callable is separate from whether it is trivial; the different declaration only affects whether you can call it with a const argument, it doesn't affect the operations involved. Should this outcome be reconsidered?
Proposed resolution (April, 2016):
Change 11.4.5.3 [class.copy.ctor] paragraph 12 as follows:
A copy/move constructor for class X is trivial if it is not user-provided, its parameter-type-list is equivalent to the parameter-type-list of an implicit declaration,and if...
Change 11.4.5.3 [class.copy.ctor] paragraph 25 as follows:
A copy/move assignment operator for class X is trivial if it is not user-provided, its parameter-type-list is equivalent to the parameter-type-list of an implicit declaration,and if...
[Moved to DR at the November, 2014 meeting.]
The proposed resolution for issue 1402 overlooked some needed changes in 11.4.5.3 [class.copy.ctor] paragraph 28.
Proposed resolution (February, 2014):
Change 11.4.5.3 [class.copy.ctor] paragraph 28 as follows:
...It is unspecified whether subobjects representing virtual base classes are assigned more than once by the implicitly-defined copy/move assignment operator. [Example:
struct V { }; struct A : virtual V { }; struct B : virtual V { }; struct C : B, A { };It is unspecified whether the virtual base class subobject V is assigned twice by the implicitly-defined copy/move assignment operator for C. —end example]
[Note: This does not apply to move assignment, as a defaulted move assignment operator is deleted if the class has virtual bases. —end note]
[Adopted at the February, 2016 meeting.]
According to 11.4.5.3 [class.copy.ctor] paragraph 28,
The implicitly-defined copy/move assignment operator for a non-union class X performs memberwise copy/move assignment of its subobjects. The direct base classes of X are assigned first, in the order of their declaration in the base-specifier-list, and then the immediate non-static data members of X are assigned, in the order in which they were declared in the class definition... It is unspecified whether subobjects representing virtual base classes are assigned more than once by the implicitly-defined copy/move assignment operator.
However, the determination of whether a defaulted copy/move assignment operator is defined as deleted (paragraph 23) considers only the “potentially constructed” subobjects:
A defaulted copy/move assignment operator for class X is defined as deleted if X has:
...
a potentially constructed subobject of class type M (or array thereof) that cannot be copied/moved...
where “potentially constructed” is defined (in 11.4.4 [special] paragraph 5) as:
For a class, its non-static data members, its non-virtual direct base classes, and, if the class is not abstract (11.7.4 [class.abstract]), its virtual base classes are called its potentially constructed subobjects.
i.e., excluding direct virtual base classes of abstract classes. This seems contradictory, since an implementation is expressly permitted to assign such base classes and thus presumably is permitted to fail if no such assignment is possible.
Similarly, 11.4.7 [class.dtor] paragraph 8 says,
After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X's direct non-variant non-static data members, the destructors for X's direct base classes and, if X is the type of the most derived class (11.9.3 [class.base.init]), its destructor calls the destructors for X's virtual base classes.
This appears to allow a virtual base's destructor to be called more than once, once for each class naming it as a direct virtual base and once for the most-derived class.
Proposed resolution (February, 2016):
Change 11.4.7 [class.dtor] paragraph 8 as follows:
After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X's direct non-variant non-static data members, the destructors for X's non-virtual direct base classes and, if X is the type of the most derived class (11.9.3 [class.base.init]), its destructor calls the destructors for X's virtual base classes. All destructors are called as if...
Change 11.4.5.3 [class.copy.ctor] bullet 23.4 as follows:
A defaulted copy/move assignment operator for class X is defined as deleted if X has:
...
a potentially constructed subobjectdirect non-static data member of class type M (or array thereof) or a direct base class M that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to M's corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator.
[Moved to DR at the November, 2014 meeting.]
According to 11.4.7 [class.dtor] paragraph 3,
A declaration of a destructor that does not have an exception-specification is implicitly considered to have the same exception-specification as an implicit declaration (14.5 [except.spec]).
The implications of this are not clear for the destructor of a class template. For example,
template <class T> struct B: T { ~B(); }; template <class T> B<T>::~B() noexcept {}
The implicit exception-specification of the in-class declaration of the destructor depends on the characteristics of the template argument. Does this mean that the out-of-class definition of the destructor is ill-formed, or will it be ill-formed only in specializations where the template argument causes the implicit exception-specification to be other than noexcept?
Proposed resolution (February, 2014):
This issue is resolved by the resolution of issue 1552.
Notes from the April, 2013 meeting:
This issue was approved as a DR at the April, 2013 (Bristol) meeting, but it was not noticed that issue 1552 was not being moved at that time. It is being returned to "drafting" status pending the resolution of that issue.
[Moved to DR at the November, 2014 meeting.]
According to 11.4.7 [class.dtor] paragraph 12,
At the point of definition of a virtual destructor (including an implicit definition (11.4.5.3 [class.copy.ctor])), the non-array deallocation function is looked up in the scope of the destructor's class (6.5.2 [class.member.lookup]), and, if no declaration is found, the function is looked up in the global scope. If the result of this lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function or a function with a deleted definition (9.6 [dcl.fct.def]), the program is ill-formed. [Note: This assures that a deallocation function corresponding to the dynamic type of an object is available for the delete-expression (11.4.11 [class.free]). —end note]
This specification is not sufficiently clear regarding the nature of the lookup. Presumably the intent is that the processing be parallel to that described in 7.6.2.9 [expr.delete], but that should be made explicit.
Proposed resolution (February, 2014):
Change 11.4.7 [class.dtor] paragraph 12 as follows:
At the point of definition of a virtual destructor (including an implicit definition (11.4.5.3 [class.copy.ctor])), the non-array deallocation function islooked up in the scope of the destructor's class (6.5.2 [class.member.lookup]), and, if no declaration is found, the function is looked up in the global scopedetermined as if for the expression delete this appearing in a non-virtual destructor of the destructor's class (see 7.6.2.9 [expr.delete]). If theresult of this lookup is ambiguous or inaccessible,lookup fails or if thelookup selects a placement deallocation function or a function withdeallocation function has a deleted definition (9.6 [dcl.fct.def]), the program is ill-formed. [Note: This assures that a deallocation function corresponding to the dynamic type of an object is available for the delete-expression (11.4.11 [class.free]). —end note]
[Moved to DR at the November, 2014 meeting.]
The description of the required syntax for declaring a destructor in 11.4.7 [class.dtor] paragraph 1 says,
A declaration of a destructor uses a function declarator (9.3.4.6 [dcl.fct]) of the form
ptr-declarator ( parameter-declaration-clause ) exception-specificationopt attribute-specifier-seqopt
where the ptr-declarator...
A declaration such as
(~S())
arguably “uses” a declarator of the required form, since the cited wording does not forbid placing a declarator of that form inside parentheses. (Similar considerations apply to the syntax of constructors in 11.4.5 [class.ctor] paragraph 1.) There is implementation divergence on this point. The wording should be clarified as to whether parentheses surrounding a declarator of the required form are permitted or not.
Proposed Resolution (July, 2014):
Change 11.4.5 [class.ctor] paragraph 1 as follows:
Constructors do not have names.AIn a declaration of a constructor,usesthe declarator is a function declarator (9.3.4.6 [dcl.fct]) of the form...
Change 11.4.7 [class.dtor] paragraph 1 as follows:
AIn a declaration of a destructor,usesthe declarator is a function declarator (9.3.4.6 [dcl.fct]) of the form...
[Adopted at the February, 2016 meeting.]
The rules in 6.3 [basic.def.odr] and 11.4.7 [class.dtor] do not specify when the destructor for B can/must be defined:
struct A { virtual ~A(); }; struct B : A {}; int main() { A *p = new B; delete p; }
An implementation should be allowed, but not required, to implicitly define a virtual special member function at any point where it has been desclared, as well as being required to define it as described in 11.4.7 [class.dtor] paragraph 6, etc.
Proposed resolution (September, 2015):
Change 11.4.7 [class.dtor] paragraph 6 as follows:
A destructor that is defaulted and not defined as deleted is implicitly defined when it is odr-used (6.3 [basic.def.odr])to destroy an object of its class type (6.7.6 [basic.stc])or when it is explicitly defaulted after its first declaration.
[Adopted at the February, 2016 meeting.]
According to 9.10 [namespace.udecl] paragraph 4,
[Note: Since destructors do not have names, a using-declaration cannot refer to a destructor for a base class....
However, 11.4.7 [class.dtor] paragraph 13 says,
In an explicit destructor call, the destructor name appears as a ~ followed by a type-name or decltype-specifier that denotes the destructor's class type...
See also 6.5.5.2 [class.qual] bullet 1.1:
a destructor name is looked up as specified in 6.5.5 [basic.lookup.qual];
Proposed resolution (September, 2015):
Change 6.5.5.2 [class.qual] bullet 1.1 as follows:
a destructor name is looked up
the lookup for a destructor is as specified in 6.5.5 [basic.lookup.qual];
Change 11.4.7 [class.dtor] paragraph 13 as follows:
In an explicit destructor call, the destructorname appears asis specified by a ~ followed by a type-name or decltype-specifier that denotes the destructor's class type. The invocation...
[Moved to DR at the May, 2015 meeting.]
The conditions under which an explicit constructor will be used are given in 11.4.8.2 [class.conv.ctor] paragraph 2:
An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (9.5 [dcl.init]) or where casts (7.6.1.9 [expr.static.cast], 7.6.3 [expr.cast]) are explicitly used. A default constructor may be an explicit constructor; such a constructor will be used to perform default-initialization or value-initialization (9.5 [dcl.init]).
The existence of this wording in normative text is confusing and should probably simply say that explicit constructors are used in direct initialization, with cross-references to 9.5 [dcl.init] and 12.2.2.5 [over.match.copy], possibly as a note.
Proposed resolution (April, 2015):
Change 11.4.8.2 [class.conv.ctor] paragraph 2 as follows:
[Note: An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (9.5 [dcl.init]) or where casts (7.6.1.9 [expr.static.cast], 7.6.3 [expr.cast]) are explicitly used; see also 12.2.2.5 [over.match.copy]. A default constructor may be an explicit constructor; such a constructor will be used to perform default-initialization or value-initialization (9.5 [dcl.init]). [Example: ... —end example] —end note]
[Moved to DR at the October, 2015 meeting.]
The statement in 11.4.8.2 [class.conv.ctor] paragraph 1,
No return type can be specified.
is confusing, since a conversion operator has a return type. It would be more precise to phrase the restriction in terms of the permissible decl-specifiers in the function's decl-specifier-seq. The next sentence is also problematic,
If a conversion function is a member function, the type of the conversion function (9.3.4.6 [dcl.fct]) is “function taking no parameter returning conversion-type-id”.
as it implies that a conversion function might not be a member function.
Proposed resolution (May, 2015):
This issue is resolved by the resolution of issue 1990.
[Adopted at the June, 2016 meeting.]
The resolution of issue 1816 left many aspects of bit-fields unspecified, including whether a signed bit field has a sign bit and the meaning of the bit-field width. Also, the requirement in 6.8.2 [basic.fundamental] paragraph 1 that
For narrow character types, all bits of the object representation participate in the value representation.
should not apply to oversize character-typed bit-fields.
Notes from the June, 2014 meeting:
CWG decided to address only the issue of oversized bit-fields of narrow character types at this time, splitting off the more general questions regarding bit-fields to issue 1943.
Proposed resolution (April, 2016):
Change 6.8.2 [basic.fundamental] paragraph 1 as follows:
...For narrow character types, all bits of the object representation participate in the value representation. [Note: A bit-field of narrow character type whose length is larger than the number of bits in the object representation of that type has padding bits; see 11.4.10 [class.bit]. —end note] For unsigned narrow character types, each possible bit pattern of the value representation represents a distinct number...
It is not clear whether naming a member of a global anonymous union should be considered an id-expression or implicitly a member access expression. For example, given
static union { int i; }; template <int &> struct S {}; S<i> V;
is the last line well-formed? There is implementation variance on this question.
Notes from the February, 2014 meeting:
CWG agreed that the example should be ill-formed.
[Moved to DR at the November, 2014 meeting.]
C++ allows only non-static data member declarations in an anonymous union, but C and several C++ implementations permit static_assert declarations. Should the C++ Standard be changed accordingly?
Proposed resolution (June, 2014):
Change 11.5 [class.union] paragraph 5 as follows:
A union of the form
union { member-specification } ;
is called an anonymous union; it defines an unnamed object of unnamed type.
The member-specification of an anonymous union shall only define non-static data membersEach member-declaration in the member-specification of an anonymous union shall either define a non-static data member or be a static_assert-declaration. [Note:...
[Moved to DR at the May, 2015 meeting.]
According to 11.8.3 [class.access.base] paragraph 5,
A member m is accessible at the point R when named in class N if
...
m as a member of N is protected, and R occurs in a member or friend of class N, or in a member or friend of a class P derived from N, where m as a member of P is public, private, or protected, or
...
The granting of access via class P is troubling. At the least, there should be a restriction that P be visible at R. Alternatively, this portion of the rule could be removed altogether; this provision does not appear to be widely used in existing code and such references can be easily converted to use P instead of N for naming the member.
Notes from the June, 2014 meeting:
CWG noted that removing the friend provision would introduce an undesirable asymmetry between member functions of P and its friends. Instead, the intent is to require P to be a complete type at R.
Notes from the November, 2014 meeting:
Although the asymmetry is unfortunate, the difference between a reference in a member function and a reference in a friend is that the member function concretely determines which P is in view, while the friend could be befriended by a class that is a specialization of a class template and thus would require instantiations that would not otherwise occur. CWG thus decided simply to eliminate the friend case.
Proposed resolution, November, 2014:
Change bullet 5.3 of 11.8.3 [class.access.base] as follows:
...A member m is accessible at the point R when named in class N if
...
m as a member of N is protected, and R occurs in a member or friend of class N, or in a member
or friendof a class P derived from N, where m as a member of P is public, private, or protected, orthere exists...
[Moved to DR at the November, 2014 meeting.]
The grammar for mem-initializer-list in 11.9.3 [class.base.init] paragraph 1 (after the resolution of issue 1649) is right-recursive:
In general, however, such lists elsewhere in the Standard are described using a left-recursive grammar, e.g., for initializer-list in 9.5 [dcl.init] paragraph 1:
It would be better to be consistent in the definition of mem-initializer-list.
Proposed resolution (February, 2014):
Change the grammar in 11.9.3 [class.base.init] paragraph 1 as follows:
[Moved to DR at the May, 2015 meeting.]
When copy elision is performed, the lifetime of the merged object ends at the later of the times the two objects' lifetimes would have ended. If the copy elision is done using a move constructor, however, it might make sense to assume that the moved-from object's lifetime is no longer interesting and the lifetime should be that of the moved-to object.
Proposed resolution (April, 2015):
Change 11.4.5.3 [class.copy.ctor] paragraph 31 as follows:
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move 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/move operation as simply two different ways of referring to the same object, and. If the first parameter of the selected constructor is an rvalue reference to the object's type, the destruction of that object occurs when the target would have been destroyed; otherwise, the destruction occurs at the later of the times when the two objects would have been destroyed without the optimization.122 This elision of copy/move operations, called copy elision, is permitted...
[Moved to DR at the November, 2014 meeting.]
The current wording of the second bullet of paragraph 1 of 12.2.2.5 [over.match.copy] contains the phrase,
When initializing a temporary to be bound to the first parameter of a constructor that takes a reference to possibly cv-qualified T as its first argument...
Presumably “argument” should be “parameter.”
Proposed resolution (February, 2014):
Change 12.2.2.5 [over.match.copy] paragraph 1 as follows:
...the candidate functions are selected as follows:
The converting constructors (11.4.8.2 [class.conv.ctor]) of T are candidate functions.
When the type of the initializer expression is a class type “cv S”, the non-explicit conversion functions of S and its base classes are considered. When initializing a temporary to be bound to the first parameter of a constructor
that takes awhere the parameter is of type “reference to possibly cv-qualified T”as its first argument,and the constructor is called with a single argument in the context of direct-initialization of an object of type “cv2 T”, explicit conversion functions are also considered. Those that are not hidden...
[Moved to DR at the November, 2014 meeting.]
Consider the following example:
struct X { X(); }; struct Y { explicit operator X(); } y; X x{y};
This appears to be ill-formed, although the corresponding case with parentheses is well-formed. There seem to be two factors that prevent this from being accepted:
First, the special provision allowing an explicit conversion function to be used when initializing the parameter of a copy/move constructor is in 12.2.2.5 [over.match.copy], and this case takes us to 12.2.2.8 [over.match.list] instead.
Second, 12.2.4.2 [over.best.ics] paragraph 4 says that in this case, because we are in 12.2.2.8 [over.match.list], and we have a single argument, and we are calling a copy/move constructor, we are not allowed to consider a user-defined conversion sequence for the argument.
Similarly, in an example like
struct A { A() {} A(const A &) {} }; struct B { operator A() { return A(); } } b; A a{b};
the wording in 12.2.4.2 [over.best.ics] paragraph 4 with regard to 12.2.2.8 [over.match.list] prevents considering B's conversion function when initializing the first parameter of A's copy constructor, thereby making this code ill-formed.
Notes from the February, 2014 meeting:
This issue should be addressed by the eventual resolution of issue 1467.
Proposed resolution (June, 2014):
This issue is resolved by the resolution of issue 1467.
[Moved to DR at the November, 2014 meeting.]
According to 12.2.4.2 [over.best.ics] paragraph 9,
If no sequence of conversions can be found to convert an argument to a parameter type or the conversion is otherwise ill-formed, an implicit conversion sequence cannot be formed.
However, compare this with 12.2.4.2 [over.best.ics] paragraph 2:
Implicit conversion sequences are concerned only with the type, cv-qualification, and value category of the argument and how these are converted to match the corresponding properties of the parameter. Other properties, such as the lifetime, storage class, alignment, or accessibility of the argument and whether or not the argument is a bit-field are ignored. So, although an implicit conversion sequence can be defined for a given argument-parameter pair, the conversion from the argument to the parameter might still be ill-formed in the final analysis.
It is not clear what cases are in view in paragraph 9.
Proposed resolution (October, 2014):
Change 12.2.4.2 [over.best.ics] paragraph 2 as follows:
Implicit conversion sequences are concerned only with the type, cv-qualification, and value category of the argument and how these are converted to match the corresponding properties of the parameter. Other properties, such as the lifetime, storage class, alignment,oraccessibility of the argument,andwhetheror notthe argument is a bit-field, and whether a function is deleted (9.6.3 [dcl.fct.def.delete]), are ignored. So, although an implicit conversion sequence can be defined for a given argument-parameter pair, the conversion from the argument to the parameter might still be ill-formed in the final analysis.
Change 12.2.4.2 [over.best.ics] paragraph 9 as follows:
If no sequence of conversions can be found to convert an argument to a parameter typeor the conversion is otherwise ill-formed, an implicit conversion sequence cannot be formed.
[Adopted at the June, 2016 meeting.]
The resolution of issue 1467 made some plausible constructs ill-formed. For example,
struct A { A(int); }; struct B { B(A); }; B b{{0}};
This is now ambiguous, because the text disallowing user-defined conversions for B's copy and move constructors was removed from 12.2.4.2 [over.best.ics] paragraph 4. Another example:
struct Params { int a; int b; }; class Foo { public: Foo(Params); }; Foo foo{{1, 2}};
This is now ambiguous between Foo(Params) and Foo(Foo&&).
For non-class types, we allow initialization from a single-item list to perform a copy only if the element within the list is not itself a list (12.2.4.2.6 [over.ics.list] bullet 9.1). The analogous rule for this case would be to add back the bullet in 12.2.4.2 [over.best.ics] paragraph 4, but only in the case where the initializer is itself an initializer list:
the second phase of 12.2.2.8 [over.match.list] when the initializer list has exactly one element that is itself an initializer list, where the target is the first parameter of a constructor of class X, and the conversion is to X or reference to (possibly cv-qualified) X,
Proposed resolution (March, 2016):
Change 12.2.4.2 [over.best.ics] paragraph 4 as follows:
...and the constructor or user-defined conversion function is a candidate by
12.2.2.4 [over.match.ctor], when the argument is the temporary in the second step of a class copy-initialization,
or12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv], or 12.2.2.7 [over.match.ref] (in all cases), or
the second phase of 12.2.2.8 [over.match.list] when the initializer list has exactly one element that is itself an initializer list, and the target is the first parameter of a constructor of class X, and the conversion is to X or reference to (possibly cv-qualified) X,
user-defined conversion sequences are not considered. [Note:...
[Moved to DR at the November, 2014 meeting.]
According to bullet 1 of 12.2.4.2.6 [over.ics.list] paragraph 6,
Otherwise, if the parameter type is not a class:
if the initializer list has one element, the implicit conversion sequence is the one required to convert the element to the parameter type;
...
This wording ignores the possibility that the element might be an initializer list (as opposed to an expression with a type, as illustrated in the example). This oversight affects an example like:
struct A { int a[1]; }; struct B { B(int); }; void f(B, int); void f(int, A); int main() { f({0}, {{1}}); }
Proposed resolution (June, 2014):
This issue is resolved by the resolution of issue 1467.
[Moved to DR at the October, 2015 meeting.]
According to 12.2.4.2.6 [over.ics.list] paragraph 5 says,
Otherwise, if the parameter type is “array of N X”, if the initializer list has exactly N elements or if it has fewer than N elements and X is default-constructible, and if all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X.
There are terminological problems with this formulation. “Default constructible” is not otherwise used in the Standard and should instead refer to an implicit conversion sequence from an empty braced-init-list. Similarly, “can be implicitly converted” should instead refer to the existence of an implicit conversion sequence.
Proposed resolution, May, 2015:
Replace the indicated wording with:
Otherwise, if the parameter type is “array of N X,” if there exists an implicit conversion sequence for each element of the array from the corresponding element of the initializer list (or from {} if there is no such element), the implicit conversion sequence is the worst such implicit conversion sequence.
[Moved to DR at the November, 2014 meeting.]
The interpretation of the following example is unclear in the current wording:
void f(long); void f(initializer_list<int>); int main() { f({1L});
The problem is that a list-initialization sequence can also be a standard conversion sequence, depending on the types of the elements and the type of the parameter, so more than one bullet in the list in 12.2.4.3 [over.ics.rank] paragraph 3 applies:
Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of the following rules applies:
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if
...
the rank of S1 is better than the rank of S2, or S1 and S2 have the same rank and are distinguishable by the rules in the paragraph below, or, if not that,
...
...
List-initialization sequence L1 is a better conversion sequence than list-initialization sequence L2 if L1 converts to std::initializer_list<X> for some X and L2 does not.
These bullets give opposite results for the example above, and there is implementation variance in which is selected.
Notes from the April, 2013 meeting:
CWG determined that the latter bullet should apply only if the first one does not.
Proposed resolution (June, 2014):
This issue is resolved by the resolution of issue 1467.
[Moved to DR at the October, 2015 meeting.]
In an example like
struct A { operator int(); }; template<typename T> T operator<<(T, int); void f(A a) { 1 << a; }
Template argument deduction succeeds for the operator template, producing the signature operator<<(int,int). The resulting declaration is synthesized and added to the overload set, per 13.10.4 [temp.over] paragraph 1. However, this violates the requirement of 12.4 [over.oper] paragraph 6,
An operator function shall either be a non-static member function or be a non-member function that has at least one parameter whose type is a class, a reference to a class, an enumeration, or a reference to an enumeration.
This is not a SFINAE context, so the program is ill-formed, rather than selecting the built-in operator.
Proposed resolution (May, 2015):
Change 13.10.4 [temp.over] paragraph 1 as follows,
...If, for a given function template, argument deduction fails or the synthesized function template specialization would be ill-formed, no such function is added to the set of candidate functions for that template.
[Moved to DR at the November, 2014 meeting.]
The Standard is not clear enough that a template parameter like class T is to be interpreted as a type parameter and not an ill-formed non-type parameter of class type T.
Proposed resolution (October, 2014):
Change 13.2 [temp.param] paragraph 2 as follows, moving the example from paragraph 3 to paragraph 2:
There is no semantic difference between class and typename in a template-parameter. typename followed by an unqualified-id names a template type parameter. typename followed by a qualified-id denotes the type in a non-type137 parameter-declaration. A template-parameter of the form class identifier is a type-parameter. [Example:
class T { /* ... */ }; int i; template<class T, T i> void f(T t) { T t1 = i; // template-parameters T and i ::T t2 = ::i; // global namespace members T and i }Here, the template f has a type-parameter called T, rather than an unnamed non-type template-parameter of class T. —end example]. A storage class shall not be specified in a template-parameter declaration. Types shall not be defined in a template-parameter declaration.
[Note: A template parameter may be a class template. For example... —end note]
Change 13.2 [temp.param] paragraph 3 as follows, moving the example from paragraph 2 to paragraph 3:
A type-parameter whose identifier does not follow an ellipsis defines its identifier to be a typedef-name (if declared with class or typename) or template-name (if declared with template) in the scope of the template declaration.
[Note: Because of the name lookup rules, a template-parameter that could be interpreted as either a non-type template-parameter or a type-parameter (because its identifier is the name of an already existing class) is taken as a type-parameter. For example... —end note][Note: A template parameter may be a class template. For example,template<class T> class myarray { /* ... */ }; template<class K, class V, template<class T> class C = myarray> class Map { C<K> key; C<V> value; };—end note]
[Adopted at the February, 2016 meeting.]
According to 13.2 [temp.param] paragraph11,
If a template-parameter of a class template or alias 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 or alias template is a template parameter pack, it shall be the last template-parameter.
These requirements should apply to variable templates as well.
Proposed resolution (September, 2015):
Change 13.2 [temp.param] paragraph 11 as follows:
If a template-parameter of a class template, variable template, or alias 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, primary variable template, or alias template is a template parameter pack, it shall be the last template-parameter. A template parameter pack...
[Adopted at the February, 2016 meeting.]
Proposed resolution, October, 2015:
Add the following as a new paragraph after 13.4 [temp.arg] paragraph 7:
When the template in a template-id is an overloaded function template...
When a simple-template-id does not name a function, a default template-argument is implicitly instantiated (13.9.2 [temp.inst]) when the value of that default argument is needed. [Example:
template<typename T, typename U = int> struct S { }; S<bool>* p; // the type of p is S<bool, int>*The default argument for U is instantiated to form the type S<bool, int>*. —end example]
Notes from the November, 2014 meeting:
The preceding was extracted from the wording of the Concepts Lite draft Technical Specification.
[Adopted at the February, 2016 meeting.]
According to 13.4.2 [temp.arg.type] paragraph 3,
If a declaration acquires a function type through a type dependent on a template-parameter and this causes a declaration that does not use the syntactic form of a function declarator to have function type, the program is ill-formed.
This is not clear enough regarding which declarations are in view. For example, does it apply to a typedef declaration? Does it apply to a parameter declaration, where normal function-to-pointer decay would apply? There is implementation variance at block scope.
Also, since this applies a restriction to the usage of dependent types, not template type arguments per se, the paragraph presumably should appear in 13.8.3.2 [temp.dep.type] and not its current location.
Proposed resolution (September, 2015):
Delete 13.4.2 [temp.arg.type] paragraph 3:
If a declaration acquires a function type through a type dependent on a template-parameter and this causes a declaration that does not use the syntactic form of a function declarator to have function type, the program is ill-formed. [Example:template<class T> struct A { static T t; }; typedef int function(); A<function> a; // ill-formed: would declare A<function>::t // as a static member function
—end example]
...X<int> has a static member s of type int and X<char*> has a static member s of type char*. —end example]
If a function declaration acquired its function type through a dependent type (13.8.3.2 [temp.dep.type]) without using the syntactic form of a function declaator, the program is ill-formed. [Example:
template<class T> struct A { static T t; }; typedef int function(); A<function> a; // ill-formed: would declare A<function>::t // as a static member function—end example]
[Adopted at the November, 2014 meeting as part of paper N4268.]
According to 13.4.3 [temp.arg.nontype] bullet 1.3, only objects with linkage can be used to form non-type template arguments. Is this restriction still needed? It would be convenient to use block-scope objects as template arguments.
Rationale (February, 2012):
This is a request for an extension to the language and thus more appropriately addressed by EWG.
[Adopted at the February, 2016 meeting.]
According to 13.8.3.2 [temp.dep.type] paragraph 9, a type is dependent if it is
denoted by decltype(expression), where expression is type-dependent (13.8.3.3 [temp.dep.expr]).
However, 13.6 [temp.type] paragraph 2 says,
If an expression e involves a template parameter, decltype(e) denotes a unique dependent type. Two such decltype-specifiers refer to the same type only if their expressions are equivalent (13.7.7.2 [temp.over.link]). [Note: however, it may be aliased, e.g., by a typedef-name. —end note]
These seem to be in need of reconciliation.
Proposed resolution (January, 2016):
Change 13.6 [temp.type] paragraph 2 as follows:
If an expression einvolves a template parameteris type-dependent (13.8.3.3 [temp.dep.expr]), decltype(e) denotes a unique dependent type. Two such decltype-specifiers refer to the same type only if their expressions are equivalent (13.7.7.2 [temp.over.link]). [Note: however,itsuch a type may be aliased, e.g., by a typedef-name. —end note]
[Moved to DR at the November, 2014 meeting.]
According to 13.7.5 [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. 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.
Should this treatment of members of explicit specializations also apply to members of partial specializations?
Proposed resolution (February, 2014):
Change 13.7.5 [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 primary class template and class template partial specializations thereof is a friend of the class granting friendship. For explicit specializations and specializations of partial 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:...
[Adopted at the February, 2016 meeting.]
The rationale for the restriction in 13.7.6.1 [temp.spec.partial.general] bullet 9.1 is not clear:
A partially specialized non-type argument expression shall not involve a template parameter of the partial specialization except when the argument expression is a simple identifier. [Example:
template <int I, int J> struct A {}; template <int I> struct A<I+5, I*2> {}; // error template <int I, int J> struct B {}; template <int I> struct B<I, I> {}; // OK
—end example]
In the example, it's clear that I is non-deducible, but this rule prevents plausible uses like:
template <int I, int J> struct A {}; template <int I> struct A<I, I*2> {};
(See also issues 1647, 2033, and 2127.)
Proposed resolution (September, 2015):
Change 13.7.6.1 [temp.spec.partial.general] bullet 9.1 as follows:
A partially specialized non-type argument expression shall
not involve a template parameter of the partial specialization except when
the argument expression is a
simple identifier. Each template-parameter shall
appear at least once in the template-id outside a non-deduced
context. [Example:
template <int I, int J> struct A {}; template <int I> struct A<I+5, I*2> {}; // errortemplate <int I, int J> struct B {};template <int I> structBA<I, I> {}; // OK template <int I, int J, int K> struct B {}; template <int I> struct B<I, I*2, 2> {}; // OK
—end example]
[Moved to DR at the November, 2014 meeting.]
According to 13.7.6 [temp.spec.partial] paragraph 6,
A class template partial specialization may be declared or redeclared in any namespace scope in which its definition may be defined (13.7.2 [temp.class] and 13.7.3 [temp.mem]).
However, there is nothing in those referenced sections specifying where the definition may appear. Should this have referred to the definition of the primary template?
Also, the cross-reference to 13.7.2 [temp.class] is suspect; the actual rules for where non-member class templates may be defined are found in _N4868_.9.8.2.3 [namespace.memdef] paragraphs 1-2, 9.3.4 [dcl.meaning] paragraph 1, and 9.9.2 [namespace.def] paragraph 8.
(Apropos of 9.9.2 [namespace.def], the description in paragraph 8 mentions explicit instantiation and explicit specialization, but presumably inadvertently omits partial specializations.)
Proposed resolution (February, 2014) [SUPERSEDED]:
Change 13.7.6 [temp.spec.partial] paragraph 6 as follows:
A class template partial specialization may be declared or redeclared in any namespace scope in whichits definitionthe corresponding primary template may be defined (13.7.2 [temp.class] and 13.7.3 [temp.mem]). [Example:...
Additional note, February, 2014:
The proposed resolution approved by CWG at the February, 2014 meeting does not address the additional points raised in the issue, specifically the cross-reference to 13.7.2 [temp.class] and the omission of partial specializations from 9.9.2 [namespace.def]. The issue has been returned to "review" status to consider amending the resolution to include these items.
Proposed Resolution (July, 2014):
Change 9.9.2 [namespace.def] paragraph 8 as follows:
...Furthermore, each member of the inline namespace can subsequently be partially specialized (13.7.6 [temp.spec.partial]), explicitly instantiated (13.9.3 [temp.explicit]), or explicitly specialized (13.9.4 [temp.expl.spec]) as though it were a member of the enclosing namespace. Finally, looking up a name...
Change 13.7.6 [temp.spec.partial] paragraph 6 as follows:
A class template partial specialization may be declared or redeclared in any namespace scope in whichits definitionthe corresponding primary template may be defined (13.7.2 [temp.class]_N4868_.9.8.2.3 [namespace.memdef] and 13.7.3 [temp.mem]). [Example:...
[Adopted at the February, 2016 meeting.]
Bullets 9.3 and 9.4 of 13.7.6.1 [temp.spec.partial.general] say,
Within the argument list of a class template partial specialization, the following restrictions apply:
...
The argument list of the specialization shall not be identical to the implicit argument list of the primary template.
The specialization shall be more specialized than the primary template (13.7.6.3 [temp.spec.partial.order]).
...
The former is implied by the latter and should be omitted.
(See also issues 1315, 1647, and 2127.)
Proposed resolution (September, 2015):
Delete bullet 9.3 of 13.7.6.1 [temp.spec.partial.general]:
The argument list of the specialization shall not be
identical to the implicit argument list of the primary
template.
[Moved to DR at the November, 2014 meeting.]
A member function with no ref-qualifier can be called for a class prvalue, as can a non-member function whose first parameter is an rvalue reference to that class type. However, 13.7.7.3 [temp.func.order] does not handle this case.
Proposed resolution (February, 2014):
Change 13.7.7.3 [temp.func.order] paragraph 3 as follows:
...If only one of the function templates M is a non-static member of some class A,that function templateM is considered to have a new first parameter inserted in its function parameter list. Given cv as the cv-qualifiers ofthe function templateM (if any), the new parameter is of type “rvalue reference to cv A” if the optional ref-qualifier ofthe function templateM is &&,or if M has no ref-qualifier and the first parameter of the other template has rvalue reference type. Otherwise, the new parameter is of type “lvalue reference to cv A”otherwise. [Note: This allows a non-static member to be ordered with respect to a nonmember function and for the results to be equivalent to the ordering of two equivalent nonmembers. —end note] [Example:...
[Moved to DR at the November, 2014 meeting.]
The treatment of unused arguments in an alias template specialization is not specified by the current wording of 13.7.8 [temp.alias]. For example:
#include <iostream> template <class T, class...> using first_of = T; template <class T> first_of<void, typename T::type> f(int) { std::cout << "1\n"; } template <class T> void f(...) { std::cout << "2\n"; } struct X { typedef void type; }; int main() { f<X>(0); f<int>(0); }
Is the reference to first_of<void, T::type> with T being int equivalent to simply void, or is it a substitution failure?
(See also issues 1430, 1520, and 1554.)
Notes from the October, 2012 meeting:
The consensus of CWG was to treat this case as substitution failure.
Proposed resolution (February, 2014):
Add the following as a new paragraph before 13.7.8 [temp.alias] paragraph 3:
When a template-id refers to the specialization of an alias template, it is equivalent...
However, if the template-id is dependent, subsequent template argument substitution still applies to the template-id. [Example:
template<typename...> using void_t = void; template<typename T> void_t<typename T::foo> f(); f<int>(); // error, int does not have a nested type foo—end example]
The type-id in an alias template declaration shall not refer...
[Moved to DR at the November, 2014 meeting.]
Various characteristics of entities referred to by a non-dependent reference in a template can change between the definition context and the point of instantiation of a specialization of that template. These include initialization (which affects whether an object can be used in a constant expression), function and template default arguments, and the completeness of types. There is implementation divergence as to whether these are checked in the definition context or at the point of instantiation. Presumably a rule is needed to make it ill-formed, no diagnostic required, if the validity of such a reference changes between the two contexts.
Proposed resolution (February, 2014):
Change 13.8 [temp.res] paragraph 8 as follows:
...If atype used in a non-dependent name is incomplete at the point at which a template is defined but is complete at the point at which an instantiation is done, and if the completeness of that type affects whether or not the program is well-formed or affects the semantics of the program,hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, the program is ill-formed; no diagnostic is required. If the interpretation of such a construct in the hypothetical instantiation is different from the interpretation of the corresponding construct in any actual instantiation of the template, the program is ill-formed; no diagnostic is required. [Note: This can happen in situations including the following:
a type used in a non-dependent name is incomplete at the point at which a template is defined but is complete at the point at which an instantiation is performed, or
an instantiation uses a default argument or default template argument that had not been defined at the point at which the template was defined, or
constant expression evaluation (7.7 [expr.const]) within the template instantiation uses
- the value of a const object of integral or unscoped enumeration type or
the value of a constexpr object or
the value of a reference or
the definition of a constexpr function,
and that entity was not defined when the template was defined, or
a class template specialization or variable template specialization that is specified by a non-dependent simple-template-id is used by the template, and either it is instantiated from a partial specialization that was not defined when the template was defined or it names an explicit specialization that was not declared when the template was defined.
—end note] [Note: If a template is instantiated...
[Moved to DR at the May, 2015 meeting.]
Use of the injected-class-name of a class template with a template-argument-list that relies on default arguments is not clearly specified in the current wording of the Standard. In particular, according to 13.2 [temp.param] paragraph 10,
The set of default template-arguments available for use with a template declaration or definition is obtained by merging the default arguments from the definition (if in scope) and all declarations in scope in the same way default function arguments are (9.3.4.7 [dcl.fct.default]).
However, the injected-class-name hides the template declarations, so it is not clear whether the default arguments are available at that point or not.
Proposed resolution (November, 2014):
Change 13.2 [temp.param] paragraph 10 as follows:
The set of default template-arguments available for usewith a template declaration or definitionis obtained by merging the default arguments fromthe definition (if in scope) andall prior declarationsin scopeof the template in the same way default function arguments are (9.3.4.7 [dcl.fct.default]). [Example:...
[Moved to DR at the November, 2014 meeting.]
Is the following example well-formed?
template<class T> struct A { typedef int M; struct B { typedef void M; struct C; }; }; template<class T> struct A<T>::B::C : A<T> { M // A<T>::M or A<T>::B::M? p[2]; };
13.8.3 [temp.dep] paragraph 3 says the use of M should refer to A<T>::B::M because the base class A<T> is not searched because it's dependent. But in this case A<T> is also the current instantiation (13.8.3.2 [temp.dep.type]) so it seems like it should be searched.
Notes from the August, 2011 meeting:
The recent changes to the handling of the current instantiation may have sufficiently addressed this issue.
Additional note (September, 2012):
See also issue 1526 for additional analysis demonstrating that this issue is still current despite the changes to the description of the current instantiation. The status has consequently been changed back to "open" for further consideration.
Proposed resolution (February, 2014):
Add the following as a new paragraph before 13.8.3.2 [temp.dep.type] paragraph 4:
A dependent base class is a base class that is a dependent type and is not the current instantiation. [Note: a base class can be the current instantiation in the case of a nested class naming an enclosing class as a base. —end note] [Example:
template<class T> struct A { typedef int M; struct B { typedef void M; struct C; }; }; template<class T> struct A<T>::B::C : A<T> { M m; // OK, A<T>::M };
—end example]
A name is a member of the current instantiation if...
Change 13.8.2 [temp.local] paragraph 9 as follows:
In the definition of a class template or in the definition of a member of such a template that appears outside of the template definition, for each non-dependent base class (13.8.3.2 [temp.dep.type])which does not depend on a template-parameter (13.8.3 [temp.dep]), if the name of the base class or the name of a member of the base class is the same as the name of a template-parameter, the base class name or member name hides the template-parameter name (_N4868_.6.4.10 [basic.scope.hiding]). [Example:...
Change 13.8.3 [temp.dep] paragraph 3 as follows:
In the definition of a class or class template,if a base class depends on a template-parameter, the base class scopethe scope of a dependent base class (13.8.3.2 [temp.dep.type]) is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member. [Example:...
[Moved to DR at the November, 2014 meeting.]
The discussion of issue 1233 revealed that the dependency of function calls involving a braced-init-list containing a pack expansion is not adequately addressed by the existing wording.
Proposed resolution (February, 2014):
Change 13.8.3 [temp.dep] paragraph 1 as follows:
...In an expression of the form:
postfix-expression ( expression-listopt )
where the postfix-expression is an unqualified-id, the unqualified-id denotes a dependent name if
any of the expressions in the expression-list is a pack expansion (13.7.4 [temp.variadic]),
any of the expressions or braced-init-lists in the expression-list is
atype-dependentexpression(13.8.3.3 [temp.dep.expr]), orif the unqualified-id is...
Add the following as a new paragraph at the end of 13.8.3.3 [temp.dep.expr]:
A class member access expression (7.6.1.5 [expr.ref]) is type-dependent if...
A braced-init-list is type-dependent if any element is type-dependent or is a pack expansion.
[Moved to DR at the October, 2015 meeting.]
According to 13.8.3 [temp.dep] paragraph 1,
Expressions may be type-dependent (on the type of a template parameter) or value-dependent (on the value of a non-type template parameter).
Proposed resolution (May, 2015):
Change 13.8.3 [temp.dep] paragraph 1 to read,
An expression may be type-dependent (that is, its type may depend on a template parameter) or value-dependent (that is, its value when evaluated as a constant expression (7.7 [expr.const]) may depend on a template parameter) as described in this subclause."
[Moved to DR at the May, 2015 meeting.]
The note in 13.8.3.2 [temp.dep.type] paragraph 7 reads,
[Note: the result of name lookup differs only when the member of the current instantiation was found in a non-dependent base class of the current instantiation and a member with the same name is also introduced by the substitution for a dependent base class of the current instantiation. —end note]
However, this is not correct. Consider the following example:
struct Y { int X; }; template<typename T> struct A : Y { enum B : int; void f() { A::X; } // finds Y::X here! }; template<typename T> enum A<T>::B : int { X // introduces member A::X into A<T>! }; void g() { A<int> a; a.f(); }
A::X is a member of the current instantiation, so paragraph 7 requires it to be looked up again when instantiating and to give a diagnostic if the lookup differs from the lookup in the definition context. The note incorrectly indicates that this can only happen if the conflicting name was introduced by a dependent base class.
Proposed resolution (August, 2011) [SUPERSEDED]:
Change 13.8.3.2 [temp.dep.type] paragraph 7 as follows:
...If the result of this lookup differs from the result of name lookup in the template definition context, name lookup is ambiguous.
[Note: the result of name lookup differs only when the member of the current instantiation was found in a non-dependent base class of the current instantiation and a member with the same name is also introduced by the substitution for a dependent base class of the current instantiation. —end note][Example:struct A { int m; }; struct B { int m; }; template<typename T> struct C : A, T { int f() { return this->m; } // finds A::m in the template definition context }; int g(C<B> cb) { return cb.f(); // error: finds both A::m and B::m in the template instantiation context }—end example]
Notes from the December, 2011 teleconference:
Changes to the exposition were suggested and the issue returned to "drafting" status.
Proposed resolution (November, 2014):
Change 13.8.3.2 [temp.dep.type] paragraph 7 as follows:
...If the result of this lookup differs from the result of name lookup in the template definition context, name lookup is ambiguous.
[Note: the result of name lookup differs only when the member of the current instantiation was found in a non-dependent base class of the current instantiation and a member with the same name is also introduced by the substitution for a dependent base class of the current instantiation. —end note][Example:struct A { int m; }; struct B { int m; }; template<typename T> struct C : A, T { int f() { return this->m; } // finds A::m in the template definition context }; template int C<B>::f(); // error: finds both A::m and B::m—end example]
[Moved to DR at the May, 2015 meeting.]
According to 13.8.3.2 [temp.dep.type] paragraph 7,
If, for a given set of template arguments, a specialization of a template is instantiated that refers to a member of the current instantiation with a qualified-id or class member access expression, the name in the qualified-id or class member access expression is looked up in the template instantiation context. If the result of this lookup differs from the result of name lookup in the template definition context, name lookup is ambiguous. [Note: the result of name lookup differs only when the member of the current instantiation was found in a non-dependent base class of the current instantiation and a member with the same name is also introduced by the substitution for a dependent base class of the current instantiation. —end note]
It is not clear whether this applies to an example like,
struct A { int n; }; struct B { int n; }; template<typename T> struct C : T, B { int f() { return n; } }; int k = C<A>().f();
since the reference to n is transformed into a class member access expression, per 11.4.3 [class.mfct.non.static] paragraph 3.
Notes from the November, 2014 meeting:
The transformation to a member access expression or qualified-id should be performed only in the instantiated function, not when processing the template definition.
Proposed resolution (April, 2015):
Change 11.4.3 [class.mfct.non.static] paragraph 3 as follows:
When an id-expression (7.5 [expr.prim]) that is not part of a class member access syntax (7.6.1.5 [expr.ref]) and not used to form a pointer to member (7.6.2.2 [expr.unary.op]) is used in a member of class X in a context where this can be used (_N4567_.5.1.1 [expr.prim.general]), if name lookup (6.5 [basic.lookup]) resolves the name in the id-expression to a non-static non-type member of some class C, and if either the id-expression is potentially evaluated or C is X or a base class of X, the id-expression is transformed into a class member access expression (7.6.1.5 [expr.ref]) using (*this) (_N4868_.11.4.3.2 [class.this]) as the postfix-expression to the left of the . operator. [Note: If C is not X or a base class of X, the class member access expression is ill-formed. —end note] Similarly during name lookup, when an unqualified-id (7.5 [expr.prim]) used in the definition of a member function for class X resolves to a static member, an enumerator or a nested type of class X or of a base class of X, the unqualified-id is transformed into a qualified-id (7.5 [expr.prim]) in which the nested-name-specifier names the class of the member function. These transformations do not apply in the template definition context (13.8.3.2 [temp.dep.type]). [Example:...
Change the example in 13.8.3.2 [temp.dep.type] paragraph 7, as modified by the resolution of issue 1309, as follows:
struct A { int m; }; struct B { int m; }; template<typename T> struct C : A, T { int f() { return this->m; }// finds A::m in the template definition context int g() { return m; } // finds A::m in the template definition context }; template int C<B>::f(); // error: finds both A::m and B::m template int C<B>::g(); // OK: transformation to class member access syntax // does not occur in the template definition context; see 11.4.3 [class.mfct.non.static]
[Moved to DR at the October, 2015 meeting.]
Consider an example like:
template<typename ...Ts> struct X { X(int); }; template<typename T> using Y = int; template<typename ...Ts> void f() { X<Y<Ts>...> x; }
The presence of the ellipsis should make the reference to X a dependent type, but there is no rule making it so.
Proposed resolution (May, 2015):
Change 13.8.3.2 [temp.dep.type] paragraph 9 as follows:
A type is dependent if it is
...
a simple-template-id in which either the template name is a template parameter or any of the template arguments is a dependent type or an expression that is type-dependent or value-dependent or is a pack expansion, or
[Moved to DR at the November, 2014 meeting.]
The length of the __func__ array is implementation-defined but potentially depends on the signature of the function in which it occurs. However, __func__ is not listed among the type-dependent id-expressions in 13.8.3.3 [temp.dep.expr] paragraph 3.
Proposed resolution (February, 2014):
Change 13.8.3.3 [temp.dep.expr] paragraph 3 as follows:
An id-expression is type-dependent if it contains
an identifier associated by name lookup with one or more declarations declared with a dependent type,
an identifier associated by name lookup with one or more declarations of member functions of the current instantiation declared with a return type that contains a placeholder type (9.2.9.7 [dcl.spec.auto]),
The identifier __func__ (9.6.1 [dcl.fct.def.general]) where any enclosing function is a template, a member of a class template, or a generic lambda,
a template-id that is dependent,
...
[Moved to DR at the May, 2015 meeting.]
13.8.3.4 [temp.dep.constexpr] paragraph 1 begins,
Except as described below, a constant expression is value-dependent if...
However, this terminology is misleading, because “constant expression” is now defined in terms of evaluation, and a value-dependent expression cannot be evaluated.
Proposed resolution (November, 2014):
Change 13.8.3.2 [temp.dep.type] paragraph 8 as follows:
A type is dependent if it is
...
an array type
constructed from anywhose element type is dependenttypeor whosesizebound (if any) isspecified by a constant expression that isvalue-dependent,...
Change 13.8.3.4 [temp.dep.constexpr] paragraph 1 as follows:
Except as described below,a constantan expression used in a context where a constant expression is required is value-dependent if any subexpression is value-dependent.
[Adopted at the February, 2016 meeting.]
Consider the following example:
template<int> struct X { typedef int type; }; template<typename T> struct Y { void f() { X<false ? this - this : 0>::type x; } // missing typename? }; void g() { Y<void>().f(); }
This appears to be valid because the template argument expression is not value-dependent.
Until I discovered this, I had been assuming that any type-dependent expression is also value-dependent. The only exception to that appears to be the expression this, which may be type-dependent but is never value-dependent.
Now, this need not ever be value-dependent, because evaluation of it will never succeed when it appears as a subexpression of an expression that we're checking for constant-expression-ness. But if that's really what we want here, then the same applies to function parameters with dependent types, and probably a few other cases.
Proposed resolution (September, 2015) [SUPERSEDED]:
Change 13.8.3.4 [temp.dep.constexpr] paragraph 4 as follows:
Expressions of the following form are value-dependent:
sizeof ... ( identifuer )
this
fold-expression
Proposed resolution (March, 2016):
This issue is resolved by the resolution of issue 2109.
[Adopted at the February, 2016 meeting.]
In the following example,
struct A {}; struct X { template <typename Q> int memfunc(); }; template <int (X::* P) ()> int foo(...); template<class T> struct B { static int bar() { A a; return foo<&X::memfunc<T> >(a); } }; template <int (X::* P) ()> int foo(A a) { return 0; } int main() { return B<int>::bar(); }
the call foo<&X::memfunc<T> >(a); is dependent only if the template argument is dependent, which is only true because of the use of the template parameter T. Implementations generally agree that this is dependent, but there does not appear to be wording to support this determination.
Proposed resolution (September, 2015):
Change 13.8.3.4 [temp.dep.constexpr] paragraph 2 as follows:
An id-expression is value-dependent if:
it is
a name declared with a dependent typetype-dependent,it is the name of a non-type template parameter,
it names a member of an unknown specialization,...
This resolution also resolves issue 2066.
[Adopted at the February, 2016 meeting.]
Three points have been raised where the wording in 13.9.2 [temp.inst] may not be sufficiently clear.
A class template specialization is implicitly instantiated... if the completeness of the class type affects the semantics of the program...
It is not clear what it means for the "completeness... [to affect] the semantics." Consider the following example:
template<class T> struct A; extern A<int> a; void *foo() { return &a; } template<class T> struct A { #ifdef OPTION void *operator &() { return 0; } #endif };
The question here is whether it is necessary for template class A to declare an operator & for the semantics of the program to be affected. If it does not do so, the meaning of &a will be the same whether the class is complete or not and thus arguably the semantics of the program are not affected.
Presumably what was intended is whether the presence or absence of certain member declarations in the template class might be relevant in determining the meaning of the program. A clearer statement may be desirable.
If the overload resolution process can determine the correct function to call without instantiating a class template definition, it is unspecified whether that instantiation actually takes place.
The intent of this wording, as illustrated in the example in that paragraph, is to allow a "smart" implementation not to instantiate class templates if it can determine that such an instantiation will not affect the result of overload resolution, even though the algorithm described in Clause 12 [over] requires that all the viable functions be enumerated, including functions that might be found as members of specializations.
Unfortunately, the looseness of the wording allowing this latitude for implementations makes it unclear what "the overload resolution process" is — is it the algorithm in Clause 12 [over] or something else? — and what "the correct function" is.
If an implicit instantiation of a class template specialization is required and the template is declared but not defined, the program is ill-formed.
Here, it is not clear what conditions "require" an implicit instantiation. From the context, it would appear that the intent is to refer to the conditions in paragraph 4 that cause a specialization to be instantiated.
This interpretation, however, leads to different treatment of template and non-template incomplete classes. For example, by this interpretation,
class A; template <class T> struct TA; extern A a; extern TA<int> ta; void f(A*); void f(TA<int>*); int main() { f(&a); // well-formed; undefined if A // has operator &() member f(&ta); // ill-formed: cannot instantiate }
A different approach would be to understand "required" in paragraph 6 to mean that a complete type is required in the expression. In this interpretation, if an incomplete type is acceptable in the context and the class template definition is not visible, the instantiation is not attempted and the program is well-formed.
The meaning of "required" in paragraph 6 must be clarified.
Notes on 10/01 meeting:
It was felt that item 1 is solved by addition of the word "might" in the resolution for issue 63; item 2 is not much of a problem; and item 3 could be solved by changing "required" to "required to be complete".
Proposed resolution (January, 2016):
Change 13.9.2 [temp.inst] paragraph 1 as follows, moving the note and example from paragraph 6 and breaking it into two paragraphs:
Unless a class template specialization has been explicitly instantiated (13.9.3 [temp.explicit]) or explicitly specialized (13.9.4 [temp.expl.spec]), the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program. [Note: In particular, if the semantics of an expression depend on the member or base class lists of a class template specialization, the class template specialization is implicitly generated. For instance, deleting a pointer to class type depends on whether or not the class declares a destructor, and a conversion between pointers to class type depends on the inheritance relationship between the two classes involved. —end note] [Example:
template<class T> class B { /* ... */ }; template<class T> class D : public B<T> { /* ... */ }; void f(void*); void f(B<int>*); void g(D<int>* p, D<char>* pp, D<double>* ppp) { f(p); // instantiation of D<int> required: call f(B<int>*) B<char>* q = pp; // instantiation of D<char> required: // convert D<char>* to B<char>* delete ppp; // instantiation of D<double> required }—end example] If a class template has been declared, but not defined, at the point of instantiation (13.8.4.1 [temp.point]), the instantiation yields an incomplete class type (6.8 [basic.types]). [Example:
template<class T> class X; X<char> ch; // error: incomplete type X<char>
—end example] [Note: Within a template declaration, a local class (11.6 [class.local]) or enumeration and the members of a local class are never considered to be entities that can be separately instantiated (this includes their default arguments, exception-specifications, and non-static data member initializers, if any). As a result, the dependent names are looked up, the semantic constraints are checked, and any templates used are instantiated as part of the instantiation of the entity within which the local class or enumeration is declared. —end note]
The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not...
Delete 13.9.2 [temp.inst] paragraph 6, moving the note and example to paragraph 1 as shown above:
A class template specialization is implicitly instantiated if the class type is used in a context that requires a completely-defined object type or if the completeness of the class type might affect the semantics of the program. [Note: In particular, if the semantics of an expression depend on the member or base class lists of a class template specialization, the class template specialization is implicitly generated. For instance, deleting a pointer to class type depends on whether or not the class declares a destructor, and conversion between pointer to class types depends on the inheritance relationship between the two classes involved. —end note] [Example:template<class T> class B { /* ... */ }; template<class T> class D : public B<T> { /* ... */ }; void f(void*); void f(B<int>*); void g(D<int>* p, D<char>* pp, D<double>* ppp) { f(p); // instantiation of D<int> required: call f(B<int>*) B<char>* q = pp; // instantiation of D<char> required: // convert D<char>* to B<char>* delete ppp; // instantiation of D<double> required }
—end example]
Change 13.9.2 [temp.inst] paragraph 7 as follows:
If the function selected by overload resolutionprocess(12.2 [over.match]) candetermine the correct function to callbe determined without instantiating a class template definition, it is unspecified whether that instantiation actually takes place. [Example:...
Delete 13.9.2 [temp.inst] paragraphs 8-9:
If an implicit instantiation of a class template specialization is required and the template is declared but not defined, the program is ill-formed. [Example:template<class T> class X; X<char> ch; // error: definition of X required
—end example]
The implicit instantiation of a class template does not cause any static data members of that class to be implicitly instantiated.
[Moved to DR at the November, 2014 meeting.]
Do local classes of function templates get the same treatment as member classes of class templates? In particular, is their definition only instantiated when they are required? For example,
template<typename T> void f() { struct B { T t; }; } int main() { f<void>(); }
Implementations vary on this question.
(This question is superficially similar to the one in issue 1253. However, the entities in view in that issue can be named and defined outside the containing template and thus can be explicitly specialized, none of which is true for local classes of function templates.)
It should also be noted that the resolution of this issue should apply as well to local enumeration types.
Proposed resolution (October, 2012):
Change 13.9.2 [temp.inst] paragraph 1 as follows:
Unless a class template specialization has been explicitly instantiated (13.9.3 [temp.explicit]) or explicitly specialized (13.9.4 [temp.expl.spec]), the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program. [Note: Within a template declaration, a local class or enumeration and the members of a local class are never considered to be entities that can be separately instantiated (this includes their default arguments, exception-specifications, and non-static data member initializers, if any). As a result, the dependent names are looked up, the semantic constraints are checked, and any templates used are instantiated as part of the instantiation of the entity within which the local class or enumeration is declared. —end note] The implicit instantiation of a class template specialization...
Notes from the April, 2013 meeting:
The proposed resolution interacts with N3649 (generic lambdas), adopted at this meeting, and this issue has returned to "review" status to allow any necessary changes to be made.
[Adopted at the February, 2016 meeting.]
13.9.4 [temp.expl.spec] paragraph 2 says,
An explicit specialization shall be declared in a namespace enclosing the specialized template. 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 (9.9.2 [namespace.def]), any namespace from its enclosing namespace set.
However, an explicit specialization of a class template does not have a declarator-id.
Proposed resolution (September, 2015):
Change 13.9.4 [temp.expl.spec] paragraph 2 as follows:
An explicit specialization shall be declared in a namespace enclosing the specialized template. An explicit specialization whose declarator-id or class-head-name is not qualified shall be declared in the nearest enclosing namespace of the template, or, if the namespace is inline (9.9.2 [namespace.def]), any namespace from its enclosing namespace set. Such a declaration...
[Moved to DR at the October, 2015 meeting.]
According to 13.10.2 [temp.arg.explicit] paragraph 6,
Implicit conversions (7.3 [conv]) will be performed on a function argument to convert it to the type of the corresponding function parameter if the parameter type contains no template-parameters that participate in template argument deduction. [Note: Template parameters do not participate in template argument deduction if they are explicitly specified...
But this isn't clear about when these conversions are done. Consider
template<class T> struct A { typename T::N n; }; template<class T> struct B { }; template<class T, class T2> void foo(const A<T>& r); // #1 template<class T> void foo(const B<T>& r); // #2 void baz() { B<char> b; foo(b); // OK foo<char>(b); // error }
With the explicit template argument, the first parameter of #1 no longer participates in template argument deduction, so implicit conversions are done. If we check for the implicit conversion during the deduction process, we end up instantiating A<char>, resulting in a hard error. If we wait until later to check the conversion, we can reject #1 because T2 is not deduced and never need to consider the conversion.
But if we just accept the parameter and leave it up to normal overload resolution to reject an unsuitable candidate, that breaks this testcase:
template<class T> struct A { typename T::N n; }; template<class T> struct B { }; template <class T, class... U> typename A<T>::value_t bar(int, T, U...); template <class T> T bar(T, T); void baz() { B<char> b; bar(b, b); }
Here, if deduction succeeds, we substitute in the deduced arguments of T = B<char>, U = { }, and end up instantiating A<B<char>>, which fails.
EDG and GCC currently reject the first testcase and accept the second; clang accepts both.
Notes from the October, 2012 meeting:
The position initially favored by CWG was that implicit conversions are not considered during deduction but are only applied afterwards, so the second example is ill-formed, and that the normative wording of the referenced paragraph should be moved into the note. This approach does not handle some examples currently accepted by some implementations, however; for example:
template <class T> struct Z { typedef T::x xx; }; template <class T> Z<T>::xx f(void *, T); template <class T> void f(int, T); struct A {} a; int main() { f(1, a); // If the implementation rules out the first overload // because of the invalid conversion from int to void*, // the error instantiating Z<A> will be avoided }
Additional discussion is required.
Notes from the April, 2013 meeting:
The approach needed to accept this code appears to be doing the convertibility check between deduction and substitution.
Proposed resolution (May, 2015):
Change 13.10.3.2 [temp.deduct.call] paragraph 1 as follows:
Template argument deduction is done by comparing each function template parameter type (call it P) that contains template-parameters that participate in template argument deduction with the type of the corresponding argument of the call (call it A) as described below. IfP is a dependent type,removing references and cv-qualifiers from P gives std::initializer_list<P'> or P'[N] for some P' and N and the argument is a non-empty initializer list (9.5.5 [dcl.init.list]), then deduction is performed instead for each element of the initializer list, taking P' as a function template parameter type and the initializer element as its argument, and in the P'[N] case, if N is a non-type template parameter, N is deduced from the length of the initializer list. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context (13.10.3.6 [temp.deduct.type]). [Example:...
Delete the note in 13.10.3.2 [temp.deduct.call] paragraph 4:
[Note: as specified in 13.10.2 [temp.arg.explicit], implicit conversions will be performed on a function argument to convert it to the type of the corresponding function parameter if the parameter contains no template-parameters that participate in template argument deduction. Such conversions are also allowed, in addition to the ones described in the preceding list. —end note]
Add the following as a new paragraph at the end of 13.10.3.2 [temp.deduct.call]:
If deduction succeeds for all parameters that contain template-parameters that participate in template argument deduction, and all template arguments are explicitly specified, deduced, or obtained from default template arguments, remaining parameters are then compared with the corresponding arguments. For each remaining parameter P with a type that was non-dependent before substitution of any explicitly-specified template arguments, if the corresponding argument A cannot be implicitly converted to P, deduction fails. [Note: Parameters with dependent types in which no template-parameters participate in template argument deduction, and parameters that became non-dependent due to substitution of explicitly-specified template arguments, will be checked during overload resolution. —end note] [Example:
template <class T> struct Z { typedef typename T::x xx; }; template <class T> typename Z<T>::xx f(void *, T); // #1 template <class T> void f(int, T); // #2 struct A {} a; int main() { f(1, a); // OK, deduction fails for #1 because there is no conversion from int to void* }—end example]
Change 13.10.3.5 [temp.deduct.partial] paragraph 4 as follows:
Each type nominated above from the parameter template and the corresponding type from the argument template are used as the types of P and A. If a particular P contains no template-parameters that participate in template argument deduction, that P is not used to determine the ordering.
Change 13.10.3.6 [temp.deduct.type] paragraph 4 as follows:
In most cases, the types, templates, and non-type values that are used to compose P participate in template argument deduction. That is, they may be used to determine the value of a template argument, and the value so determined must be consistent with the values determined elsewhere. In certain contexts, however, the value does not participate in type deduction, but instead uses the values of template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails. [Note: Under 13.10.3.2 [temp.deduct.call] and 13.10.3.5 [temp.deduct.partial], if P contains no template-parameters that appear in deduced contexts, no deduction is done, and so P and A need not have the same form. —end note]
This resolution also resolves issue 1847.
Additional note October, 2015:
See also issue 1939.
[Moved to DR at the November, 2014 meeting.]
13.10.3 [temp.deduct] paragraph 9 reads,
Except as described above, the use of an invalid value shall not cause type deduction to fail. [Example: In the following example 1000 is converted to signed char and results in an implementation-defined value as specified in (7.3.9 [conv.integral]). In other words, both templates are considered even though 1000, when converted to signed char, results in an implementation-defined value.
template <int> int f(int); template <signed char> int f(int); int i1 = f<1>(0); // ambiguous int i2 = f<1000>(0); // ambiguous—end example]
This is no longer correct, even ignoring the fact that some implementations may be able to represent the value 1000 as a signed char: integral and enumeration non-type template arguments are now converted constant expressions (13.4.3 [temp.arg.nontype] paragraph 1), and converted constant expressions disallow narrowing conversions (7.7 [expr.const] paragraph 3).
Proposed resolution (February, 2014):
Change 13.10.3 [temp.deduct] paragraph 9 as follows:
Except as described above, the use of an invalid value shall not cause type deduction to fail.[Example: In the following example,1000 is converted to signed char and results in an implementation-defined value as specified in (7.3.9 [conv.integral]). In other words, both templates are considered even though 1000, when converted to signed char, results in an implementation-defined valueassuming a signed char cannot represent the value 1000, a narrowing conversion would be required to convert the template-argument of type int to signed char, therefore substitution fails for the second template (13.4.3 [temp.arg.nontype])..template <int> int f(int); template <signed char> int f(int); int i1 = f<1000>(0); //ambiguousOK int i2 = f<1000>(0); // ambiguous; not narrowing—end example]
[Moved to DR at the November, 2014 meeting.]
Currently, 13.10.3.2 [temp.deduct.call] paragraph 1 says,
Template argument deduction is done by comparing each function template parameter type (call it P) with the type of the corresponding argument of the call (call it A) as described below. If removing references and cv-qualifiers from P gives std::initializer_list<P'> for some P' and the argument is an initializer list (9.5.5 [dcl.init.list]), then deduction is performed instead for each element of the initializer list, taking P' as a function template parameter type and the initializer element as its argument. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context (13.10.3.6 [temp.deduct.type]).
It would seem reasonable, however, to allow an array bound to be deduced from the number of elements in the initializer list, e.g.,
template<int N> void g(int const (&)[N]); void f() { g( { 1, 2, 3, 4 } ); }
Additional note (March, 2013):
The element type should also be deducible.
Proposed resolution (February, 2014):
Change 13.10.3.2 [temp.deduct.call] paragraph 1 as follows:
Template argument deduction is done by comparing each function template parameter type (call it P) with the type of the corresponding argument of the call (call it A) as described below. If P is a dependent type, removing references and cv-qualifiers from P gives std::initializer_list<P'> or P'[N] for some P' and N, and the argument is
ana non-empty initializer list (9.5.5 [dcl.init.list]), then deduction is performed instead for each element of the initializer list, taking P' as a function template parameter type and the initializer element as its argument, and in the P'[N] case, if N is a non-type template parameter, N is deduced from the length of the initializer list. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context (13.10.3.6 [temp.deduct.type]). [Example:template<class T> void f(std::initializer_list<T>); f({1,2,3}); // T deduced to int f({1,"asdf"}); // error: T deduced to both int and const char* template<class T> void g(T); g({1,2,3}); // error: no argument deduced for T template<class T, int N> void h(T const(&)[N]); h({1,2,3}); // T deduced to int, N deduced to 3 template<class T> void j(T const(&)[3]); j({42}); // T deduced to int, array bound not considered struct Aggr { int i; int j; }; template<int N> void k(Aggr const(&)[N]); k({1,2,3}); // error: deduction fails, no conversion from int to Aggr k({{1},{2},{3}}); // OK, N deduced to 3 template<int M, int N> void m(int const(&)[M][N]); m({{1,2},{3,4}}); // M and N both deduced to 2 template<class T, int N> void n(T const(&)[N], T); n({{1},{2},{3}},Aggr()); // OK, T is Aggr, N is 3—end example] For a function parameter pack...
Change the penultimate bullet of 13.10.3.6 [temp.deduct.type] paragraph 5 as follows:
The non-deduced contexts are:
...
A function parameter for which the associated argument is an initializer list (9.5.5 [dcl.init.list]) but the parameter does not have
std::initializer_list or reference to possibly cv-qualified std::initializer_list typea type for which deduction from an initializer list is specified (13.10.3.2 [temp.deduct.call]). [Example:...A function parameter pack that does not occur at the end of the parameter-declaration-list.
[Adopted at the February, 2016 meeting.]
The current wording of 13.10.3.2 [temp.deduct.call] paragraph 1 dealing with deduction for a trailing parameter pack refers to “the type” of the argument, which does not apply to an initializer list. There is implementation divergence in the handling of an example like
#include <initializer_list> template <typename... T> void q(std::initializer_list<std::initializer_list<T>>... tt); void bar() { q({{0}}, {{'\0'}}); }
Proposed resolution (September, 2015):
Change 13.10.3.2 [temp.deduct.call] paragraph 1 as follows:
...For a function parameter pack that occurs at the end of the parameter-declaration-list,the type A ofdeduction is performed for each remaining argument of the call,is compared withtaking the type P of the declarator-id of the function parameter pack as the corresponding function template parameter type. Eachcomparisondeduction deduces template arguments for subsequent positions in the template parameter packs expanded by the function parameter pack. When a function parameter pack appears in a non-deduced context (13.10.3.6 [temp.deduct.type]), the type of that parameter pack is never deduced. [Example:...
[Moved to DR at the November, 2014 meeting.]
The current wording of 13.10.3.5 [temp.deduct.partial] paragraph 10 is:
If for each type being considered a given template is at least as specialized for all types and more specialized for some set of types and the other template is not more specialized for any types or is not at least as specialized for any types, then the given template is more specialized than the other template. Otherwise, neither template is more specialized than the other.
This is confusing and needs to be clarified.
Proposed resolution (September, 2013) [SUPERSEDED]:
Change 13.10.3.5 [temp.deduct.partial] paragraphs 9 and 10 as follows:
If, for a given type, deduction succeeds in both directions (i.e., the types are identical after the transformations above) and both P and A were reference types (before being replaced with the type referred to above):
if the type from the argument template was an lvalue reference and the type from the parameter template was not,
the argument type is considered to be more specialized than the otherthe other type is not considered to be at least as specialized as the argument type; otherwise,if the type from the argument template is more cv-qualified than the type from the parameter template (as described above),
the argument type is considered to be more specialized than the other; otherwise,the other type is not considered to be at least as specialized as the argument type.
neither type is more specialized than the other.
If for each type being considered a given template is at least as specialized for all types and more specialized for some set of types and the other template is not more specialized for any types or is not at least as specialized for any types, then the given template is more specialized than the other template. Otherwise, neither template is more specialized than the other.A given template is at least as specialized as another template if it is at least as specialized as the other template for all types being considered. A given template is more specialized than another template if it is at least as specialized as the other template for all types being considered, and the other template is not at least as specialized as the given template for any type being considered.
Proposed resolution (February, 2014):
Change 13.10.3.5 [temp.deduct.partial] paragraphs 9-10 as follows:
If, for a given type, deduction succeeds in both directions (i.e., the types are identical after the transformations above) and both P and A were reference types (before being replaced with the type referred to above):
if the type from the argument template was an lvalue reference and the type from the parameter template was not,
the argument type is considered to be more specialized than the otherthe parameter type is not considered to be at least as specialized as the argument type; otherwise,if the type from the argument template is more cv-qualified than the type from the parameter template (as described above),
the argument type is considered to be more specialized than the other; otherwise,the parameter type is not considered to be at least as specialized as the argument type.
neither type is more specialized than the other.
If for each type being considered a given template is at least as specialized for all types and more specialized for some set of types and the other template is not more specialized for any types or is not at least as specialized for any types, then the given template is more specialized than the other template. Otherwise, neither template is more specialized than the other.Function template F is at least as specialized as function template G if, for each pair of types used to determine the ordering, the type from F is at least as specialized as the type from G. F is more specialized than G if F is at least as specialized as G and G is not at least as specialized as F.
[Moved to DR at the October, 2015 meeting.]
There is implementation divergence in the handling of an example like
template<typename D> struct A { }; template<typename T> struct Wrap1 { typedef T type; }; template<typename T> struct Wrap2 { typedef T type; }; template<typename T1> A<typename Wrap1<T1>::type> fn(const A<T1>& x, const A<T1>& y); template<typename T2, typename U> A<typename Wrap2<T2>::type> fn(const A<T2>& x, const A<U>& y); A<int> (*p)(const A<int>&, const A<int>&) = fn;
The implementations that accept this example do so by not comparing the return types of the two templates during partial ordering, which seems to make sense given that partial ordering would not have been performed if the candidate specializations were not indistinguishable from the perspective of overload resolution. However, the existing wording is not clear that that is how such types are be handled.
Proposed resolution (May, 2015):
This issue is resolved by the resolution of issue 1391.
[Adopted at the June, 2016 meeting.]
According to 13.10.3.6 [temp.deduct.type] paragraph 17,
If P has a form that contains <i>, and if the type of the corresponding value of A differs from the type of i, deduction fails.
This gives the wrong result for an example like:
template<int &> struct X; template<int &N> void f(X<N>&); int n; void g(X<n> &x) { f(x); }
Here, P is X<N>, which contains <i>. The type of i is int&. The corresponding value from A is n, which is a glvalue of type int. Presumably this should be valid.
I think this rule means to say something like,
If P has a form that contains <i>, and the type of i differs from the type of the corresponding template parameter of the template named by the enclosing simple-template-id, deduction fails.
Proposed resolution (March, 2016):
Change 13.10.3.6 [temp.deduct.type] paragraph 17 as follows:
If P has a form that contains <i>, and if the type ofthe corresponding value of A differs from the type ofi differs from the type of the corresponding template parameter of the template named by the enclosing simple-template-id, deduction fails. If P has a form that contains [i]...
[Moved to DR at the October, 2015 meeting.]
N3690 comment CA 12The specification of std::current_exception() in 17.9.7 [propagation] allows either referring to the exception object itself or to a copy thereof, implying that the exception object must be copyable. However, the specification of throw-expression allows throwing objects that cannot be copied, only moved. Presumably the requirements should include a non-deleted accessible copy constructor that is odr-used by a throw-expression, even if the object being thrown is moved to the exception object.
Additional note, February, 2014:
This issue was referred to CWG by EWG at the September, 2013 meeting but was overlooked at that time.
Proposed resolution (May, 2015):
Change 11.4.7 [class.dtor] paragraph 11 as follows:
...A destructor is potentially invoked if it is invoked or as specified in 7.6.2.8 [expr.new]and, 11.9.3 [class.base.init], and 14.2 [except.throw]. A program is ill-formed if...
Change 14.2 [except.throw] paragraph 5 as follows:
When the thrown object is a class object, the constructor selected for the copy-initialization as well as the constructor selected for a copy-initialization considering the thrown object as an lvalueand the destructorshall be non-deleted and accessible, even if the copy/move operation is elided (11.4.5.3 [class.copy.ctor]). The destructor is potentially invoked (11.4.7 [class.dtor]).
[Moved to DR at the November, 2014 meeting.]
N3690 comment CA 24The current wording of 14.6.2 [except.terminate] paragraph 2 affords implementations a significant degree of freedom when exception handling results in a call to std::terminate:
In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before std::terminate() is called. In the situation where the search for a handler (14.4 [except.handle]) encounters the outermost block of a function with a noexcept-specification that does not allow the exception (14.5 [except.spec]), it is implementation-defined whether the stack is unwound, unwound partially, or not unwound at all before std::terminate() is called. In all other situations, the stack shall not be unwound before std::terminate() is called.
This contrasts with the treatment of subobjects and objects constructed via delegating constructos in 14.3 [except.ctor] paragraph 2:
An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (11.9.3 [class.base.init]) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object's destructor will be invoked.
Here the destructors must be called. It would be helpful if these requirements were harmonized.
Notes from the September, 2013 meeting:
Although the Canadian NB comment principally was a request to reconsider the resolution of issue 1424, which CWG decided to retain, the comment also raised the question above, which CWG felt merited its own issue.
Proposed resolution (June, 2014):
Change all of 14.3 [except.ctor], reparagraphing as follows:
As control passes from the point where an exception is thrown to a handler, destructors are invoked by a process, specified in this section, called stack unwinding. If a destructor directly invoked by stack unwinding exits with an exception, std::terminate is called (14.6.2 [except.terminate]). [Note: Consequently, destructors should generally catch exceptions and not let them propagate out of the destructor. —end note]
The destructor is invoked for
all automatic objectseach automatic object of class type constructed since the try block was entered. The automatic objects are destroyed in the reverse order of the completion of their construction.
AnFor an object of class type of any storage duration whose initialization or destruction is terminated by an exceptionwill have destructors executed, the destructor is invoked foralleach ofitsthe object's fully constructed subobjects (excluding the variant members of a union-like class), that is, forsubobjectseach subobject for which the principal constructor (11.9.3 [class.base.init]) has completed execution and the destructor has not yet begun execution. The subobjects are destroyed in the reverse order of the completion of their construction. Such destruction is sequenced before entering a handler of the function-try-block of the constructor or destructor, if any.Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object's destructor
will beis invoked. Such destruction is sequenced before entering a handler of the function-try-block of a delegating constructor for that object, if any.[Note: If the object was allocated
inby a new-expression (7.6.2.8 [expr.new]), the matching deallocation function (6.7.6.5.3 [basic.stc.dynamic.deallocation], 7.6.2.8 [expr.new], 11.4.11 [class.free]), if any, is called to free the storage occupied by the object. —end note]
The process of calling destructors for automatic objects constructed on the path from a try block to the point where an exception is thrown is called “stack unwinding.” If a destructor called during stack unwinding exits with an exception, std::terminate is called (14.6.2 [except.terminate]). [Note: So destructors should generally catch exceptions and not let them propagate out of the destructor. —end note]
Delete 14.4 [except.handle] paragraph 11:
The fully constructed base classes and members of an object shall be destroyed before entering the handler of a function-try-block of a constructor for that object. Similarly, if a delegating constructor for an object exits with an exception after the non-delegating constructor for that object has completed execution, the object's destructor shall be executed before entering the handler of a function-try-block of a constructor for that object. The base classes and non-variant members of an object shall be destroyed before entering the handler of a function-try-block of a destructor for that object (11.4.7 [class.dtor]).
This resolution also resolves issue 1807.
[Moved to DR at the November, 2014 meeting.]
The destruction of fully-constructed array elements when array initialization is terminated by an exception is required by 14.3 [except.ctor] paragraph 2, but the order in which they are to be destroyed is not specified. Presumably it should be in reverse order of construction.
Proposed resolution (June, 2014):
This issue is resolved by the resolution of issue 1774.
[Moved to DR at the November, 2014 meeting.]
According to 14.3 [except.ctor] paragraph 2,
An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (11.9.3 [class.base.init]) has completed execution and the destructor has not yet begun execution.
This introduces a potential leak if a variant member is initialized and has a non-trivial destructor. If the assumption can't be made that such an initialized member is the active member at the time an exception occurs so that it can be destroyed, perhaps variant members of types having a non-trivial destructor should be prohibited.
Notes from the June, 2014 meeting:
CWG favored removing the exclusion of variant members from the destruction following an exception during construction, though not during destruction. If the active member of the union has changed between the initialization and destruction, the behavior is undefined.
Proposed Resolution (July, 2014):
Change 14.3 [except.ctor] paragraph 2 as follows:
An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects(excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (11.9.3 [class.base.init]) has completed execution and the destructor has not yet begun execution, except that in the case of destruction, the variant members of a union-like class are not destroyed. Similarly, if the non-delegating constructor...
[Adopted at the February, 2016 meeting.]
The criteria for matching an exception handler in 14.4 [except.handle] paragraph 3 include a qualification conversion for a handler of pointer or reference to pointer type but not for a handler of pointer-to-member type. However, current implementations permit such conversions.
Proposed resolution (October, 2015):
Change 14.4 [except.handle] bullet 3.3 as follows:
A handler is a match for an exception object of type E if
...
the handler is of type cv T or const T& where T is a pointer or pointer to member type and E is a pointer or pointer to member type that can be converted to T by either or both of
a standard pointer conversion (7.3.12 [conv.ptr]) not involving conversions to pointers to private or protected or ambiguous classes
a qualification conversion (7.3.6 [conv.qual]), or
the handler is...
Notes from the October, 2015 meeting:
This resolution should not be adopted as a Defect Report, only a change in the working paper for future revisions of the Standard, because it could silently change the behavior of well-defined programs in implementations that conform to the existing wording.
[Adopted at the October, 2015 meeting as P0012R1.]
It was tentatively agreed at the Santa Cruz meeting that exception specifications should fully participate in the type system. This change would address gaps in the current static checking of exception specifications such as
void (*p)() throw(int); void (**pp)() throw() = &p; // not currently an error
This is such a major change that it deserves to be a separate issue.
See also issues 25, 87, and 133.
Additional note (March, 2013):
The advent of the noexcept operator makes this issue more relevant in C++11.
Notes from the April, 2013 meeting:
CWG feels that a paper on this topic is needed before any action can be taken.
Notes from the September, 2013 meeting:
CWG feels that EWG would be a better venue for this issue. Possible options might include removal of dynamic-exception-specifications and incorporation of the binary noexcept status in the type system.
Rationale (February, 2014):
EWG determined that no action should be taken on this issue.
Additional note, April, 2015:
EWG has expressed interest in further exploring this issue (see EWG issue 169), so it has been returned to "extension" status. See also issues 1946 and 2010.
[Moved to DR at the November, 2014 meeting.]
The determination of the exception-specification for an implicitly-declared special member function, as described in 14.5 [except.spec] paragraph 14, does not take into account the fact that nonstatic data member initializers and default arguments in default constructors can contain throw-expressions, which are not part of the exception-specification of any function that is “directly invoked” by the implicit definition. Also, the reference to “directly invoked” functions is not restricted to potentially-evaluated expressions, thus possibly including irrelevant exception-specifications.
Additional note (August, 2012):
The direction established by CWG for resolving this issue was to consider functions called from default arguments and non-static data member initializers in determining the exception-specification. This leads to a problem with ordering: because non-static data member initializers can refer to members declared later, their effect cannot be known until the end of the class. However, a non-static data member initializer could possibly refer to an implicitly-declared constructor, either its own or that of an enclosing class.
Proposed resolution (October, 2012) [SUPERSEDED]:
Add the following two new paragraphs and make the indicated changes to 14.5 [except.spec] paragraph 14:
A set of potential exceptions may contain types and the special value “any.” The set of potential exceptions of an expression is the union of all sets of potential exceptions of each potentially-evaluated subexpression e:
If e is a call to a function, member function, function pointer, or member function pointer (including implicit calls, such as a call to the allocation function in a new-expression):
if it has a non-throwing exception-specification or the call is a core constant expression (7.7 [expr.const]), the set is empty;
otherwise, if it has a dynamic-exception-specification, the set consists of every type in that dynamic-exception-specification;
otherwise, the set consists of “any.”
If e is a throw-expression (14.2 [except.throw]), the set consists of the type of the exception object that would be initialized by the operand if present, or “any” otherwise.
If e is a dynamic_cast expression that casts to a reference type and requires a run-time check (7.6.1.7 [expr.dynamic.cast]), the set consists of the type std::bad_cast.
If e is a typeid expression applied to a glvalue expression whose type is a polymorphic class type (7.6.1.8 [expr.typeid]), the set consists of the type std::bad_typeid.
If e is a new-expression with a non-constant expression in the noptr-new-declarator (7.6.2.8 [expr.new]), the set also includes the type std::bad_array_new_length.
Otherwise, the set is the empty set.
The set of potential exceptions of a function f of some class X, where f is an inheriting constructor or an implicitly-declared special member function, is defined as follows:
If f is a constructor, the set is the union of the sets of potential exceptions of the constructor invocations for X's non-variant non-static data members, for X's direct base classes, and, if X is non-abstract (11.7.4 [class.abstract]), for X's virtual base classes, as selected by overload resolution for the implicit definition of f (11.4.5 [class.ctor]), including default argument expressions used in such invocations. [Note: Even though destructors for fully constructed subobjects are invoked when an exception is thrown during the execution of a constructor (14.3 [except.ctor]), their exception-specifications do not contribute to the exception-specification of the constructor, because an exception thrown from such a destructor could never escape the constructor (14.2 [except.throw], 14.6.2 [except.terminate]). —end note]
If f is a default constructor or inheriting constructor, the set also contains all members of the sets of potential exceptions of the initialization of non-static data members from brace-or-equal-initializers.
If f is an assignment operator, the set is the union of the sets of potential exceptions of the assignment operator invocations for X's non-variant non-static data members and for X's virtual and direct base classes, as selected by overload resolution for the implicit definition of f (11.4.5.3 [class.copy.ctor]), including default argument expressions used in such invocations.
If f is a destructor, the set is the union of the sets of potential exceptions of the destructor invocations for X's non-variant non-static data members and for X's virtual and direct base classes.
An inheriting constructor (_N4527_.12.9 [class.inhctor]) and an
implicitly declaredimplicitly-declared special member function (11.4.4 [special])have anare considered to have an implicit exception-specification.If f is an inheriting constructor or an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f's implicit definition; f allows all exceptions if any function it directly invokes allows all exceptions, and f has the exception-specification noexcept(true) if every function it directly invokes allows no exceptions.The implicit exception-specification is noexcept(false) if the set of potential exceptions of the function contains “any;” otherwise, if that set contains at least one type, the implicit exception-specification specifies each type T contained in the set; otherwise, the implicit exception-specification is noexcept(true). [Note: An instantiation of an inheriting constructor template has an implied exception-specification as if it were a non-template inheriting constructor. —end note] [Example:struct A { A(); A(const A&) throw(); A(A&&) throw(); ~A() throw(X); }; struct B { B() throw(); B(const B&) throw(); B(B&&, int = (throw Y(), 0)) throw(Y)noexcept; ~B() throw(Y); }; struct D : public A, public B { // Implicit declaration of D::D(); // Implicit declaration of D::D(const D&) noexcept(true); // Implicit declaration of D::D(D&&) throw(Y); // Implicit declaration of D::~D() throw(X, Y); };Furthermore, if...
Change 7.6.2.7 [expr.unary.noexcept] paragraph 3 as follows:
The result of the noexcept operator is false if
in a potentially-evaluated contextthe set of potential exceptions of the expression (14.5 [except.spec])would containcontains “any” or at least one type and true otherwise.
a potentially evaluated call80 to a function, member function, function pointer, or member function pointer that does not have a non-throwing exception-specification (14.5 [except.spec]), unless the call is a constant expression (7.7 [expr.const]),
a potentially evaluated throw-expression (14.2 [except.throw]),
a potentially evaluated dynamic_cast expression dynamic_cast<T>(v), where T is a reference type, that requires a run-time check (7.6.1.7 [expr.dynamic.cast]), or
a potentially evaluated typeid expression (7.6.1.8 [expr.typeid]) applied to a glvalue expression whose type is a polymorphic class type (11.7.3 [class.virtual]).
Otherwise, the result is true.
(This resolution also resolves issues 1356 and 1465.)
Additional note (October, 2012):
The preceding wording has been modified from the version that was reviewed following the October, 2012 meeting and thus has been returned to "review" status.
Additional note (March, 2013):
It has been suggested that it might be more consistent with other parts of the language, and particularly in view of the deprecation of dynamic-exception-specifications, if a potentially-throwing non-static data member initializer simply made an implicit constructor noexcept(false) instead of giving it a set of potential exception types.
Additional note, April, 2013:
One problem with the approach suggested in the preceding note would be something like the following example:
struct S { virtual ~S() throw(int); }; struct D: S { };
This approach would make the example ill-formed, because the derived class destructor would be declared to throw types not permitted by the base class destructor's exception-specification. A further elaboration on the suggestion above that would not have this objection would be to define all dynamic-exception-specifications as simply equivalent to noexcept(false).
(See also issue 1639.)
Additional note, April, 2013:
The version of this resolution approved in Bristol assumed the underlying text of the C++11 IS; however, the wording of 14.5 [except.spec] paragraph 14 has been changed by previous resolutions, so this and the related issues are being returned to "review" status.
Proposed resolution, February, 2014 [SUPERSEDED]:
Change 14.5 [except.spec] paragraph 5 as follows:
If a virtual function has an exception-specification, all declarations, including the definition, of any function that overrides that virtual function in any derived class shall only allow exceptions that are allowed by the exception-specification of the base class virtual function, unless the overriding function is defined as deleted. [Example:...
Add the following two new paragraphs and change 14.5 [except.spec] paragraph 14 as indicated:
A set of potential exceptions may contain types and the special value “any”. The set of potential exceptions of an expression is the union of all sets of potential exceptions of each potentially-evaluated subexpression e:
If e is a core constant expression (7.7 [expr.const]), the set is empty.
Otherwise, if e is a function call (7.6.1.3 [expr.call]) whose postfix-expression is not a (possibly parenthesized) id-expression (_N4567_.5.1.1 [expr.prim.general]) or class member access (7.6.1.5 [expr.ref]), the set consists of “any”.
Otherwise, if e invokes a function, member function, or function pointer (including implicit calls, such as to an overloaded operator or to an allocation function in a new-expression):
if its declaration has a non-throwing exception-specification, the set is empty;
otherwise, if its declaration has a dynamic-exception-specification, the set consists of every type in that dynamic-exception-specification;
otherwise, the set consists of “any”.
If e is a throw-expression (14.2 [except.throw]), the set consists of the type of the exception object that would be initialized by the operand if present, or “any” otherwise.
If e is a dynamic_cast expression that casts to a reference type and requires a run-time check (7.6.1.7 [expr.dynamic.cast]), the set consists of the type std::bad_cast.
If e is a typeid expression applied to a glvalue expression whose type is a polymorphic class type (7.6.1.8 [expr.typeid]), the set consists of the type std::bad_typeid.
If e is a new-expression with a non-constant expression in the noptr-new-declarator (7.6.2.8 [expr.new]), the set also includes the type std::bad_array_new_length.
If none of the previous items applies, the set is the empty set.
The set of potential exceptions of an implicitly-declared special member function f of some class X is defined as follows:
If f is a constructor, the set is the union of the sets of potential exceptions of the constructor invocations for X's non-variant non-static data members, for X's direct base classes, and, if X is non-abstract (11.7.4 [class.abstract]), for X's virtual base classes, as selected by overload resolution for the implicit definition of f (11.4.5 [class.ctor]), including default argument expressions used in such invocations. [Note: Even though destructors for fully constructed subobjects are invoked when an exception is thrown during the execution of a constructor (14.3 [except.ctor]), their exception-specifications do not contribute to the exception-specification of the constructor, because an exception thrown from such a destructor could never escape the constructor (14.2 [except.throw], 14.6.2 [except.terminate]). —end note]
If f is a default constructor, the set also contains all members of the sets of potential exceptions of the initialization of non-static data members from brace-or-equal-initializers.
If f is an assignment operator, the set is the union of the sets of potential exceptions of the assignment operator invocations for X's non-variant non-static data members and for X's virtual and direct base classes, as selected by overload resolution for the implicit definition of f (11.4.5.3 [class.copy.ctor]), including default argument expressions used in such invocations.
If f is a destructor, the set is the union of the sets of potential exceptions of the destructor invocations for X's non-variant non-static data members and for X's virtual and direct base classes.
An inheriting constructor (_N4527_.12.9 [class.inhctor]) and an implicitly-declared special member function (11.4.4 [special])
haveare considered to have an implicit exception-specification.If f is an inheriting constructor or an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f's implicit definition; f allows all exceptions if any function it directly invokes allows all exceptions, and f has the exception-specification noexcept(true) if every function it directly invokes allows no exceptions. [Note: It follows that f has the exception-specification noexcept(true) if it invokes no other functions. —end note][Note: An instantiation of an inheriting constructor template has an implied exception-specification as if it were a non-template inheriting constructor. —end note] The implicit exception-specification is noexcept(false) if the set of potential exceptions of the special member function contains “any”; otherwise, if that set contains at least one type, the implicit exception-specification specifies each type T contained in the set; otherwise, the implicit exception-specification is noexcept(true). [Example:struct A { A(); A(const A&) throw(); A(A&&) throw(); ~A() throw(X); }; struct B { B() throw(); B(const B&)= default; // Declaration of B::B(const B&) noexcept(true)throw(); B(B&&, int = (throw Y(), 0))throw(Y)noexcept; ~B() throw(Y); }; struct D : public A, public B { // Implicit declaration of D::D(); // Implicit declaration of D::D(const D&) noexcept(true); // Implicit declaration of D::D(D&&) throw(Y); // Implicit declaration of D::~D() throw(X, Y); };Furthermore...
Change 7.6.2.7 [expr.unary.noexcept]paragraph 3 as follows:
The result of the noexcept operator is
falsetrue ifin a potentially-evaluated contextthe set of potential exceptions of the expression (14.5 [except.spec])would containis empty, and false otherwise.
a potentially-evaluated call [[Footnote: This includes implicit calls such as the call to an allocation function in a new-expression. —end footnote] to a function, member function, function pointer, or member function pointer that does not have a non-throwing exception-specification (14.5 [except.spec]), unless the call is a constant expression (7.7 [expr.const]),
a potentially-evaluated throw-expression (14.2 [except.throw]),
a potentially-evaluated dynamic_cast expression dynamic_cast<T>(v), where T is a reference type, that requires a run-time check (7.6.1.7 [expr.dynamic.cast]), or
a potentially-evaluated typeid expression (7.6.1.8 [expr.typeid]) applied to a glvalue expression whose type is a polymorphic class type (11.7.3 [class.virtual]).
Otherwise, the result is true.
(This resolution also resolves issues 1356, 1465, and 1639.)
Additional note, May, 2014:
The current version of the proposed resolution only defines the set of potential exceptions for special member functions; since an inheriting constructor is not a special member function, the exception-specification for an inheriting constructor is no longer specified.
In addition, the structure of the specification of the set of potential exceptions of an expression is unclear. If the bulleted list is intended to be the definition of general statement (“union of all sets of potential exceptions...”), it's incomplete because it doesn't consider exceptions thrown by the evaluation of function arguments in a call, just the exceptions thrown by the function itself; if it's intended to be a list of exceptions to the general rule, the rule about core constant expressions doesn't exclude unselected subexpressions that might throw, so those exceptions are incorrect included in the union.
The issue has been returned to "review" status to allow discussion of these points.
See also the discussion in messages 25290 through 25293.
Proposed resolution (June, 2014):
If a virtual function has an exception-specification, all declarations, including the definition, of any function that overrides that virtual function in any derived class shall only allow exceptions that are allowed by the exception-specification of the base class virtual function, unless the overriding function is defined as deleted. [Example:...
Add the following new paragraphs following 14.5 [except.spec] paragraph 13:
An exception-specification is not considered part of a function's type.
A potential exception of a given context is either a type that might be thrown as an exception or a pseudo-type, denoted by “any”, that represents the situation where an exception of an arbitrary type might be thrown. A subexpression e1 of an expression e is an immediate subexpression if there is no subexpression e2 of e such that e1 is a subexpression of e2.
The set of potential exceptions of a function, function pointer, or member function pointer f is defined as follows:
If the declaration of f has a non-throwing exception-specification, the set is empty.
Otherwise, if the declaration of f has a dynamic-exception-specification, the set consists of every type in that dynamic-exception-specification.
Otherwise, the set consists of the pseudo-type “any”.
The set of potential exceptions of an expression e is empty if e is a core constant expression (7.7 [expr.const]). Otherwise, it is the union of the sets of potential exceptions of the immediate subexpressions of e, including default argument expressions used in a function call, combined with a set S defined by the form of e, as follows:
If e is a function call (7.6.1.3 [expr.call]):
If its postfix-expression is a (possibly parenthesized) id-expression (_N4567_.5.1.1 [expr.prim.general]), class member access (7.6.1.5 [expr.ref]), or pointer-to-member operation (7.6.4 [expr.mptr.oper]) whose cast-expression is an id-expression, S is the set of potential exceptions of the entity selected by the contained id-expression (after overload resolution, if applicable).
Otherwise, S contains the pseudo-type “any”.
If e implicitly invokes a function (such as an overloaded operator, an allocation function in a new-expression, or a destructor if e is a full-expression), S is the set of potential exceptions of the function.
if e is a throw-expression (14.2 [except.throw]), S consists of the type of the exception object that would be initialized by the operand, if present, or the pseudo-type “any” otherwise.
if e is a dynamic_cast expression that casts to a reference type and requires a run-time check (7.6.1.7 [expr.dynamic.cast]), S consists of the type std::bad_cast.
if e is a typeid expression applied to a glvalue expression whose type is a polymorphic class type (7.6.1.8 [expr.typeid]), S consists of the type std::bad_typeid.
if e is a new-expression with a non-constant expression in the noptr-new-declarator (7.6.2.8 [expr.new]), S consists of the type std::bad_array_new_length.
[Example: Given the following declarations
void f() throw(int); void g(); struct A { A(); }; struct B { B() noexcept; }; struct D() { D() throw (double); };the set of potential exceptions for some sample expressions is:
for f(), the set consists of int;
for g(), the set consists of “any”;
for new A, the set consists of “any”;
for B(), the set is empty;
for new D, the set consists of “any” and double.
—end example]
Given a member function f of some class X, where f is an inheriting constructor (_N4527_.12.9 [class.inhctor]) or an implicitly-declared special member function, the set of potential exceptions of the implicitly-declared member function f consists of all the members from the following sets:
if f is a constructor,
the sets of potential exceptions of the constructor invocations
for X's non-variant non-static data members,
for X's direct base classes, and
if X is non-abstract (11.7.4 [class.abstract]), for X's virtual base classes,
(including default argument expressions used in such invocations) as selected by overload resolution for the implicit definition of f (11.4.5 [class.ctor]). [Note: Even though destructors for fully-constructed subobjects are invoked when an exception is thrown during the execution of a constructor (14.3 [except.ctor]), their exception-specifications do not contribute to the exception-specification of the constructor, because an exception thrown from such a destructor could never escape the constructor (14.2 [except.throw], 14.6.2 [except.terminate]). —end note]
the sets of potential exceptions of the initialization of non-static data members from brace-or-equal-initializers that are not ignored (11.9.3 [class.base.init]);
if f is an assignment operator, the sets of potential exceptions of the assignment operator invocations for X's non-variant non-static data members and for X's direct base classes (including default argument expressions used in such invocations), as selected by overload resolution for the implicit definition of f (11.4.5.3 [class.copy.ctor]);
if f is a destructor, the sets of potential exceptions of the destructor invocations for X's non-variant non-static data members and for X's virtual and direct base classes.
Change 14.5 [except.spec] paragraph 14 as follows:
An inheriting constructor (_N4527_.12.9 [class.inhctor]) and an
implicitly declaredimplicitly-declared special member function (11.4.4 [special]) are considered to have an implicit exception-specification, as follows, where f is the member function and S is the set of potential exceptions of the implicitly-declared member function f:.
if S contains the pseudo-type “any”, the implicit exception-specification is noexcept(false);
otherwise, if S contains at least one type, the implicit exception-specification specifies each type T contained in S;
otherwise, the implicit exception-specification is noexcept(true).
If f is an inheriting constructor or an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f's implicit definition; f allows all exceptions if any function it directly invokes allows all exceptions, and f has the exception-specification noexcept(true) if every function it directly invokes allows no exceptions. [Note: It follows that f has the exception-specification noexcept(true) if it invokes no other functions. —end note][Note: An instantiation of an inheriting constructor template has an implied exception-specification as if it were a non-template inheriting constructor. —end note] [Example:struct A { A(int = (A(5), 0)) noexcept; A(const A&) throw(); A(A&&) throw(); ~A() throw(X); }; struct B { B() throw(); B(const B&) = default; // Declaration of B::B(const B&) noexcept(true) B(B&&, int = (throw Y(), 0))throw(Y)noexcept; ~B() throw(Y); }; int n = 7; struct D : public A, public B { int * p = new (std::nothrow) int[n]; // Implicit declaration of D::D() throw(X, std::bad_array_new_length);// Implicit declaration of D::D();// Implicit declaration of D::D(const D&) noexcept(true); // Implicit declaration of D::D(D&&) throw(Y); // Implicit declaration of D::~D() throw(X, Y); };
Change 7.6.2.7 [expr.unary.noexcept] paragraph 3 as follows:
The result of the noexcept operator is
falsetrue ifin a potentially-evaluated contextthe set of potential exceptions of the expressionwould containis empty, and false otherwise.
a potentially-evaluated call83 to a function, member function, function pointer, or member function pointer that does not have a non-throwing exception-specification (14.5 [except.spec]), unless the call is a constant expression (7.7 [expr.const]),
a potentially-evaluated throw-expression (14.2 [except.throw]),
a potentially-evaluated dynamic_cast expression dynamic_cast<T>(v), where T is a reference type, that requires a run-time check (7.6.1.7 [expr.dynamic.cast]), or
a potentially-evaluated typeid expression (7.6.1.8 [expr.typeid]) applied to a glvalue expression whose type is a polymorphic class type (11.7.3 [class.virtual]).
Otherwise, the result is true.
This resolution also resolves issues 1356, 1465, and 1639.
[Moved to DR at the November, 2014 meeting.]
It is unspecified if an implicitly-defined copy assignment operator directly invokes the copy assignment operators of virtual bases. The exception-specification of such a copy assignment operator is thus also unspecified. The specification in 14.5 [except.spec] paragraph 14 should explicitly include the exceptions from the copy assignment operators of virtual base classes, regardless of whether the implicit definition actually invokes the virtual base assignment operators or not.
Proposed resolution (June, 2014):
This issue is resolved by the resolution of issue 1351.
[Moved to DR at the November, 2014 meeting.]
The current specification is not clear whether the exception-specification for a function is propagated to the result of taking its address. For example:
template<class T> struct A { void f() noexcept(false) {} void g() noexcept(true) {} }; int main() { if (noexcept((A<short>().*(&A<short>::f))())) return 1; if (!noexcept((A<long>().*(&A<long>::g))())) return 1; return 0; }
There is implementation variance on whether main returns 0 or 1 for this example. (It also appears that taking the address of a member function of a class template requires instantiating its exception-specification, but it is not clear whether the Standard currently specifies this or not.)
(See also issues 92 and 1351.)
Proposed resolution (June, 2013):
This issue is resolved by the proposed resolution of issue 1351.
[Moved to DR at the November, 2014 meeting.]
According to 13.7.4 [temp.variadic] paragraph 6, describing an empty pack expansion,
When N is zero, the instantiation of the expansion produces an empty list. Such an instantiation does not alter the syntactic interpretation of the enclosing construct, even in cases where omitting the list entirely would otherwise be ill-formed or would result in an ambiguity in the grammar.
This leaves open the question of whether something like
template<typename...T> void f() throw(T...);
should be considered to have a non-throwing exception-specification when T... is empty. The definition in 14.5 [except.spec] paragraph 12 appears to be syntactic regarding dynamic-exception-specifications:
An exception-specification is non-throwing if it is of the form throw(), noexcept, or noexcept(constant-expression ) where the constant-expression yields true. A function with a non-throwing exception-specification does not allow any exceptions.
It seems evident, however, that a dynamic-exception-specification with an empty pack expansion “does not allow any exceptions.”
Proposed resolution (February, 2014):
Change 14.5 [except.spec] paragraph 12 as follows:
A function with no exception-specification or with an exception-specification of the form noexcept(constant-expression ) where the constant-expression yields false allows all exceptions. An exception-specification is non-throwing if it isof the form throw(), noexcept, or noexcept(constant-expression ) where thea dynamic-exception-specification whose set of adjusted types is empty (after any packs are expanded) or a noexcept-specification whose constant-expression is either absent or yields true. A function with a non-throwing exception-specification does not allow any exceptions.
[Adopted at the October, 2015 meeting as P0012R1.]
The resolution of issue 1351 results in the following:
void (*p)() throw(int);
void (&r)() throw(int) = *p; // ill-formed
The reason is that the set of potential exceptions for an indirection is “any” instead of maintaining the known potential exceptions of the operand. It would seem to be reasonable to propagate the set in such cases.
A similar issue arises with function template argument deduction:
template<typename T> T& f(T* p);
void (*p)() throw(int);
void (&r)() throw(int) = f(p); // ill-formed
See also issues 2010, 1995, 1975, and 1798.
Additional note, May, 2015:
See also issue 92 and EWG issue 169.
[Moved to DR at the October, 2015 meeting.]
The declarations in which an exception-specification may appear is not completely clear from the current wording of 14.5 [except.spec] paragraph 2.
Suggested resolution:
Change 14.5 [except.spec] paragraph 2 as follows:
An exception-specification shall appear only on a function declarator for a function type, pointer to function type, reference to function type, or pointer to member function type that is the top-level type of a declarationor definitionof a function, variable, or non-static data member, or on such a type appearing as a parameter or return type in such a function declarator.An exception-specification shall not appear in a typedef declaration or alias-declaration.[Example:...
See also issues 2010, 1995, 1946, and 1798.
Proposed resolution (May, 2015):
Change 14.5 [except.spec] paragraph 2 as follows:
An exception-specification shall appear onlyonwithin a lambda-declarator or within a function declarator for a function type, pointer to function type, reference to function type, or pointer to member function type that is the top-level type of a declarationor definition, or on such a type appearing as a parameter or return type in a function declarator. An exception-specification shall not appear in a typedef declaration or alias-declaration.of a function, variable, or non-static data member. It shall appear only on the top-level declarator, on the declarator of one of its parameter-declarations (if any), or on its return type. [Example:...
[Adopted at the October, 2015 meeting as P0012R1.]
In the following example,
struct A { template<class T> T foo(T t) noexcept { return t; } }; template<class T, int (T::*fptr)(int)> struct S { A a; S() noexcept(noexcept((a.*fptr)(1))) {} }; int foo() { return noexcept(S<A,&A::foo>()); }
what should foo return? The question hinges upon whether the noexcept property of the template argument is preserved or lost in the process of substitution.
See also issues 2010, 1975, 1946, and 1798.
[Adopted at the October, 2015 meeting as P0012R1.]
It is not possible to provide an exception-specification for a conversion-type-id, which prevents the result from being used in assignment or initialization of a function pointer with a non-throwing exception-specification. This seems unnecessarily restrictive.
See also issues 1995, 1975, 1946, and 1798.
Additional note, May, 2015:
See also issue 92 and EWG issue 169.
[Adopted at the February, 2016 meeting.]
According to 14.5 [except.spec] paragraph 1,
In a noexcept-specification, the constant-expression, if supplied, shall be a constant expression (7.7 [expr.const]) that is contextually converted to bool ( 7.3 [conv]).
This allows the expression to have any type that can be converted to bool, which is too lenient; it should instead say something like “a converted constant expression of type bool (7.7 [expr.const]).” This would include the conversion to bool in the determination of whether the expression is constant or not and would also disallow narrowing conversions.
A similar consideration applies to static_assert (9.1 [dcl.pre] paragraph 6), which should probably be recast to something like, “an expression that is a constant expression (7.7 [expr.const]) after contextual conversion to bool (7.3 [conv]).”
Proposed resolution (September, 2015):
Change 7.7 [expr.const] paragraph 4 as follows:
...binds directly. [Note: such expressions may be used in new expressions (7.6.2.8 [expr.new]), as case expressions (8.5.3 [stmt.switch]), as enumerator initializers if the underlying type is fixed (9.8.1 [dcl.enum]), as array bounds (9.3.4.5 [dcl.array]), and as non-type template arguments (13.4 [temp.arg]). —end note] A contextually converted constant expression of type bool is an expression, contextually converted to bool (7.3 [conv]), where the converted expression is a constant expression and the conversion sequence contains only the conversions above.
Change 9.1 [dcl.pre] paragraph 6 as follows:
In a static_assert-declaration, the constant-expression shall be a contextually converted constant expression of type bool (7.7 [expr.const])that can be contextually converted to bool (7.3 [conv]). If the value of the expression when so converted is true...
Change 14.5 [except.spec] paragraph 1 as follows:
In a noexcept-specification, the constant-expression, if supplied, shall be a contextually converted constant expression of type bool (7.7 [expr.const])that is contextually converted to bool ( 7.3 [conv]). A ( token that follows noexcept...
[Adopted at the February, 2016 meeting.]
The resolutions of issues 330 and 1351 use different terminology for an exception specification that can throw anything: the former refers to a “(conceptual) set of all types,” while the latter uses a “pseudo-type, denoted by 'any'.” These should be unified.
Proposed resolution (October, 2015) [SUPERSEDED]:
Change 14.5 [except.spec] paragraph 13 as follows:
AThe set of potential exceptions of a given context iseither a typea set of types that might be thrown as an exceptionor a pseudo-type, denoted by “any”, that represents the situation where an exception of an arbitrary type might be thrown; the (conceptual) set of all types is used to denote that an exception of arbitrary type might be thrown. A subexpression e1 of an expression e is an immediate subexpression if there is no subexpression e2 of e such that e1 is a subexpression of e2.
Delete 14.5 [except.spec] paragraph 14:
The set of potential exceptions of a function, function pointer, or member function pointer f is defined as follows:
If the exception specification of f is the set of all types, the set consists of the pseudo-type “any”.
Otherwise, the set consists of every type in the exception specification of f.
Change 14.5 [except.spec] paragraph 15 as follows:
The set of potential exceptions of an expression e is empty if e is a core constant expression (7.7 [expr.const]). Otherwise, it is the union of the sets of potential exceptions of the immediate subexpressions of e, including default argument expressions used in a function call, combined with a set S defined by the form of e, as follows:
If e is a function call (7.6.1.3 [expr.call]):
If its postfix-expression is a (possibly parenthesized) id-expression (_N4567_.5.1.1 [expr.prim.general]), class member access (7.6.1.5 [expr.ref]), or pointer-to-member operation (7.6.4 [expr.mptr.oper]) whose cast-expression is an id-expression, S is the set of
potential exceptionstypes in the exception specification of the entity selected by the contained id-expression (after overload resolution, if applicable).Otherwise, S
contains the pseudo-type “any”is the set of all types.If e implicitly invokes a function (such as an overloaded operator, an allocation function in a new-expression, or a destructor if e is a full-expression (6.9.1 [intro.execution])), S is the set of
potential exceptionstypes in the exception specification of the function.if e is a throw-expression (7.6.18 [expr.throw]), S consists of the type of the exception object that would be initialized by the operand, if present, or is the
pseudo-type “any”set of all types otherwise.if e is a dynamic_cast expression that casts to a reference type and requires a run-time check (7.6.1.7 [expr.dynamic.cast]), S consists of the type std::bad_cast.
if e is a typeid expression applied to a glvalue expression whose type is a polymorphic class type (7.6.1.8 [expr.typeid]), S consists of the type std::bad_typeid.
if e is a new-expression with a non-constant expression in the noptr-new-declarator (7.6.2.8 [expr.new]), S consists of the type std::bad_array_new_length.
[Example: Given the following declarations
void f() throw(int); void g(); struct A { A(); }; struct B { B() noexcept; }; struct D(){ D() throw (double); };the set of potential exceptions for some sample expressions is:
for f(), the set consists of int;
for g(), the set
consists of “any”is the set of all types;for new A, the set
consists of “any”is the set of all types;for B(), the set is empty;
for new D, the set
consists of “any” and doubleis the set of all types. [Note: This set conceptually includes the type double. —end note]—end example]
Change 14.5 [except.spec] paragraph 16 as follows:
Given aA member function f of some class X, where f is an inheriting constructor (_N4527_.12.9 [class.inhctor]) or an implicitly-declared special member function,the set of potential exceptions of the implicitly-declared member function fis considered to have an implicit exception specification that consists of all the members from the following sets...
Delete the normative portion of 14.5 [except.spec] paragraph 17 and merge the note and example into the preceding paragraph, as follows:
An inheriting constructor (_N4527_.12.9 [class.inhctor]) and an implicitly-declared special member function ( 11.4.4 [special]) are considered to have an implicit exception specification, as follows, where S is the set of potential exceptions of the implicitly-declared member function:
if S contains the pseudo-type “any”, the implicit exception specification is the set of all types;
otherwise, the implicit exception specification contains all the types in S.[Note: An instantiation of an inheriting constructor template...
Additional note (November, 2015):
The base text underlying the preceding proposed resolution was changed at the October, 2015 meeting by the adoption of paper P0136R1. As a result, this issue has been returned to "drafting" status to allow reconciliation of the two sets of changes.
Proposed resolution (January, 2016):
Change 14.5 [except.spec] paragraph 12 as follows:
AThe set of potential exceptions of a given context iseither a typea set of types that might be thrown as an exceptionor a pseudo-type, denoted by “any”, that represents the situation where an exception of an arbitrary type might be thrown; the (conceptual) set of all types is used to denote that an exception of arbitrary type might be thrown. A subexpression e1 of an expression e is an immediate subexpression if there is no subexpression e2 of e such that e1 is a subexpression of e2.
Delete 14.5 [except.spec] paragraph 13:
The set of potential exceptions of a function, function pointer, or member function pointer f is defined as follows:
If the exception specification of f is the set of all types, the set consists of the pseudo-type “any”.
Otherwise, the set consists of every type in the exception specification of f.
Change 14.5 [except.spec] paragraph 14 as follows:
The set of potential exceptions of an expression e is empty if e is a core constant expression (7.7 [expr.const]). Otherwise, it is the union of the sets of potential exceptions of the immediate subexpressions of e, including default argument expressions used in a function call, combined with a set S defined by the form of e, as follows:
If e is a function call (7.6.1.3 [expr.call]):
If its postfix-expression is a (possibly parenthesized) id-expression (_N4567_.5.1.1 [expr.prim.general]), class member access (7.6.1.5 [expr.ref]), or pointer-to-member operation (7.6.4 [expr.mptr.oper]) whose cast-expression is an id-expression, S is the set of
potential exceptionstypes in the exception specification of the entity selected by the contained id-expression (after overload resolution, if applicable).Otherwise, if the postfix-expression has type “noexcept function” or “pointer to noexcept function”, S is the empty set.
Otherwise, S
contains the pseudo-type “any”is the set of all types.If e implicitly invokes a function (such as an overloaded operator, an allocation function in a new-expression, or a destructor if e is a full-expression (6.9.1 [intro.execution])), S is the set of
potential exceptionstypes in the exception specification of the function.If e initializes an object of type D using an inherited constructor for a class of type B (11.9.4 [class.inhctor.init]), S also contains the sets of potential exceptions of the implied constructor invocations for subobjects of D that are not subobjects of B (including default argument expressions used in such invocations) as selected by overload resolution, and the sets of potential exceptions of the initialization of non-static data members from brace-or-equal-initializers (11.9.3 [class.base.init]).
If e is a throw-expression (7.6.18 [expr.throw]), S consists of the type of the exception object that would be initialized by the operand, if present, or is the
pseudo-type “any”set of all types otherwise.If e is a dynamic_cast expression that casts to a reference type and requires a run-time check (7.6.1.7 [expr.dynamic.cast]), S consists of the type std::bad_cast.
If e is a typeid expression applied to a glvalue expression whose type is a polymorphic class type (7.6.1.8 [expr.typeid]), S consists of the type std::bad_typeid.
If e is a new-expression with a non-constant expression in the noptr-new-declarator (7.6.2.8 [expr.new]), S consists of the type std::bad_array_new_length.
[Example: Given the following declarations
void f() throw(int); void g(); struct A { A(); }; struct B { B() noexcept; }; struct D { D() throw (double); };the set of potential exceptions for some sample expressions is:
for f(), the set consists of int;
for g(), the set
consists of “any”is the set of all types;for new A, the set
consists of “any”is the set of all types;for B(), the set is empty;
for new D, the set
consists of “any” and doubleis the set of all types.—end example]
Change 14.5 [except.spec] paragraph 16 as follows:
Given anAn implicitly-declared special member function f of some class X, the set of potential exceptions of the implicitly-declared special member function fis considered to have an implicit exception specification that consists of all the members from the following sets:...
Delete the normative text of 14.5 [except.spec] paragraph 17 and merge the example with the preceding paragraph:
An implicitly-declared special member function ( 11.4.4 [special]) is considered to have an implicit exception specification, as follows, where S is the set of potential exceptions of the implicitly-declared special member function:
if S contains the pseudo-type “any”, the implicit exception specification is the set of all types;
otherwise, the implicit exception specification contains all the types in S.[Example:...
[Adopted at the February, 2016 meeting.]
The Standard needs to describe non-directives more fully, e.g., whether they are ill-formed, conditionally-supported, etc. Since they are, in fact, directives, a different name might also be in order.
Proposed resolution (October, 2015):
Change Clause 15 [cpp] paragraph 1 as follows:
Change Clause 15 [cpp] paragraph 2 as follows:
A text line shall not begin with a # preprocessing token. Anon-directiveconditionally-supported-directive shall not begin with any of the directive names appearing in the syntax. A conditionally-supported-directive is conditionally supported with implementation-defined semantics.
[Adopted at the February, 2016 meeting.]
An #elif is treated differently from an #else followed immediately by an #if: assuming the preceding #if's condition was true, the condition in the second #if need not be a valid expression, while the condition in the #elif directive, though not evaluated, still must be syntactically correct.
C DR 412 changes that for C; C++ should make the corresponding change.
Proposed resolution (November, 2014):
Change 15.2 [cpp.cond] paragraph 6 as follows:
Each directive's condition is checked in order. If it evaluates to false (zero), the group that it controls is skipped: directives are processed only through the name that determines the directive in order to keep track of the level of nested conditionals; the rest of the directives' preprocessing tokens are ignored, as are the other preprocessing tokens in the group. Only the first group whose control condition evaluates to true (nonzero) is processed; any following groups are skipped and their controlling directives are processed as if they were in a group that is skipped. If none of the conditions...
[Adopted at the February, 2016 meeting.]
The adoption of document N3922 at the November, 2014 meeting introduces a new incompatibility that should be documented in Annex Clause Annex C [diff]:
int x1 = 1; auto x2{x1}; // Is now int before was std::initializer<int>
Proposed resolution (September, 2015):
Insert the following as a new section following C.4.2 [diff.cpp14.lex]:
C.4.2 Clause 7: Declarations
[diff.cpp14.dcl]
9.2.9.7 [dcl.spec.auto]
Change: auto deduction from braced-init-list
Rationale: More intuitive deduction behavior
Effect on original feature: Valid C++14 code may fail to compile or may change meaning in this International Standard. For example:auto x1{1}; // Was std::initializer_list<int>, now int auto x2{1, 2}; // Was std::initializer_list<int>, now ill-formed
[Moved to DR at the October, 2015 meeting.]
The introduction of rvalue references in C++11 changed the interpretation of some previously well-formed examples such as the following:
struct Struct { template <typename T> operator T(); }; bool example_1 = new int && false; // #1 bool example_2 = &Struct::operator int && false; // #2
Previously the && was interpreted as an operator, while it is now part of a type-name. However, this change is not mentioned in Annex Clause Annex C [diff].
Proposed resolution (May, 2015):
Add the following as a new subsection in C.6.3 [diff.cpp03.expr]:
7.6.15 [expr.log.or]
Change: && is valid in a type-name
Rationale: Required for new features
Effect on original feature: Valid C++ 2003 code may fail to compile or produce different results in this International Standard, as the following example illustrates:bool b1 = new int && false; // previously false, now ill-formed struct S { operator int(); }; bool b2 = &S::operator int && false; // previously false, now ill-formed
C permits decrementing a _Bool operand, but C++ has never allowed that operation on bool operands. Since there is some code that maps those types together for cross-language compatibility, it might be worth mentioning the different behavior (and, with the adoption of P0002R1, for increment as well).
Notes from the June, 2016 meeting:
This issue will be handled editorially and is placed in "review" status until that point.
[Voted into the WP at the July, 2017 meeting.]
According to _N4567_.5.1.1 [expr.prim.general] paragraph 3,
Unlike the object expression in other contexts, *this is not required to be of complete type for purposes of class member access (7.6.1.5 [expr.ref]) outside the member function body.
Is this special treatment of member access expressions intended to apply only to *this, or does it apply to other ways of specifying the class being defined in the object expression? For example,
struct S { int i; auto f1() -> decltype((*this).i); // okay auto f2(S& This) -> decltype(This.i); // okay? auto f3() -> decltype(((S*)0)->i); // okay? };
There is implementation divergence on this question.
If the intent is to allow object expressions other than *this to have the current class type, this specification should be moved from _N4567_.5.1.1 [expr.prim.general] to 7.6.1.5 [expr.ref] paragraph 2, which is where the general requirement for complete object expression types is found.
On a related point, the note immediately following the above-cited passage is not quite correct:
[Note: only class members declared prior to the declaration are visible. —end note]
This does not apply when the member is a “member of an unknown specialization,” per 13.8.3.2 [temp.dep.type] bullet 5.3 sub-bullet 1; for example,
template<typename T> struct S : T { auto f() -> decltype(this->x); };
Here x is presumed to be a member of the dependent base T and is not “declared prior to the declaration” that refers to it.
Proposed resolution (May, 2017):
Change 7.5.3 [expr.prim.this] paragraph 2 as follows:
Unlike the object expression in other contexts, *this is not required to be of complete type for purposes of class member access (7.6.1.5 [expr.ref]) outside the member function body. [Note: Only class members declared prior to the declaration are visible. —end note][Note: In a trailing-return-type, the class being defined is not required to be complete for purposes of class member access (7.6.1.5 [expr.ref]). Class members declared later are not visible. [Example:struct A { char g(); template<class T> auto f(T t) -> decltype(t + g()) { return t + g(); } }; template auto A::f(int t) -> decltype(t + g());—end example] —end note]
Change 7.6.1.5 [expr.ref] paragraph 2 as follows, splitting the paragraph into two paragraphs as indicated:
For the first option (dot) the first expression shall be a glvalue having
completeclass type. For the second option (arrow) the first expression shall be a prvalue having pointer tocompleteclass type. In both cases, the class type shall be complete unless the class member access appears in the definition of that class. [Note: If the class is incomplete, lookup in the complete class type is required to refer to the same declaration (6.4.7 [basic.scope.class]). —end note]The expression E1->E2 is converted to the equivalent form (*(E1)).E2; the remainder...
[Accepted as a DR at the February, 2019 meeting.]
The current wording is not clear regarding examples like the following:
struct A { int x; } a; struct B {} b; template<int> int &get(const B&); struct C {}; int c[1]; auto [A] = a; // ok? auto [B] = b; // ok? auto [C] = c; // ok?
Notes from the April, 2017 teleconference:
Structured bindings have no C compatibility implications, so the tag/nontag treatment need not apply.
Proposed resolution (February, 2019):
Change _N4868_.6.4.1 [basic.scope.declarative] paragraph 4 as follows:
Given a set of declarations in a single declarative region, each of which specifies the same unqualified name,
they shall all refer to the same entity, or all refer to functions and function templates; or
exactly one declaration shall declare a class name or enumeration name that is not a typedef name and the other declarations shall all refer to the same variable, non-static data member, or enumerator, or all refer to functions and function templates; in this case the class name or enumeration name is hidden (_N4868_.6.4.10 [basic.scope.hiding]). [Note: A structured binding (9.7 [dcl.struct.bind]), namespace name (9.9 [basic.namespace]), or
aclass template name (Clause 13 [temp]) must be unique in its declarative region(9.9.3 [namespace.alias], Clause 13 [temp]). —end note]
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
Consider the following example:
const int i = -1; namespace T { namespace N { const int i = 1; } namespace M { using namespace N; int a[i]; } }
According to 6.5.3 [basic.lookup.unqual], lookup for i finds T::N::i and stops. However, according to _N4868_.6.4.10 [basic.scope.hiding] paragraph 1, the appearance of T::N::i in namespace T does not hide ::i, so both declarations of i are visible in the declaration of a.
It seems strange that we specify this name hiding rule in two different ways in two different places, but they should at least be consistent.
On a related note, the wording in 6.5.3 [basic.lookup.unqual] paragraph 2, “as if they were declared in the nearest enclosing namespace...” could be confusing with regard to the “declared in the same scope” provisions of _N4868_.6.4.10 [basic.scope.hiding].
Proposed resolution (November, 2017)
Change _N4868_.6.4.10 [basic.scope.hiding] paragraphs 1 and 2 as follows:
A name can be hidden by an explicit declaration of that same name in a nested declarative region or derived class (6.5.2 [class.member.lookup])A declaration of a name in a nested declarative region hides a declaration of the same name in an enclosing declarative region; see _N4868_.6.4.1 [basic.scope.declarative] and 6.5.3 [basic.lookup.unqual].
A class name (11.3 [class.name]) or enumeration name (9.8.1 [dcl.enum]) can be hidden by the name of a variable, data member, function, or enumerator declared in the same scope.If a class name (11.3 [class.name]) or enumeration name (9.8.1 [dcl.enum]) and a variable, data member, function, or enumerator are declared in the samescopedeclarative region (in any order) with the same name (excluding declarations made visible via using-directives (6.5.3 [basic.lookup.unqual])), the class or enumeration name is hidden wherever the variable, data member, function, or enumerator name is visible.
[Accepted as a DR at the July, 2019 meeting.]
_N4868_.6.5.6 [basic.lookup.classref] does not mention template aliases as the possible result of the lookup but should do so.
Proposed resolution, June, 2019:
Change _N4868_.6.5.6 [basic.lookup.classref] paragraph 1 as follows:
In a class member access expression (7.6.1.5 [expr.ref]), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (13.3 [temp.names]) or a less-than operator. The identifier is first looked up in the class of the object expression (6.5.2 [class.member.lookup]). If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name aclasstemplate whose specializations are types.
[Accepted as a DR at the February, 2019 meeting.]
Whether an implementation is hosted or freestanding is only required to be documented by the value of the __STDC_HOSTED__ macro (15.12 [cpp.predefined]). Should this characteristic be classified as implementation-defined, thus requiring documentation?
Proposed resolution (January, 2019):
Change 16.4.2.5 [compliance] paragraph 1 as follows:
Two kinds of implementations are defined: hosted and freestanding (4.1 [intro.compliance]) the kind of the implementation is implementation-defined. For a hosted implementation, this document describes the set of available headers.
According to 5.3.1 [lex.charset] paragraph 2,
The character designated by the universal-character-name \UNNNNNNNN is that character whose character short name in ISO/IEC 10646 is NNNNNNNN; the character designated by the universal-character-name \uNNNN is that character whose character short name in ISO/IEC 10646 is 0000NNNN. If the hexadecimal value for a universal-character-name corresponds to a surrogate code point (in the range 0xD800-0xDFFF, inclusive), the program is ill-formed. 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.
It is not specified what should happen if the hexadecimal value does not designate a Unicode code point: is that undefined behavior or does it make the program ill-formed?
As an aside, a note should be added explaining why these requirements apply to to an r-char-sequence when, as the footnote at the end of the paragraph explains,
A sequence of characters resembling a universal-character-name in an r-char-sequence (5.13.5 [lex.string]) does not form a universal-character-name.
Additional note, February, 2021:
This issue was resolved editorially in N4842.
[Adopted at the February, 2019 meeting as part of paper P1041R4.]
The resolution of issue 1802 clarified that char16_t string literals can contain surrogate pairs, in contrast to char16_t character literals. However, there is no explicit requirement that char16_t literals be encoded as UTF-16, although that is explicitly stated for char16_t character literals, so it's not clear what the value is required to be in the surrogate-pair case.
Rationale (February, 2019)
The Standard is now clear on this point.
[Adopted at the February, 2019 meeting as part of paper P1103R3.]
The English word “attributes” is used occasionally in the Standard to refer to general characteristics rather than to the constructs described by the attribute syntactic nonterminal. For example, 6.2 [basic.def] paragraph 1 says,
A declaration (9.1 [dcl.pre]) may introduce one or more names into a translation unit or redeclare names introduced by previous declarations. If so, the declaration specifies the interpretation and attributes of these names.
A similar use occurs in 6.5 [basic.lookup] paragraph 1:
Only after name lookup, function overload resolution (if applicable) and access checking have succeeded are the attributes introduced by the name's declaration used further in expression processing (Clause 7 [expr]).
These nonspecific uses of the term are potentially confusing and should be replaced, possibly with a specification of which “attributes” are intended.
Perhaps less confusing, although in a similar vein, is 11.4.10 [class.bit] paragraph 1:
The bit-field attribute is not part of the type of the class member.
The use of the term here should also be reconsidered.
Notes from the October, 2018 teleconference:
These points are addressed by further work on the modules TS.
[Accepted as a DR as paper P0859R0 at the October, 2017 meeting.]
11.4.4 [special] is perfectly clear that special member functions are only implicitly defined when they are odr-used. This creates a problem for constant expressions in unevaluated contexts:
struct duration {
constexpr duration() {}
constexpr operator int() const { return 0; }
};
// duration d = duration(); // #1
int n = sizeof(short{duration(duration())});
The issue here is that we are not permitted to implicitly define constexpr duration::duration(duration&&) in this program, so the expression in the initializer list is not a constant expression (because it invokes a constexpr function which has not been defined), so the braced initializer contains a narrowing conversion, so the program is ill-formed.
If we uncomment line #1, the move constructor is implicitly defined and the program is valid. This spooky action at a distance is extremely unfortunate. Implementations diverge on this point.
There are also similar problems with implicit instantiation of constexpr functions. It is not clear which contexts require their instantiation. For example:
template<int N> struct U {}; int g(int); template<typename T> constexpr int h(T) { return T::error; } template<typename T> auto f(T t) -> U<g(T()) + h(T())> {} int f(...); int k = f(0);
There are at least two different ways of modeling the current rules:
constexpr function instantiation is triggered by constant expression evaluation. In that case, the validity of the above program depends on the order in which that evaluation proceeds:
If the LHS of the + is evaluated first, the program might be valid, because the implementation might bail out evaluation before triggering the ill-formed instantiation of h<int>.
If the RHS is evaluated first, the program is invalid, because the instantiation fails.
constexpr function instantiation is triggered whenever a constexpr function is referenced from an expression which could be evaluated (note that this is not the same as being potentially-evaluated)
These two approaches can be distinguished by code like this:
int k = sizeof(U<0 && h(0)>);
Under the first approach, this code is valid; under the second, it is ill-formed.
A possible approach to resolving this issue would be to change the definition of “potentially-evaluated” such that template arguments, array bounds, and braced-init-lists (and any other expressions which are constant evaluated) are always potentially-evaluated, even if they appear within an unevaluated context, and to change 13.9.2 [temp.inst] paragraph 3 to say simply that function template specializations are implicitly instantiated when they are odr-used.
A related question is whether putatively constexpr constructors must be instantiated in order to determine whether their class is a literal type or not. See issue 1358.
Jason Merrill:
I'm concerned about unintended side-effects of such a large change to “potentially-evaluated;” I would prefer something that only affects constexpr.
It occurs to me that this is parallel to issue 1330: just like we want to instantiate exception specifiers for calls in unevaluated context, we also want to instantiate constexpr functions. I think we should define some other term to say when there's a reference to a declaration, and then say that the declaration is odr-used when that happens in potentially-evaluated context.
Notes from the April, 2013 meeting:
An additional question was raised about whether constexpr functions should be instantiated as a result of appearing within unevaluated subexpressions of constant expressions. For example:
#include <type_traits> template <class T> constexpr T f(T t) { return +t; } struct A { }; template <class T> decltype(std::is_scalar<T>::value ? T::fail : f(T())) g() { } template <class T> void g(...); int main() { g<A>(); }
If constexpr instantiation happens during constant expression evaluation, f<A> is never instantiated and the program is well-formed. If constexpr instantiation happens during parsing, f<A> is instantiated and the program is ill-formed.
[Accepted as a DR at the February, 2019 meeting.]
According to 6.3 [basic.def.odr] paragraph 3,
A function whose name appears as a potentially-evaluated expression is odr-used if it is the unique lookup result or the selected member of a set of overloaded functions (6.5 [basic.lookup], 12.2 [over.match], 12.3 [over.over]), unless it is a pure virtual function and its name is not explicitly qualified. [Note: This covers calls to named functions (7.6.1.3 [expr.call]), operator overloading (Clause 12 [over]), user-defined conversions (11.4.8.3 [class.conv.fct]), allocation function for placement new (7.6.2.8 [expr.new]), as well as non-default initialization (9.5 [dcl.init]). A constructor selected to copy or move an object of class type is odr-used even if the call is actually elided by the implementation (11.4.5.3 [class.copy.ctor]). —end note] An allocation or deallocation function for a class is odr-used by a new expression appearing in a potentially-evaluated expression as specified in 7.6.2.8 [expr.new] and 11.4.11 [class.free]. A deallocation function for a class is odr-used by a delete expression appearing in a potentially-evaluated expression as specified in 7.6.2.9 [expr.delete] and 11.4.11 [class.free].
There are a couple of problems with this specification. First, contrary to the note, the names of overloaded operators, conversion functions, etc., do not appear in potentially-evaluated expressions, so the normative text does not make the note true. Also, the “as specified in” references do not cover odr-use explicitly, only the invocation of the functions.
One possible way of addressing these deficiencies would be a blanket rule like,
A function is odr-used if it is invoked by a potentially-evaluated expression.
(The existing wording about appearing in a potentially-evaluated expression would still be needed for non-call references.)
Proposed resolution (January, 2019):
Change 6.3 [basic.def.odr] paragraph 2 as follows:
An expression or conversion is potentially evaluated unless it is an unevaluated operand (7.2 [expr.prop]),ora subexpression thereof, or a conversion in an initialization or conversion sequence in such a context. The set of potential results of an expression e is defined as follows:...
Cbange 6.3 [basic.def.odr] paragraph 3 as follows:
A function is named by
an expressionan expression or conversion as follows:
A function
whose name appears in an expressionis named bythatan expression or conversion if it is the uniquelookupresult of a name lookup or the selected member of a set of overloaded functions (6.5 [basic.lookup], 12.2 [over.match], 12.3 [over.over]) in an overload resolution performed as part of forming that expression or conversion, unless it is a pure virtual function and eitherits name is notthe expression is not an id-expression naming the function with anexplicitly qualified name or the expression forms a pointer to member (7.6.2.2 [expr.unary.op]). [Note: This covers taking the address of functions (7.3.4 [conv.func], 7.6.2.2 [expr.unary.op]), calls to named functions (7.6.1.3 [expr.call]), operator overloading (Clause 12 [over]), user-defined conversions (11.4.8.3 [class.conv.fct]), allocation functions forplacementnew-expressions (7.6.2.8 [expr.new]), as well as non-default initialization (9.5 [dcl.init]). A constructor selected to copy or move an object of class type is considered to be named by an expression or conversion even if the call is actually elided by the implementation (11.9.6 [class.copy.elision]). —end note]A
n allocation ordeallocation function for a class is named by a new-expression if it is the single matching deallocation function for the allocation function selected by o verload resolution, as specified in 7.6.2.8 [expr.new]and 11.4.11 [class.free].A deallocation function for a class is named by a
delete expressiondelete-expression if it is the selected usual deallocation function as specified in 7.6.2.9 [expr.delete] and 11.4.11 [class.free].
Change 6.3 [basic.def.odr] paragraph 7 as follows:
A virtual member function is odr-used if it is not pure. A function is odr-used if it is named by a potentially-evaluated expression or conversion. A non-placement...
[Accepted as a DR at the February, 2019 meeting.]
The resolution of issue 1741 was not intended to cause odr-use to occur in cases where it did not do so previously. However, in an example like
extern int globx; int main() { const int &x = globx; struct A { const int *foo() { return &x; } } a; return *a.foo(); }
x satisfies the requirements for appearing in a constant expression, but applying the lvalue-to-rvalue converstion to x does not yield a constant expression. Similarly,
struct A { int q; constexpr A(int q) : q(q) { } constexpr A(const A &a) : q(a.q * 2) { } }; int main(void) { constexpr A a(42); constexpr int aq = a.q; struct Q { int foo() { return a.q; } } q; return q.foo(); }
a satisfies the requirements for appearing in a constant expression, but applying the lvalue-to-rvalue conversion to a invokes a non-trivial function.
Proposed resolution (January, 2019):
Change 6.3 [basic.def.odr] bullet 2.4 as follows:
An expression is potentially evaluated unless it is an unevaluated operand (7.2 [expr.prop]) or a subexpression thereof. The set of potential results of an expression e is defined as follows:
...
If e is a pointer-to-member expression (7.6.4 [expr.mptr.oper])
whose second operand is a constant expression, the set contains the potential results of the object expression....
Otherwise, the set is empty.
Change 6.3 [basic.def.odr] paragraph 4, converting the running text into bullets, as follows:
A variable x whose name appears as a potentially-evaluated expression e
xis odr-used by exunlessapplying the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) to x yields a constant expression (7.7 [expr.const]) that does not invoke a function other than a trivial special member function (11.4.4 [special]) and, if x is an object,
x is a reference that is usable in constant expressions (7.7 [expr.const]), or
x is a variable of non-reference type that is usable in constant expressions and has no mutable subobjects, and e
xis an element of the set of potential results of an expressione, where eitherof non-volatile-qualified non-class type to which the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is appliedto e, ore isx is a variable of non-reference type, and e is an element of the set of potential results of a discarded-value expression (7.2 [expr.prop]) to which the lvalue-to-rvalue conversion is not applied.
[Accepted as a DR at the February, 2019 meeting.]
Issue 1741 accidentally caused 6.3 [basic.def.odr] to indicate that an lvalue-to-rvalue conversion is necessary for the odr-use of a reference. This is incorrect; any appearance of the reference name in a potentially-evaluated expression should require the reference to be defined.
See also issue 2083.
Proposed resolution (January, 2019):
This issue is resolved by the resolution of issue 2083.
[Accepted as a DR at the February, 2019 meeting.]
The current definition of odr-use of a variable is problematic when applied to an array:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) to x yields a constant expression (7.7 [expr.const]) that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is applied to e, or e is a discarded-value expression (Clause 7 [expr]).
Consider an example like
struct S { constexpr static const int arr[3] = { 0, 1, 2 }; }; int i = S::arr[1]; // Should not require S::arr to be defined
Although the “set of potential results” test correctly handles the subscripting operation (since the resolution of issue 1926), it requires applying the lvalue-to-rvalue conversion to S::arr itself and not just to the result of the subscripting operation. Class objects exhibit a similar problem.
Proposed resolution (January, 2019):
This issue is resolved by the resolution of issue 2083.
[Accepted as a DR at the July, 2019 meeting.]
A lambda expression in two translation units has distinct closure types, because each such expression's type is unique within the program. This results in an issue with the ODR, which requires that the definitions of an entity are identical. For example, if
template <int> void f() {std::function<void()> f = []{};}
appears in two translation units, different specializations of function's constructor template are called, which violates 6.3 [basic.def.odr] bullet 6.4.
Issue 765 dealt with a similar problem for inline functions, but the issue still remains for templates.
Proposed resolution, April, 2019:
Change 6.3 [basic.def.odr] paragraph 12 as follows:
...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, for which the definition of a closure type is considered to consist of the sequence of tokens of the corresponding lambda-expression; and
in each definition of D, corresponding names, looked up according to 6.5 [basic.lookup],
shall refer to an entity defined within the definition of D, orshall refer to the same entity, after overload resolution (12.2 [over.match]) and after matching of partial template specialization (13.10.4 [temp.over]), except that a name can refer to
a non-volatile const object with internal or no linkage if the object
has the same literal type in all definitions of D,
is initialized with a constant expression (7.7 [expr.const]),
is not odr-used in any definition of D, and
has the same value in all definitions of D,
or
a reference with internal or no linkage initialized with a constant expression such that the reference refers to the same entity in all definitions of D;
and
in each definition of D, except within the default arguments and default template arguments of D, corresponding lambda-expressions shall have the same closure type (see below), and
in each definition of D, corresponding entities shall have the same language linkage; and
in each definition of D, the overloaded operators referred to, the implicit calls to conversion functions, constructors, operator new functions and operator delete functions, shall refer to the same function
, or to a function defined within the definition of D; andin each definition of D, a default argument used by an (implicit or explicit) function call or a default template argument used by an (implicit or explicit) template-id or simple-template-id is treated as if its token sequence were present in the definition of D; that is, the default argument or default template argument is subject to the requirements described in this paragraph (
and, if the default argument has subexpressions with default arguments, this requirement appliesrecursively)[Footnote: 9.3.4.7 [dcl.fct.default] describes how default argument names are looked up. —end footnote]; and...
If D is a template and is defined in more than one translation unit, then the preceding requirements shall apply both to names from the template's enclosing scope used in the template definition (_N4868_.13.8.4 [temp.nondep]), and also to dependent names at the point of instantiation (13.8.3 [temp.dep]).
If the definitions of D satisfy all these requirements, then the behavior is as if there were a single definition of D.These requirements also apply to corresponding entities defined within each definition of D (including the closure types of lambda-expressions, but excluding entities defined within default arguments or defualt template arguments of either D or an entity not defined within D). For each such entity and for D itself, the behavior is as if there is a single entity with a single definition, including in the application of these requirements to other entities. [Note: The entity is still declared in multiple translation units, and 6.6 [basic.link] still applies to these declarations. In particular, lambda-expressions (7.5.6 [expr.prim.lambda]) appearing in the type of D may result in the different declarations having distinct types, and lambda-expressions appearng in a default argument of D may still denote different types in different translation units. —end note] If the definitions of D do not satisfy these requirements, then thebehavior is undefined.program is ill-formed, no diagnostic required. [Example:inline void f(bool cond, void (*p)()) { if (cond) f(false, []{}); } inline void g(bool cond, void (*p)() = []{}) { if (cond) g(false); } struct X { void h(bool cond, void (*p)() = []{}) { if (cond) h(false); } };If the definition of f appears in multiple translation units, the behavior of the program is as if there is only one definition of f. If the definition of g appears in multiple translation units, the program is ill-formed (no diagnostic required) because each such definition uses a default argument that refers to a distinct lambda-expression closure type. The definition of X can sppear in multiple translation units of a valid program; the lambda-expressions defined within the default argumeht of X::h within the definition of X denote the same closure type in each translation unit. —end example]
[Accepted as a DR at the February, 2019 meeting.]
According to 6.3 [basic.def.odr] bullet 2.3, the potential results of a member access expression are simply the object expression. This rule incorrectly handles an example like:
struct X { static const int n = 0; }; X x = {}; int b = x.n;
Because X::n is not one of the potential results, the expression x.n odr-uses X::n, requiring it to be defined.
Notes from the April, 2018 teleconference:
CWG agreed with the suggested direction to make the member a potential result in cases like the example.
Proposed resolution (February, 2019):
Change 6.3 [basic.def.odr] paragraph 2 as follows:
An expression is potentially evaluated unless it is an unevaluated operand (7.2 [expr.prop]) or a subexpression thereof. The set of potential results of an expression e is defined as follows:
...
If e is a class member access expression (7.6.1.5 [expr.ref]) of the form e1 . templateopt e2 naming a non-static data member, the set contains the potential results of
the object expressione1.if e is a class member access expression naming a static data member, the set contains the id-expression designating the data member.
If e is a pointer-to-member expression (7.6.4 [expr.mptr.oper])
whose second operand is a constant expressionof the form e1 .* e2, the set contains the potential results ofthe object expressione1....
[Accepted as a DR at the February, 2019 meeting.]
The current rule for determining when a local entity is odr-usable because of a capture-default is too broad. For example:
void f() { int n; void g(int k = n); // ill-formed [](int k = n) {}; // ill-formed [=](int k = n) {}; // valid! [=](int k = [=]{ return n; }()) {}; // valid! }
Proposed resolution (January, 2019):
Change 6.3 [basic.def.odr] bullet 9.2.2 and add to the example as follows:
A local entity (6.3 [basic.def.odr]) is odr-usable in a declarative region (_N4868_.6.4.1 [basic.scope.declarative]) if:
...
...
the intervening declarative region is the function parameter scope of a lambda-expression that has a simple-capture naming the entity or has a capture-default, and the block scope of the lambda-expression is also an intervening declarative region.
If a local entity is odr-used in a declarative region in which it is not odr-usable, the program is ill-formed. [Example:
void f(int n) { [] { n = 1; }; // error, n is not odr-usable due to intervening lambda-expression struct A { void f() { n = 2; } // error, n is not odr-usable due to intervening function definition scope }; void g(int = n); // error, n is not odr-usable due to intervening function parameter scope [=](int k = n) {}; // error, n is not odr-usable due to being outside the block scope of the lambda-expression [&] { [n]{ return n; }; }; // OK }
[Adopted at the November, 2018 meeting as part of paper P1131R2.]
The Standard does not completely specify how to look up the type-name(s) in a pseudo-destructor-name (7.6.1 [expr.post] paragraph 1, _N4778_.7.6.1.4 [expr.pseudo]), and what information it does have is incorrect and/or in the wrong place. Consider, for instance, _N4868_.6.5.6 [basic.lookup.classref] paragraphs 2-3:
If the id-expression in a class member access (7.6.1.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 (_N4778_.7.6.1.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 (7.6.1.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 6.5.5 [basic.lookup.qual] paragraph 5:
If a pseudo-destructor-name (_N4778_.7.6.1.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, _N4868_.6.5.6 [basic.lookup.classref] deals only with the case where the ~ immediately follows the . or ->, and 6.5.5 [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 7.6.1 [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 (_N4778_.7.6.1.4 [expr.pseudo]); otherwise, it shall be a class member access (7.6.1.5 [expr.ref]).
Change _N4778_.7.6.1.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 typeThe 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 7.6.1.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 6.5 [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 _N4868_.6.5.6 [basic.lookup.classref] paragraph 2:
If the id-expression in a class member access (7.6.1.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.
Notes from the August, 2011 meeting:
The proposed resolution must be updated with respect to the current wording of the WP.
[Accepted as a DR at the February, 2019 meeting.]
According to 6.6 [basic.link] paragraph 6,
The name of a function declared in block scope and the name of a variable declared by a block scope extern declaration have linkage. If there is a visible declaration of an entity with linkage having the same name and type, ignoring entities declared outside the innermost enclosing namespace scope, the block scope declaration declares that same entity and receives the linkage of the previous declaration. If there is more than one such matching entity, the program is ill-formed. Otherwise, if no matching entity is found, the block scope entity receives external linkage.
The requirement that the entities have the same type does not cover all cases that it should. Consider an example like:
static int a[3]; void g() { printf("%p\n", (void*)a); extern int a[]; printf("%p\n", (void*)a); }
According to the cited wording, the block-scope declaration of a does not match the namespace scope declaration because int[] and int[3] are different types, thus the first reference to a refers to the static variable while the second one refers to a variable a defined in some other translation unit. This is clearly not intended, and current implementations treat both references as referring to the static variable.
Notes from the October, 2018 teleconference:
CWG agreed with the direction and noted that a similar situation occurs with extern "C" functions.
Proposed resolution (November, 2018):
Change 6.6 [basic.link] paragraph 6 as follows:
The name of a function declared in block scope and the name of a variable declared by a block scope extern declaration have linkage. If there is a visible declaration of an entity with linkage
having the same name and type, ignoring entities declared outside the innermost enclosing namespace scope, such that the block scope declaration would be a (possibly ill-formed) redeclaration if the two declarations appeared in the same declarative region, the block scope declaration declares that same entity and receives the linkage of the previous declaration. If there is more than one such matching entity, the program is ill-formed. Otherwise, if no matching entity is found, the block scope entity receives external linkage. If, within a translation unit, the same entity is declared with both internal and external linkage, the program is ill-formed. [Example:static void f(); extern "C" void h(); static int i = 0; // #1 void g() { extern void f(); // internal linkage extern void h(); // C language linkage int i; // #2: i has no linkage { extern void f(); // internal linkage extern int i; // #3: external linkage, ill-formed } }Without the declaration at line #2, the declaration at line #3 would link with the declaration at line #1. Because the declaration with internal linkage is hidden, however, #3 is given external linkage, making the program ill-formed. —end example]
[Accepted as a DR at the February, 2019 meeting.]
The list in 6.6 [basic.link] paragraph 3 specifying which entities receive internal linkage does not mention variable templates, so presumably a variable template has external linkage. Clause 13 [temp] paragraph 6 gives the impression that a specialization of a template with external linkage also has external linkage. However, current implementations appear to give internal linkage to specializations of const-qualified variable templates. Should const-qualified variable templates have internal linkage?
Notes from the December, 2018 teleconference:
CWG felt that a const type should not affect the linkage of a variable template or its instances.
Proposed resolution (February, 2019):
Change 6.6 [basic.link] paragraph 3 as follows:
A name having namespace scope (6.4.6 [basic.scope.namespace]) has internal linkage if it is the name of
a variable, variable template, function or function template that is explicitly declared static; or,
a non-inline non-template variable of non-volatile const-qualified type that is neither explicitly declared extern nor previously declared to have external linkage; or
a data member of an anonymous union.
[Note: An instantiated variable template that has const-qualified type can have external linkage, even if not declared extern. —end note]
[Accepted as a DR at the February, 2019 meeting.]
There is implementation variance in the treatment of an example like:
enum struct alignas(64) A {}; A a[10];
The Standard does not appear to indicate whether padding bits are added to the object representation for an enumeration or not, affecting the result of sizeof and the alignment of the array elements.
If the extended alignment does not affect the object representation of the type, it would be useful to specify that the array declaration is ill-formed because it requires violating the alignment requirement (and similarly for an array new-expression where the bound is known at compile time).
Notes from the April, 2018 teleconference:
It appears that the same question exists for class types that are directly given extended alignment, as opposed to receiving it from a subobject.
Notes from the November, 2018 meeting:
CWG agreed to remove permission for alignas to be applied to enumerations. The class case is already handled by the wording in 7.6.2.5 [expr.sizeof] paragraph 2 that the size of a class “includes any padding required for placing objects of that type in an array.”
Proposed resolution (December, 2018):
Change 9.13.2 [dcl.align] paragraph 1 as follows:
An alignment-specifier may be applied to a variable or to a class data member, but it shall not be applied to a bit-field, a function parameter, or an exception-declaration (14.4 [except.handle]). An alignment-specifier may also be applied to the declaration of a class (in an elaborated-type-specifier (9.2.9.5 [dcl.type.elab]) or class-head ( Clause 11 [class]), respectively)and to the declaration of an enumeration (in an opaque-enum-declaration or enum-head, respectively (9.8.1 [dcl.enum])). An alignment-specifier with an ellipsis...
[Accepted as a DR at the February, 2019 meeting.]
According to 6.5 [basic.lookup] bullet 1.4, the following example has defined behavior because the lifetime of n extends until its storage is released, which is after a's destructor runs:
void f() { struct A { int *p; ~A() { *p = 0; } } a; int n; a.p = &n; }
It would be more consistent if the end of the lifetime of all objects, regardless of whether they have a non-trivial destructor, were treated the same.
Notes from the March, 2018 meeting:
CWG agreed with the suggested direction.
Proposed resolution (November, 2018):
Change 6.7.4 [basic.life] paragraph 1 as follows:
The lifetime of an object or reference is a runtime property of the object or reference.
An object is said to have non-vacuous initialization if it is of a class or array type and it or one of its subobjects is initialized by a constructor other than a trivial default constructor. [Note: : Initialization by a trivial copy/move constructor is non-vacuous initialization. —end note]A variable is said to have vacuous initialization if it is default-initialized and, if it is of class type or (possibly multi-dimensional) array thereof, that class type has a trivial default constructor. The lifetime of an object of type T begins when:
storage with the proper alignment and size for type T is obtained, and
if the object has non-vacuous initialization,its initialization (if any) is complete (including vacuous initialization) (9.5 [dcl.init],except that if the object is a union member or subobject thereof, its lifetime only begins if that union member is the initialized member in the union (9.5.2 [dcl.init.aggr], 11.9.3 [class.base.init]), or as described in 11.5 [class.union]. The lifetime of an object o of type T ends when:
if T is a non-class type, the object is destroyed, or
if T is a class type
with a non-trivial destructor (11.4.7 [class.dtor]), the destructor call starts, orthe storage which the object occupies is released, or is reused by an object that is not nested within o (6.7.2 [intro.object]).
Change 6.9.3.4 [basic.start.term] paragraphs 1 and 2 as follows:
Destructors (11.4.7 [class.dtor]) for initializedConstructed objects (that is, objects whose lifetime (6.7.4 [basic.life]) has begun9.5 [dcl.init]) with static storage duration,are destroyed and functions registered with std::atexit,are called as part of a call to std::exit (17.5 [support.start.term]). The call to std::exit is sequenced before theinvocations of the destructorsdestructions and the registered functions. [Note: Returning from main invokes std::exit (6.9.3.1 [basic.start.main]). —end note]
Destructors for initializedConstructed objects with thread storage duration within a given thread arecalleddestroyed as a result of returning from the initial function of that thread and as a result of that thread calling std::exit. Thecompletions of the destructors fordestruction of allinitializedconstructed objects with thread storage duration within that thread strongly happens beforethe initiation of the destructors ofdestroying any object with static storage duration.
Change 8.7 [stmt.jump] paragraph 2 as follows:
...[Note: However, the program can be terminated (by calling std::exit() or std::abort() (17.5 [support.start.term]), for example) without destroyingclassobjects with automatic storage duration. —end note]
Change 8.9 [stmt.dcl] paragraph 2 as follows:
It is possible to transfer into a block, but not in a way that bypasses declarations with initialization (including ones in conditions and init-statements). A program that jumps92 from a point where a variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable hasscalar type, class type with a trivial default constructor and a trivial destructor, a cv-qualified version of one of these types, or an array of one of the preceding types and is declared without an initializervacuous initialization (9.5 [dcl.init]). In such a case, the variables with vacuous initialization are constructed in the order of their declaration. [Example:...
Change 8.9 [stmt.dcl] paragraph 5 as follows:
The destructor for aA block-scope object with static or thread storage duration will beexecuteddestroyed if and only if it was constructed. [Note: 6.9.3.4 [basic.start.term] describes the order in which block-scope objects with static and thread storage duration are destroyed. —end note]
Change 9.5 [dcl.init] paragraph 21 as follows:
An object whose initialization has completed is deemed to be constructed, even if the object is of non-class type or no constructor of the object's class is invoked for the initialization. [Note: Such an object might have been value-initialized or initialized by aggregate initialization (9.5.2 [dcl.init.aggr]) or by an inherited constructor (11.9.4 [class.inhctor.init]). —end note] Destroying an object of class type invokes the destructor of the class. Destroying a scalar type has no effect other than ending the lifetime of the object (6.7.4 [basic.life]). Destroying an array destroys each element in reverse subscript order.
Change 11.4.7 [class.dtor] paragraph 2 as follows:
A destructor is used to destroy objects of its class type.The address of a destructor...
Change 14.3 [except.ctor] paragraphs 1 and 2 as follows:
As control passes from the point where an exception is thrown to a handler,
destructors are invokedobjects with automatic storage duration are destroyed by a process, specified in this subclause, called stack unwinding.
The destructor is invoked for each automatic object of class typeEach object with automatic storage duration is destroyed if it has been constructed, but not yet destroyed, since the try block was entered. If an exception is thrown during the destruction of temporaries or local variables for a return statement (8.7.4 [stmt.return]), the destructor for the returned object (if any) is also invoked. The objects are destroyed in the reverse order of the completion of their construction. [Example:...
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
According to 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 3,
If an allocation function declared with a non-throwing exception-specification (14.5 [except.spec]) fails to allocate storage, it shall return a null pointer. Any other allocation function that fails to allocate storage shall indicate failure only by throwing an exception (14.2 [except.throw]) of a type that would match a handler (14.4 [except.handle]) of type std::bad_alloc (17.6.4.1 [bad.alloc]).
The use of the word “shall” to constrain runtime behavior is inappropriate, as it normally identifies cases requiring a compile-time diagnostic.
Proposed resolution (November, 2017)
Change 6.7.6.5 [basic.stc.dynamic] paragraph 3 as follows:
Any allocation and/or deallocation functions defined in a C ++ program, including the default versions in the library, shall conform to the semanticsIf the behavior of an allocation or deallocation function does not satisfy the semantic constraints specified in 6.7.6.5.2 [basic.stc.dynamic.allocation] and 6.7.6.5.3 [basic.stc.dynamic.deallocation], the behavior is undefined.
Change 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 1 as follows:
...The value of the first parametershall beis interpreted as the requested size of the allocation...
Change 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 2 as follows:
TheAn allocation function attempts to allocate the requested amount of storage. If it is successful, itshall returnreturns the address of the start of a block of storage whose length in bytesshall beis at least as large as the requested size.There are no constraints on the contents of the allocated storage on return from the allocation function.The order, contiguity, and initial value of storage allocated by successive calls to an allocation function are unspecified.TheFor an allocation function other than a reserved placement allocation function (17.6.3.4 [new.delete.placement], the pointer returnedshall beis suitably aligned so that it can be converted to a pointer to any suitable complete object type (17.6.3.2 [new.delete.single]) and then used to access the object or array in the storage allocated (until the storage is explicitly deallocated by a call to a corresponding deallocation function). Even if the size of the space requested is zero, the request can fail. If the request succeeds, the value returnedshall beby a replaceable allocation function is a non-null pointer value (7.3.12 [conv.ptr]) p0 different from any previously returned value p1, unless that value p1 was subsequently passed toan operator deletea replaceable deallocation function. Furthermore, for the library allocation functions in 17.6.3.2 [new.delete.single] and 17.6.3.3 [new.delete.array], p0shall representrepresents the address of a block of storage disjoint from the storage for any other object accessible to the caller. The effect of indirecting through a pointer returnedasfrom a request for zero size is undefined.38
Change 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 3 as follows:
An allocation function that fails to allocate storage can invoke the currently installed new-handler function (17.6.4.3 [new.handler]), if any. [Note: A program-supplied allocation function can obtain the address of the currently installed new_handler using the std::get_new_handler function (17.6.4.4 [set.new.handler]). —end note]If anAn allocation function that has a non-throwing exception specification (14.5 [except.spec])fails to allocate storage, it shall returnindicates failure by returning a null pointer value. Any other allocation functionthat fails to allocate storage shall indicatenever returns a null pointer value and indicates failure only by throwing an exception (14.2 [except.throw]) of a type that would match a handler (14.4 [except.handle]) of type std::bad_alloc (17.6.4.1 [bad.alloc]).
[Accepted as a DR at the July, 2019 meeting.]
According to 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 2,
For an allocation function other than a reserved placement allocation function (17.6.3.4 [new.delete.placement]), the pointer returned is suitably aligned so that it can be converted to a pointer to any suitable complete object type (17.6.3.2 [new.delete.single]) and then used to access the object or array in the storage allocated (until the storage is explicitly deallocated by a call to a corresponding deallocation function).
This requirement seems excessive, as it appears to require the strictest fundamental alignment even for objects that are too small to require such an alignment.
Proposed resolution (March, 2019):
Change 6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 2 as follows:
...The order, contiguity, and initial value of storage allocated by successive calls to an allocation function are unspecified.For an allocation function other than a reserved placement allocation function (17.6.3.4 [new.delete.placement]), the pointer returned is suitably aligned so that it can be converted to a pointer to any suitable complete object type (17.6.3.2 [new.delete.single]) and then used to access the object or array in the storage allocated (until the storage is explicitly deallocated by a call to a corresponding deallocation function).Even if the size of the space requested...
Add the following as a new paragraph to 6.7.6.5.2 [basic.stc.dynamic.allocation] after the existing paragraph 2:
For an allocation function other than a reserved placement allocation function (17.6.3.4 [new.delete.placement]), the pointer returned on a successful call shall represent the address of storage that is aligned as follows:
If the allocation function takes an argument of type std::align_val_t, then the storage will have the alignment specified by the value of this argument.
Otherwise, if the allocation function is named operator new[], then the storage is aligned for any object that does not have new-extended alignment (6.7.3 [basic.align]) and is no larger than the requested size.
Otherwise, the storage is aligned for any object that does not have new-extended alignment and is of the requested size.
Change 17.6.3.2 [new.delete.single] paragraph 1 as follows:
Effects: The allocation functions (6.7.6.5.2 [basic.stc.dynamic.allocation]) called by a new-expression (7.6.2.8 [expr.new]) to allocate size bytes of storage. The second form is called for a type with new-extended alignment, andallocates storage with the specified alignment. Thethe first form is called otherwise, and allocates storage suitably aligned to represent any object of that size provided the object's type does not have new-extended alignment.
Change 17.6.3.3 [new.delete.array] paragraph 1 as follows:
Effects: The allocation functions (6.7.6.5.2 [basic.stc.dynamic.allocation]) called by the array form of a new-expression (7.6.2.8 [expr.new]) to allocate size bytes of storage. The second form is called for a type with new-extended alignment, andallocates storage with the specified alignment. Thethe first form is called otherwise, and allocates storage suitably aligned to represent any array object of that size or smaller, provided the object's type does not have new-extended alignment.218
[Voted into the WP at the July, 2017 meeting as document P0727R0.]
The Standard is insufficiently precise in dealing with temporaries. It is not always clear when the term “temporary” is referring to an expression whose result is a prvalue and when it is referring to a temporary object.
(See also issue 1568.)
Proposed resolution (February, 2014) [SUPERSEDED]:
The resolution is contained in document N3918.
This resolution also resolves issues 1651 and 1893.
Additional note, November, 2014:
Concerns have been raised that the meaning of “corresponding temporary object” is not clear enough in the proposed wording. In addition, N3918 says that it resolves issue 1300, but that issue is 1) marked as a duplicate of issue 914 and 2) the subject of continuing deliberations in EWG. This issue is being returned to "review" status to allow CWG to address these concerns.
Proposed resolution (March, 2017):
Change Clause 7 [expr] paragraph 12 as follows:
...is applied. [Note: If the expression is an lvalue of class type, it must have a volatile copy constructor to initialize the temporary object that is the result object of the lvalue-to-rvalue conversion. —end note] The glvalue expression...
Change 6.7.7 [class.temporary] paragraph 6 as follows:
The third context is when a reference is bound to a temporary object.116 The temporary object to which the reference is bound or the temporary object that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference if the glvalue referring to the temporary object was obtained through one of the following:
a temporary materialization conversion (7.3.5 [conv.rval]),
( expression ), where expression is one of these expressions,
subscripting (7.6.1.2 [expr.sub]) of an array operand, where that operand is one of these expressions,
a class member access (7.6.1.5 [expr.ref]) using the . operator where the left operand is one of these expressions and the right operand designates a non-static data member of non-reference type,
a pointer-to-member operation (7.6.4 [expr.mptr.oper]) using the .* operator where the left operand is one of these expressions and the right operand is a pointer to data member of non-reference type,
a const_cast (7.6.1.11 [expr.const.cast], static_cast (7.6.1.9 [expr.static.cast]), dynamic_cast (7.6.1.7 [expr.dynamic.cast]), or reinterpret_cast (7.6.1.10 [expr.reinterpret.cast]) converting a glvalue operand that is one of these expressions to a glvalue referring to that operand,
a conditional expression (7.6.16 [expr.cond]) that is a glvalue where the second or third operand is one of these expressions, or
a comma expression (7.6.20 [expr.comma]) that is a glvalue where the right operand is one of these expressions.
[Example:
template<typename T> using id = T; int&& a = id<int[3]>{1, 2, 3}[i]; // temporary array has same lifetime as a const int& b = static_cast<const int&>(0); // temporary int has same lifetime as b int&& c = cond ? id<int[3]>{1, 2, 3}[i] : static_cast<int&&>(0); // exactly one of the two temporaries is lifetime-extended—end example] [Note: If a temporary object has a reference member initialized by another temporary object, lifetime extension applies recursively to such a member's initializer. [Example:
struct S { const int& m; }; const S& s = S{1}; // both S and int temporaries have lifetime of s—end example] —end note]
exceptThe exceptions to this lifetime rule are:
A temporary object bound to a reference parameter...
Change 12.2.2.5 [over.match.copy] bullet 1.2 as follows:
When the type of the initializer expression is a class type “cv S”, the non-explicit conversion functions of S and its base classes are considered. When initializing a temporary object (11.4 [class.mem]) to be bound to the first parameter of a constructor...
Change 16.4.5.9 [res.on.arguments] bullet 1.3 as follows:
...[Note: If a program casts an lvalue to an xvalue while passing that lvalue to a library function (e.g. by calling the function with the argument std::move(x)), the program is effectively asking that function to treat that lvalue as a temporary object. The implementation is free...
Change 20.3.2.3.4 [util.smartptr.weak.assign] paragraph 2 as follows:
Remarks: The implementation may meet the effects (and the implied guarantees) via different means, without creating a temporary object.
Change the footnote in 29.6.2.1 [template.valarray.overview] paragraph 1 as follows:
...generalized subscript operators. [Footnote: The intent is to specify an array template that hass the minimum functionality necessary to address aliasing ambiguities and the proliferation oftemporariestemporary objects. Thus... —end footnote]
Change the last bullet of C.6.16 [diff.cpp03.input.output] as follows:
initializing a const bool & which would bind to a temporary object.
This resolution also resolves issues 943 and 1076.
[Accepted as a DR at the February, 2019 meeting.]
There is implementation divergence on the following example:
#include <stdio.h> struct A { bool live; A() : live(true) { static int cnt; if (cnt++ == 1) throw 0; } ~A() { fprintf(stderr, "live: %d\n", live); live = false; } }; struct AA { A &&a0, &&a1; }; void doit() { static AA aa = { A(), A() }; } int main(void) { try { doit(); } catch (...) { fprintf(stderr, "in catch\n"); doit(); } }
Some implementations produce
in catch live: 1 live: 1 live: 0
While others produce
live: 1 in catch live: 1 live: 1
With regard to the reference to which the first-constructed object of type A is bound, at what point should its lifetime end? Perhaps it should be specified that the lifetime of the temporary is only extended if said initialization does not exit via an exception (which means that calling ::std::exit(0) as opposed to throwing would not result in a call to ~A()).
See also issue 1634.
Notes from the December, 2016 teleconference:
The consensus was that the temporaries should be destroyed immediately if an exception occurs. 14.3 [except.ctor] paragraph 3 should be extended to apply to static initialization, so that even if a temporary is lifetime-extended (because it has static storage duration), it will be destroyed in case of an exception.
Proposed resolution (January, 2019):
Change 14.3 [except.ctor] paragraph 3 as follows:
If the initialization or destruction of an object other than by delegating constructor is terminated by an exception, the destructor is invoked for each of the object's direct subobjects and, for a complete object, virtual base class subobjects, whose initialization has completed (9.5 [dcl.init]) and whose destructor has not yet begun execution, except that in the case of destruction, the variant members of a union-like class are not destroyed. [Note: If such an object has a reference member that extends the lifetime of a temporary object, this ends the lifetime of the reference member, so the lifetime of the temporary object is effectively not extended.—end note] The subobjects are destroyed in the reverse order of the completion of their construction. Such destruction is sequenced before entering a handler of the function-try-block of the constructor or destructor, if any.
[Resolved by paper P1236R1, adopted at the November, 2018 meeting.]
The relationship between the values representable by corresponding signed and unsigned integer types is not completely described, but 6.8 [basic.types] paragraph 4 says,
The value representation of an object is the set of bits that hold the value of type T.
and 6.8.2 [basic.fundamental] paragraph 3 says,
The range of nonnegative values of a signed integer type is a subrange of the corresponding unsigned integer type, and the value representation of each corresponding signed/unsigned type shall be the same.
I.e., the maximum value of each unsigned type must be larger than the maximum value of the corresponding signed type.
C90 doesn't have this restriction, and C99 explicitly says (6.2.6.2, paragraph 2),
For signed integer types, the bits of the object representation shall be divided into three groups: value bits, padding bits, and the sign bit. There need not be any padding bits; there shall be exactly one sign bit. Each bit that is a value bit shall have the same value as the same bit in the object representation of the corresponding unsigned type (if there are M value bits in the signed type and N in the unsigned type, then M <= N).
Unlike C++, the sign bit is not part of the value, and on an architecture that does not have native support of unsigned types, an implementation can emulate unsigned integers by simply ignoring what would be the sign bit in the signed type and be conforming.
The question is whether we intend to make a conforming implementation on such an architecture impossible. More generally, what range of architectures do we intend to support? And to what degree do we want to follow C99 in its evolution since C89?
(See paper J16/08-0141 = WG21 N2631.)
[Voted into the WP at the July, 2017 meeting.]
According to 11.5 [class.union] paragraph 2,
[Note: A union object and its non-static data members are pointer-interconvertible (6.8.4 [basic.compound], 7.6.1.9 [expr.static.cast]). As a consequence, all non-static data members of a union object have the same address. —end note]
However, the normative wording now only requires this for standard-layout unions.
Proposed resolution (April, 2017):
Change 6.8.4 [basic.compound] bullet 4.2 as follows:
Two objects a and b are pointer-interconvertible if:
they are the same object, or
one is a
standard-layoutunion object and the other is a non-static data member of that object (11.5 [class.union]), or...
[Accepted as a DR at the July, 2019 meeting.]
According to 6.9.3.2 [basic.start.static] paragraph 2,
Constant initialization is performed if a variable or temporary object with static or thread storage duration is initialized by a constant initializer for the entity. If constant initialization is not performed, a variable with static storage duration (6.7.6.2 [basic.stc.static]) or thread storage duration (6.7.6.3 [basic.stc.thread]) is zero-initialized (9.5 [dcl.init]). Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization.
This appears to require an explicit initializer for constant initialization and would preclude examples like:
struct S { int i = 1; }; static constexpr S s;
where there is no initializer.
Notes from the October, 2018 teleconference:
CWG agreed that the example should be well-formed.
Proposed resolution, June, 2019:
Change 6.9.3.2 [basic.start.static] paragraph 2 as follows:
Constant initialization is performed if a variable or temporary object with static or thread storage duration is constant-initializedby a constant initializer(7.7 [expr.const])for the entity. If constant initialization is not performed, a variable with static storage duration (6.7.6.2 [basic.stc.static]) or thread storage duration (6.7.6.3 [basic.stc.thread]) is zero-initialized (9.5 [dcl.init]). Together, zero-initialization and constant initialization...
Change 7.7 [expr.const] paragraph 2 as follows:
A
constant initializer for avariable or temporary object o isan initializer for which interpreting its full-expression as a constant-expression results in a constant expressionconstant-initialized if
either it has an initializer or its default-initialization results in some initialization being performed, and
its initialization full-expression is a constant expression when interpreted as a constant-expression, except that if o is an object,
such an initializerthe initialization full-expression may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types. [Note: Such a class may have a non-trivial destructor. Within this evaluation, std::is_constant_evaluated() (21.3.11 [meta.const.eval]) returns true. —end note]
Change 7.7 [expr.const] paragraph 3 as follows:
A variable is usable in constant expressions after its initializing declaration is encountered if it is a constexpr variable, or it is a constant-initialized variable of reference type or of const-qualified integral or enumeration type, and its initializer is a constant initializer.
Change 9.2.6 [dcl.constexpr] paragraph 5 as follows:
For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression (7.7 [expr.const]), or, for a constructor,a constant initializer foran evaluated subexpression of the initialization full-expression of some constant-initialized object (6.9.3.2 [basic.start.static]), the program is ill-formed, no diagnostic required. [Example:...
[Voted into the WP at the July, 2017 meeting as document P0727R0.]
The taxonomy of value categories in 7.2.1 [basic.lval] classifies temporaries as prvalues. However, some temporaries are explicitly referred to as lvalues (cf 14.2 [except.throw] paragraph 3) .
Proposed resolution (March, 2017):
This issue is resolved by the resolution of issue 1299.
[Accepted as a DR at the February, 2019 meeting.]
The aliasing rules of 7.2.1 [basic.lval] paragraph 10 were adapted from C with additions for C++. However, a number of the points either do not apply or are subsumed by other points. For example, the provision for aggregate and union types is needed in C for struct assignment, which in C++ is done via constructors and assignment operators in C++, not by accessing the complete object.
Suggested resolution:
Replace 7.2.1 [basic.lval] paragraph 10 as follows:
If a program attempts to access the stored value of an object through a glvalue whose type is not similar (7.3.6 [conv.qual]) to one of the following types the behavior is undefined: [Footnote:... —end footnote]
the dynamic type of the object,
the signed or unsigned type corresponding to the dynamic type of the object, or
a char or unsigned char type.
Additional note, October, 2015:
It has been suggested that the aliasing rules should be extended to permit an object of an enumeration with a fixed underlying type to alias an object with that underlying type.
Proposed resolution (January, 2019):
Change 7.2.1 [basic.lval] paragraph 11 as follows:
If a program attempts to access the stored value of an object through a glvalue
of other thanwhose type is not similar (7.3.6 [conv.qual]) to one of the following types the behavior is undefined:58
the dynamic type of the object,
a cv-qualified version of the dynamic type of the object,
a type similar (as defined in 7.3.6 [conv.qual]) to the dynamic type of the object,a type that is the signed or unsigned type corresponding to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,a char, unsigned char, or std::byte type.
If a program invokes a defaulted copy/move constructor or copy/move assignment operator for a union of type U with a glvalue argument that does not denote an object of type cv U within its lifetime, the behavior is undefined. [Note: Unlike in C, C++ has no accesses of class type. —end note]
Change 7.3.6 [conv.qual] paragraph 1 as follows:
A cv-decomposition of a type T is a sequence of cvi and Pi such that T is
“cv0 P0 ··· cvn-1 Pn-1 cvn U” for n
>≥ 0,where...
[Accepted as a DR at the February, 2019 meeting.]
According to 7.3.14 [conv.fctptr] paragraph 1,
A prvalue of type “pointer to member of type noexcept function” can be converted to a prvalue of type “pointer to member of type function”. The result designates the member function.
However, 7.2.2 [expr.type] paragraph 4 does not allow for this conversion in determining the composite pointer type of two pointers to member functions. The corresponding conversion is considered for pointers to non-member functions, but only qualification conversions are considered for pointers to members:
The composite pointer type of two operands p1 and p2 having types T1 and T2, respectively, where at least one is a pointer or pointer-to-member type or std::nullptr_t, is:
...
if T1 or T2 is “pointer to noexcept function” and the other type is “pointer to function”, where the function types are otherwise the same, “pointer to function”;
...
if T1 is “pointer to member of C1 of type cv1 U1” and T2 is “pointer to member of C2 of type cv2 U2” where C1 is reference-related to C2 or C2 is reference-related to C1 (9.5.4 [dcl.init.ref]), the cv-combined type of T2 and T1 or the cv-combined type of T1 and T2, respectively;
Note also that the places that refer to “composite pointer type” only refer to 7.3.13 [conv.mem] for conversions involving pointers to members, omitting any reference to 7.3.14 [conv.fctptr] for pointers to member functions. This affects 7.6.9 [expr.rel], 7.6.10 [expr.eq], and 7.6.16 [expr.cond].
Proposed resolution (February, 2019):
Change 7.2.2 [expr.type] paragraph 4 as follows:
The composite pointer type of two operands p1 and p2 having types T1 and T2, respectively, where at least one is a pointer or pointer-to-member type or std::nullptr_t, is:
...
if T1 or T2 is “pointer to member of C1 of type function”, the other type is “pointer to member of C2 of type noexcept function”, and C1 is reference-related to C2 or C2 is reference-related to C1 (9.5.4 [dcl.init.ref]), where the function types are otherwise the same, “pointer to member of C2 of type function” or “pointer to member of C1 of type function”, respectively;
if T1 is “pointer to member of C1 of type cv1
U1U” and T2 is “pointer to member of C2 of type cv2U2U”, for some non-function type U, where C1 is reference-related to C2 or C2 is reference-related to C1 (9.5.4 [dcl.init.ref]), the cv-combined type of T2 and T1 or the cv-combined type of T1 and T2, respectively;
Change 7.6.10 [expr.eq] paragraph 4 as follows:
If at least one of the operands is a pointer to member, pointer-to-member conversions (7.3.13 [conv.mem]), function pointer conversions (7.3.14 [conv.fctptr]), and qualification conversions (7.3.6 [conv.qual]) are performed on both operands to bring them to their composite pointer type (7.2 [expr.prop]). Comparing...
Change 7.6.16 [expr.cond] bullet 7.4 as follows:
Lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the second and third operands. After those conversions, one of the following shall hold:
...
One or both of the second and third operands have pointer-to-member type; pointer to member conversions (7.3.13 [conv.mem]), function pointer conversions (7.3.14 [conv.fctptr]), and qualification conversions (7.3.6 [conv.qual]) are performed to bring them to their composite pointer type (7.2 [expr.prop]). The result is of the composite pointer type.
...
[Accepted as a DR at the February, 2019 meeting.]
The specification of derived-to-base pointer conversions in 7.3.12 [conv.ptr] paragraph 3 does not require that the derived class be complete at the point of the conversion. This leaves unclear the status of an example like the following, on which there is implementation divergence:
template<typename A, typename B> struct check_derived_from { static A a; static constexpr B *p = &a; }; struct W {}; struct X {}; struct Y {}; struct Z : W, X, check_derived_from<Z, X>, // #1 check_derived_from<Z, Y>, Y { // #2 check_derived_from<Z, W> cdf; // #3 };
Notes from the March, 2018 meeting:
The consensus of CWG was that the derived class must be complete at the point of the conversion, and thus all three attempted conversions in the example are ill-formed.
Proposed resolution (November, 2018):
Change 7.3.12 [conv.ptr] paragraph 3 as follows:
A prvalue of type “pointer to cv D”, where D is a complete class type, can be converted to a prvalue of type “pointer to cv B”, where B is a base class (11.7 [class.derived]) of D. If B is an inaccessible...
Change 7.3.13 [conv.mem] paragraph 2 as follows:
A prvalue of type “pointer to member of B of type cv T”, where B is a class type, can be converted to a prvalue of type “pointer to member of D of type cv T”, where D is a complete class derivedclass(11.7 [class.derived])offrom B. If B is an inaccessible...
Change 7.6.1.9 [expr.static.cast] paragraphs 11 and 12 as followed:
A prvalue of type “pointer to cv1 B”, where B is a complete class type, can be converted to a prvalue of type “pointer to cv2 D”, where D is a class derived (11.7 [class.derived]) from B , if cv2 is the same...
A prvalue of type “pointer to member of D of type cv1 T” can be converted to a prvalue of type “pointer to member of B of type cv2 T”, where B is a base class (11.7 [class.derived]) of a complete class D, if cv2 is the same...
[Accepted as a DR at the November, 2018 (San Diego) meeting.]
According to 7.3.14 [conv.fctptr] paragraph 1,
For direct-initialization (9.5 [dcl.init]), a prvalue of type std::nullptr_t can be converted to a prvalue of type bool; the resulting value is false.
The mention of direct-initialization in this context (added by issue 1423) seems odd; standard conversions are on a level below initialization. Should this wording be moved to 9.5 [dcl.init], perhaps as a bullet in paragraph 1?
(See also issue 1781.)
Proposed resolution (June, 2018):
This issue is resolved by the resolution of issue 1781.
[Accepted as a DR at the June, 2018 (Rapperswil) meeting.]
According to 7.5.5.2 [expr.prim.id.unqual] paragraph 1,
An identifier is an id-expression provided it has been suitably declared (Clause 9 [dcl]).
Not only is an identifier an id-expression by (grammatical) definition, declarator-id is defined in terms of id-expression, which makes this circular. If the intention was to disallow use of undeclared identifiers as primary expressions, this should be altered accordingly.
Proposed resolution, February, 2018:
Change 7.5.5.2 [expr.prim.id.unqual] paragraph 1 as follows:
An identifier is only an id-expressionprovidedif it has been suitably declared ( Clause 9 [dcl]) or if it appears as part of a declarator-id (9.3 [dcl.decl]). [Note: For operator-function-ids, see...
[Accepted as a DR at the February, 2019 meeting.]
According to 7.5.5.3 [expr.prim.id.qual] paragraph 5,
In a qualified-id, if the unqualified-id is a conversion-function-id, its conversion-type-id shall denote the same type in both the context in which the entire qualified-id occurs and in the context of the class denoted by the nested-name-specifier.
However, _N4868_.6.5.6 [basic.lookup.classref] paragraph 7 says,
If the id-expression is a conversion-function-id, its conversion-type-id is first looked up in the class of the object expression and the name, if found, is used. Otherwise it is looked up in the context of the entire postfix-expression. In each of these lookups, only names that denote types or templates whose specializations are types are considered.
The latter was changed by issue 1111. It seems the former may have been overlooked in that change.
Proposed resolution (February, 2019):
Change 7.5.5.3 [expr.prim.id.qual] paragraph 4 as follows:
In a qualified-id, if the unqualified-id is a conversion-function-id, its conversion-type-idshall denote the same type in both the context in which the entire qualified-id occurs and in the context of the class denoted by the nested-name-specifieris first looked up in the class denoted by the nested-name-specifier of the qualified-id and the name, if found, is used. Otherwise, it is looked up in the context in which the entire qualified-id occurs. In each of these lookups, only names that denote types or templates whose specializations are types are considered.
[Accepted as a DR as part of paper P0588R1 at the October, 2017 meeting.]
According to 7.5.6 [expr.prim.lambda] paragraph 19,
Every occurrence of decltype((x)) where x is a possibly parenthesized id-expression that names an entity of automatic storage duration is treated as if x were transformed into an access to a corresponding data member of the closure type that would have been declared if x were an odr-use of the denoted entity.
This formulation is problematic because it assumes that x can be captured and, if captured, would result in a member of the closure class. The former is not true if the lambda has no capture-default, and the latter is not guaranteed if the capture-default is &.
[Accepted at the November, 2017 meeting as part of paper P0624R2.]
It would be more consistent if the closure type of a lambda expression with no lambda-capture were default-constructible and copy-assignable, since such lambdas are already convertible to a function pointer, which has those characteristics.
Rationale (June, 2014):
CWG was sympathetic to this request, although it felt that the conversion to a function pointer was irrelevant to this question. However, EWG should examine this request for a language change before any action is taken.
[Accepted as a DR at the February, 2019 meeting.]
According to 7.5.6 [expr.prim.lambda] paragraph 6,
The closure type for a non-generic lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function with C ++ language linkage (9.12 [dcl.link]) having the same parameter and return types as the closure type's function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type's function call operator.
This does not mention the object for which the function call operator would be invoked (although since there is no capture, presumably the function call operator makes no use of the object pointer). This could be addressed by relating the behavior of the function call operator to a notional temporary, or the function call operator for such closure classes could be made static.
Proposed resolution (January, 2019):
Change 7.5.6.2 [expr.prim.lambda.closure] paragraph 7 as follows, splitting it into two paragraphs:
...The value returned by this conversion function is the address of a function F that, when invoked, has the same effect as invoking the closure type's function call operator on a default-constructed instance of the closure type. F is a constexpr function if the function call operator is a constexpr function.
For a generic lambda with no lambda-capture, the closure type has...
Change 7.5.6.2 [expr.prim.lambda.closure] paragraph 9 as follows:
The value returned by any given specialization of this conversion function template is the address of a function F that, when invoked, has the same effect as invoking the generic lambda's corresponding function call operator template specialization on a default-constructed instance of the closure type. F is a constexpr function if the corresponding specialization is a constexpr function and is an immediate function if the function call operator template specialization is an immediate function. [Note: This will result...
[Resolved by the adoption of P0588R1 at the November, 2017 meeting.]
According to 7.5.6 [expr.prim.lambda] paragraph 11, a variable is implicitly captured if it is odr-used. In the following example,
struct P { virtual ~P(); }; P &f(int&); int f(const int&); void g(int x) { [=] { typeid(f(x)); }; }
x is only odr-used if the operand of typeid is a polymorphic lvalue; otherwise, the operand is unevaluated (7.6.1.8 [expr.typeid] paragraphs 2-3). Whether the operand is a polymorphic lvalue depends on overload resolution in this case, which depends on whether x is captured or not: if x is captured, since the lambda is not mutable, the type of x in the body of the lambda is const int, while if it is not captured, it is just int. However, the const int version of f returns int and the int version of f returns a polymorphic lvalue, leading to a conundrum: x is only captured if it is not captured, and vice versa.
Notes from the October, 2012 meeting:
The approach favored by CWG was to specify that the operand of typeid is considered to be odr-used for the purpose of determining capture.
[Accepted as a DR as part of paper P0588R1 at the October, 2017 meeting.]
According to 7.5.6 [expr.prim.lambda] paragraph 9,
A lambda-expression whose smallest enclosing scope is a block scope (6.4.3 [basic.scope.block]) is a local lambda expression; any other lambda-expression shall not have a capture-list in its lambda-introducer. The reaching scope of a local lambda expression is the set of enclosing scopes up to and including the innermost enclosing function and its parameters.
Consequently, lambdas appearing in mem-initializers and brace-or-equal-initializers cannot have a capture-list. However, these expressions are evaluated in the context of the constructor and are permitted to access this and non-static data members.
Should the definition of a local lambda be modified to permit capturing lambdas within these contexts?
Notes from the April, 2013 meeting:
CWG agreed with the intent of this issue.
[Accepted as a DR at the February, 2019 meeting.]
The status of an example like the following is unclear:
int foo(int a = ([loc=1] { return loc; })()) { return a; }
because of 7.5.6.3 [expr.prim.lambda.capture] paragraph 9:
A lambda-expression appearing in a default argument shall not implicitly or explicitly capture any entity.
However, there doesn't appear to be a good reason for prohibiting such a capture.
Notes from the April, 2018 teleconference:
CWG felt that the rule for capturing should be something like the prohibition for local classes odr-using a variable with automatic storage duration in 11.6 [class.local] paragraph 1.
Proposed resolution (November, 2018):
Change 7.5.6.3 [expr.prim.lambda.capture] paragraph 9 as follows:
A lambda-expression appearing in a default argument shall not implicitly or explicitly capture any entity, except for an init-capture for which any full-expression in its initializer satisfies the constraints of an expression appearing in a default argument (9.3.4.7 [dcl.fct.default]). [Example:
void f2() { int i = 1; void g1(int = ([i]{ return i; })()); // ill-formed void g2(int = ([i]{ return 0; })()); // ill-formed void g3(int = ([=]{ return i; })()); // ill-formed void g4(int = ([=]{ return 0; })()); // OK void g5(int = ([]{ return sizeof i; })()); // OK void g6(int = ([x=1] { return x; }))(); // OK void g7(int = ([x=i] { return x; }))(); // ill-formed }—end example]
[Adopted at the June, 2018 meeting as part of paper P0929R2.]
According to 7.6.1.3 [expr.call] paragraph 11,
If a function call is a prvalue of object type:
if the function call is either
the operand of a decltype-specifier or
the right operand of a comma operator that is the operand of a decltype-specifier,
a temporary object is not introduced for the prvalue. The type of the prvalue may be incomplete. [Note: as a result, storage is not allocated for the prvalue and it is not destroyed; thus, a class type is not instantiated as a result of being the type of a function call in this context. This is true regardless of whether the expression uses function call notation or operator notation (12.2.2.3 [over.match.oper]). —end note] [Note: unlike the rule for a decltype-specifier that considers whether an id-expression is parenthesized (9.2.9.3 [dcl.type.simple]), parentheses have no special meaning in this context. —end note]
otherwise, the type of the prvalue shall be complete.
Thus, an example like
template <class T> struct A: T { }; template <class T> A<T> f(T) { return A<T>(); }; decltype(f(42)) *p;
is well-formed. However, a function template specialization in which the return type is an abstract class should be a deduction failure, per 13.10.3 [temp.deduct] paragraph 8, last bullet:
...
Attempting to create a function type in which a parameter type or the return type is an abstract class type (11.7.4 [class.abstract]).
The requirement that the return type in a function call in a decltype-specifier not be instantiated prevents the detection of this deduction failure in an example like:
template <class T> struct A { virtual void f() = 0; }; template <class T> A<T> f(T) { return A<T>(); }; decltype(f(42)) *p;
It is not clear how this should be resolved.
(See also issue 1640.)
Various aspects of overload resolution, such as determining viable functions, specification of conversion sequences, etc., should apply when there is only one function in the overload set, but currently overload resolution is only invoked to choose among multiple functions.
Proposed resolution (March, 2018):
This issue is resolved by the resolution of issue 2092.
[Voted into the WP at the July, 2017 meeting as document P0727R0.]
According to 7.6.1.4 [expr.type.conv] paragraphs 1 and 3 (stated directly or by reference to another section of the Standard), all the following expressions create temporaries:
T(1) T(1, 2) T{1} T{}
However, paragraph 2 says,
The expression T(), where T is a simple-type-specifier or typename-specifier for a non-array complete effective object type or the (possibly cv-qualified) void type, creates an rvalue of the specified type, which is value-initialized (9.5 [dcl.init]; no initialization is done for the void() case).
This does not say that the result is a temporary, which means that the lifetime of the result is not specified by 6.7.7 [class.temporary]. Presumably this is just an oversight.
Notes from the October, 2009 meeting:
The specification in 7.6.1.4 [expr.type.conv] is in error, not because it fails to state that T() is a temporary but because it requires a temporary for the other cases with fewer than two operands. The case where T is a class type is covered by 6.7.7 [class.temporary] paragraph 1 (“a conversion that creates an rvalue”), and a temporary should not be created when T is not a class type.
Proposed resolution (March, 2017):
This issue is resolved by the resolution of issue 1299.
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
According to 7.6.1.4 [expr.type.conv] paragraph 1,
A simple-type-specifier (9.2.9.3 [dcl.type.simple]) or typename-specifier (13.8 [temp.res]) followed by a parenthesized expression-list constructs a value of the specified type given the expression list. If the expression list is a single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression (7.6.3 [expr.cast]). If the type specified is a class type, the class type shall be complete. If the expression list specifies more than a single value, the type shall be a class with a suitably declared constructor (9.5 [dcl.init], 11.4.5 [class.ctor]), and the expression T(x1, x2, ...) is equivalent in effect to the declaration T t(x1, x2, ...); for some invented temporary variable t, with the result being the value of t as a prvalue.
This does not cover the cases when the expression-list contains a single braced-init-list (which is neither an expression nor more than a single value) or if it contains no expressions as the result of an empty pack expansion.
Proposed resolution (June, 2014): [SUPERSEDED]
This issue is resolved by the resolution of issue 1299.
Proposed resolution (November, 2017)
Change 7.6.1.4 [expr.type.conv] paragraph 2 as follows:
If the initializer is a parenthesized single expression, the type conversion expression is equivalent to the corresponding cast expression (7.6.3 [expr.cast]). Otherwise, if the type is cv void and the initializer is () (after pack expansion, if any), the expression is a prvalue of the specified type that performs no initialization. Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized (9.5 [dcl.init]) with the initializer.For an expression of the form T(), TIf the initializer is a parenthesized optional expression-list, the specified type shall not be an array type.
[Accepted as a DR at the June, 2018 (Rapperswil) meeting.]
According to 7.6.1.4 [expr.type.conv] paragraph 2,
If the initializer is a parenthesized single expression, the type conversion expression is equivalent to the corresponding cast expression (7.6.3 [expr.cast]). Otherwise, if the type is cv void and the initializer is () , the expression is a prvalue of the specified type that performs no initialization. Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized (9.5 [dcl.init]) with the initializer. For an expression of the form T(), T shall not be an array type.
It seems an oversight that void{} is not treated like void().
Proposed resolution, April, 2018:
Change 7.6.1.4 [expr.type.conv] paragraph 2 as follows:
...Otherwise, if the type is cv void and the initializer is () or {}, the expression is a prvalue of the specified type that performs no initialization. Otherwise...
[Accepted as a DR at the February, 2019 meeting.]
From editorial issue 1453.
According to 7.6.1.7 [expr.dynamic.cast] paragraph 4,
If the value of v is a null pointer value in the pointer case, the result is the null pointer value of type T.
Paragraph 5 deals with the case of a simple up-cast, where no runtime type identification is required. Paragraph 6 says,
Otherwise, v shall be a pointer to or a glvalue of a polymorphic type (11.7.3 [class.virtual]).
This organization of the material makes it sound as if the requirement for polymorphic class types does not apply if the argument is a null pointer value, which, of course, cannot be determined at compile time. The intent is that a null pointer value argument produces a null pointer value result, regardless of whether the relationship between the classes requires runtime type identification or not, but that the requirement for polymorphic classes applies for all casts that are not simple up-casts. The wording should be clarified.
Proposed resolution (November, 2018):
Change 7.6.1.7 [expr.dynamic.cast] paragraphs 4-6 as follows:
If the value of v is a null pointer value in the pointer case, the result is the null pointer value of type T.If T is “pointer to cv1 B” and v has type “pointer to cv2 D” such that B is a base class of D, the result is a pointer to the unique B subobject of the D object pointed to by v, or a null pointer value if v is a null pointer value. Similarly, if T is...
Otherwise, v shall be a pointer to or a glvalue of a polymorphic type (11.7.3 [class.virtual]).
If v is a null pointer value, the result is a null pointer value.
[Accepted as a DR at the November, 2017 meeting.]
The specifications of std::byte (17.2.5 [support.types.byteops]) and bitmask (16.3.3.3.3 [bitmask.types]) have revealed a problem with the integral conversion rules, according to which both those specifications have, in the general case. undefined behavior. The problem is that a conversion to an enumeration type has undefined behavior unless the value to be converted is in the range of the enumeration.
For enumerations with an unsigned fixed underlying type, this requirement is overly restrictive, since converting a large value to an unsigned integer type is well-defined.
Proposed resolution (August, 2017):
Change 7.6.1.9 [expr.static.cast] paragraph 10 as follows:
A value of integral or enumeration type can be explicitly converted to a complete enumeration type.TheIf the enumeration type has a fixed underlying type, the value is first converted to that type by integral conversion, if necessary, and then to the enumeration type. If the enumeration type does not have a fixed underlying type, the value is unchanged if the original value is within the range of the enumeration values (9.8.1 [dcl.enum]). Otherwise, and otherwise, the behavior is undefined.
[Accepted as a DR at the November, 2017 meeting.]
The changes from document P0137 make it clear that this is valid:
struct A { int n; } a; int *p = reinterpret_cast<int*>(&a); // ok, a and a.n are pointer-interconvertible int m = *p; // ok, p points to a.n
but the handling for that case does not extend to this one:
int &r = reinterpret_cast<int&>(a); int n = r;
The relevant rule is 7.6.1.10 [expr.reinterpret.cast] paragraph 11:
A glvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result refers to the same object as the source glvalue, but with the specified type. [Note: That is, for lvalues, a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators (and similarly for reinterpret_cast<T&&>(x)). —end note]
Note that the normative rule and the note specify different rules: under the rule described in the note, the result would be a reference to the object a.n. According to the normative rule, however, we get a reference to the object a with type n.
Proposed resolution (July, 2017):
Change 7.6.1.10 [expr.reinterpret.cast] paragraph 11 as follows:
A glvalue expression of type T1, designating an object x, can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The resultrefers to the same object as the source glvalue, but with the specified typeis that of *reinterpret_cast<T2 *>(p) where p is a pointer to x of type “pointer to T1”.[Note: That is, for lvalues, a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators (and similarly for reinterpret_cast<T&&>(x)). —end note]No temporary is created, no copy is made, and constructors (11.4.5 [class.ctor]) or conversion functions (11.4.8 [class.conv]) are not called. [Footnote: This is sometimes referred to as a type pun when the result refers to the same object as the source glvalue. —end footnote]
[Resolved by issue 2382, which was accepted as a DR at the November, 2019 meeting.]
(See also issue 256.)
An implementation may have an unspecified amount of array allocation overhead (7.6.2.8 [expr.new] paragraph 10), so that evaluation of a new-expression in which the new-type-id is T[n] involves a request for more than n * sizeof(T) bytes of storage through the relevant operator new[] function.
The placement operator new[] function does not and cannot check whether the requested size is less than or equal to the size of the provided region of memory (17.6.3.4 [new.delete.placement] paragraphs 5-6). A program using placement array new must calculate what the requested size will be in advance.
Therefore any program using placement array new must take into account the implementation's array allocation overhead, which cannot be obtained or calculated by any portable means.
Notes from the April, 2005 meeting:
While the CWG agreed that there is no portable means to accomplish this task in the current language, they felt that a paper is needed to analyze the numerous mechanisms that might address the problem and advance a specific proposal. There is no volunteer to write such a paper at this time.
Note, January, 2012:
It has been suggested that this issue is more appropriate for EWG and should thus be closed with "extension" status.
Rationale (February, 2012):
The CWG agreed that EWG is the appropriate venue for dealing with this issue.
Additional note (November, 2019):
The resolution of issue 2382 effectively addresses the request in this issue by eliminating the array overhead in the case of the standard placement allocator.
EWG (January, 2021):
Resolved by issue 2382. See vote.
[Adopted at the February, 2019 meeting as part of paper P1009R2.]
The syntax for noptr-new-declarator in 7.6.2.8 [expr.new] paragraph 1 requires an expression, even though the bound could be inferred from a braced-init-list initializer. It is not clear whether 9.5.2 [dcl.init.aggr] paragraph 4,
An array of unknown size initialized with a brace-enclosed initializer-list containing n initializer-clauses, where n shall be greater than zero, is defined as having n elements (9.3.4.5 [dcl.array]).
should be considered to apply to the new-type-id variant, e.g.,
new (int[]){1, 2, 3}
Additional note (August, 2012):
The consensus during the 2012-08-13 drafting review teleconference was that this issue should be referred to the Evolution Working Group and not handled by the Core Working Group.
[Resolved by CWG1880 (November, 2014) and CWG2177 (November, 2017).]
The description in 7.6.2.8 [expr.new] paragraph 23 regarding calling a deallocation function following an exception during the initialization of an object resulting from a placement new-expression says,
If a placement deallocation function is called, it is passed the same additional arguments as were passed to the placement allocation function, that is, the same arguments as those specified with the new-placement syntax. If the implementation is allowed to make a copy of any argument as part of the call to the allocation function, it is allowed to make a copy (of the same original value) as part of the call to the deallocation function or to reuse the copy made as part of the call to the allocation function. If the copy is elided in one place, it need not be elided in the other.
This seems curious, as it allows reuse of a parameter object that presumably is destroyed immediately upon the return of the allocation function (but see issue 1880 for a question about the timing of such destructions).
Notes from the November, 2014 meeting:
The resolution for issue 1880 should mostly resolve this issue. The resolution should handle the case in which an object can only be constructed into the parameter object and neither copied nor moved.
Additional notes (July, 2022):
Issue 1880 permitted the destruction of parameter objects at the end of the full-expression enclosing the function call. Issue 2177 clarified the exceptions to the parameter object reuse.
Additional notes (CWG teleconference 2022-08-12):
This issue is resolved by issues 1880 and 2177. Reusing the parameter objects of the placement-new call for the placement-delete invocation is intentional.
[Adopted at the February, 2017 meeting as part of paper P0620R0.]
According to 7.6.2.8 [expr.new] paragraph 2,
If a placeholder type (9.2.9.7 [dcl.spec.auto]) appears in the type-specifier-seq of a new-type-id or type-id of a new-expression, the new-expression shall contain a new-initializer of the form
( assignment-expression )
Now that auto v{x}; is permitted, this restriction on new-expressions should be revised to allow a single-element braced-init-list as well.
[Accepted as a DR at the November, 2017 meeting.]
Consider the following example:
void *operator new(size_t n, std::string s) { std::string t = std::move(s); std::cout << "new " << t << std::endl; return operator new(n); } void operator delete(void*, std::string s) { std::cout << "delete " << s << std::endl; } struct X { X() { throw 0; } }; int main() { try { new ("longer than the small string buffer") X(); } catch (...) {} }
Current implementations print
new longer than the small string buffer delete
because the same std::string object is used for both the new and delete calls. We should introduce additional copies to separate out the parameters in this case or make non-trivially-copyable parameter types ill-formed here.
Notes from the October, 2015 meeting:
CWG favored limiting the parameters of an overloaded deallocation function to trivially-copyable types.
Proposed resolution (October, 2017):
Change 7.6.2.8 [expr.new] paragraph 24 as follows:
If a new-expression calls a deallocation function, it passes the value returned from the allocation function call as the first argument of type void*. If a placement deallocation function is called, it is passed the same additional arguments as were passed to the placement allocation function, that is, the same arguments as those specified with the new-placement syntax. If the implementation is allowed to introduce a temporary object or make a copy of any argument as part of the call to the allocation function, it isallowed to make a copy (of the same original value) as part of the call to the deallocation function or to reuse the copy made as part of the call to the allocation function. If the copy is elided in one place, it need not be elided in the otherunspecified whether the same object is used in the call to both the allocation and deallocation functions,
[Accepted as a DR at the November, 2019 meeting.]
According to 7.6.2.8 [expr.new] paragraph 12,
When a new-expression calls an allocation function and that allocation has not been extended, the new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t . That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array. For arrays of char, unsigned char, and std::byte, 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 (6.7.3 [basic.align]) of any object type whose size is no greater than the size of the array being created.
There is no exemption for the non-allocating (void*,size_t) placement-new allocation function, so programs must allow for the possibility that the provided buffer may need to be larger (by an indeterminate amount) than the size of an array placed into existing storage.
Should the non-allocating placement-new allocation function be exempt from the array allocation overhead? (This question was explicitly referred to CWG by the EWG chair.)
Proposed resolution (February, 2019):
Change 7.6.2.8 [expr.new] paragraph 12 as follows:
When a new-expression calls an allocation function and that allocation has not been extended, the new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t . That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array and the allocation function is not a non-allocating form (17.6.3.4 [new.delete.placement]). For arrays of...
Change 7.6.2.8 [expr.new] paragraph 16 as follows:
...Here, each instance of x is a non-negative unspecified value representing array allocation overhead; the result of the new-expression will be offset by this amount from the value returned by operator new[]. This overhead may be applied in all array new-expressions, including those referencing a placement allocation function, but not when referencing the library function operator new[](std::size_t, void*)and other placement allocation functions. The amount of overhead may vary from one invocation of new to another. —end example]
(This resolution effectively resolves issue 476, which was closed for EWG input.)
[Adopted at the November, 2018 meeting as part of paper P1236R1.]
The resolution of issue 1796 addressed only the relationship of “bits” with the null character value. The values and arrangements of bits within an object are also mentioned in other contexts; these should also be considered for revision. For example, 7.6.7 [expr.shift] paragraph 2 says,
The value of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are zero-filled.
This appears to place constraints on the bit representation, which (as noted in issue 1796) is not accessible to the program. A similar statement appears in paragraph 3 for >>.
The specification of the bitwise operations in 7.6.11 [expr.bit.and], 7.6.12 [expr.xor], and 7.6.13 [expr.or] uses the undefined term “bitwise” in describing the operations, without specifying whether it is the value or object representation that is in view.
Part of the resolution of this might be to define “bit” (which is otherwise currently undefined in C++) as a value of a given power of 2.
Notes from the June, 2014 meeting:
CWG decided to reformulate the description of the operations themselves to avoid references to bits, splitting off the larger questions of defining “bit” and the like to issue 1943 for further consideration.
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
In the following example,
const T a; T b; false ? a : std::move(b);
the most appropriate result would seem to be that the expression is an lvalue of type const T that refers to either a or b. However, because 7.6.16 [expr.cond] bullet 4.1 requires that the conversion bind directly to an lvalue, while std::move(b) is an xvalue, the result is a const T temporary copy-initialized from std::move(b).
Proposed resolution (November, 2017)
Change 7.6.16 [expr.cond] bullet 4.1 as follows:
Attempts are made to form an implicit conversion sequence from an operand expression E1 of type T1 to a target type related to the type T2 of the operand expression E2 as follows:
If E2 is an lvalue, the target type is “lvalue reference to T2”, subject to the constraint that in the conversion the reference must bind directly (9.5.4 [dcl.init.ref]) to
an lvaluea glvalue....
[Accepted as a DR at the June, 2018 (Rapperswil) meeting.]
The following example is ill-formed:
struct A {}; struct B : A {}; using T = const B; A a = true ? A() : T();
We don't convert from A to T because we can't form an implicit conversion sequence. We don't convert from T to A because T is more cv-qualified (even though we could form an implicit conversion sequence). It would seem reasonable to accept this case; it seems that we should only be using cv-qualifiers as a tie-breaker if the class types are otherwise the same.
Proposed resolution (March, 2018):
Change 7.6.16 [expr.cond] bullet 4.3 as follows:
...Attempts are made to form an implicit conversion sequence from an operand expression E1 of type T1 to a target type related to the type T2 of the operand expression E2 as follows:
...
If E2 is a prvalue or if neither of the conversion sequences above can be formed and at least one of the operands has (possibly cv-qualified) class type:
if T1 and T2 are the same class type (ignoring cv-qualification)
, or one is a base class of the other,and T2 is at least as cv-qualified as T1 , the target type is T2,otherwise, if T2 is a base class of T1, the target type is cv1 T2, where cv1 denotes the cv-qualifiers of T1,
otherwise, the target type is the type that E2 would have after applying the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions.
[Adopted as a DR at the November, 2019 meeting.]
According to 7.6.19 [expr.assign] paragraph 3,
If the left operand is not of class type, the expression is implicitly converted (7.3 [conv]) to the cv-unqualified type of the left operand.
Since the second operand of an assignment operator can now be an initializer-clause, the referent of “expression” is unclear.
See also issue 1542.
Proposed resolution (May, 2019): [SUPERSEDED]
Change 7.6.19 [expr.assign] paragraph 3 as follows:
If the left operand is not of class type and the right operand is an assignment-expression, theexpressionassignment-expression is implicitly converted (7.3 [conv]) to the cv-unqualified type of the left operand.
Proposed resolution (October, 2019):
Change 7.6.19 [expr.assign] paragraph 3 as follows:
The expressionIf the right operand is an expression, it is implicitly converted (7.3 [conv]) to the cv-unqualified type of the left operand.
[Accepted as a DR at the February, 2019 meeting.]
The resolution of issue 2022 does not work, as it is mathematically impossible to guarantee the named return value optimization in all cases. For example:
struct B { B *self = this; }; extern const B b; constexpr B f() { B b; if (&b == &::b) return B(); else return b; } constexpr B b = f(); // is b.self == &b?
Here an implementation is required to perform the optimization if and only if it does not perform the optimization.
The resolution would appear to be to reverse the resolution of issue 2022 and guarantee that named return value optimization is not performed in constant expression evaluation.
Notes from the March, 2018 meeting:
CWG concurred with the suggested direction.
Proposed resolution (November, 2018)
Change 7.7 [expr.const] paragraph 1 as follows:
Expressions that satisfy these requirements, assuming that copy elision (11.9.6 [class.copy.elision]) is not performed, are called constant expressions. [Note: Constant expressions can be evaluated during translation. —end note]
Change 9.2.6 [dcl.constexpr] paragraph 7 as follows:
A call to a constexpr function produces the same result as a call to an equivalent non-constexpr function in all respects except that
a call to a constexpr function can appear in a constant expression (7.7 [expr.const]) and
copy elision is
mandatorynot performed in a constant expression (11.9.6 [class.copy.elision]).
Change 11.9.6 [class.copy.elision] paragraph 1 as follows:
...Copy elision isrequirednot permitted where an expression is evaluated in a context requiring a constant expression (7.7 [expr.const]) and in constant initialization (6.9.3.2 [basic.start.static]). [Note: Copy elision mightnotbe performed if the same expression is evaluated in another context. —end note]
[Accepted as a DR at the February, 2019 meeting.]
According to 7.7 [expr.const] bullets 2.21 and 2.22, the characteristics of three-way and relational comparisons that disqualify them as constant expressions are different:
a three-way comparison (7.6.8 [expr.spaceship]) comparing pointers that do not point to the same complete object or to any subobject thereof;
a relational (7.6.9 [expr.rel]) or equality (7.6.10 [expr.eq]) operator where the result is unspecified;
These are not equivalent, with odd results:
struct A { int a; private: int b; constexpr auto f() { return &a < &b; } // not constant constexpr auto g() { return &a <=> &b; } // returns unspecified value };
Similarly,
struct B { int n; }; struct C : B { int m; } c; constexpr auto x = &c.n < &c.m; // not constant constexpr auto y = &c.n <=> &c.m; // returns unspecified value
The three-way rule seems to be the correct one, but additional wording is needed in 7.6.9 [expr.rel] to specify the relational ordering within a single object: addresses of subobjects of the same complete object should be weakly ordered, and when restricted to subobjects that are not permitted to have the same address, should be totally ordered.
Notes from the October, 2018 teleconference:
The consensus of CWG was to make the 3-way operator cases non-constant, as the relational cases are.
Proposed resolution (November, 2018):
Change 7.7 [expr.const] bullets 2.22 and 2.23 as follows, merging tbe bullets:
An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following expressions:
...
a three-way comparison (7.6.8 [expr.spaceship]),
comparing pointers that do not point to the same complete object or to any subobject thereof;
arelational (7.6.9 [expr.rel]), or equality (7.6.10 [expr.eq]) operator where the result is unspecified;...
[Accepted as a DR at the July, 2019 meeting.]
Consider an example like the following:
struct A { constexpr virtual int f() const { return 1; } }; struct B : A { constexpr virtual int f() const { return 2; } }; constexpr B b{}; constexpr A&& ref = (B)b; static_assert(ref.f() == 2, "");
Since the temporary bound to ref is non-const, it can be re-newed to something else, which would make the invocation ref.f() undefined behavior, which the interpreter is required to catch.
Presumably, ref.f() should not be a constant expression, and 7.7 [expr.const] paragraph 2 should have a bullet for invoking a virtual function of a non-const object unless its lifetime began within the evaluation of the constant expression.
Proposed resolution (March, 2019):
Add the following as a new bullet following 7.7 [expr.const] bullet 4.4:
An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following expressions:
...
an invocation of an instantiated constexpr function or constexpr constructor that fails to satisfy the requirements for a constexpr function or constexpr constructor (9.2.6 [dcl.constexpr]);
an invocation of a virtual function (11.7.3 [class.virtual]) for an object unless
the object is usable in constant expressions or
its lifetime began within the evaluation of e;
an expression that would exceed the implementation-defined limits (see Clause Annex B [implimits]);
...
[Accepted as a DR at the July, 2019 meeting.]
The term “usable in constant expressions” (7.7 [expr.const] paragraph 3) is only defined for variables:
A variable is usable in constant expressions after its initializing declaration is encountered if it is a constexpr variable, or it is of reference type or of const-qualified integral or enumeration type, and its initializer is a constant initializer.
However, uses of the term assume that it applies more widely. For example, 7.7 [expr.const] bullet 4.7.1 mentions “a non-volatile glvalue that refers to an object that is usable in constant expressions” (not all objects are variables), and bullet 4.10.1 speaks of a “data member of reference type” (also not a variable) that is usable in constant expressions.
Proposed resolution, June, 2019:
Change 7.7 [expr.const] paragraph 3 as follows:
A variable is usable in constant expressions after its initializing declaration is encountered if it is a constexpr variable, or it is of reference type or of const-qualified integral or enumeration type, and its initializer is a constant initializer. An object or reference is usable in constant expressions if it is
a variable that is usable in constant expressions, or
a template parameter object (13.2 [temp.param]), or
a string literal object (5.13.5 [lex.string]), or
a non-mutable subobject or reference member of any of the above, or
a complete temporary object of non-volatile const-qualified integral or enumeration type that is initialized with a constant expression.
According to 8.5.2 [stmt.if] paragraph 1,
If the condition (8.5 [stmt.select]) yields true the first substatement is executed. If the else part of the selection statement is present and the condition yields false, the second substatement is executed. If the first substatement is reached via a label, the condition is not evaluated and the second substatement is not executed.
Although 8.9 [stmt.dcl] paragraph 3 forbids bypassing a declaration with initialization, a condition is not syntactically a declaration, and the permission to jump into a then clause and the statement that the condition “is not evaluated” could be read to indicate that a jump across a condition with initialization is permitted. Presumably the prohibition in 8.9 [stmt.dcl] would apply to an init-statement, since it can be a declaration syntactically, but one would expect the same restrictions to apply to both.
Notes from the April, 2018 teleconference:
This issue will be handled editorially (see editorial issue 1949) and will be left in "review" status until CWG verifies that the necessary changes have been made.
[Voted into the WP at the July, 2017 meeting.]
According to the general rule for declarations in 6.4.2 [basic.scope.pdecl] paragraph 1,
The point of declaration for a name is immediately after its complete declarator (9.3 [dcl.decl]) and before its initializer (if any), except as noted below.
However, the rewritten expansion of the range-based for statement in 8.6.5 [stmt.ranged] paragraph 1 contradicts this general rule, so that the index variable is not visible in the range-init:
for (int i : {i}) ; // error: i not in scope
(See also issue 1498 for another question regarding the rewritten form of the range-based for.)
Notes from the October, 2012 meeting:
EWG is discussing issue 900 and the outcome of that discussion should be taken into consideration in addressing this issue.
Notes from the April, 2013 meeting:
The approach favored by CWG for resolving this issue is to change the point of declaration of the variable in the for-range-declaration to be after the ).
Proposed resolution (May, 2017):
Add the following as a new pareagraph following 6.4.2 [basic.scope.pdecl] paragraph 9:
The point of declaration for a function-local predefined variable (9.6 [dcl.fct.def]) is immediately before the function-body of a function definition.
The point of declaration for the variable or the structured bindings declared in the for-range-declaration of a range-based for statement (8.6.5 [stmt.ranged]) is immediately after the for-range-initializer.
The point of declaration for a template parameter...
[Accepted at the February, 2019 meeting as part of paper P1091R3.]
According to 9.1 [dcl.pre] paragraph 8,
A simple-declaration with an identifier-list is called a structured binding declaration (9.7 [dcl.struct.bind]). The decl-specifier-seq shall contain only the type-specifier auto (9.2.9.7 [dcl.spec.auto]) and cv-qualifiers.
This precludes block-scope structured bindings of static storage duration. However, namespace-scope structured bindings are permitted, and since those have static storage duration, it seems inconsistent to prohibit them at block scope. This restriction also prohibits inline structured bindings, which could be useful.
On the other hand, allowing storage class specifiers raises the question of to what extent they apply to the bindings as opposed to the container variable. That's subtle, because in two out of three cases, the bindings are not variables.
A related issue is that the linkage of structured bindings at namespace scope is not specified.
[Accepted at the July, 2019 meeting as part of paper P1766R1 (Mitigating minor modules maladies)].
Consider:
typedef struct { void f() { extern decltype(*this) x; void *p = &x; } } X;
This forms a complete anonymous type, builds a variable of that type, which is declared, but not defined, then the variable is odr-used, then the class is given a typedef name for linkage purposes, which changes the linkage of the class and the variable.
More insidious examples can be constructed where the use of the class's linkage happens while parsing the body of the class, not one of its methods, so we can't entirely fix this by delaying the delayed parts of the class a bit longer.
It might be reasonable to expect an implementation to look ahead past the close brace for a typedef name for linkage when it sees typedef struct {, but the possibility that the typedef keyword might be after the close brace means even that would not be entirely correct.
One approach would be to make this ill-formed by fixating the linkage of the type at the point where it is used in a way that requires linkage, and giving an error when the linkage would change. Another approach would be to limit an anonymous type to the feature subset of C (no this, no member functions, no static members).
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
It is not clear whether a constexpr function can be a vararg function or not. In particular, it is unclear if va_list is a literal type and whether va_start, va_arg, and va_end produce constant expressions.
Proposed resolution (November, 2017)
Add a new bullet to the list in 7.7 [expr.const] paragraph 2, and update the text as follows:
An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following expressions:
...
a relational (7.6.9 [expr.rel]) or equality (7.6.10 [expr.eq]) operator where the result is unspecified;
ora throw-expression (7.6.18 [expr.throw])
.; oran invocation of the va_arg macro (17.14.2 [cstdarg.syn]).
If e satisfies the constraints of a core constant expression, but evaluation of e would evaluate an operation that has undefined behavior as specified in Clause 16 [library] through Clause 32 [thread] of this document, or an invocation of the va_start macro (17.14.2 [cstdarg.syn]), it is unspecified whether e is a core constant expression.
[Accepted as a DR at the February, 2019 meeting.]
Section 9.2.6 [dcl.constexpr] bullet 3.4 specifies a list of constructs that that the body of a constexpr function shall not contain. However, the meaning of the word “contain” is not clear. For example, are things appearing in the body of a nested constexpr lambda “contained” in the body of the constexpr function?
Proposed resolution (November, 2018):
Add the following two paragraphs after Clause 8 [stmt] paragraph 1:
...The optional attribute-specifier-seq appertains to the respective statement.
A substatement of a statement is one of the following:
for a labeled-statement, its contained statement,
for a compound-statement, any statement of its statement-seq,
for a selection-statement, any of its statements (but not its init-statement), or
for an iteration-statement, its contained statement (but not an init-statement).
[Note: The compound-statement of a lambda-expression is not a substatement of the statement (if any) in which the lambda-expression lexically appears. —end note]
A statement S1 encloses a statement S2 if
S2 is a substatement of S1 (Clause 9 [dcl]),
S1 is a selection-statement or iteration-statement and S2 is the init-statement of S1,
S1 is a try-block and S2 is its compound-statement or any of the compound-statements of its handlers, or
S1 encloses a statement S3 and S3 encloses S2.
Delete the following sentence from 8.5 [stmt.select] paragraph 1:
In Clause 8 [stmt], the term substatement refers to the contained statement or statements that appear in the syntax notation.
Change 9.2.6 [dcl.constexpr] bullet 3.3 as follows:
The definition of a constexpr function shall satisfy the following requirements:
...
its function-body shall be = delete, = default, or a compound-statement that does not
containenclose (Clause 8 [stmt]
...
[Accepted as a DR at the February, 2019 meeting.]
Paper P0091R3 has the following example:
template<typename T> struct X { template<typename Iter> X(Iter b, Iter e) { /* ... */ } template<typename Iter> auto foo(Iter b, Iter e) { return X(b, e); // X<U> to avoid breaking change } template<typename Iter> auto bar(Iter b, Iter e) { return X<Iter::value_type>(b, e); // Must specify what we want } };
The intent was presumably to avoid breaking existing code, but the new wording in 9.2.9.3 [dcl.type.simple] paragraph 2 appears to make the expression X(b, e) ill-formed:
A type-specifier of the form typenameopt nested-name-specifieropt template-name is a placeholder for a deduced class type (9.2.9.8 [dcl.type.class.deduct]). The template-name shall name a class template that is not an injected-class-name.
Suggested resolution:
Deleting the wording in question and replacing it with a cross-reference to 13.8.2 [temp.local], which makes it clear that the injected-class-name is a type-name and not a template-name in this context, would seem to address the problem adequately.
Proposed resolution (November, 2018):
Change 9.2.9.3 [dcl.type.simple] paragraph 2 as follows:
The simple-type-specifier auto is a placeholder for a type to be deduced (9.2.9.7 [dcl.spec.auto]). A type-specifier of the form typenameopt nested-name-specifieropt template-name is a placeholder for a deduced class type (9.2.9.8 [dcl.type.class.deduct]). The template-name shall name a class templatethat is not an injected-class-name. [Note: An injected-class-name is never interpreted as a template-name in contexts where a type-specifier may appear (13.8.2 [temp.local]). —end note] The other simple-type-specifiers specify...
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
Use of function return type deduction makes it possible to define functions whose return type is a type without linkage. Although 6.6 [basic.link] paragraph 8 permits such a usage if the function is defined in the same translation unit as it is used, it may be helpful to consider changing the overall rules regarding the use of types with internal or no linkage. As an example, the following example permits access to a local static variable that has not been initialized:
auto f() { static int n = 123; struct X { int &f() { return n; } }; return X(); } int &r = decltype(f())().f();
Notes from the February, 2016 meeting:
CWG agreed that the current rule in 6.6 [basic.link] paragraph 8 is unneeded; the ODR already prohibits use of an entity that is not defined in the current translation unit and cannot be defined in a different translation unit.
Proposed resolution (November, 2017)
Change 6.6 [basic.link] paragraph 8 as follows:
...
A type without linkage shall not be used as the type of a variable or function with external linkage unless
the entity has C language linkage (9.12 [dcl.link]), or
the entity is declared within an unnamed namespace (9.9.2 [namespace.def]), or
the entity is not odr-used (6.3 [basic.def.odr]) or is defined in the same translation unit.[Note: In other words, a type without linkage contains a class or enumeration that cannot be named outside its translation unit.
An entity with external linkage declared using such a type could not correspond to any other entity in another translation unit of the program and thus must be defined in the translation unit if it is odr-used. Also note that classesClasses with linkage may contain members whose types do not have linkage, and that typedef. Typedef names are ignored in the determination of whether a type has linkage. —end note] [Example:template <class T> struct B { void g(T) { } void h(T); friend void i(B, T) { } }; void f() { struct A { int x; }; // no linkage A a = { 1 }; B<A> ba; // declares B<A>::g(A) and B<A>::h(A) ba.g(a); // OK ba.h(a); // error: B<A>::h(A) not defined; A cannot be named intheanother translation unit i(ba, a); // OK }—end example]
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
9.2.9.7 [dcl.spec.auto] paragraph 13 says,
Redeclarations or specializations of a function or function template with a declared return type that uses a placeholder type shall also use that placeholder, not a deduced type.
The inverse should also be true (a specialization cannot use a placeholder type if the template used a non-placeholder), but this is not said explicitly.
Proposed resolution (November, 2017)
Change 9.2.9.7 [dcl.spec.auto] paragraph 11 as follows:
Redeclarations or specializations of a function or function template with a declared return type that uses a placeholder type shall also use that placeholder, not a deduced type. Similarly, redeclarations or specializations of a function or function template with a declared return type that does not use a placeholder type shall not use a placeholder. [Example:
[Adopted at the June, 2018 meeting as part of papeer P0929R2.]
According to 9.3.4.5 [dcl.array] paragraph 1, an array declarator whose element type is an abstract class is ill-formed. However, if the element type is a class template specialization, it may not be known that the class is abstract; because forming an array of an incomplete type is permitted (6.8 [basic.types] paragraphs 5-6), the class template is not required to be instantiated in order to use it as an element type. The expected handling if the class template is later instantiated is unclear; should the compiler issue an error about the earlier array array type at the point at which the class template is instantiated?
This also affects overload resolution:
template<typename> struct Abstract { virtual void f() = 0; typedef int type; }; template<typename T> char &abstract_test(T[1]); // #1 template<typename T> char (&abstract_test(...))[2]; // #2 // Abstract<int>::type n; static_assert(sizeof(abstract_test<Abstract<int>>(nullptr)) == 2, "");
Overload resolution will select #1 and fail the assertion; if the commented line is uncommented, there is implementation variance, but presumably #2 should be selected and satisfy the assertion.
These effects of completing the type are troublesome. Would it be better to allow array types of abstract element type and simply prohibit creation of objects of such arrays?
(See also issue 1646.)
[Accepted as a DR at the June, 2018 (Rapperswil) meeting.]
The resolution of issue 777 attempts to make this valid:
template<typename ...T> void f(int n = 0, T ...t);
However, it fails to do so, since any parameters resulting from the expansion of the pack would be ordinary parameters without default arguments following a parameter with a default argument, which is ill-formed. Thus only an empty pack would be usable with such a declaration, which violates the restriction against such contexts in 13.8 [temp.res] bullet 8.3.
Proposed resolution, February, 2018:
Change 7.6.1.3 [expr.call] paragraph 4 as follows:
When a function is called, each parameter (9.3.4.6 [dcl.fct])
shall beis initialized (9.5 [dcl.init], 11.4.5.3 [class.copy.ctor], 11.4.5 [class.ctor]) with its corresponding argument. If there is no corresponding argument, the default argument for the parameter is used; the program is ill-formed if one is not present. [Example:template<typename ...T> int f(int n = 0, T ...t); int x = f<int>(); // error: no argument for second function parameter
—end example] If the function is a non-static member function, the this parameter of the function (_N4868_.11.4.3.2 [class.this])
shall beis initialized with a pointer to the object of the call, converted as if by an explicit type conversion (7.6.3 [expr.cast]). [Note: There is no access or ambiguity checking...
Change 9.3.4.7 [dcl.fct.default] paragraph 1 as follows:
If an initializer-clause is specified in a parameter-declaration this initializer-clause is used as a default argument. [Note: Default arguments will be used in calls where trailing arguments are missing (7.6.1.3 [expr.call]). —end note]
Change 9.3.4.7 [dcl.fct.default] paragraph 4 as follows:
For non-template functions, default arguments can be added in later declarations of a function in the same scope. Declarations in different scopes have completely distinct sets of default arguments. That is, declarations in inner scopes do not acquire default arguments from declarations in outer scopes, and vice versa. In a given function declaration, each parameter subsequent to a parameter with a default argument shall have a default argument supplied in this or a previous declaration, unless the parameter was expanded from a parameter pack, or shall be a function parameter pack. A default argument shall not be redefined by a later declaration (not even to the same value). [Example:
void g(int = 0, ...); // OK, ellipsis is not a parameter so it can follow // a parameter with a default argument void f(int, int); void f(int, int = 7); void h() { f(3); // OK, calls f(3, 7) void f(int = 1, int); // error: does not use default from surrounding scope } void m() { void f(int, int); // has no defaults f(4); // error: wrong number of arguments void f(int, int = 5); // OK f(4); // OK, calls f(4, 5); void f(int, int = 5); // error: cannot redefine, even to same value } void n() { f(6); // OK, calls f(6, 7) } template<class ... T> struct C { void f(int n = 0, T...); }; C<int> c; // OK; instantiates declaration void C::f(int n = 0, int)
[Adopted as a DR as part of paper P0588R1 at the October, 2018 meeting.]
According to 9.3.4.7 [dcl.fct.default] paragraph 7,
A local variable shall not appear as a potentially-evaluated expression in a default argument.
This prohibits plausible uses of constexpr and static local variables. Presumably this rule should be similar to the one in 11.6 [class.local] paragraph 1, regarding local classes, which applies to odr-use, not potential evaluation, and to variables with automatic storage duration.
[Adopted at the June, 2018 meeting as part of paper P1008R1.]
Should a class with a deleted non-user-provided default constructor be considered an aggregate?
[Accepted at the June, 2018 (Rapperswil) meeting.]
According to 9.5.2 [dcl.init.aggr] bullet 4.2,
Otherwise, the element is copy-initialized from the corresponding initializer-clause or the brace-or-equal-initializer of the corresponding designated-initializer-clause.
This sounds as if the initialization performed by a designated initializer is always copy-initialization. However, it was intended that the kind of initialization match the form of the initializer, i.e., a designated-initializer-clause of the form
{ .x{3} }
was intended to perform direct-initialization.
Proposed resolution, April, 2018:
Change 9.5.2 [dcl.init.aggr] bullet 4.2 as follows:
Otherwise, the element is copy-initialized from the corresponding initializer-clause or is initialized with the brace-or-equal-initializer of the corresponding designated-initializer-clause.
[Accepted as a DR at the February, 2019 meeting.]
Consider the following example:
struct A {} a; struct B { explicit B(const A&); }; struct D { D(); }; struct C { explicit operator D(); } c; B b1(a); // #1, ok const B &b2{a}; // #2. ok const B &b3(a); // #3, error D d1(c); // ok const D &d2{c}; // ok const D &d3(c); // #6, ok
The disparity between #3 and #6 is suprising, as is the difference from #1 and #2. The reason for this difference is in 9.5.5 [dcl.init.list] bullet 3.10:
Otherwise, if T is a reference type, a prvalue of the type referenced by T is generated. The prvalue initializes its result object by copy-list-initialization or direct-list-initialization, depending on the kind of initialization for the reference. The prvalue is then used to direct-initialize the reference.
(reflecting the resolution of issue 1494).
Notes from the March, 2018 meeting:
CWG felt that initialization of the temporary should always be copy initialization, regardless of whether the top-level initialization is copy or direct initialization. This would make #2, #3, #5, and #6 all ill-formed.
Proposed resolution (November, 2018):
Change 9.5.5 [dcl.init.list] bullet 3.10 as follows:
Otherwise, if T is a reference type, a prvalue of the type referenced by T is generated. The prvalue initializes its result object by copy-list-initializationor direct-list-initialization, depending on the kind of initialization for the reference. The prvalue is then used to direct-initialize the reference. [Note: As usual, the binding will fail and the program is ill-formed if the reference type is an lvalue reference to a non-const type. —end note]
Add the following to the example in 9.5.5 [dcl.init.list] bullet 3.10:
struct A { } a; struct B { explicit B(const A&); }; const B &b2(a); // error: cannot copy-initialize B temporary from A
Change 12.2.2.7 [over.match.ref] bullet 1.1 as follows:
...For direct-initialization, those explicit conversion functions that are not hidden within S and yield type “lvalue reference to cv2 T2” (when initializing an lvalue reference or an rvalue reference to function)or “cv2 T2”or “rvalue reference to cv2 T2” (when initializing an rvalue reference or an lvalue reference to function),respectively,where T2 is the same type as T or can be converted to type T with a qualification conversion (7.3.6 [conv.qual]), are also candidate functions.
[Accepted as a DR at the February, 2019 meeting.]
In an example like
int *ptr; const int *const &f() { return ptr; }
What is returned is a reference to a temporary instead of binding directly to ptr. The rules for reference-related types should say that T is reference-related to U if U* can be converted to T* by a qualification conversion.
Notes from the April, 2018 teleconference:
CWG agreed with the proposed direction.
Proposed resolution (November, 2018):
Change 9.5.4 [dcl.init.ref] paragraph 4 as follows:
Given types “cv1 T1” and “cv2 T2”, “cv1 T1” is reference-related to “cv2 T2” if T1 is
the same type assimilar (7.3.6 [conv.qual]) to T2, or T1 is a base class of T2. “cv1 T1” is reference-compatible with “cv2 T2” if
T1 is reference-related to T2, or
T2 is “noexcept function” and T1 is “function”, where the function types are otherwise the same,
and cv1 is the same cv-qualification as, or greater cv-qualification than, cv2a prvalue of type “pointer to cv2 T2” can be converted to the type “pointer to cv1 T1” via a standard conversion sequence (7.3 [conv]). In all cases where thereference-related orreference-compatible relationship of two types is used to establish the validity of a reference binding, and T1 is a base class of T2and the standard conversion sequence would be ill-formed, a program that necessitates such a binding is ill-formedif T1 is an inaccessible (11.8 [class.access]) or ambiguous (6.5.2 [class.member.lookup]) base class of T2.
Additional notes (September, 2023)
Issue 2018 is a duplicate of this issue.
[Adopted at the November, 2017 meeting as part of paper P0641R2.]
The current requirements of 9.6.2 [dcl.fct.def.default] paragraph 1 state that a defaulted copy constructor or copy assignment operator can have a reference to const parameter only if all its subobjects have corresponding functions with a reference to const parameter, even if that function is never called. This prevents some useful template classes.
(See also library issue 2068.)
See also issue 1426.
Rationale (August, 2011):
Possible resolutions to this issue need to be considered in a wider context, so it is more appropriate for the Evolution Working Group.
[Adopted at the November, 2017 meeting as part of paper P0641R2.]
The current wording of 9.6.2 [dcl.fct.def.default] paragraph 1 allows copy constructors and copy assignment operators to have a reference to non-const parameter, even if the implicitly-declared function would have had a reference to const parameter. This is safe because the sub-object copy functions that take a reference to const parameter can be invoked with a non-const lvalue.
It would also be possible to allow the inverse situation — permitting the defaulted function to be defined with a reference to const parameter, even though the sub-object functions have a reference to non-const parameter — by defining the defaulted function as deleted.
See also issue 1331.
Rationale (February, 2012):
This is a request to extend the language and is thus more appropriately considered by EWG.
[Accepted at the February, 2019 meeting as part of paper P1286R2.]
The current rules requiring a defaulted member function to have an exception-specification compatible with that of the implicitly-declared function are overly constraining. It should be possible, for example, to specify that a defaulted move constructor will be non-throwing, based on knowledge available to the programmer, even if the implicitly-declared constructor would be throwing.
Rationale (June, 2014):
This suggested extension should be investigated by EWG before any action is taken.
[Accepted as a DR at the June, 2018 (Rapperswil) meeting.]
What is the point of declaration of a name introduced by a structured binding? If it's the point at which it appears, we're missing a rule to make it ill-formed to reference the name before its type is deduced (similar to what we have for 'auto'). [Suggestion: point of declaration is after the identifier-list, program is ill-formed if the name is mentioned before the end of the initializer.]
Are structured bindings permitted at namespace scope? There doesn't seem to be a rule against that. If so, what's their linkage? Is it intentional that static , extern are disallowed? Should we only allow automatic storage duration? [Suggestion: only permit automatic storage duration, per the design paper.]
(If the answer to 2 is yes...) is the declaration in a variable template permitted to use structured bindings? If so, how do you name the result? (The bindings themselves aren't introduced as template-names by the current wording.) If not, we're missing a restriction on that. [Suggestion: no to question 2.]
Did we intend to guarantee that the object whose members are denoted by bindings is kept "together":
auto f() -> int (&)[2]; auto [x, y] = f(); assert(&x + 1 == &y); // ok? struct S { int a, b, c; }; // standard-layout auto [a,b,c] = S(); assert(&((S*)&a)->b == &b); // ok?
(If yes, this means we can't synthesize independent variables for each element of an array or struct that's bound in this way, and it's harder to remove dead components of a destructured object.) Obviously we may need to keep the object together if it has a non-trivial destructor. [Suggestion: do not allow reaching the complete object from a binding.]
Should the copy->move promotion be permitted for a return of a structured binding?
struct A { string s; int n; };
string f() {
auto [s,n] = A();
return s; // copy required here?
}
[Suggestion: allow promotion to move -- as if the binding were a real local variable -- if the implicit underlying variable is not a reference. Maybe also allow NRVO, depending on answer to question 8.]
Notes from the April, 2017 teleconference:
Items 1 and 3 are core issues; item 4 is NAD - the bindings are kept together, which is implied by the existing rules about copying the object into a hidden temporary. The remaining items are questions for EWG and new items in "extension" status will be opened for them.
Proposed resolution, February, 2018:
Change 6.4.2 [basic.scope.pdecl] paragraph 9 as follows and add the following new paragraph thereafter:
The point of declaration for a function-local predefined variable (
9.6 [dcl.fct.def]9.6.1 [dcl.fct.def.general]) is immediately before the function-body of a function definition.The point of declaration of a structured binding (9.7 [dcl.struct.bind]) is immediately after the identifier-list of the structured binding declaration.
Add the following as a new paragraph following 9.7 [dcl.struct.bind] paragraph 1:
...taken from the corresponding structured binding declaration. The type of the id-expression e is called E. [Note: E is never a reference type (Clause 7 [expr]). —end note]
If the initializer refers to one of the names introduced by the structured binding declaration, the program is ill-formed.
(Note: In response to item 3, the current wording of Clause 13 [temp] paragraph 1 does not allow templated structured binding declarations, and no change is being proposed.)
[Accepted as a DR at the November, 2017 meeting.]
According to the current rules for structured binding declarations, the user-defined case declares the bindings as variables of reference type. This presumably makes an example like the following valid:
auto [a] = std::tuple<int>(0);
extern int &&a; // ok, redeclaration, could even be in a different TU
This seems unreasonable, especially in light of the fact that it only works for the user-defined case and not the built-in case (where the bindings are not modeled as references).
Proposed resolution (August, 2017):
Change 9.7 [dcl.struct.bind] paragraph 3 as follows:
Otherwise, if the qualified-id std::tuple_size<E> names a complete type, the expression std::tuple_size<E>::value shall be a well-formed integral constant expression and the number of elements in the identifier-list shall be equal to the value of that expression. The unqualified-id get is looked up in the scope of E by class member access lookup (_N4868_.6.5.6 [basic.lookup.classref]), and if that finds at least one declaration, the initializer is e.get<i>(). Otherwise, the initializer is get<i>(e) where get is looked up in the associated namespaces (6.5.4 [basic.lookup.argdep]). In either case, get<i> is interpreted as a template-id. [Note: Ordinary unqualified lookup (6.5.3 [basic.lookup.unqual]) is not performed. —end note] In either case, e is an lvalue if the type of the entity e is an lvalue reference and an xvalue otherwise. Given the type Ti designated by std::tuple_element<i, E>::type ,each vi is a variablevariables are introduced with unique names ri of type “reference to Ti ” initialized with the initializer (9.5.4 [dcl.init.ref]), where the reference is an lvalue reference if the initializer is an lvalue and an rvalue reference otherwise. Each vi is the name of an lvalue of type Ti that refers to the object bound to ri; the referenced type is Ti.
[Accepted as a DR at the June, 2018 (Rapperswil) meeting.]
According to 9.7 [dcl.struct.bind] paragraph 3,
Otherwise, if the qualified-id std::tuple_size<E> names a complete type, the expression std::tuple_size<E>::value shall be a well-formed integral constant expression and the number of elements in the identifier-list shall be equal to the value of that expression. The unqualified-id get is looked up in the scope of E by class member access lookup (_N4868_.6.5.6 [basic.lookup.classref]), and if that finds at least one declaration, the initializer is e.get<i>(). Otherwise, the initializer is get<i>(e), where get is looked up in the associated namespaces (6.5.4 [basic.lookup.argdep]). In either case, get<i> is interpreted as a template-id.
It is not clear what i is in this description, and in particular, its type is not specified.
Proposed resolution, March, 2018:
Change 9.7 [dcl.struct.bind] paragraph 3 as follows:
Otherwise, if the qualified-id std::tuple_size<E> names a complete type, the expression std::tuple_size<E>::value shall be a well-formed integral constant expression and the number of elements in the identifier-list shall be equal to the value of that expression. Let i be an index prvalue of type std::size_t corresponding to vi. The unqualified-id get is looked up in the scope of E by class member access lookup (_N4868_.6.5.6 [basic.lookup.classref]), and if that finds at least one declaration, the initializer is e.get<i>(). Otherwise, the initializer is get<i>(e), where get is looked up in the associated namespaces (6.5.4 [basic.lookup.argdep]). In either case, get<i> is interpreted as a template-id. [Note: Ordinary unqualified lookup...
[Accepted as a DR at the February, 2019 meeting.]
According to 9.7 [dcl.struct.bind] paragraph 4,
Otherwise, if the qualified-id std::tuple_size<E> names a complete type, the expression std::tuple_size<E>::value shall be a well-formed integral constant expression and the number of elements in the identifier-list shall be equal to the value of that expression.
However, the common idiom in the library is that SFINAE tests the presence or absence of a value member rather than the completeness of the class; see 22.4.7 [tuple.helper] paragraph 4. The core language requirement should be changed to match the common library practice.
Proposed resolution (December, 2018):
Change 9.6 [dcl.fct.def] paragraph 4 as follows:
Otherwise, if the qualified-id std::tuple_size<E> names a complete class type with a member value, the expression std::tuple_size<E>::value shall be a well-formed integral constant expression and the number of elements in the identifier-list shall be equal to the value of that expression...
[Accepted as a DR at the November, 2018 (San Diego) meeting.]
According to 9.8.1 [dcl.enum] paragraph 7,
For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. Otherwise, for an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values in the range bmin to bmax, defined as follows: Let K be 1 for a two's complement representation and 0 for a one's complement or sign-magnitude representation. bmax is the smallest value greater than or equal to max(|emin|-K,|emax|) and equal to 2M-1, where M is a non-negative integer. bmin is zero if emin is non-negative and -(bmax+K) otherwise. The size of the smallest bit-field large enough to hold all the values of the enumeration type is max(M,1) if bmin is zero and M+1 otherwise.
The result of these calculations is that the number of bits required for
enum { N = -1, Z = 0 }
is 1, but the number required for
enum { N = -1 }
is 2. This is surprising. This could be fixed by changing |emax| to emax.
Proposed resolution (June, 2018):
Change 9.8.1 [dcl.enum] paragraph 8 as follows:
bmax is the smallest value greater than or equal to max(|emin| - K, |emax|), emax) and equal to 2M - 1, where M is a non-negative integer.
[Accepted at the July, 2019 meeting as part of paper P1099R5 (Using enum).]
A using-declaration cannot name a scoped enumerator, according to 9.10 [namespace.udecl] paragraph 7. This is presumably because a scoped enumerator belongs to an enumeration scope and thus logically cannot belong to the non-enumeration scope in which the using-declaration appears. It seems inconsistent, however, to permit using-declarations to name unscoped enumerators but not scoped enumerators.
Also, 9.10 [namespace.udecl] paragraph 3 says,
In a using-declaration used as a member-declaration, the nested-name-specifier shall name a base class of the class being defined.
The consequence of this is that
enum E { e0 }; void f() { using E::e0; }
is well-formed, but
struct B { enum E { e0 }; }; struct D : B { using B::E::e0; };
is not. Again, this seems inconsistent. Should these rules be relaxed?
[Accepted as a DR at the July, 2019 meeting.]
According to 9.13.5 [dcl.attr.fallthrough] paragraph 1,
A fallthrough statement may only appear within an enclosing switch statement (8.5.3 [stmt.switch]). The next statement that would be executed after a fallthrough statement shall be a labeled statement whose label is a case label or default label for the same switch statement.
The meaning of “next statement that would be executed” is unclear with respect to the controlled substatement of an iteration statement. There is implementation divergence on an example like:
void f(int n) {
switch (n) {
case 0:
while (true)
[[fallthrough]]; // Well-formed?
case 1:
break;
}
}
Proposed resolution, April, 2019:
Change 9.13.5 [dcl.attr.fallthrough] paragraph 1 as follows:
The attribute-token fallthrough may be applied to a null statement (8.3 [stmt.expr]); such a statement is a fallthrough statement. The attribute-token fallthrough shall appear at most once in each attribute-list and no attribute-argument-clause shall be present. A fallthrough statement may only appear within an enclosing switch statement (8.5.3 [stmt.switch]). The next statement that would be executed after a fallthrough statement shall be a labeled statement whose label is a case label or default label for the same switch statement and, if the fallthrough statement is contained in an iteration statement, the next statement shall be part of the same execution of the substatement of the innermost enclosing iteration statement. The program is ill-formed if there is no such statement.
Change the example in 9.13.5 [dcl.attr.fallthrough] paragraph 3 as follows:
[Example:
void f(int n) { void g(), h(), i(); switch (n) { case 1: case 2: g(); [[fallthrough]]; case 3: // warning on fallthrough discouraged do { [[fallthrough]]; // error: next statement is not part of the same substatement execution } while (false); case 6: do { [[fallthrough]]; // error: next statement is not part of the same substatement execution } while (n--); case 7: while (false) { [[fallthrough]]; // error: next statement is not part of the same substatement execution } case 5: h(); case 4: // implementation may warn on fallthrough i(); [[fallthrough]]; // ill-formed } }—end example]
[Accepted as a DR at the February, 2019 meeting.]
According to9.13.8 [dcl.attr.unused] paragraph 2,
The attribute may be applied to the declaration of a class, a typedef-name, a variable, a non-static data member, a function, an enumeration, or an enumerator.
This does not include structured bindings, although there seems to be no good reason to prohibit uses like
[[maybe_unused]] auto [a, b] = std::make_pair(42, 0.23);
Notes from the October, 2018 teleconference:
CWG agreed that such an annotation should be permitted and apply to the underlying variable; i.e., a compiler might warn in the absence of such an attribute if none of the structured bindings were used, and the presence of the attribute would silence such warnings.
Proposed resolution (November, 2018):
Change 9.13.8 [dcl.attr.unused] paragraphs 2 and 3 as follows:
The attribute may be applied to the declaration of a class, a typedef-name, a variable (including a structured binding declaration), a non-static data member, a function, an enumeration, or an enumerator.
[Note:For an entity marked maybe_unused, implementations should not emit a warning that the entityisor its structured bindings (if any) are used or unused., or that the entity is used despite the presence of the attribute. —end note]For a structured binding declaration not marked maybe_unused, implementations should not emit such a warning unless all of its structured bindings are unused.
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
There does not seem to be a rule that prohibits an example like:
template<typename T> struct X; struct X<int> { };
Proposed resolution (November, 2017)
Change Clause 11 [class] paragraph 1 as follows:
...A class declaration where the class-name in the class-head-name is a simple-template-id shall be an explicit specialization (13.9.4 [temp.expl.spec]) or a partial specialization (13.7.6 [temp.spec.partial]). A class-specifier whose class-head omits the class-head-name defines an unnamed class. [Note: An unnamed class thus can't be final. —end note]
[Accepted as a DR at the June, 2018 (Rapperswil) meeting.]
There is currently no requirement that a simple-template-id used as a class-name (Clause 11 [class] paragraph 1) must have template arguments for every template parameter that does not have a default template argument.
Proposed resolution (March, 2018):
Change _N4868_.6.4.1 [basic.scope.declarative] 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 isvalidvalid, that is, in which that name may be used as an unqualified name to refer to the same entity. In general...
Add the following as a new paragraph after 13.3 [temp.names] paragraph 7:
A template-id that names an alias template specialization is a type-name.
A template-id is valid if
there are at most as many arguments as there are parameters or a parameter is a template parameter pack (13.7.4 [temp.variadic]),
there is an argument for each non-deducible non-pack parameter that does not have a default template-argument,
each template-argument matches the corresponding template-parameter (13.4 [temp.arg]),
substitution of each template argument into the following template parameters (if any) succeeds, and
if the template-id is non-dependent, the associated constraints are satisfied as specified in the next paragraph.
A simple-template-id shall be valid unless it names a function template specialization (13.10.3 [temp.deduct]). [Example:
template<class T, T::type n = 0> class X; struct S { using type = int; }; using T1 = X<S, int, int>; // error: too many arguments using T2 = X<>; // error: no default argument for first template parameter using T3 = X<1>; // error: value 1 does not match type-parameter using T4 = X<int>; // error: substitution failure for second template parameter using T5 = X<S>; // OK—end example]
Change 13.10.3 [temp.deduct] paragraph 2 as follows, converting from bullets to running text:
When an explicit template argument list is specified, if the
template arguments are not compatible with the template parameter list or do not result in a valid function type as described belowgiven template-id is invalid (13.3 [temp.names]), type deduction fails.Specifically, the following steps are performed when evaluating an explicitly specified template argument list with respect to a given function template:
If the specified template arguments do not match the template parameters in kind (i.e., type, non-type, template), or if there are more arguments than there are parameters and no parameter is a template parameter pack, or if there is not an argument for each non-pack parameter, type deduction fails.
If any non-type argument does not match the type of the corresponding non-type template parameter, and is not convertible to the type of the corresponding non-type parameter as specified in 13.4.3 [temp.arg.nontype], type deduction fails.The specified template argument values are substituted for the corresponding template parameters as specified below.
[Resolved editorially by editorial issue 2353.]
According to 11.4 [class.mem] paragraph 1,
A member shall not be declared twice in the member-specification, except that a nested class or member class template can be declared and then later defined, and except that an enumeration can be introduced with an opaque-enum-declaration and later redeclared with an enum-specifier.
However, the grammar for member-declaration does not have a production that allows an opaque-enum-declaration.
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
The restriction in 11.4 [class.mem] paragraph 8 that a virt-specifier may appear only in the declaration of a virtual function is insufficient to rule out examples like the following:
struct A { virtual void f(); }; struct B { friend void A::f() final; }; template<typename T> struct C { virtual void f() {} }; template void C<int>::f() final; template<> void C<char>::f() final;
One possibility might be to require that a virt-specifier appear only on the first declaration of a function.
Proposed resolution (November, 2017)
Change 11.4 [class.mem] paragraph 13 as follows:
A virt-specifier-seq shall contain at most one of each virt-specifier. A virt-specifier-seq shall appear only in the first declaration of a virtual member function (11.7.3 [class.virtual]).
[Accepted as a DR at the June, 2018 (Rapperswil) meeting.]
According to 11.4 [class.mem] paragraph 25,
If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member. Otherwise, its address is the same as the address of its first base class subobject (if any). [Note: There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning, as necessary to achieve appropriate alignment. —end note] [Note: The object and its first subobject are pointer-interconvertible (6.8.4 [basic.compound], 7.6.1.9 [expr.static.cast]). —end note]
This wording does not consider the case when the first non-static data member is a bit-field, which cannot have its address taken.
Proposed resolution, February, 2018: [SUPERSEDED]
Change 11.4 [class.mem] paragraph 25 as follows:
If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member if that member is not a bit-field.Otherwise, itsIts address is also the same as the address of each of itsfirstbase class subobjects(if any). [Note: There might therefore be unnamed padding within a standard-layout struct object inserted by an implementation, but not at its beginning, as necessary to achieve appropriate alignment. —end note] [Note: The object and its first subobject are pointer-interconvertible (6.8.4 [basic.compound], 7.6.1.9 [expr.static.cast]). —end note]
Notes from the March, 2018 meeting:
It was pointed out that the definition of pointer interconvertibility in 6.7.6.3 [basic.stc.thread] paragraph 4 refers to “the first base class subobject” of the object and must also be updated to reflect the above proposed resolution.
Proposed resolution (March, 2018):
Change 6.8.4 [basic.compound] bullet 4.3 as follows:
Two objects a and b are pointer-interconvertible if:
...
one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members,
the firstany base class subobject of that object (11.4 [class.mem]), or...
Change 11.4 [class.mem] paragraph 25 as follows:
If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member if that member is not a bit-field.Otherwise, itsIts address is also the same as the address of each of itsfirstbase class subobjects(if any). [Note: There might therefore be unnamed padding within a standard-layout struct object inserted by an implementation, but not at its beginning, as necessary to achieve appropriate alignment. —end note] [Note: The object and its first subobject are pointer-interconvertible (6.8.4 [basic.compound], 7.6.1.9 [expr.static.cast]). —end note]
[Accepted as a DR at the July, 2019 meeting.]
According to 11.4 [class.mem] paragraph 19,
Non-static data members of a (non-union) class with the same access control (11.8 [class.access]) are allocated so that later members have higher addresses within a class object.
With the advent of the [[no_unique_address]] attribute, “higher addresses” is no longer strictly accurate. According to the FAQ in P0840R2, next-to-last question:
Q: Suppose I have members a, b, c (in that order, with the same access). Today we guarantee that &a < &b < &c. What happens if b has the attribute?
Two cases:
If the type of b is empty, then there is no guarantee about the address of b (other than that it is somewhere within the containing object).
If the type of b is nonempty, then we still guarantee that &a < &b < &c.
Presumably the wording in 11.4 [class.mem] paragraph 19 needs to be changed to reflect that intent.
Proposed resolution, March, 2019:
Change 11.4 [class.mem] paragraph 19 as follows:
[Note: Non-static data members of a (non-union) class with the same access control (11.8 [class.access]) and non-zero size (6.7.2 [intro.object]) are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified(11.8 [class.access]). Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions (11.7.3 [class.virtual]) and virtual base classes (11.7.2 [class.mi]). —end note]
[Accepted at the March, 2018 (Jacksonville) meeting.]
Bullet 1.2 of 11.4.5 [class.ctor], describing declarator forms that are considered to declare a constructor, says:
...and the id-expression has one of the following forms:
...
in a member-declaration that belongs to the member-specification of a class template but is not a friend declaration, the id-expression is a class-name that names the current instantiation (13.8.3.2 [temp.dep.type]) of the immediately-enclosing class template; or
...
The term class-name includes simple-template-id. It is not clear that allowing a constructor declaration of the form
template<class T> struct X {
X<T>(T); // constructor
};
is useful or helpful.
Proposed resolution (November, 2017)
Change 11.4.5 [class.ctor] paragraph 1 as follows:
...and the id-expression has one of the following forms:
in a member-declaration that belongs to the member-specification of a class or class template but is not a friend declaration (11.8.4 [class.friend]), the id-expression is the injected-class-name ( Clause 11 [class]) of the immediately-enclosing
class;entity or
in a member-declaration that belongs to the member-specification of a class template but is not a friend declaration, the id-expression is a class-name that names the current instantiation (13.8.3.2 [temp.dep.type]) of the immediately-enclosing class template; orin a declaration at namespace scope or in a friend declaration, the id-expression is a qualified-id that names a constructor (6.5.5.2 [class.qual]).
Change 11.4.7 [class.dtor] paragraph 1 as follows:
...and the id-expression has one of the following forms:
in a member-declaration that belongs to the member-specification of a class or class template but is not a friend declaration (11.8.4 [class.friend]), the id-expression is ~class-name and the class-name is the injected-class-name (Clause 11 [class]) of the immediately-enclosing
class;entity or
in a member-declaration that belongs to the member-specification of a class template but is not a friend declaration, the id-expression is ~class-name and the class-name names the current instantiation (13.8.3.2 [temp.dep.type]) of the immediately-enclosing class template; orin a declaration at namespace scope or in a friend declaration, the id-expression is nested-name-specifier ~class-name and the class-name names the same class as the nested-name-specifier.
Add the following as a new paragraph in C.3 [diff.cpp17]:
C.5.x Clause 15: Special member functions [diff.cpp17.special]
Affected subclauses: 11.4.5 [class.ctor], 11.4.7 [class.dtor]
Change: A simple-template-id is no longer valid as the declarator-id of a constructor or destructor.
Rationale: Remove potentially error-prone option for redundancy.
Effect on original feature: Valid C++ 2017 code may fail to compile.template<class T> struct A { A<T>(); // error: simple-template-id not allowed for constructor A(int); // OK, injected-class-name used ~A<T>(); // error: simple-template-id not allowed for destructor };
(Note that this resolution is a change for C++20, NOT a defect report against C++17 and earlier versions.)
[Voted into the WP at the July, 2017 meeting.]
In an example like
struct A { A(int = 0); }; struct B : A { using A::A; }; B b0(0); // #1 B b; // #2
Is #2 valid (presumably calling the constructor inherited from A, or ill-formed due to ambiguity with 's implicit default constructor?
Proposed resolution (May, 2017):
Change 9.10 [namespace.udecl] paragraph 16 as follows:
For the purpose of forming a set of candidates during overload resolution, the functions that are introduced by a using-declaration into a derived class are treated as though they were members of the derived class. In particular, the implicit this parameter shall be treated as if it were a pointer to the derived class rather than to the base class. This has no effect on the type of the function, and in all other respects the function remains a member of the base class. Likewise, constructors that are introduced by a using-declaration are treated as though they were constructors of the derived class when looking up the constructors of the derived class (6.5.5.2 [class.qual]) or forming a set of overload candidates (12.2.2.4 [over.match.ctor], 12.2.2.5 [over.match.copy], 12.2.2.8 [over.match.list]). If such a constructor is selected to perform the initialization of an object of class type, all subobjects other than the base class from which the constructor originated are implicitly initialized (11.9.4 [class.inhctor.init]). [Note: A member of a derived class is sometimes preferred to a member of a base class if they would otherwise be ambiguous (12.2.4 [over.match.best]). —end note]
Insert the following as a new bullet following 12.2.4 [over.match.best] bullet 1.7:
...
F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in 13.7.7.3 [temp.func.order], or, if not that,
F1 is a constructor for a class D, F2 is a constructor for a base class B of D, and for all arguments the corresponding parameters of F1 and F2 have the same type. [Example:
struct A {
A(int = 0);
};
struct B: A {
using A::A;
B();
};
int main() {
B b; // OK, B::B()
}
—end example], or, if not that,
F1 is generated from a deduction-guide (12.2.2.9 [over.match.class.deduct])...
This resolution also resolves issue 2277.
[Accepted as a DR at the February, 2019 meeting.]
After the changes for comment RU 1 in P0490R0, a defaulted default constructor is acceptable for default initialization of a const object under certain circumstances; for example,
struct A {}; const A a;
is well-formed. However, default-initialization of such a class member still requires a user-provided constructor:
struct B { const A a; };
B b; //error
Proposed resolution (November, 2018):
Change 11.4.5.2 [class.default.ctor] bullet 2.4 as follows:
A defaulted default constructor for class X is defined as deleted if:
...
any non-variant non-static data member of const-qualified type (or array thereof) with no brace-or-equal-initializer
does not have a user-provided default constructoris not const-default-constructible (9.5 [dcl.init]) ,...
[Accepted as a DR at the November, 2017 meeting.]
According to 11.4.5.3 [class.copy.ctor] bullet 10.1,
A defaulted copy/move constructor for a class X is defined as deleted (9.6.3 [dcl.fct.def.delete]) if X has:
a variant member with a non-trivial corresponding constructor and X is a union-like class,
However, it is not clear from this specification how to handle an example like:
struct A { A(); A(const A&); }; union B { A a; };
since there is no corresponding special member in A.
Proposed resolution (August, 2017):
Change 11.4.5.3 [class.copy.ctor] paragraph 10 as follows:
An implicitly-declared copy/move constructor is an inline public member of its class. A defaulted copy/move constructor for a class X is defined as deleted (9.6.3 [dcl.fct.def.delete]) if X has:
a variant member with a non-trivial corresponding constructor and X is a union-like class,a potentially constructed subobject type M (or array thereof) that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to find M's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,
a variant member whose corresponding constructor as selected by overload resolution is non-trivial,
any potentially constructed subobject of a type with a destructor that is deleted or inaccessible from the defaulted constructor, or,
for the copy constructor, a non-static data member of rvalue reference type.
[Adopted at the November, 2018 meeting as part of paper P1236R1.]
CWG decided at the 2014-06 (Rapperswil) meeting to address only a limited subset of the questions raised by issues 1857 and 1861. This issue is a placeholder for the remaining questions, such as defining a “bit” in terms of a value of 2n, specifying whether a bit-field has a sign bit, etc.
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
According to 11.4.10 [class.bit] paragraph 2, unnamed bit-fields are not members, but there does not appear to be a prohibition against their being declared volatile. Is this intended?
Proposed resolution (November, 2017)
Change 11.4.10 [class.bit] paragraph 2 as follows:
A declaration for a bit-field that omits the identifier declares an unnamed bit-field. Unnamed bit-fields are not members and cannot be initialized. An unnamed bit-field shall not be declared with a cv-qualified type. [Note: An unnamed bit-field is useful for padding...
[Voted into the WP at the July, 2017 meeting.]
The current wording of the Standard does not clearly state that zero-initialization applies to unnamed bit-fields.
Notes from the December, 2016 teleconference:
The consensus was that unnamed bit-fields do constitute padding; more generally, padding should be normatively defined, along the lines suggested in 11.4.10 [class.bit] paragraphs 1-2.
Proposed resolution (March, 2017):
Change 6.8 [basic.types] paragraph 4 as follows:
The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T). The value representation of an object is the set of bits that hold the value of type T. Bits in the object representation that are not part of the value representation are padding bits. For trivially copyable 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.44
Change 9.5 [dcl.init] paragraph 6 as follows:
To zero-initialize an object or reference of type T means:
if T is a scalar type (6.8 [basic.types]), the object is initialized to the value obtained by converting the integer literal 0 (zero) to T;103
if T is a (possibly cv-qualified) non-union class type, its padding bits are initialized to zero bits and each non-static data member and each base-class subobject is zero-initialized
and padding is initialized to zero bits;if T is a (possibly cv-qualified) union type, its padding bits are initialized to zero bits and the object's first non-static named data member is zero-initialized
and padding is initialized to zero bits;...
Change 11.4.10 [class.bit] paragraph 1 as follows:
...The constant-expression shall be an integral constant expression with a value greater than or equal to zero. The value of the integral constant expression may be larger than the number of bits in the object representation (6.8 [basic.types]) of the bit-field's type; in such cases the extra bits areused aspadding bits (6.8 [basic.types])and do not participate in the value representation (6.8 [basic.types]) of the bit-field. Allocation of bit-fields...
Change 6.8.2 [basic.fundamental] paragraph 1 as follows:
...For narrow character types, all bits of the object representation participate in the value representation. [Note: A bit-field of narrow character type whose length is larger than the number of bits in the object representation of that type has padding bits; see11.4.10 [class.bit]6.8 [basic.types]. —end note] For unsigned narrow character types...
The example in 11.5 [class.union] paragraph 8 reads,
union U {
int x = 0;
union { };
union {
int z;
int y = 1; // error: initialization for second variant member of U
};
};
The empty anonymous union appears to be a violation of the requirement in 11.4 [class.mem] paragraph 1,
Except when used to declare friends (11.8.4 [class.friend]), to declare an unnamed bit-field (11.4.10 [class.bit]), or to introduce the name of a member of a base class into a derived class (9.10 [namespace.udecl]), or when the declaration is an empty-declaration, member-declarations declare members of the class, and each such member-declaration shall declare at least one member name of the class.
Additional note, February, 2021:
The issue was resolved editorially.
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
There is implementation divergence on the validity of the following:
class X { ~X(); }; struct Y { X x = {}; };
Should X's destructor be potentially invoked by this attempt to initialize an X object? Or,
auto *y = new Y {};
No constructor for Y is used, because this is aggregate initialization, and a destructor for X is not strictly necessary as there is no later initialization that might throw, but in the corresponding default constructor case we do require that the destructor be valid.
Perhaps the most consistent answer is that the default member initializer should not potentially invoke the destructor unless it's used (for symmetry with default arguments), but that aggregate initialization should potentially invoke the destructors of all subobjects (including the final one - exceptions could theoretically be thrown between the completion of the construction of the final aggregate element and the notional completion of the construction of the aggregate itself.
Proposed resolution (November, 2017)
Add the following as a new paragraph following 9.5.2 [dcl.init.aggr] paragraph 7:
An aggregate that is a class can also be initialized with a single expression not enclosed in braces, as described in 9.5 [dcl.init].
The destructor for each element of class type is potentially invoked (11.4.7 [class.dtor]) from the context where the aggregate initialization occurs. [Note: This provision ensures that destructors can be called for fully-constructed subobjects in case an exception is thrown (14.3 [except.ctor]). —end note]
Change 11.4.7 [class.dtor] paragraph 12 as follows:
...A destructor is potentially invoked if it is invoked or as specified in 7.6.2.8 [expr.new], 9.5.2 [dcl.init.aggr], 11.9.3 [class.base.init], and 14.2 [except.throw]. A program is ill-formed if a destructor that is potentially invoked is deleted or not accessible from the context of the invocation.
[Accepted as a DR at the February, 2019 meeting.]
Consider an example like:
struct A { int n = A{}.n; };
There doesn't seem to be a good reason to support this kind of thing, and it would be simpler to say that a default member initializer can't trigger any direct or indirect use of itself in general, rather than just the two special cases that were banned by issues 1696 and 1397.
Notes from the March, 2018 meeting:
There was a suggestion that creating an object of the containing class in a default member initializer should be prohibited. That would presumably be a difference between the reference member and non-reference member cases, since the intent is to allow creation of a temporary for a reference member to bind to. The suggested approach for drafting was simply to remove the restriction to references in 9.5.2 [dcl.init.aggr] paragraph 11.
Proposed resolution (November, 2018):
Change 9.5.2 [dcl.init.aggr] paragraph 12 as follows:
If areferencememberis initialized from itshas a default member initializer and a potentially-evaluated subexpression thereof is an aggregate initialization that would use that default member initializer, the program is ill-formed. [Example:struct A; extern A a; struct A { const A& a1 { A{a,a} }; // OK const A& a2 { A{} }; // error }; A a{a,a}; // OK struct B { int n = B{}.n; // error };—end example]
[Voted into the WP at the July, 2017 meeting.]
According to 12.2.2 [over.match.funcs] paragraph 8,
A defaulted move constructor or assignment operator (11.4.5.3 [class.copy.ctor]) that is defined as deleted is excluded from the set of candidate functions in all contexts.
It is unclear whether this is intended to apply to all defaulted assignment operators or only move assignment operators.
Proposed resolution (April, 2017):
Change 12.2.2 [over.match.funcs] paragraph 8 as follows:
A defaulted moveconstructor or assignment operatorspecial function (11.4.5.3 [class.copy.ctor]) that is defined as deleted is excluded from the set of candidate functions in all contexts.
[Accepted as a DR at the June, 2018 (Rapperswil) meeting.]
Base class copy and move constructors brought into a derived class via a using-declaration should not be considered by overload resolution when constructing a derived class object.
Proposed resolution, February, 2018:
Change 12.2.2 [over.match.funcs] paragraph 8 as follows:
A defaulted move special function (11.4.5.3 [class.copy.ctor]) that is defined as deleted is excluded from the set of candidate functions in all contexts. A constructor inherited from class type C (11.9.4 [class.inhctor.init]) that has a first parameter of type “reference to cv1 P” (including such a constructor instantiated from a template) is excluded from the set of candidate functions when constructing an object of type cv2 D if the argument list has exactly one argument and C is reference-related to P and P is reference-related to D. [Example:
struct A { A(); A(A &&); // #1 template<typename T> A(T &&); // #2 }; struct B : A { using A::A; B(const B &); // #3 B(B &&) = default; // #4, implicitly deleted struct X { X(X &&) = delete; } x; }; extern B b1; B b2 = static_cast<B&&>(b1); // calls #3: #1, #2, and #4 are not viable struct C { operator B&&(); }; B b3 = C(); // calls #3—end example]
[Accepted as a DR at the November, 2018 (San Diego) meeting.]
According to 12.2.2.6 [over.match.conv] paragraph 1, when a class type S is used as an initializer for an object of type T,
The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S and yield type T or a type that can be converted to type T via a standard conversion sequence (12.2.4.2.2 [over.ics.scs]) are candidate functions.
Because conversion from std::nullptr_t to bool is only permitted in direct-initialization (7.3.14 [conv.fctptr]), it is not clear whether there is a standard conversion sequence from std::nullptr_t to bool, considering that an implicit conversion sequence is intended to model copy-initialization. Should 12.2.2.6 [over.match.conv] be understood to refer only to conversions permitted in copy-initialization, or should the form of the initialization be considered? For example,
struct SomeType {
operator std::nullptr_t();
};
bool b{ SomeType() }; // Well-formed?
Note also 12.2.4.3 [over.ics.rank] paragraph 4, which may bear on the intent (or, alternatively, might describe a situation that cannot arise):
A conversion that does not convert a pointer, a pointer to member, or std::nullptr_t to bool is better than one that does.
See also issues 2133 and 2243.)
Proposed resolution (June, 2018):
Change 7.3.15 [conv.bool] paragraph 1 as follows:
A prvalue of arithmetic, unscoped enumeration, pointer, or pointer-to-member type can be converted to a prvalue of type bool. A zero value, null pointer value, or null member pointer value is converted to false; any other value is converted to true.For direct-initialization (9.5 [dcl.init]), a prvalue of type std::nullptr_t can be converted to a prvalue of type bool; the resulting value is false.
Add the following bullet before 9.5 [dcl.init] bullet 17.8:
The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the source type is not defined.
...
Otherwise, if the initialization is direct-initialization, the source type is std::nullptr_t, and the destination type is bool, the initial value of the object being initialized is false.
Otherwise, the initial value of the object being initialized is the (possibly converted) value...
Change 12.2.4.3 [over.ics.rank] bullet 4.1 as follows:
Standard conversion sequences are ordered by their ranks: an Exact Match is a better conversion than a Promotion, which is a better conversion than a Conversion. Two conversion sequences with the same rank are indistinguishable unless one of the following rules applies:
A conversion that does not convert a pointer
,or a pointer to member, or std::nullptr_tto bool is better than one that does....
This resolution also resolves issue 2133.
[Accepted as a DR at the July, 2019 meeting.]
An example like
template <class ...T> struct A { A(T...) {} }; A x[29]{};
Appears to be permitted by the current wording of the Standard, but existing implementations reject it. Should this usage be supported (in which case some mention of it in the wording would be useful) or prohibited?
Notes from the November, 2018 meeting:
The example is intended to be ill-formed; the intent is that declarator operators are not permitted, as with decltype(auto).
Proposed resolution, March, 2019:
Change 9.2.9.8 [dcl.type.class.deduct] paragraph 1 as follows:
If a placeholder for a deduced class type appears as a decl-specifier in the decl-specifier-seq of an initializing declaration (9.5 [dcl.init]) of a variable, the declared type of the variable shall be cv T, where T is the placeholder. [Example:
template <class ...T> struct A { A(T...) {} }; A x[29]{}; // error: no declarator operators allowed const A& y{}; // error: no declarator operators allowed—end example] The placeholder is replaced by the return type of the function selected by overload resolution for class template deduction (12.2.2.9 [over.match.class.deduct]). If the decl-specifier-seq is followed by an init-declarator-list or member-declarator-list containing more than one declarator, the type that replaces the placeholder shall be the same in each deduction.
[Voted into the WP at the July, 2017 meeting.]
In an example like:
struct A { A(int, int = 0); void f(int, int = 0); }; struct B : A { B(int); using A::A; void f(int); using A::f; }
calls to B(int) and B::f(int) are ambiguous, because they could equally call the version inherited from the base class. This doesn't match the intent in 9.10 [namespace.udecl], which usually makes derived-class functions take precedence over ones from a base class.
The above patterns are not common, although they sometimes cause breakage when refactoring a base class. However, P0136R1 brings this into sharp focus, because it causes the rejection of the following formerly-valid and very reasonable code:
struct A { A(int = 0); }; struct B : A { using B::B; }; B b;
Proposed resolution (May, 2017):
This issue is resolved by the resolution of issue 2273.
[Accepted as a DR as paper P1131R2 at the November, 2018 (San Diego) meeting.]
The grammar term simple-template-id is used in the definition of both class-name (Clause 11 [class] paragraph 1) and type-name (9.2.9.3 [dcl.type.simple] paragraph 1). The latter case is intended to apply to alias template specializations. It would be helpful to have separate grammar terms for these uses.
[Accepted as a DR at the November, 2017 meeting.]
During the discussion of issue 1804, it was noted that the process of determining whether a member of an explicit or partial specialization corresponds to a member of the primary template is not well specified. In particular, it should be clarified that the primary template should not be instantiated during this process; instead, the template arguments from the specialization should simply be substituted into the member declaration.
Proposed resolution (October, 2017):
Change 13.7.5 [temp.friend] paragraph 4 as follows:
A template friend declaration may declare a member of a
class template may be declareddependent type to be a friendof a non-template class. The friend declaration shall declare a function or specify a type with an elaborated-type-specifier, in either case with a nested-name-specifier ending with a simple-template-id, C, whose template-name names a class template. The template parameters of the template friend declaration shall be deducible from C (13.10.3.6 [temp.deduct.type]). In this case,the corresponding member of every specialization of the primary class template and class template partial specializations thereofa member of a specialization S of the class template is a friend of the class granting friendship. For explicit specializations and specializations of partial 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 generatedif deduction of the template parameters of C from S succeeds, and substituting the deduced template arguments into the friend declaration produces a declaration that would be a valid redeclaration of the member of the specialization. [Example:template<class T> struct A { struct B { }; void f(); struct D { void g(); }; T h(); template<T U> T i(); }; template<> struct A<int> { struct B { }; int f(); struct D { void g(); }; template<int U> int i(); }; template<> struct A<float*> { int *h(); }; 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>::Dill-formed, A<T>::D does not end with a simple-template-id template<class T> friend int *A<T*>::h(); // grants friendship to A<int*>::h() and A<float*>::h() template<class T> template<T U> // grants friendship to instantiations of A<T>::i() and to A<int>::i() friend T A<T>::i(); // and thereby to all specializations of those function templates };—end example]
It is not clear what should happen for an example like:
template<typename T> struct A { class B { class C {}; }; }; class X { static int x; template <typename T> friend class A<T>::B::C; }; template<> struct A<int> { typedef struct Q B; }; struct Q { class C { int f() { return X::x; } }; };
It appears that the friend template matches Q::C, because that class is also A<int>::B::C, but neither GCC nor EDG allow this code (saying X::x is inaccessible). (Clang doesn't support friend template declarations with a dependent scope.)
A strict reading of 13.7.5 [temp.friend] paragraph 5 might suggest that the friend declaration itself is ill-formed, because it does not declare a member of a class template, but I can't find any compiler that implements template friends that way.
Additional note (January, 2024)
The example is ill-formed per the resolution of issue 1862 (adopted in November, 2017).
During the discussion of issue 1918, it was decided that the last part of the issue should be split off into a separate issue. According to 13.7.5 [temp.friend] paragraph 5,
A member of a class template may be declared to be a friend of a non-template class.
Does this make the example from issue 1918,
template<typename T> struct A { class B { class C {}; }; }; class X { static int x; template <typename T> friend class A<T>::B::C; }; template<> struct A<int> { typedef struct Q B; }; struct Q { class C { int f() { return X::x; } }; };
ill-formed because the friend declaration does not refer to a member of a class template? This does not appear to be the interpretation chosen by most implementations.
Additional note (January, 2024)
The example is ill-formed per the resolution of issue 1862 (adopted in November, 2017).
[Accepted as a DR at the February, 2019 meeting.]
According to 13.7.5 [temp.friend] paragraph 8,
When a friend declaration refers to a specialization of a function template, the function parameter declarations shall not include default arguments, nor shall the inline specifier be used in such a declaration.
Presumably this should also include the constexpr specifier.
Notes from the December, 2018 teleconference:
This should also cover the newly-added consteval specifier.
Proposed resolution (February, 2019):
Change 13.7.5 [temp.friend] paragraph 8 as follows:
When a friend declaration refers to a specialization of a function template, the function parameter declarations shall not include default arguments, nor shall theinline specifierinline, constexpr, or consteval specifiers be used in such a declaration.
[Adopted at the July, 2017 meeting as part of paper P0734R0.]
The requirement of 13.7.7.2 [temp.over.link] paragraph 6 that equivalent function templates must have “identical” template parameter lists is confusing, since the names of template parameters are not considered (13.7.7.2 [temp.over.link] paragraph 3) .
[Accepted as a DR at the November, 2018 (San Diego) meeting.]
According to 13.7.7.3 [temp.func.order] paragraph 3,
...If only one of the function templates M is a non-static member of some class A, M is considered to have a new first parameter inserted in its function parameter list. Given cv as the cv-qualifiers of M (if any), the new parameter is of type “rvalue reference to cv A” if the optional ref-qualifier of M is && or if M has no ref-qualifier and the first parameter of the other template has rvalue reference type. Otherwise, the new parameter is of type “lvalue reference to cv A”. [Note: This allows a non-static member to be ordered with respect to a non-member function and for the results to be equivalent to the ordering of two equivalent non-members. —end note]
This gives the wrong answer for an example like:
struct Foo { template <typename T> static T* f(Foo*) { return nullptr; } template <typename T, typename A1> T* f(const A1&) { return nullptr; } }; int main() { Foo x; x.f<int>(&x); }
Presumably this should say something like,
...If only one of the function templates M is a member function, and that function is a non-static member...
Proposed resolution (October, 2018):
Change 13.7.7.3 [temp.func.order] paragraph 3 as follows:
...If only one of the function templates M is a member function, and that function is a non-static member of some class A, M is considered to have a new first parameter inserted in its function parameter list. Given cv as the cv-qualifiers of M (if any), the new parameter is of type “rvalue reference to cv A” if the optional ref-qualifier of M is && or if M has no ref-qualifier and the first parameter of the other template has rvalue reference type. Otherwise, the new parameter is of type “lvalue reference to cv A”. [Note: This allows a non-static member to be ordered with respect to a non-member function and for the results to be equivalent to the ordering of two equivalent non-members. —end note]
[Accepted as a DR at the March, 2018 (Jacksonville) meeting as part of paper P0634R3.]
According to 13.8 [temp.res] paragraph 3,
When a qualified-id is intended to refer to a type that is not a member of the current instantiation (13.8.3.2 [temp.dep.type]) and its nested-name-specifier refers to a dependent type, it shall be prefixed by the keyword typename, forming a typename-specifier. If the qualified-id in a typename-specifier does not denote a type, the program is ill- formed.
The intent of the programmer cannot form the basis for a compiler determining whether to issue a diagnostic or not.
Suggested resolution:Let N be a qualified-id with a nested-name-specifier that denotes a dependent type. If N is not prefixed by the keyword typename, N shall refer to a member of the current instantiation or it shall not refer to a type.
typename-specifier:
typename nested-name-specifier identifier
typename nested-name-specifier templateopt simple-template-idIf the qualified-id in a typename-specifier does not denote a type, the program is ill-formed.
(See also issues 590 and 591.)
Notes from the November, 2016 meeting:
The resolution for this issue should describe the type to which a typename-specifier refers, effectively the type named by the corresponding simple-type-specifier with typename removed.
Notes from the November, 2017 meeting:
This topic is addressed in paper P0634.
[Accepted as a DR at the February, 2019 meeting.]
According to 13.8.3.2 [temp.dep.type] bullet 6.3.2, one criterion for a name being a member of an unknown specialization is if the name is an id-expression denoting the member in a member access expression and
the type of the object expression is dependent and is not the current instantiation.
This should presumably say that the object expression is type-dependent and not that it has a dependent type; “has a dependent type” should be applied only to declarations, not expressions.
Proposed resolution (February, 2019):
Change 13.8.3.2 [temp.dep.type] bullet 6.3.2 as follows:
A name is a member of an unknown specialization if it is
...
An id-expression denoting the member in a class member access expression (7.6.1.5 [expr.ref]) in which either
the type of the object expression is the current instantiation, the current instantiation has at least one dependent base class, and name lookup of the id-expression does not find a member of a class that is the current instantiation or a non-dependent base class thereof; or
the type ofthe object expression is type-dependent and is not the current instantiation.
[Accepted as a DR at the November, 2017 meeting.]
The description of whether a template argument is equivalent to a template parameter in 13.8.3.2 [temp.dep.type] paragraph 3 is unclear as it applies to non-type template parameters:
A template argument that is equivalent to a template parameter (i.e., has the same constant value or the same type as the template parameter) can be used in place of that template parameter in a reference to the current instantiation. In the case of a non-type template argument, the argument must have been given the value of the template parameter and not an expression in which the template parameter appears as a subexpression.
For example:
template<int N> struct A { typedef int T[N]; static const int AlsoN = N; A<AlsoN>::T s; // #0, clearly supposed to be OK static const char K = N; A<K>::T t; // #1, OK? static const long L = N; A<L>::T u; // #2, OK? A<(N)>::T v; // #3, OK? static const int M = (N); A<M>::T w; // #4, OK? };
#1 narrows the template argument. This obviously should not be the injected-class-name, because A<257>::T may well be int[1] not int[257] . However, the wording above seems to treat it as the injected-class-name.
#2 is questionable: there is potentially a narrowing conversion here, but it doesn't actually narrow any values for the original template parameter.
#3 is hard to decipher. On the one hand, this is an expression involving the template parameter. On the other hand, a parenthesized expression is specified as being equivalent to its contained expression.
#4 should presumably go the same way that #3 does.
Proposed resolution (August, 2017):
Change 13.8.3.2 [temp.dep.type] paragraph 3 as follows:
A template argument that is equivalent to a template parameter
(i.e., has the same constant value or the same type as the template parameter)can be used in place of that template parameter in a reference to the current instantiation. For a template type-parameter, a template argument is equivalent to a template parameter if it denotes the same type. For a non-type template parameter, a template argument is equivalent to a template parameter if it is an identifier that names a variable that is equivalent to the template parameter. A variable is equivalent to a template parameter if
it has the same type as the template parameter (ignoring cv-qualification) and
its initializer consists of a single identifier that names the template parameter or, recursively, such a variable.
[Note: Using a parenthesized variable name breaks the equivalence. —end note]
In the case of a non-type template argument, the argument must have been given the value of the template parameter and not an expression in which the template parameter appears as a subexpression.[Example:template <class T> class A { A* p1; // A is the current instantiation A<T>* p2; // A<T> is the current instantiation A<T*> p3; // A<T*> is not the current instantiation ::A<T>* p4; // ::A<T> is the current instantiation class B { B* p1; // B is the current instantiation A<T>::B* p2; // A<T>::B is the current instantiation typename A<T*>::B* p3; // A<T*>::B is not the current instantiation }; }; template <class T> class A<T*> { A<T*>* p1; // A<T*> is the current instantiation A<T>* p2; // A<T> is not the current instantiation }; template <class T1, class T2, int I> struct B { B<T1, T2, I>* b1; // refers to the current instantiation B<T2, T1, I>* b2; // not the current instantiation typedef T1 my_T1; static const int my_I = I; static const int my_I2 = I+0; static const int my_I3 = my_I; static const long my_I4 = I; static const int my_I5 = (I); B<my_T1, T2, my_I>* b3; // refers to the current instantiation B<my_T1, T2, my_I2>* b4; // not the current instantiation B<my_T1, T2, my_I3>* b5; // refers to the current instantiation B<my_T1, T2, my_I4>* b6; // not the current instantiation B<my_T1, T2, my_I5>* b7; // not the current instantiation };—end example]
Additional note (November, 2017):
It was observed that the proposed resolution does not address partial specializations, which also depend on the definition of equivalence. For example:
template <typename T, unsigned N> struct A;
template <typename T> struct A<T, 42u> {
typedef int Ty;
static const unsigned num = 42u;
static_assert(!A<T, num>::Ty(), "");
};
A<int, 42u> a; // GCC, MSVC, ICC accepts; Clang rejects
The issue is being returned to "review" status in order to consider these additional questions.
Notes from the November, 2017 (Albuquerque) meeting:
CWG decided to proceed with this resolution and deal with partial specialization in a separate issue.
[Accepted as a DR at the June, 2018 (Rapperswil) meeting.]
The list of type-dependent id-expressions in 13.8.3.3 [temp.dep.expr] paragraph 3 should include the case when a static data member is declared with the auto type specifier and the initializer is dependent.
Proposed resolution, March, 2018:
Add the following as a new bullet after 13.8.3.3 [temp.dep.expr] bullet 3.2:
...
an identifier associated by name lookup with a non-type template-parameter declared with a type that contains a placeholder type (9.2.9.7 [dcl.spec.auto]),
an identifier associated by name lookup with a variable declared with a type that contains a placeholder type (9.2.9.7 [dcl.spec.auto]) where the initializer is type-dependent,
...
C++11 expanded the lookup rules for dependent function calls (13.8.4.2 [temp.dep.candidate] bullet 1.2) to include functions with internal linkage; previously only functions with external linkage were considered. However, 13.8.4.1 [temp.point] paragraph 6 still says,
The instantiation context of an expression that depends on the template arguments is the set of declarations with external linkage declared prior to the point of instantiation of the template specialization in the same translation unit.
Presumably this wording was overlooked and should be harmonized with the new specification.
Additional note (February, 2022):
The quoted paragraph was removed by P1103R3 (Merging Modules), approved 2019-02.
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
The current wording does not state that a specialization of a static data member template (Clause 13 [temp] paragraph 1) is a static data member, which leaves the status of an example like the following unclear (since 7.6.1.5 [expr.ref] bullet 4.1 is phrased in terms of static data members):
template <class T> struct A { template <class U> static const U x = 1; static const int y = 2; }; int main() { A<int> a; int y = a.y; // OK int x = a.x<int>; // ??? }
Proposed resolution (November, 2017)
Change 13.9 [temp.spec] paragraph 2 as follows:
A function instantiated from a function template is called an instantiated function. A class instantiated from a class template is called an instantiated class. A member function, a member class, a member enumeration, or a static data member of a class template instantiated from the member definition of the class template is called, respectively, an instantiated member function, member class, member enumeration, or static data member. A member function instantiated from a member function template is called an instantiated member function. A member class instantiated from a member class template is called an instantiated member class. A variable instantiated from a variable template is called an instantiated variable. A static data member instantiated from a static data member template is called an instantiated static data member.
[Accepted as a DR at the February, 2019 meeting.]
Presumably paragraphs 1-3 of 13.9 [temp.spec] are intended to apply to variable templates, but the term does not appear in the current wording of these paragraphs.
Proposed resolution (November, 2018):
Change 13.9 [temp.spec] paragraph 1 as follows:
The act of instantiating a function, a variable, a class, a member of a class template or a member template is referred to as template instantiation.
Change 13.9 [temp.spec] paragraphs 3 and r as follows:
An explicit specialization may be declared for a function template, a variable template, a class template, a member of a class template or a member template. An explicit specialization declaration is introduced by template<> . In an explicit specialization declaration for a variable template, a class template, a member of a class template or a class member template, the name of the the variable or class that is explicitly specialized shall be a simple-template-id. In the explicit specialization declaration for a function template or a member function template, the name of the function or member function explicitly specialized may be a template-id. [Example:...
An instantiated template specialization can be either implicitly instantiated (13.9.2 [temp.inst]) for a given argument list or be explicitly instantiated (13.9.3 [temp.explicit]). A specialization is a class, variable, function, or class member that is either instantiated or explicitly specialized (13.9.4 [temp.expl.spec]).
[ Resolved by P0859R0, approved in November, 2017. ]
A template instantiation can be “required” without there being a need for it at link time if it can appear in a constant expression:
template <class T> struct A { static const T t; }; template <class T> const T A<T>::t = 0; template <int I> struct B { }; int a = sizeof(B<A<int>::t>); template <class T> constexpr T f(T t) { return t; } int b = sizeof(B<f(42)>);
It seems like it might be useful to define a term other than odr-used for this sort of use, which is like odr-used but doesn't depend on potentially evaluated context or lvalue-rvalue conversions.
Nikolay Ivchenkov:
Another possibility would be to introduce the extension described in the closed issue 1272 and then change 6.3 [basic.def.odr] paragraph 2 as follows:
An expression E is potentially evaluated
unless it is an unevaluated operand ( Clause 7 [expr]) or a subexpression thereof.if and only if
E is a full-expression, or
E appears in a context where a constant expression is required, or
E is a direct subexpression of a potentially-evaluated expression and E is not an unevaluated operand.
An expression S is a direct subexpression of an expression E if and only if S and E are different expressions, S is a subexpression of E, and there is no expression X such that X differs from both S and E, S is a subexpression of X, and X is a subexpression of E. A variable whose name appears as a potentially-evaluated expression is odr-used
unless it is an object that satisfies the requirements for appearing in a constant expression (7.7 [expr.const]) and the lvalue-to-rvalue conversion (4.1) is immediately applied...[Example:
template <class T> struct X { static int const m = 1; static int const n; }; template <class T> int const X<T>::n = 2; int main() { // X<void>::m is odr-used, // X<void>::m is defined implicitly std::cout << X<void>::m << std::endl; // X<void>::n is odr-used, // X<void>::n is defined explicitly std::cout << X<void>::n << std::endl; // OK (issue 712 is not relevant here) std::cout << (1 ? X<void>::m : X<void>::n) << std::endl; }
(See also issues 712 and 1254.)
Additional notes (June, 2023)
This was addressed by the introduction of "needed for constant evaluation" in P0859R0 (Core Issue 1581: When are constexpr member functions defined?).
[Voted into the WP at the July, 2017 meeting.]
It is not clear whether the following is well-formed or not:
template<typename T> int arr[sizeof(T)] = {}; template int arr<int>[];
Are we supposed to instantiate the specialization and treat the explicit instantiation declaration as if it were a redeclaration (in which case the omitted array bound would presumably be OK), or is the type of the explicit instantiation declaration required to exactly match the type that the instantiated specialization has (in which case the omitted bound would presumably not be OK)? Or something else?
(See also issue 1728.)
Proposed resolution (May, 2017):
Change 13.9.3 [temp.explicit] paragraph 3 as follows:
If the explicit instantiation is for a class or member class, the elaborated-type-specifier in the declaration shall include a simple-template-id; otherwise, the declaration shall be a simple-declaration whose init-declarator-list comprises a single init-declarator that does not have an initializer. If the explicit instantiation is for a function or member function, the unqualified-id in thedeclarationdeclarator shall be either a template-id or, where all template arguments can be deduced, a template-name or operator-function-id. [Note: The declaration may declare a qualified-id, in which case the unqualified-id of the qualified-id must be a template-id. —end note] If the explicit instantiation is for a member function, a member class or a static data member of a class template specialization, the name of the class template specialization in the qualified-id for the member name shall be a simple-template-id. If the explicit instantiation is for a variable template specialization, the unqualified-id in thedeclarationdeclarator shall be a simple-template-id. An explicit instantiation shall appear in an enclosing namespace of its template. If the name declared in the explicit instantiation is an unqualified name, the explicit instantiation shall appear in the namespace where its template is declared or, if that namespace is inline (9.9.2 [namespace.def]), any namespace from its enclosing namespace set. [Note:...
Add the following as a new paragraph following 13.9.3 [temp.explicit] paragraph 4:
The declaration in an explicit-instantiation and the declaration produced by the corresponding substitution into the templated function, variable, or class are two declarations of the same entity. [Note: These declarations are required to have matching types as specified in 6.6 [basic.link], except as specified in 14.5 [except.spec]. [Example:
template<typename T> T var = {}; template float var<float>; // OK, instantiated variable has type float template int var<int[16]>[]; // OK, absence of major array bound is permitted template int *var<int>; // error: instantiated variable has type int template<typename T> auto av = T(); template int av<int>; // OK, variable with type int can be redeclared with type auto template<typename T> auto f() {} template void f<int>(); // error: function with deduced return type redeclared with non-deduced return type (9.2.9.7 [dcl.spec.auto])—end example] —end note] Despite its syntactic form, the declaration in an explicit-instantiation for a variable is not itself a definition and does not conflict with the definition instantiated by an explicit instantiation definition for that variable.
Change 13.9.3 [temp.explicit] paragraph 10 as follows:
Except for inline functions and variables, declarations with types deduced from their initializer or return value (9.2.9.7 [dcl.spec.auto]), const variables of literal types, variables of reference types, and class template specializations, explicit instantiation declarations have the effect of suppressing the implicit instantiation of the definition of the entity to which they refer. [Note:...
This resolution also resolves issue 1728.
[Voted into the WP at the July, 2017 meeting.]
It is not clear to what extent the type in an explicit instantiation must match that of a variable template. For example:
template<typename T> T var = T(); template float var<float>; // #1. template int* var<int>; // #2. template auto var<char>; // #3.
(See also issue 1704.)
Proposed resolution (May, 2017):
This issue is resolved by the resolution of issue 1704.
[Accepted as a DR at the November, 2017 meeting.]
According to 13.9.3 [temp.explicit] paragraph 1,
An explicit instantiation of a function template or member function of a class template shall not use the inline or constexpr specifiers.
Should this apply to explicit specializations of variable templates as well?
(See also issues 1704 and 1728).
Proposed resolution (August, 2017):
Change 13.9.3 [temp.explicit] paragraph 1 as follows:
A class, function, variable, or member template specialization can be explicitly instantiated from its template. A member function, member class or static data member of a class template can be explicitly instantiated from the member definition associated with its class template. An explicit instantiation of a function templateor, member function of a class template, or variable template shall not use the inline or constexpr specifiers.
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
Although the Standard allows for explicitly specializing a deleted function template, member function of a class template, or member function template with a non-deleted definition, this seems to be problematic for non-template member functions of class templates. For example:
template<typename T> struct A { A(const A&) = delete; A(A&&) = default; }; static_assert(is_trivially_copyable(A<int>)); template<> struct A<int>::A(const A&) { /* ... */ } static_assert(is_trivially_copyable(A<int>)); template<typename T> struct B { virtual void f() = delete; }; struct C : B<int> { void f() override = delete; }; // ok, overriding deleted with deleted template<> void B<int>::f() {} // would make C retroactively ill-formed?
Notes from the December, 2016 teleconference:
=delete definitions of member functions should be instantiated when instantiating a class template. That would make the example an ill-formed redefinition.
Proposed resolution (November, 2017)
Change 13.9.2 [temp.inst] paragraph 2, breaking the running text into bullets, as follows:
The implicit instantiation of a class template specialization causes
the implicit instantiation of the declarations, but not of the definitions,
default arguments, or noexcept-specifiersof the non-deleted class member functions, member classes, scoped member enumerations, static data members, member templates, and friends; and
it causesthe implicit instantiation of the definitions of deleted member functions, unscoped member enumerations, and member anonymous unions.The implicit instantiation of a class template specialization does not cause the implicit instantiation of default arguments or noexcept-specifiers of the class member functions. [Example:
template<class T> struct C { void f() { T x; } void g() = delete; }; C<void> c; // OK, definition of C<void>::f is not instantiated at this point template<> void C<int>::g() { } // error: redefinition of C<int>::g—end example] However, for the purpose of determining whether an instantiated redeclaration is valid according to 6.3 [basic.def.odr] and 11.4 [class.mem], a declaration that corresponds to a definition in the template is considered to be a definition. [Example:
[Accepted as a DR at the February, 2019 meeting.]
Although it is not possible to specify a constructor's template arguments in a constructor invocation (because the constructor has no name but is invoked by use of the constructor's class's name), it is possible to “name” the constructor in declarative contexts: per 6.5.5.2 [class.qual] paragraph 2,
In a lookup in which the constructor is an acceptable lookup result, if the nested-name-specifier nominates a class C, and the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C ( Clause 11 [class]), the name is instead considered to name the constructor of class C... Such a constructor name shall be used only in the declarator-id of a declaration that names a constructor.
Should it therefore be possible to specify template-arguments for a templated constructor in an explicit instantiation or specialization? For example,
template <int dim> struct T {}; struct X { template <int dim> X (T<dim> &) {}; }; template X::X<> (T<2> &);
If so, that should be clarified in the text. In particular, 11.4.5 [class.ctor] paragraph 1 says,
Constructors do not have names. A special declarator syntax using an optional sequence of function-specifiers (9.2.3 [dcl.fct.spec]) followed by the constructor's class name followed by a parameter list is used to declare or define the constructor.
This certainly sounds as if the parameter list must immediately follow the class name, with no allowance for a template argument list.
It would be worthwhile in any event to revise this wording to utilize the “considered to name” approach of 6.5.5.2 [class.qual]; as it stands, this wording sounds as if the following would be acceptable:
struct S {
S();
};
S() { } // qualified-id not required?
Notes from the October, 2006 meeting:
It was observed that explicitly specifying the template arguments in a constructor declaration is never actually necessary because the arguments are, by definition, all deducible and can thus be omitted.
Additional notes, October, 2018:
The wording in 13.10.2 [temp.arg.explicit] paragraph 1 refers to a “function name,” which constructors do not have, and so presumably the current wording does not permit an explicit specialization of a constructor template. Nevertheless, there is implementation divergence in the treatment of an example like:
class C { template <typename T> C(const T &) {} }; template C::C<double>(const double &);
with some accepting and some rejecting.
Notes from the October, 2018 teleconference:
The consensus was to allow template arguments on the constructor name but not something like C<int>::C<float>::f.
Proposed resolution (February, 2019):
Cbange 13.10.2 [temp.arg.explicit] paragraph 1 as follows:
Template arguments can be specified when referring to a function template specialization that is not a specialization of a constructor template by qualifying the function template name with the list of template-arguments in the same way as template-arguments are specified in uses of a class template specialization. [Example:
Add the following as a new paragraph following 13.10.2 [temp.arg.explicit] paragraph 1:
Template arguments shall not be specified when referring to a specialization of a constructor template (11.4.5 [class.ctor], 6.5.5.2 [class.qual]).
[Accepted as a DR at the June, 2018 (Rapperswil) meeting.]
According to 13.10.3 [temp.deduct] paragraph 7,
The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions. The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered.
However, the same type can be represented in different lexical orders. For example, there is implementation variance on the following example, presumably because of preferring different declarations:
template <class T> struct A { using X = typename T::X; }; template <class T> typename T::X f(typename A<T>::X); template <class T> auto f(typename A<T>::X) -> typename T::X; template <class T> void f(...) { } void h() { f<int>(0); }
Proposed resolution, March, 2018:
Change 13.10.3 [temp.deduct] paragraph 7 as follows:
The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions. The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered. If substitution into different declarations of the same function template would cause template instantiations to occur in a different order or not at all, the program is ill-formed; no diagnostic required. [Note: The equivalent substitution in exception specifications is done only when the noexcept-specifier is instantiated, at which point a program is ill-formed if the substitution results in an invalid type or expression. —end note] [Example:
template <class T> struct A { using X = typename T::X; }; template <class T> typename T::X f(typename A<T>::X); template <class T> void f(...) { } template <class T> auto g(typename A<T>::X) -> typename T::X; template <class T> void g(...) { } template <class T> typename T::X h(typename A<T>::X); template <class T> auto h(typename A<T>::X) -> typename T::X; // redeclaration template <class T> void h(...) { } voidhx() { f<int>(0); // OK, substituting return type causes deduction to fail g<int>(0); // error, substituting parameter type instantiates A<int> h<int>(0); // ill-formed, no diagnostic required }—end example]
[Accepted as a DR at the February, 2019 meeting.]
The status of an example like the following is not clear:
template <typename... T> struct A; template <> struct A<> {}; template <typename T, typename... Ts> struct A<T, Ts...> : A<Ts...> {}; struct B : A<int> {}; template <typename... T> void f(const A<T...>&); void g() { f(B{}); }
This seems to be ambiguous in the current wording because A<> and A<int> both succeed in deduction. It would be reasonable to prefer the more derived specialization.
Notes from the March, 2018 meeting:
The relevant specification is in 13.10.3.2 [temp.deduct.call] bullet 4.3 and paragraph 5, which specifies that if there is more than one possible deduced A, deduction fails. The consensus was to add wording similar to that of overload resolution preferring “nearer” base classes.
Proposed resolution (November, 2018):
Change 13.10.3.2 [temp.deduct.call] bullet 4.3 as follows:
In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above). However, there are three cases that allow a difference:
...
If P is a class and P has the form simple-template-id, then the transformed A can be a derived class D of the deduced A. Likewise, if P is a pointer to a class of the form simple-template-id, the transformed A can be a pointer to a derived class D pointed to by the deduced A. However, if there is a class C that is a (direct or indirect) base class of D and derived (directly or indirectly) from a class B and that would be a valid deduced A, the deduced A cannot be B or pointer to B, respectively. [Example:
template <typename... T> struct X; template <> struct X<> {}; template <typename T, typename... Ts> struct X<T, Ts...> : X<Ts...> {}; struct D : X<int> {}; template <typename... T> int f(const X<T...>&); int x = f(D()); // calls f<int>, not f<> // B is X<>, C is X<int>—end example]
[Accepted as a DR at the February, 2019 meeting.]
Issue 349 resulted in the following specification in 13.10.3.4 [temp.deduct.conv] paragraph 7:
When the deduction process requires a qualification conversion for a pointer or pointer-to-member type as described above, the following process is used to determine the deduced template argument values: If A is a type
cv1,0 “pointer to...” cv1,n-1 “pointer to” cv1,n T1
and P is a type
cv2,0 “pointer to...” cv2,n-1 “pointer to” cv2,n T2
then the cv-unqualified T1 and T2 are used as the types of A and P respectively for type deduction. [Example:
struct A { template <class T> operator T***(); }; A a; const int * const * const * p1 = a; // T is deduced as int, not const int—end example]
This rule is not widely implemented and may not be desirable. Should it be removed?
Proposed resolutions (December, 2018):
Delete 13.10.3.4 [temp.deduct.conv] paragraph 7:
When the deduction process requires a qualification conversion for a pointer or pointer-to-member type as described above, the following process is used to determine the deduced template argument values: If A is a type
cv1,0 “pointer to...” cv1,n-1 “pointer to” cv1,n T1
and P is a type
cv2,0 “pointer to...” cv2,n-1 “pointer to” cv2,n T2
then the cv-unqualified T1 and T2 are used as the types of A and P respectively for type deduction. [Example:struct A { template <class T> operator T***(); }; A a; const int * const * const * p1 = a; // T is deduced as int, not const int
—end example]
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
The late tiebreakers for lvalue-vs-rvalue references and cv-qualification in 13.10.3.5 [temp.deduct.partial] paragraph 9 are applied
If, for a given type, deduction succeeds in both directions (i.e., the types are identical after the transformations above) and both P and A were reference types (before being replaced with the type referred to above):
However, this is based on a false assumption. For example,
template <typename T> struct A { struct typeA { }; struct typeB { }; using convTyA = T (*const &&)(typename A<T>::typeA); using convTyB = T (*const &)(typename A<T>::typeB); operator convTyA(); operator convTyB(); }; template <typename T> void foo(T (*const &&)(typename A<T>::typeA)); template <typename T> int foo(T (*const &)(typename A<T>::typeB)); int main() { return foo<int>(A<int>()); }
(see also issues 1847 and 1157.). We need to decide whether the rule is “deduction succeeds in both directions” or “the types are identical.” The latter seems more reasonable.
Proposed resolution (November, 2017)
Change 13.10.3.5 [temp.deduct.partial] paragraph 9 as follows:
If, for a given type,
deduction succeeds in both directions (i.e.,the types are identical after the transformations above)and both P and A were reference types (before being replaced with the type referred to above):
if the type from the argument template was an lvalue reference and the type from the parameter template was not, the parameter type is not considered to be at least as specialized as the argument type; otherwise,
if the type from the argument template is more cv-qualified than the type from the parameter template (as described above), the parameter type is not considered to be at least as specialized as the argument type.
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
Paragraph 12 of 13.10.3.5 [temp.deduct.partial] contains the following example:
template <class T> T f(int); // #1 template <class T, class U> T f(U); // #2 void g() { f<int>(1); // calls #1 }
However, paragraph 4 states,
If a particular P contains no template-parameters that participate in template argument deduction, that P is not used to determine the ordering.
Thus, we ignore the P=int, A=U case and deduction succeeds for the P=U, A=int case, so both templates are at least as specialized as each other. And consider:
template <class... T> struct V {}; template <class... Ts, class... Us> void Foo(V<Ts...>, V<Us&...>) {} // #3 template <class... Us> void Foo(V<>, V<Us&...>) {} // #4 void h() { Foo(V<>(), V<>()); }
The intent is that this should call #4; that template clearly ought to be more specialized.
Proposed resolution (November, 2017)
Change 13.10.3.5 [temp.deduct.partial] paragraph 4 as follows:
Each type nominated above from the parameter template and the corresponding type from the argument template are used as the types of P and A.If a particular P contains no template-parameters that participate in template argument deduction, that P is not used to determine the ordering.
Change 13.10.3.6 [temp.deduct.type] paragraph 4 as follows:
...If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails. [Note: Under 13.10.3.2 [temp.deduct.call]and 13.10.3.5 [temp.deduct.partial], if P contains no template-parameters that appear in deduced contexts, no deduction is done, so P and A need not have the same form. —end note]
[Accepted as a DR at the February, 2019 meeting.]
The status of an example like the following is unclear:
template<typename T, int N> void g(T (* const (&)[N])(T)) { } int f1(int); int f4(int); char f4(char); void f() { g({ &f1, &f4 }); // OK, T deduced to int, N deduced to 2? }
The problem is the interpretation of 13.10.3.6 [temp.deduct.type] paragraph 4:
In most cases, the types, templates, and non-type values that are used to compose P participate in template argument deduction. That is, they may be used to determine the value of a template argument, and the value so determined must be consistent with the values determined elsewhere. In certain contexts, however, the value does not participate in type deduction, but instead uses the values of template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.
According to 13.10.3.2 [temp.deduct.call] paragraph 1, deduction is performed independently for each element of the initializer list:
Template argument deduction is done by comparing each function template parameter type (call it P) that contains template-parameters that participate in template argument deduction with the type of the corresponding argument of the call (call it A) as described below. If removing references and cv-qualifiers from P gives std::initializer_list<P'> or P'[[N] for some P' and N and the argument is a non-empty initializer list (9.5.5 [dcl.init.list]), then deduction is performed instead for each element of the initializer list, taking P' as a function template parameter type and the initializer element as its argument, and in the P'[N] case, if N is a non-type template parameter, N is deduced from the length of the initializer list. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context (13.10.3.6 [temp.deduct.type]).
Deduction fails for the second element of the list, &f4, because of ambiguity. Does this mean that deduction fails for the entire call, or does the successful deduction of T from the first element and N from the length of the list result in successful deduction for the call?
Notes from the July, 2017 meeting:
CWG determined that the call is well-formed.
Proposed resolution (November, 2018):
Change 13.10.3.2 [temp.deduct.call] paragraph 1 as follows:
Template argument deduction is done by comparing each function template parameter type (call it P) that contains template-parameters that participate in template argument deduction with the type of the corresponding argument of the call (call it A) as described below. If removing references and cv-qualifiers from P gives std::initializer_list<P'> or P' [N] for some P" and N and the argument is a non-empty initializer list (9.5.5 [dcl.init.list]), then deduction is performed instead for each element of the initializer list independently, taking P' as
aseparate function template parameter types P'i and the i-th initializer element asitsthe corresponding argument., and inIn the P' [N] case, if N is a non-type template parameter, N is deduced from the length of the initializer list. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context (13.10.3.6 [temp.deduct.type]). [Example:... template<class T, int N> void n(T const(&)[N], T); n({{1},{2},{3}},Aggr()); // OK, T is Aggr, N is 3 template<typename T, int N> void o(T (* const (&)[N])(T)) { } int f1(int); int f4(int); char f4(char); o({ &f1, &f4 }); // OK, T deduced as int from first element, nothing deduced from second element, N deduced as 2 o({ &f1, static_cast<char(*)(char)>(&f4) }); // error: conflicting deductions for T
[Accepted as a DR at the March, 2018 (Jacksonville) meeting.]
Given an example like
template <class T = int> void foo(T*); void test() { foo(0); // #1 valid? foo<>(0); // #2 valid? }
most/all implementations reject this code. However, the wording of the Standard only invokes 13.10.4 [temp.over] (“Overload resolution”) in cases where there is more than one function or function template, which is not the case here. The current wording would appear to make this well-formed because of the application of 13.10.2 [temp.arg.explicit] paragraph 2. Perhaps overload resolution should apply even when there is a single function template?
Notes from the May, 2015 meeting:
This issue is mostly a duplicate of issue 1582. However, CWG felt that it should be clarified that overload resolution applies in all cases, not just when templates are overloaded, so the issue is being left open to deal with that aspect.
Proposed resolution (November, 2017)
Change 7.6.1.3 [expr.call] paragraph 1, splitting it into three paragraphs, as follows:
A function call is a postfix expression followed by parentheses containing a possibly empty, comma-separated list of initializer-clauses which constitute the arguments to the function. The postfix expression shall have function type or function pointer type. For a call to a non-member function or to a static member function, the postfix expression shall be either an lvalue that refers to a function (in which case the function-to-pointer standard conversion (7.3.4 [conv.func]) is suppressed on the postfix expression), or it shall have function pointer type.
Calling a function through an expression whose function type is different from the function type of the called function's definition results in undefined behavior (9.12 [dcl.link]).For a call to a non-static member function, the postfix expression shall be an implicit (11.4.3 [class.mfct.non.static], 11.4.9 [class.static]) or explicit class member access (7.6.1.5 [expr.ref]) whose id-expression is a function member name, or a pointer-to-member expression (7.6.4 [expr.mptr.oper]) selecting a function member; the call is as a member of the class object referred to by the object expression. In the case of an implicit class member access, the implied object is the one pointed to by this. [Note: A member function call of the form f() is interpreted as (*this).f() (see 11.4.3 [class.mfct.non.static]). —end note]
If a function or member function name is used,
the name can be overloaded (Clause 12 [over]), in which casethe appropriate functionshall be selectedand the validity of the call are determined according to the rules in 12.2 [over.match]. If the selected function is non-virtual, or if the id-expression in the class member access expression is a qualified-id, that function is called. Otherwise, its final overrider (11.7.3 [class.virtual]) in the dynamic type of the object expression is called; such a call is referred to as a virtual function call. [Note: The dynamic type is the type of the object referred to by the current value of the object expression. 11.9.5 [class.cdtor] describes the behavior of virtual function calls when the object expression refers to an object under construction or destruction. —end note]
Add the following to 7.6.1.3 [expr.call] as a new paragraph before the existing paragraph 4:
Calling a function through an expression whose function type is different from the function type of the called function's definition results in undefined behavior (9.12 [dcl.link]).
When a function is called, each parameter (9.3.4.6 [dcl.fct]) shall be initialized...
Change Clause 12 [over] paragraph 2 as follows:
Whenan overloadeda function name is used in a call, whichoverloadedfunction declaration is being referencedisand the validity of the call are determined by comparing the types of the arguments at the point of use with the types of the parameters in theoverloadeddeclarations that are visible at the point of use. This function selection process is called overload resolution...
Change 13.10.4 [temp.over] paragraph 1 as follows:
A function template can be overloaded either by (non-template) functions of its name or by (other) function templates of the same name.When a call tothatthe name of a function or function template is written (explicitly, or implicitly using the operator notation), template argument deduction...
This resolution also resolves issue 2241.
[Accepted as a DR at the February, 2019 meeting.]
According to 14.5 [except.spec] paragraph 8,
The exception specification for an implicitly-declared destructor, or a destructor without a noexcept-specifier, is potentially-throwing if and only if any of the destructors for any of its potentially constructed subojects is potentially throwing.
11.4.4 [special] paragraph 5 defines “potentially constructed subobjects” as follows:
For a class, its non-static data members, its non-virtual direct base classes, and, if the class is not abstract (11.7.4 [class.abstract]), its virtual base classes are called its potentially constructed subobjects.
This leads to the following problem:
class V { public: virtual ~V() noexcept(false); }; class B : virtual V { virtual void foo () = 0; // implicitly defined virtual ~B () noexcept(true); }; class D : B { virtual void foo (); // implicitly defined virtual ~D () noexcept(false); };
Here, D::~D() is throwing but overrides the non-throwing B::~B().
There are similar problems with the deletedness of destructors per 11.4.7 [class.dtor] paragraph 5, which also only considers potentially constructed subobjects.
Proposed resolution (November, 2018):
Change 14.5 [except.spec] paragraph 8 as follows:
The exception specification for an implicitly-declared destructor, or a destructor without a noexcept-specifier, is potentially-throwing if and only if any of the destructors for any of its potentially constructed subobjects ispotentially throwingpotentially-throwing or the destructor is virtual and the destructor of any virtual base class is potentially-throwing.
[Accepted as a DR at the July, 2019 meeting.]
The Standard does not specify whether the argument of __has_cpp_attribute is macro-expanded before being tested to see if it names an attribute or not, and there is implementation divergence.
Notes from the February, 2019 meeting:
CWG observed that a use of an attribute would be macro-expanded, so it seemed reasonable to expect to be able to specify the same macro as the argument to __has_cpp_attribute and get the corresponding result.
Proposed resolution (June, 2019):
Change 15.2 [cpp.cond] paragraph 5 as follows:
Each has-attribute-expression is replaced by a non-zero pp-number matching the form of an integer-literal if the implementation supports an attribute with the name specified by interpreting the pp-tokens, after macro expansion, as an attribute-token, and by 0 otherwise. The program is ill-formed if the pp-tokens do not match the form of an attribute-token.
Change 15.2 [cpp.cond] paragraph 11 as follows:
After all replacements due to macro expansion and evaluations of defined-macro-expressions,andhas-include-expressions, and has_attribute_expressions have been performed, all remaining identifiers and keywords, except for true and false, are replaced with the pp-number 0, and then each preprocessing token is converted into a token. [Note: An alternative token (5.9 [lex.digraph]) is not an identifier, even when its spelling consists entirely of letters and underscores. Therefore it is not subject to this replacement. —end note]
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
There are at least a couple of problems in the description of the various id-expressions in _N4567_.5.1.1 [expr.prim.general]:
Paragraph 4 embodies an incorrect assumption about the syntax of qualified-ids:
The operator :: followed by an identifier, a qualified-id, or an operator-function-id is a primary-expression.
The problem here is that the :: is actually part of the syntax of qualified-id; consequently, “:: followed by... a qualified-id” could be something like “:: ::i,” which is ill-formed. Presumably this should say something like, “A qualified-id with no nested-name-specifier is a primary-expression.”
More importantly, some kinds of id-expressions are not described by _N4567_.5.1.1 [expr.prim.general]. The structure of this section is that the result, type, and lvalue-ness are specified for each of the cases it covers:
paragraph 4 deals with qualified-ids that have no nested-name-specifier
paragraph 7 deals with bare identifiers and with qualified-ids containing a nested-name-specifier that names a class
paragraph 8 deals with qualified-ids containing a nested-name-specifier that names a namespace
This treatment leaves unspecified all the non-identifier unqualified-ids (operator-function-id, conversion-function-id, and template-id), as well as (perhaps) “:: template-id” (it's not clear whether the “:: followed by a qualified-id” case is supposed to apply to template-ids or not). Note also that the proposed resolution of issue 301 slightly exacerbates this problem by removing the form of operator-function-id that contains a tmeplate-argument-list; as a result, references like “::operator+<X>” are no longer covered in _N4567_.5.1.1 [expr.prim.general].
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The description of the use of this found in _N4567_.5.1.1 [expr.prim.general] paragraphs 3 and 4 allow it to appear in the declaration of a non-static member function following the optional cv-qualifier-seq and in the brace-or-equal-initializer of a non-static data member; all other uses are prohibited. These restrictions appear to allow questionable uses of this in several contexts. For example:
template <typename T> struct Fish { static const bool value = true; }; struct Other { int p(); auto q() -> decltype(p()) *; }; class Outer { // The following declares a member function of class Other. // Is this interpreted as Other* or Outer*? friend auto Other::q() -> decltype(this->p()) *; int g(); int f() { extern void f(decltype(this->g()) *); struct Inner { // The following are all within the declaration of Outer::f(). // Is this Outer* or Inner*? static_assert(Fish<decltype(this->g())>::value, ""); enum { X = Fish<decltype(this->f())>::value }; struct Inner2 : Fish<decltype(this->g())> { }; friend void f(decltype(this->g()) *); friend auto Other::q() -> decltype(this->p()) *; }; return 0; } };
struct A { int f(); bool b = [] { struct Local { static_assert(sizeof this->f() == sizeof(int), ""); // A or Local? }; }; };
There is implementation divergence on the treatment of these examples.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The definition of “declarative region” given in _N4868_.6.4.1 [basic.scope.declarative] paragraph 1 is,
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.
According to 9.9 [basic.namespace] paragraph 1,
Unlike other declarative regions, the definition of a namespace can be split over several parts of one or more translation units.
This seems like a misuse of the term “declarative region”; in particular, a name x declared in namespace N in translation unit A cannot be used as an unqualified name in the part of namespace N in translation unit B unless it is also declared in B. See also issue 1884.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The Standard talks about looking up a conversion-type-id as if it were an identifier (_N4868_.6.5.6 [basic.lookup.classref] paragraph 7), but that is not exactly accurate. Presumably it should talk instead about looking up names (if any) appearing in the type-specifier-seq of the conversion-type-id.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
According to _N4868_.6.5.6 [basic.lookup.classref] paragraph 1,
In a class member access expression (7.6.1.5 [expr.ref]), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (13.3 [temp.names]) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a class template.
Given
template<typename T> T end(T); template<typename T> bool Foo(T it) { return it->end < it->end; }
since it is dependent and thus end cannot be looked up in the class of the object expression, it is looked up in the context of the postfix-expression. This lookup finds the function template, making the expression ill-formed.
One possibility might be to limit the lookup to the class of the object expression when the object expression is dependent.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
According to _N4868_.6.5.6 [basic.lookup.classref] paragraph 3,
If the unqualified-id is ~type-name, the type-name is looked up in the context of the entire postfix-expression. If the type T of the object expression is of a class type C, the type-name is also looked up in the scope of class C. At least one of the lookups shall find a name that refers to (possibly cv-qualified) T.
This would apply to an example like
namespace K { template <typename T, typename U = char> struct A { }; A<short> *a; } template <typename T> using A = K::A<short, T>; int main() { K::a->~A<char>(); }
Current implementations, however, only apply the dual lookup when the type-name is not a template-id. The specification should be changed to reflect current practice.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
_N4868_.9.8.2.3 [namespace.memdef] paragraph 3 says,
If a friend declaration in a non-local class first declares a class or function the friend class or function is a member of the innermost enclosing namespace... When looking for a prior declaration of a class or a function declared as a friend, scopes outside the innermost enclosing namespace scope are not considered.It is not clear from this passage how to determine whether an entity is "first declared" in a friend declaration. One question is whether a using-declaration influences this determination. For instance:
void foo(); namespace A{ using ::foo; class X{ friend void foo(); }; }Is the friend declaration a reference to ::foo or a different foo?
Part of the question involves determining the meaning of the word "synonym" in 9.10 [namespace.udecl] paragraph 1:
A using-declaration introduces a name into the declarative region in which the using-declaration appears. That name is a synonym for the name of some entity declared elsewhere.Is "using ::foo;" the declaration of a function or not?
More generally, the question is how to describe the lookup of the name in a friend declaration.
John Spicer: When a declaration specifies an unqualified name, that name is declared, not looked up. There is a mechanism in which that declaration is linked to a prior declaration, but that mechanism is not, in my opinion, via normal name lookup. So, the friend always declares a member of the nearest namespace scope regardless of how that name may or may not already be declared there.
Mike Miller: 6.5.3 [basic.lookup.unqual] paragraph 7 says:
A name used in the definition of a class X outside of a member function body or nested class definition shall be declared in one of the following ways:... [Note: when looking for a prior declaration of a class or function introduced by a friend declaration, scopes outside of the innermost enclosing namespace scope are not considered.]The presence of this note certainly implies that this paragraph describes the lookup of names in friend declarations.
John Spicer: It most certainly does not. If that section described the friend lookup it would yield the incorrect results for the friend declarations of f and g below. I don't know why that note is there, but it can't be taken to mean that that is how the friend lookup is done.
void f(){} void g(){} class B { void g(); }; class A : public B { void f(); friend void f(); // ::f not A::f friend void g(); // ::g not B::g };
Mike Miller: If so, the lookups for friend functions and classes behave differently. Consider the example in 6.5.6 [basic.lookup.elab] paragraph 3:
struct Base { struct Data; // OK: declares nested Data friend class Data; // OK: nested Data is a friend };
If the friend declaration is not a reference to ::foo, there is a related but separate question: does the friend declaration introduce a conflicting (albeit "invisible") declaration into namespace A, or is it simply a reference to an as-yet undeclared (and, in this instance, undeclarable) A::foo? Another part of the example in 6.5.6 [basic.lookup.elab] paragraph 3 is related:
struct Data { friend struct Glob; // OK: Refers to (as yet) undeclared Glob // at global scope. };
John Spicer: You can't refer to something that has not yet been declared. The friend is a declaration of Glob, it just happens to declare it in a such a way that its name cannot be used until it is redeclared.
(A somewhat similar question has been raised in connection with issue 36. Consider:
namespace N { struct S { }; } using N::S; struct S; // legal?
According to 11.3 [class.name] paragraph 2,
A declaration consisting solely of class-key identifier ; is either a redeclaration of the name in the current scope or a forward declaration of the identifier as a class name.
Should the elaborated type declaration in this example be considered a redeclaration of N::S or an invalid forward declaration of a different class?)
(See also issues 95, 136, 139, 143, 165, and 166, as well as paper J16/00-0006 = WG21 N1229.)
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The Standard does not allow overloading of member functions that differ only in their return type (cf enable_if).
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The normative text of _N4868_.12.3 [over.dcl] relies on the term “equivalent,” for which it refers to _N4868_.12.2 [over.load], but the term appears there only in non-normative text. The resolution of this issue should be coordinated with that of issue 1668.
[Accepted at the October, 2021 meeting as part of paper P2314R4.]
According to 5.2 [lex.phases] paragraph 1, in translation phase 1,
Any source file character not in the basic source character set (5.3.1 [lex.charset]) is replaced by the universal-character-name that designates that character.
If a character that is not in the basic character set is preceded by a backslash character, for example
"\á"
the result is equivalent to
"\\u00e1"
that is, a backslash character followed by the spelling of the universal-character-name. This is different from the result in C99, which accepts characters from the extended source character set without replacing them with universal-character-names.
See also issue 1335.
Additional note (February, 2022):
P2314R4 Character sets and encodings (approved in October, 2021) effected changes so that extended characters are no longer translated to UCNs in phase 1.
[Addressed by paper P2314R4, adopted at the October, 2021 plenary.]
According to 5.2 [lex.phases] paragraph 1, concatenation of adjacent string literals is performed in translation phase 6, after conversion of the literal values to the execution character set. However, 5.13.5 [lex.string] paragraph 11 indicates that the interpretation of the string contents is dependent on the encoding-prefixes specified for the literals being concatenated:
In translation phase 6 (5.2 [lex.phases]), adjacent string-literals are concatenated. If both string-literals have the same encoding-prefix, the resulting concatenated string-literal has that encoding-prefix. If one string-literal has no encoding-prefix, it is treated as a string-literal of the same encoding-prefix as the other operand. If a UTF-8 string literal token is adjacent to a wide string literal token, the program is ill-formed. Any other concatenations are conditionally-supported with implementation-defined behavior. [Note: This concatenation is an interpretation, not a conversion. Because the interpretation happens in translation phase 6 (after each character from a string-literal has been translated into a value from the appropriate character set), a string-literal's initial rawness has no effect on the interpretation or well-formedness of the concatenation. —end note]
This seems to indicate that string-literals with different encoding-prefixes are separately converted and then joined, potentially resulting in strings containing code unit sequences corresponding to different character encodings. This reading would contradict the intent, expressed in adjacent table, that, e.g., u"a" "b" means the same as u"ab".
There is implementation divergence in the handling of this specification.
Phases 5 and 6 cannot simply be reversed, because interpretation of escape sequences must precede concatenation, as specified later in the same paragraph:
Characters in concatenated strings are kept distinct.
[Example:
"\xA" "B"contains the two characters '\xA' and 'B' after concatenation (and not the single hexadecimal character '\xAB'). —end example]
Richard Smith suggested here that "we should remove phases 5 and 6 entirely, parse one or more string-literal tokens as a string literal expression, and only perform the translation from the contents of the string literal tokens into characters in the execution character set as part of specifying the semantics of a string literal expression."
[ Resolved by P2314R4, adopted in October, 2021. ]
According to 5.3.1 [lex.charset] paragraph 2,
If the hexadecimal value for a universal-character-name corresponds to a surrogate code point (in the range 0xD800-0xDFFF, inclusive), the program is ill-formed. 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.
These restrictions should not apply to comment text. Arguably the prohibitions of control characters and characters in the basic character set already do not apply, as they require that the preprocessing tokens for literals have already been recognized; this occurs in phase 3, which also replaces comments with single spaces. However, the prohibition of surrogate code points is not so limited and might conceivably be applied within comments.
Probably the most straightforward way of addressing this problem would be simply to state in 5.4 [lex.comment] that character sequences that resemble universal-character-names are not recognized as such within comment text.
Additional note (February, 2022):
P2314R4 Character sets and encodings (approved in October, 2021) effected changes so that extended characters are no longer translated to UCNs in phase 1.
[Accepted at the June, 2021 meeting as part of paper P1949R7 (C++ Identifier Syntax using Unicode Standard Annex 31).]
According to 5.11 [lex.name] paragraph 1,
Each universal-character-name in an identifier shall designate a character whose encoding in ISO 10646 falls into one of the ranges specified in _N4606_.E.1 [charname.allowed].
However, identifier-nondigit is also used in the grammar for pp-number. Should this restriction also be understood to apply in that non-identifier context?
[Accepted at the November, 2020 meeting as part of paper P2029R4.]
According to 5.13.3 [lex.ccon] paragraph 4,
The escape \ooo consists of the backslash followed by one, two, or three octal digits that are taken to specify the value of the desired character. The escape \xhhh consists of the backslash followed by x followed by one or more hexadecimal digits that are taken to specify the value of the desired character. There is no limit to the number of digits in a hexadecimal sequence. A sequence of octal or hexadecimal digits is terminated by the first character that is not an octal digit or a hexadecimal digit, respectively. The value of a character literal is implementation-defined if it falls outside of the implementation-defined range defined for char (for literals with no prefix), char16_t (for literals prefixed by 'u'), char32_t (for literals prefixed by 'U'), or wchar_t (for literals prefixed by 'L').
It is not clearly stated whether the “desired character” being specified reflects the source or the target encoding. This particularly affects UTF-8 string literals (5.13.5 [lex.string] paragraph 7) :
A string literal that begins with u8, such as u8"asdf", is a UTF-8 string literal and is initialized with the given characters as encoded in UTF-8.
For example, assuming the source encoding is Latin-1, is u8"\xff" supposed to specify a three-byte string whose first two bytes are 0xc3 0xbf (the UTF-8 encoding of \u00ff) or a two-byte string whose first byte has the value 0xff? (At least some current implementations assume the latter interpretation.)
Notes from the September, 2013 meeting:
The second interpretation (that the escape sequence specifies the execution-time code unit) is intended.
[Accepted at the November, 2020 meeting as part of paper P2029R4.]
The meaning of a numeric escape appearing in a UTF-8 character literal is not clear. 5.13.3 [lex.ccon] paragraph 3 assumes that the contents of the quoted string is a character with an ISO 10646 code point value, which is not necessarily the case with a numeric escape, and paragraph 8 could be read to indicate that a numeric escape specifies the actual runtime value of the object rather than a Unicode code point. In addition, paragraph 8 only specifies the result for unprefixed and wide-character literals, not for UTF-8 literals, so that could be read as indicating that a numeric escape in a UTF-8 character literal is undefined behavior (i.e., not defined by the Standard).
Notes from the August, 2017 teleconference:
An escape sequence in a UTF-8 character literal should be ill-formed.
[Adopted at the November, 2020 meeting as part of paper P2029R4.]
According to 5.13.3 [lex.ccon] paragraphs 3-5, a Unicode character literal “containing multiple c-chars is ill-formed.” However, it is not clear in what phase of translation that restriction applies.
One possible resolution would be to add a note saying that the pp-token is formed according to the grammar and the restriction to a single c-char is checked in phase 7.
[Accepted at the July, 2022 meeting.]
Subclause 5.13.3 [lex.ccon] does not specify how the characters in an octal-escape-sequence or hexadecimal-escape-sequence are interpreted to obtain the integer value v that is used in bullet 3.2:
- ...
- A character-literal with a c-char-sequence consisting of a single numeric-escape-sequence that specifies an integer value v has a value as follows:
- ...
Proposed resolution (approved by CWG 2022-03-11):
- A character-literal with a c-char-sequence consisting of a single numeric-escape-sequence
that specifies an integer value vhas a value as follows:
- Let v be the integer value represented by the octal number comprising the sequence of octal-digits in an octal-escape-sequence or by the hexadecimal number comprising the sequence of hexadecimal-digits in a hexadecimal-escape-sequence.
- If v does not exceed the range of representable values of the character-literal's type, then the value is v.
- ...
- Each numeric-escape-sequence (5.13.3 [lex.ccon])
that specifies an integer value vcontributes a single code unit with a value as follows:
- Let v be the integer value represented by the octal number comprising the sequence of octal-digits in an octal-escape-sequence or by the hexadecimal number comprising the sequence of hexadecimal-digits in a hexadecimal-escape-sequence.
- If v does not exceed the range of representable values of the string-literal's array element type, then the value is v.
- ...
[Accepted at the November, 2020 meeting as part of paper P2029R4.]
5.13.5 [lex.string] paragraph 5 reads
Escape sequences and universal-character-names in string literals have the same meaning as in character literals, except that the single quote ' is representable either by itself or by the escape sequence \', and the double quote " shall be preceded by a \. In a narrow string literal, a universal-character-name may map to more than one char element due to multibyte encoding.
The first sentence refers us to 5.13.3 [lex.ccon], where we read in the first paragraph that "An ordinary character literal that contains a single c-char has type char [...]." Since the grammar shows that a universal-character-name is a c-char, something like '\u1234' must have type char (and thus be a single char element); in paragraph 5, we read that "A universal-character-name is translated to the encoding, in the execution character set, of the character named. If there is no such encoding, the universal-character-name is translated to an implemenation-defined encoding."
This is in obvious contradiction with the second sentence. In addition, I'm not really clear what is supposed to happen in the case where the execution (narrow-)character set is UTF-8. Consider the character \u0153 (the oe in the French word oeuvre). Should '\u0153' be a char, with an "error" value, say '?' (in conformance with the requirement that it be a single char), or an int, with the two char values 0xC5, 0x93, in an implementation defined order (in conformance with the requirement that a character representable in the execution character set be represented). Supposing the former, should "\u0153" be the equivalent of "?" (in conformance with the first sentence), or "\xC5\x93" (in conformance with the second).
Notes from October 2003 meeting:
We decided we should forward this to the C committee and let them resolve it. Sent via e-mail to John Benito on November 14, 2003.
Reply from John Benito:
I talked this over with the C project editor, we believe this was handled by the C committee before publication of the current standard.
WG14 decided there needed to be a more restrictive rule for one-to-one mappings: rather than saying "a single c-char" as C++ does, the C standard says "a single character that maps to a single-byte execution character"; WG14 fully expect some (if not many or even most) UCNs to map to multiple characters.
Because of the fundamental differences between C and C++ character types, I am not sure the C committee is qualified to answer this satisfactorily for WG21. WG14 is willing to review any decision reached for compatibility.
I hope this helps.
(See also issue 912 for a related question.)
[ Resolved by issue 2494, adopted in February, 2022. ]
The description in 6.3 [basic.def.odr] paragraph 6 of when entities can be multiply-declared in a program does not, but should, discuss variable templates.
[Accepted at the February, 2022 meeting.]
According to 6.3 [basic.def.odr] paragraph 10,
Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement (8.5.2 [stmt.if]); no diagnostic required.
This wording could be interpreted as allowing multiple definitions of non-inline variables and functions if they are not odr-used. That is presumably not the intent.
Notes from the August, 2021 teleconference:
CWG observed that there is a similar problem in paragraph 13. See also issue 1849.
Proposed resolution, December, 2021:
Change 6.3 [basic.def.odr] paragraph 1 as follows:
Each of the following is termed a definable item:
a class type ( Clause 11 [class]),
an enumeration type (9.8.1 [dcl.enum]),
a function (9.3.4.6 [dcl.fct]),
a variable (6.1 [basic.pre]),
a templated entity (13.1 [temp.pre]),
a default argument for a parameter (for a function in a given scope) (9.3.4.7 [dcl.fct.default]), or
a default template argument (13.2 [temp.param]).
No translation unit shall contain more than one definition of any
variable, function, class type, enumeration type, template, default argument for a parameter (for a function in a given scope), or default template argumentdefinable item.
Change 6.3 [basic.def.odr] paragraph 10 as follows:
Every program shall containexactlyat least one definition of everynon-inlinefunction or variable that is odr-used in that program outside of a discarded statement (8.5.2 [stmt.if]); no diagnostic required. The definition...
Change 6.3 [basic.def.odr] paragraph 13 as follows:
There can be more than one definition of a
class type (Clause 11 [class]),
enumeration type (9.8.1 [dcl.enum]),
inline function or variable (9.2.8 [dcl.inline]),
templated entity (13.1 [temp.pre]),
default argument for a parameter (for a function in a given scope) (9.3.4.7 [dcl.fct.default]), or
default template argument (13.2 [temp.param])
in a program provided that each definition appears in a different translation unit and the definitions satisfy the following requirements.For any definable item D with definitions in multiple translation units,
if D is a non-inline non-templated function or variable, or
if the definitions in different translation units do not satisfy the following requirements,
the program is ill-formed; a diagnostic is required only if the definable item is attached to a named module and a prior definition is reachable at the point where a later definition occurs. Given such an
entity D defined in more than one translation unititem, for all definitions of D, or, if D is an unnamed enumeration, for all definitions of D that are reachable at any given program point, the following requirements shall be satisfied...
Delete 6.3 [basic.def.odr] paragraph 15:
If these definitions do not satisfy these requirements, then the program is ill-formed; a diagnostic is required only if the entity is attached to a named module and a prior definition is reachable at the point where a later definition occurs.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
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, 6.4 [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 6.4 [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 6.4 [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” (_N4868_.6.5.6 [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 6.4 [basic.scope] at the same time, as all other kinds of scopes are described there.)
Proposed resolution (November, 2006):
Change 6.4 [basic.scope] paragraph 1 as follows:
Every name is introduced in some portion of program text called a declarative region, which isthe 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 entitya statement, block, function declarator, function-definition, class, handler, template-declaration, template-parameter-list of a template template-parameter, or namespace. In general, each particular nameis validmay 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 6.4 [basic.scope] paragraph 3 as follows:
The names declared by a declaration are introduced into thescope in which the declaration occursdeclarative region that directly encloses the declaration, except that declaration-statements, function parameter names in the declarator of a function-definition, exception-declarations (6.4.3 [basic.scope.block]), the presence of a friend specifier (11.8.4 [class.friend]), certain uses of the elaborated-type-specifier (9.2.9.5 [dcl.type.elab]), and using-directives (9.9.4 [namespace.udir]) alter this general behavior.
Change 6.4.3 [basic.scope.block] paragraphs 1-3 and add a new paragraph 4 before the existing paragraph 4 as follows:
A name declared in a block (8.4 [stmt.block]) is local to that block. Its potential scope begins at its point of declaration (6.4.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 (8.4 [stmt.block]). Such a name is local to the block.The
potential scopedeclarative region of a function parameter name(including one appearingin the declarator of a function-definition or in a lambda-parameter-declaration-clause)or of a function-local predefined variable in a function definition (9.6 [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 nameis the entire function definition or lambda-expression. Such a name is local to the function definition and shall not be redeclared intheany outermost block of thefunction definition nor in the outermost block of any handler associated with a function-try-blockfunction-body (including handlers of a function-try-block) or lambda-expression.
The name in a catch exception-declarationThe 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 (6.4.2 [basic.scope.pdecl]) and ends at the end of its declarative region.
Change _N4868_.6.4.5 [basic.funscope] as indicated:
Labels (8.2 [stmt.label]) have function scope and may be used anywhere in the function in which they are declared except in members of local classes (11.6 [class.local]) of that function. Only labels have function scope.
Change 8.9 [stmt.dcl] paragraph 1 as follows:
A declaration statement introduces one or more new
identifiersnames into a block; it has the formdeclaration-statement:
block-declaration
[Note: If
an identifiera 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 (_N4868_.6.4.10 [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.]
Notes from the October, 2015 meeting:
This issue has been returned to "drafting" status to be reconciled with changes to the underlying existing text.
[Accepted at the February, 2022 meeting.]
The changes of P1787R6 inadvertently made constructs like
if (int a = 1) if (int a = 1) ...
ill-formed.
Proposed resolution (September, 2021):
Change 6.4.3 [basic.scope.block] bullet 2.2 as follows:
If a declaration whose target scope is the block scope S of a
compound-statement of a lambda-expression, function-body, or function-try-block,
substatement of a selection or iteration statement that is not itself a selection or iteration statement, or
handler of a function-try-block
potentially conflicts with a declaration whose target scope is the parent scope of S, the program is ill-formed.
(See editorial issue 4843.)
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Point 2 of the rules of class scope in 6.4.7 [basic.scope.class] paragraph 1 says,
A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule.
It is not clear that this provision does not apply to names appearing in function bodies, default arguments, exception-specifications, and brace-or-equal-initializers. It is also not clear what it means to “re-evaluate” a name.
One possible approach to this problem would be to say that all names declared in a class are visible throughout the class and simply make it ill-formed to refer to a name that has not been declared yet in the contexts in which that is problematic, such as types and template arguments.
In addition, the fourth point says,
A name declared within a member function hides a declaration of the same name whose scope extends to or past the end of the member function's class.
This rule is unneeded, as it simply restates the normal hiding rule in _N4868_.6.4.1 [basic.scope.declarative] paragraph 1:
The scope of a declaration is the same as its potential scope unless the potential scope contains another declaration of the same name. In that case, the potential scope of the declaration in the inner (contained) declarative region is excluded from the scope of the declaration
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
[Accepted as a DR at the February, 2019 meeting.]
The first four paragraphs of 6.4.7 [basic.scope.class] are somewhat redundant. In particular:
The normative paragraphs are 4 and 2.
Paragraph 1 is subsumed by paragraph 4.
Paragraph 3 follows from existing rules for name hiding.
This is editorial issue 1169.
Proposed resolution (November, 2018):
In 6.4.7 [basic.scope.class], delete paragraph 1, move paragraph 4 to the beginning, and make paragraph 3 a note:
The potential scope of a name declared in a class consists not only of the declarative region following the name's point of declaration, but also of all complete-class contexts (11.4 [class.mem]) of that class.The potential scope of a declaration that extends to or past the end of a class definition also extends to the regions defined by its member definitions, even if the members are defined lexically outside the class (this includes static data member definitions, nested class definitions, and member function definitions, including the member function body and any portion of the declarator part of such definitions which follows the declarator-id, including a parameter-declaration-clause and any default arguments (9.3.4.7 [dcl.fct.default])).
A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule.
[Note: A name declared within a member function hides a declaration of the same name whose scope extends to or past the end of the member function's class (_N4868_.6.4.10 [basic.scope.hiding]). —end note]
The potential scope of a declaration that extends to or past the end of a class definition also extends to the regions defined by its member definitions, even if the members are defined lexically outside the class (this includes static data member definitions, nested class definitions, and member function definitions, including the member function body and any portion of the declarator part of such definitions which follows the declarator-id, including a parameter-declaration-clause and any default arguments (9.3.4.7 [dcl.fct.default])).
Additional note, March, 2019:
The resolution emoves the rule that a class member name can be found by unqualified lookup prior to its point of definition in complete-class contexts, at least in non-defining member declarations:
struct X {
void f(int n = k); // was valid, now ill-formed
static int k;
};
Relatedly, the "member definitions" rule (formerly p4, now p2) that was used to justify the removal of p1 is wrong (both before and after that change):
struct A {
void f(B b) {} // was always (incorrectly) valid
struct B {};
};
For these reasons, this issue has been returned to "drafting" status.
[Accepted at the July, 2022 meeting.]
Consider:
typedef int T;
struct A {
struct B {
static T t;
};
typedef float T; // IFNDR?
};
Subclause 6.5.2 [class.member.lookup] paragraph 6 specifies:
The result of the search is the declaration set of S(N, T). If it is an invalid set, the program is ill-formed. If it differs from the result of a search in T for N from immediately after the class-specifier of T, the program is ill-formed, no diagnostic required.
It is unclear whether the lookup of T inside A::B is subject to the "if it differs" rule, given that the class-specifier of A::B ends before introducing A::T.
Proposed resolution (approved by CWG 2022-05-06):
Change in 6.5.2 [class.member.lookup] paragraph 6 as follows:
If it differs from the result of a search in T for Nfrom immediately after the class-specifierin a complete-class context of T, the program is ill-formed, no diagnostic required.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The current description of unqualified name lookup in 6.5.3 [basic.lookup.unqual] paragraph 8 does not correctly handle complex cases of nesting. The Standard currently reads,
A name used in the definition of a function that is a member function (9.3) of a class X shall be declared in one of the following ways:In particular, this formulation does not handle the following example:
- before its use in the block in which it is used or in an enclosing block (6.3), or
- shall be a member of class X or be a member of a base class of X (10.2), or
- if X is a nested class of class Y (9.7), shall be a member of Y, or shall be a member of a base class of Y (this lookup applies in turn to Y's enclosing classes, starting with the innermost enclosing class), or
- if X is a local class (9.8) or is a nested class of a local class, before the definition of class X in a block enclosing the definition of class X, or
- if X is a member of namespace N, or is a nested class of a class that is a member of N, or is a local class or nested class within a local class of a function that is a member of N, before the member function definition, in namespace N or in one of N's enclosing namespaces.
struct outer { static int i; struct inner { void f() { struct local { void g() { i = 5; } }; } }; };Here the reference to i is from a member function of a local class of a member function of a nested class. Nothing in the rules allows outer::i to be found, although intuitively it should be found.
A more comprehensive formulation is needed that allows traversal of any combination of blocks, local classes, and nested classes. Similarly, the final bullet needs to be augmented so that a function need not be a (direct) member of a namespace to allow searching that namespace when the reference is from a member function of a class local to that function. That is, the current rules do not allow the following example:
int j; // global namespace struct S { void f() { struct local2 { void g() { j = 5; } }; } };
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
There seems to be some confusion in the Standard regarding the relationship between 6.5.3 [basic.lookup.unqual] (Unqualified name lookup) and 6.5.4 [basic.lookup.argdep] (Argument-dependent lookup). For example, 6.5.3 [basic.lookup.unqual] paragraph 3 says,
The lookup for an unqualified name used as the postfix-expression of a function call is described in 6.5.4 [basic.lookup.argdep].
In other words, nothing in 6.5.3 [basic.lookup.unqual] applies to function names; the entire lookup is described in 6.5.4 [basic.lookup.argdep].
6.5.4 [basic.lookup.argdep] does not appear to share this view of its responsibility. The closest it comes is in 6.5.4 [basic.lookup.argdep] paragraph 2a:
...the set of declarations found by the lookup of the function name is the union of the set of declarations found using ordinary unqualified lookup and the set of declarations found in the namespaces and classes associated with the argument types.
Presumably, "ordinary unqualified lookup" is a reference to the processing described in 6.5.3 [basic.lookup.unqual], but, as noted above, 6.5.3 [basic.lookup.unqual] explicitly precludes applying that processing to function names. The details of "ordinary unqualified lookup" of function names are not described anywhere.
The other clauses that reference 6.5.4 [basic.lookup.argdep], clauses Clause 12 [over] and Clause 13 [temp], are split over the question of the relationship between 6.5.3 [basic.lookup.unqual] and 6.5.4 [basic.lookup.argdep]. 12.2.2.2.2 [over.call.func] paragraph 3, for instance, says
The name is looked up in the context of the function call following the normal rules for name lookup in function calls (6.5.4 [basic.lookup.argdep]).
I.e., this reference assumes that 6.5.4 [basic.lookup.argdep] is self-contained. The same is true of 12.2.2.3 [over.match.oper] paragraph 3, second bullet:
The set of non-member candidates is the result of the unqualified lookup of operator@ in the context of the expression according to the usual rules for name lookup in unqualified function calls (6.5.4 [basic.lookup.argdep]), except that all member functions are ignored.
On the other hand, however, 13.8.4.2 [temp.dep.candidate] paragraph 1 explicitly assumes that 6.5.3 [basic.lookup.unqual] and 6.5.4 [basic.lookup.argdep] are both involved in function name lookup and do different things:
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 (6.5.3 [basic.lookup.unqual], 6.5.4 [basic.lookup.argdep]) except that:
- For the part of the lookup using unqualified name lookup (6.5.3 [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 (6.5.4 [basic.lookup.argdep]), only function declarations with external linkage found in either the template definition context or the template instantiation context are found.
Suggested resolution:
Change 6.5.3 [basic.lookup.unqual] paragraph 1 from
...name lookup ends as soon as a declaration is found for the name.
to
...name lookup ends with the first scope containing one or more declarations of the name.
Change the first sentence of 6.5.3 [basic.lookup.unqual] paragraph 3 from
The lookup for an unqualified name used as the postfix-expression of a function call is described in 6.5.4 [basic.lookup.argdep].
to
An unqualified name used as the postfix-expression of a function call is looked up as described below. In addition, argument-dependent lookup (6.5.4 [basic.lookup.argdep]) is performed on this name to complete the resulting set of declarations.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Although 6.4.9 [basic.scope.temp] now describes the scope of a template parameter, the description of unqualified name lookup in 6.5.3 [basic.lookup.unqual] do not cover uses of template parameter names. The note in 6.5.3 [basic.lookup.unqual] paragraph 16 says,
the rules for name lookup in template definitions are described in 13.8 [temp.res].
but the rules there cover dependent and non-dependent names, not template parameters themselves.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Issue 1906 discussed unqualified lookup in friend declarations of class member functions, and CWG decided to reaffirm the existing specification without change. However, there is a similar issue regarding friend declarations of namespace-scope functions. According to 6.5.3 [basic.lookup.unqual] paragraph 9,
Name lookup for a name used in the definition of a friend function (11.8.4 [class.friend]) defined inline in the class granting friendship shall proceed as described for lookup in member function definitions. If the friend function is not defined in the class granting friendship, name lookup in the friend function definition shall proceed as described for lookup in namespace member function definitions.
In particular, “as described for lookup in member function definitions” does not consider names declared in the namespace of the friend function, and non-defining friend declarations of namespace-scope functions are not described at all. There is implementation divergence on these points. For example:
namespace N { typedef int type; void f(type); void g(type); void h(type); } class C { typedef N::type N_type; friend void N::f(type) { } // Ill-formed: cannot define namespace friend friend void N::g(type); // Unclear whether type is found or not friend void N::h(N_type); // Unclear whether N_type is found or not };
Notes from the November, 2018 meeting:
CWG agreed that the lookup for functions in namespaces should be similar to that for class member functions.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
N3690 comment CA 21Consider the following example:
template <typename T> struct B { }; namespace N { namespace L { template <int> void A(); } namespace M { template <int> struct A { typedef int y; }; } using namespace L; using namespace M; } B<N::/*template */A<0>::y> (x);
Which A is referenced in the last line? According to 6.5.5 [basic.lookup.qual] paragraph 1,
If a :: scope resolution operator in a nested-name-specifier is not preceded by a decltype-specifier, lookup of the name preceding that :: considers only namespaces, types, and templates whose specializations are types.
It is not clear whether this applies to the example or not, and the interpretation of the < token depends on the result of the lookup.
Notes from the September, 2013 meeting:
The restricted lookup mentioned in 6.5.5 [basic.lookup.qual] paragraph 1 is based on a one-token lookahead; because the next token following A in the example is not ::, the restricted lookup does not apply, and the result is ambiguous. Uncommenting the template keyword in the example does not affect the lookup.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Issue 125 concerned an example like
friend A::B::C();
which might be parsed as either
friend A (::B::C)();
or
friend A::B (::C)();
Its resolution attempted to make such constructs unambiguously ill-formed by allowing any identifier, not just namespaces and types, to appear in a nested-name-specifier, apparently on the assumption that C in this case would become part of an ill-formed nested-name-specifier instead of being taken as the unqualified-id in a qualified-id. Unfortunately, the current specification does not implement that intent, leaving both parses as valid possibilities.
A different approach might be to adjust the specification of the lookup of names appearing in nested-name-specifiers from
If a :: scope resolution operator in a nested-name-specifier is not preceded by a decltype-specifier, lookup of the name preceding that :: considers only namespaces, types, and templates whose specializations are types. If the name found does not designate a namespace or a class, enumeration, or dependent type, the program is ill-formed.
to
Lookup of an identifier followed by a :: scope resolution operator considers only namespaces, types, and templates whose specializations are types. If an identifer, template-id, or decltype-specifier is followed by a :: scope resolution operator, the name shall designate a namespace, class, enumeration, or dependent type, and shall form part of a nested-name-specifier.
This approach would also remove the need for deferred lookup for template-ids and thus resolve issue 1771.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Both 6.5.5.2 [class.qual] and 6.5.5.3 [namespace.qual] specify that some lookups are to be performed “in the context of the entire postfix-expression,” ignoring the fact that qualified-ids can appear outside of expressions.
It was suggested in document J16/05-0156 = WG21 N1896 that these uses be changed to “the context in which the qualified-id occurs,” but it isn't clear that this formulation adequately covers all the places a qualified-id can occur.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
It makes no sense for a user to write a class template that contains a using-declaration that is sometimes an inheriting constructor declaration and sometimes pulls in a named value from a base class; These are sufficiently different things that we're doing them a disservice by conflating them. We're also doing a disservice to all readers of the code, by allowing an inheriting constructor to be written using a syntax that does not look like one.
In an inheriting constructor using-declaration, the nested-name-specifier and the unqualified-id should be required to be the same identifier.
Notes from the May, 2015 meeting:
The consensus of CWG was that the same name should be required when the nested-name-specifier is dependent and in the using-declaration case but should be allowed to be different in all other cases. See also issues 156 and 399.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The standard says that an unnamed class or enum definition can be given a "name for linkage purposes" through a typedef. E.g.,
typedef enum {} E; extern E *p;
can appear in multiple translation units.
How about the following combination?
// Translation unit 1: struct S; extern S *q; // Translation unit 2: typedef struct {} S; extern S *q;
Is this valid C++?
Also, if the answer is "yes", consider the following slight variant:
// Translation unit 1: struct S {}; // <<-- class has definition extern S *q; // Translation unit 2: typedef struct {} S; extern S *q;
Is this a violation of the ODR because two definitions of type S consist of differing token sequences?
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The following declarations are allowed within a translation unit:
struct S; enum { S };
However, 6.6 [basic.link] paragraph 9 seems to say these two declarations cannot appear in two different translation units. That also would mean that the inclusion of a header containing the above in two different translation units is not valid C++.
I suspect this is an oversight and that users should be allowed to have the declarations above appear in different translation units. (It is a fairly common thing to do, I think.)
Mike Miller: I think you meant "enum E { S };" -- enumerators only have external linkage if the enumeration does (6.6 [basic.link] paragraph 4) , and 6.6 [basic.link] paragraph 9 only applies to entities with external linkage.
I don't remember why enumerators were given linkage; I don't think it's necessary for mangling non-type template arguments. In any event, I can't think why cross-TU name collisions between enumerators and other entities would cause a problem, so I guess a change here would be okay. I can think of three changes that would have that effect:
Daveed Vandevoorde: I don't think any of these are sufficient in the sense that the problem isn't limited to enumerators. E.g.:
struct X; extern void X();shouldn't create cross-TU collisions either.
Mike Miller: So you're saying that cross-TU collisions should only be prohibited if both names denote entities of the same kind (both functions, both objects, both types, etc.), or if they are both references (regardless of what they refer to, presumably)?
Daveed Vandevoorde: Not exactly. Instead, I'm saying that if two entities (with external linkage) can coexist when they're both declared in the same translation unit (TU), then they should also be allowed to coexist when they're declared in two different translation units.
For example:
int i; void i(); // ErrorThis is an error within a TU, so I don't see a reason to make it valid across TUs.
However, "tag names" (class/struct/union/enum) can sometimes coexist with identically named entities (variables, functions & enumerators, but not namespaces, templates or type names).
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
According to 6.6 [basic.link] paragraph 6,
The name of a function declared in block scope and the name of a variable declared by a block scope extern declaration have linkage. If there is a visible declaration of an entity with linkage having the same name and type, ignoring entities declared outside the innermost enclosing namespace scope, the block scope declaration declares that same entity and receives the linkage of the previous declaration. If there is more than one such matching entity, the program is ill-formed.
It is not clear how declarations that are in the lexical scope of the block-scope declaration but not members of the nearest enclosing namespace (see 9.9.2 [namespace.def] paragraph 6) should be treated. (For example, the definition of the function in which the block extern appears might be defined in an enclosing namespace, with a visible declaration of the name in that namespace, or it might be a member function of a class containing a member function of the name being declared.) Should such declarations be produce an error or should the lexically-nearer declaration simply be ignored? There is implementation divergence on this point.
Proposed resolution, April, 2019:
Change 6.6 [basic.link] paragraph 8 as follows:
The name of aA function declared in block scopeand the name ofor a variable declared by a block scope extern declarationhaveis a member of the innermost enclosing namespace and its name has linkage. If such a declaration is attached to a named module, the program is ill-formed. If there is avisibleprior declaration ofan entity with linkage, ignoring entities declared outside the innermost enclosing namespace scopethat name in that namespace, such that the block scope declaration would be a (possibly ill-formed) redeclaration if the two declarations appeared in the same declarative region, the block scope declaration declares that same entity and its name receives the linkage of the previous declaration.If there is more than one such matching entity, the program is ill-formed.Otherwise,if no matching entity is found,the block scope entity receivesexternalthe linkage of the innermost enclosing namespace.If, within a translation unit, the same entity is declared with both internal and external linkage, the program is ill-formed.[Example:static void f(); extern "C" void h(); static int i = 0; // #1 void g() { extern void f(); // internal linkage extern void h(); // C language linkage extern void k(); // ::k, external linkage int i; // #2: i has no linkage { extern void f(); // internal linkage extern int i; // #3:externalinternal linkage, ill-formed} }
Without the declaration at line #2, the declaration at line #3 would link with the declaration at line #1. Because the declaration with internal linkage is hidden, however, #3 is given external linkage, making the program ill-formed.Even though the declaration at line #2 hides the declaration at line #1, the declaration at line #3 still redeclares #1 and receives internal linkage. —end example]
Change 6.6 [basic.link] paragraph 9 as follows:
When aA block scope declaration of an entity with linkageis not found to refer to some other declaration, then that entity is a member of the innermost enclosing namespace. However such a declarationdoes notintroduceby itself make the member name visible to any form of name lookup in its namespace scope or eligible for declaration by qualified-id. [Example:namespace X { void p() { q(); // error: q not yet declared extern void q(); // q is a member of namespace X extern void r(); // r is a member of namespace X } void middle() { q(); // error: q notyet declaredvisible to name lookup } void q() { /* ... */ } // definition of X::q } void q() { /* ... */ } // some other, unrelated q void X::r() { /* ... */ } // error: r cannot be declared by qualified-id—end example]
Additional note, July, 2019:
The proposed resolution removes the sentence from the existing text reading:
If, within a translation unit, the same entity is declared with both internal and external linkage, the program is ill-formed.
Such a sitution can still arise, however:
void f() { void g(); // external linkage } static void g(); // internal linkage
The remaining wording dealing with linkage agreement, 9.2.2 [dcl.stc] paragraph 6,
The linkages implied by successive declarations for a given entity shall agree. That is, within a given scope, each declaration declaring the same variable name or the same overloading of a function name shall imply the same linkage.
does not apply to this example because the declarations are not within the same scope.
The issue has been returned to "review" status to allow consideration of how best to address this problem.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
According to 6.6 [basic.link] paragraph 9,
Two names that are the same (6.1 [basic.pre]) and that are declared in different scopes shall denote the same variable, function, type, enumerator, template or namespace if
both names have external linkage or else both names have internal linkage and are declared in the same translation unit; and
both names refer to members of the same namespace or to members, not by inheritance, of the same class; and
when both names denote functions, the parameter-type-lists of the functions (9.3.4.6 [dcl.fct]) are identical; and
when both names denote function templates, the signatures (13.7.7.2 [temp.over.link]) are the same.
This is not as clear as it should be. The intent is that this rule prevents declaring a name with extenal linkage to be, for instance, a type in one translation unit and a namespace in a different translation unit. It has instead been read as begging the question of what it means for two entities to be the same. The wording should be tweaked to make the intention clear. Among other things, it should be clarified that "declared in" refers to the namespace of which the name is a member, not the lexical scope in which the declaration appears (which affects friend declarations, block-scope extern declarations, and elaborated-type-specifiers).
There is a similar restriction in _N4868_.6.4.1 [basic.scope.declarative] paragraph 4 dealing with declarations within a single declarative region, while 6.6 [basic.link] paragraph 9 deals with names that are associated via linkage. The relationship between these complementary requirements may need to be clarified as well.
See also issue 2165.
Additional note, March, 2019:
6.6 [basic.link] paragraph 11 concludes by saying,
A violation of this rule on type identity does not require a diagnostic.
Presumably a diagnostic should be required if the differing types appear within a single translation unit.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Issue 1603 dealt with omissions in the application of the change to give unnamed namespaces internal linkage, but its resolution overlooked a couple of items. According to 6.6 [basic.link] paragraph 6,
The name of a function declared in block scope and the name of a variable declared by a block scope extern declaration have linkage. If there is a visible declaration of an entity with linkage having the same name and type, ignoring entities declared outside the innermost enclosing namespace scope, the block scope declaration declares that same entity and receives the linkage of the previous declaration. If there is more than one such matching entity, the program is ill-formed. Otherwise, if no matching entity is found, the block scope entity receives external linkage.
The last sentence should say, “...receives the linkage of the innermost enclosing namespace.”
Also, 6.6 [basic.link] paragraph 8 says,
A type without linkage shall not be used as the type of a variable or function with external linkage unless
...
the entity is declared within an unnamed namespace (9.9.2 [namespace.def]), or
...
This bullet cannot occur, since a function or variable declared within an unnamed namespace cannot have external linkage.
According to 6.7.2 [intro.object] paragraph 3,
If a complete object is created (7.6.2.8 [expr.new]) in storage associated with another object e of type “array of N unsigned char” or of type “array of N std::byte” (17.2.1 [cstddef.syn]), that array provides storage for the created object if:
the lifetime of e has begun and not ended, and
the storage for the new object fits entirely within e, and
there is no smaller array object that satisfies these constraints.
The intent of the third bullet is to select a unique array object among those satisfying the first two bullets. However, it is possible to have multiple array objects of the same size satisfying the first two bullets. For example:
unsigned char buffer[8]; struct OhNo { std::byte data[8]; }; static_assert(sizeof(OhNo) == 8 && sizeof(int) == 4); OhNo *p = new (buffer) OhNo; // buffer provides storage for OhNo int *q = new (p->data) int; // who provides storage for this? int *r = new (buffer + 4) int; // who provides storage for this?
Suggested resolution:
Change 6.7.2 [intro.object] bullet 3.3 as follows:
there is no smaller
array object that satisfies these
constraints nested within e.
Proposed resolution (February, 2021):
Change 6.7.2 [intro.object] bullet 3.3 as follows:
there is no smaller
array object that satisfies these
constraints nested within e.
[Resolved by issue 2448, accepted as a DR at the June, 2021 meeting.]
The definitions of integral, floating, and arithmetic types in 6.8.2 [basic.fundamental] paragraphs 7-8 do not, but presumably should, include cv-qualified versions of those fundamental types.
Notes from the June, 2016 meeting:
This issue subsumes issue 251.
[Accepted as a DR at the June, 2021 meeting.]
According to the definitions in 6.8.2 [basic.fundamental], the arithmetic types include only the non-cv-qualified versions. In the taxonomy of fundamental types, the first mention of “cv-qualified versions of these types” is for scalar types (6.8 [basic.types] paragraph 9). However, 7.6.1.6 [expr.post.incr] paragraph 1 and 7.6.2.3 [expr.pre.incr] paragraph 1 both say:
The type of the operand shall be an arithmetic type other than cv bool, or...
which is a contradiction, since cv-qualified bool is not an arithmetic type. Similarly, 7.6.19 [expr.assign] paragraph 6 requires an arithmetic type for += and -=. D.4 [depr.volatile.type] deprecates the increment and decrement operators when applied to volatile-qualified arithmetic types, but the wording already made those ill-formed (since the normative wording requires an arithmetic type and not a possibly cv-qualified version thereof).
A related question is whether 12.5 [over.built], which explicitly allows for cv-qualified arithmetic types, should also note the deprecation.
See also issue 2185.
Notes from the July, 2020 teleconference:
CWG felt that no changes should be made to 12.5 [over.built].
Proposed resolution (April, 2021):
Change 6.8.2 [basic.fundamental] paragraphs 11 and 12 as follows, splitting paragraph 12 as indicated:
Types bool, char, wchar_t, char8_t, char16_t, char32_t, and the signed and unsigned integer types, and cv-qualified versions (6.8.5 [basic.type.qualifier]) thereof, are collectively
calledtermed integral types. A synonym for integral type is integer type. [Note 8: Enumerations (9.8.1 [dcl.enum]) are not integral; however, unscoped enumerations can be promoted to integral types as specified in 7.3.7 [conv.prom]. —end note]
There are three floating-point types:The three distinct types float, double, and long double can represent floating-point numbers. The type double provides at least as much precision as float, and the type long double provides at least as much precision as double. The set of values of the type float is a subset of the set of values of the type double; the set of values of the type double is a subset of the set of values of the type long double. The types float, double, and long double, and cv-qualified versions (6.8.5 [basic.type.qualifier]) thereof, are collectively termed floating-point types. The value representation of floating-point types is implementation-defined. [Note 9: This document imposes no requirements on the accuracy of floating-point operations; see also 17.3 [support.limits]. —end note]Integral and floating-point types are collectively
calledtermed arithmetic types. Specializations of the standard library template std::numeric_limits (17.3 [support.limits]) shall specify the maximum and minimum values of each arithmetic type for an implementation.
Change 6.8.5 [basic.type.qualifier] paragraph 1 as follows, splitting the paragraph as indicated:
A type mentioned in 6.8.2 [basic.fundamental] and 6.8.4 [basic.compound] is a cv-unqualified type.Each typewhich is a cv-unqualified object type or is void (6.8 [basic.types]) has three corresponding cv-qualified versions of its typeother than a function or reference type is part of a group of four distinct, but related, types: a cv-unqualified version, a const-qualified version, a volatile-qualified version, and a const-volatile-qualified version.The type of an object (6.7.2 [intro.object]) includes the cv-qualifiers specified in the decl-specifier-seq (9.2 [dcl.spec]), declarator (9.3 [dcl.decl]), type-id (9.3.2 [dcl.name]), or new-type-id (7.6.2.8 [expr.new]) when the object is created.The types in each such group shall have the same representation and alignment requirements (6.7.3 [basic.align]). [Footnote: The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and non-static data members of unions. —end footnote] A function or reference type is always cv-unqualified.
A const object is an object of type const T or a non-mutable subobject of a const object.
A volatile object is an object of type volatile T or a subobject of a volatile object.
A const volatile object is an object of type const volatile T, a non-mutable subobject of a const volatile object, a const subobject of a volatile object, or a non-mutable volatile subobject of a const object.
The cv-qualified or cv-unqualified versions of a type are distinct types; however, they shall have the same representation and alignment requirements (6.7.3 [basic.align]).40[Note: The type of an object (6.7.2 [intro.object]) includes the cv-qualifiers specified in the decl-specifier-seq (9.2 [dcl.spec]), declarator (9.3 [dcl.decl]), type-id (9.3.2 [dcl.name]), or new-type-id (7.6.2.8 [expr.new]) when the object is created. —end note]
Change 12.5 [over.built] paragraphs 2-10 as follows:
In this subclause, the term promoted integral type is used to refer to those cv-unqualified integral types which are preserved by integral promotion (7.3.7 [conv.prom]) (including e.g. int and long but excluding e.g. char ). [Note 2: In all cases where a promoted integral type is required, an operand of unscoped enumeration type will be acceptable by way of the integral promotions. —end note]
In the remainder of this subclause, vq represents either volatile or no cv-qualifier.
For every pair (T, vq), where T is
ana cv-unqualified arithmetic type other than bool or a cv-unqualified pointer to (possibly cv-qualified) object type, there exist candidate operator functions of the formvq T& operator++(vq T&);
T operator++(vq T&, int);
For every pair (T, vq), where T is an arithmetic type other than bool, there exist candidate operator functions of the formvq T& operator--(vq T&);
T operator--(vq T&, int);
For every pair (T, vq), where T is a cv-qualified or cv-unqualified object type, there exist candidate operator functions of the form
T*vq& operator++(T*vq&);
T*vq& operator--(T*vq&);
T* operator++(T*vq&, int);
T* operator--(T*vq&, int);For every
cv-qualified or cv-unqualified(possibly cv-qualified) object type T and for every function type T that has neither cv-qualifiers nor a ref-qualifier, there exist candidate operator functions of the formT& operator*(T*);
For every function type T that does not have cv-qualifiers or a ref-qualifier, there exist candidate operator functions of the form
T& operator*(T*);For every type T there exist candidate operator functions of the form
T* operator+(T*);
For every cv-unqualified floating-point or promoted integral type T, there exist candidate operator functions of the form
T operator+(T);
Tl operator-(T);
[Drafting note: Clause 21 [meta] regarding type traits appropriately handles cv-qualified and cv-unqualified types and does not require revision.]
[Accepted at the February, 2022 meeting.]
The changes for issue 2254 included the following:
Change 6.8.4 [basic.compound] bullet 4.3 as follows:
Two objects a and b are pointer-interconvertible if:
...
one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members,
the firstany base class subobject of that object (11.4 [class.mem]), or
This should also have removed the phrase,
or, if the object has no non-static data members,
since the change to 11.4 [class.mem] paragraph 25 specifies that all bases of a standard-layout class have the same address, regardless of whether the derived class has non-static data members.
Proposed resolution (November, 2021):
Change 6.8.4 [basic.compound] bullet 4.3 as follows:
Two objects a and b are pointer-interconvertible if:
...
one is a standard-layout class object and the other is the first non-static data member of that object
,or, if the object has no non-static data members,any base class subobject of that object (11.4 [class.mem]), or...
[Accepted as a DR at the June, 2021 meeting.]
There are several places where the consteval and/or constinit keywords should be mentioned but are not:
6.9.3.1 [basic.start.main] paragraph 3:
A program that defines main as deleted or that declares main to be inline, static, or constexpr is ill-formed.
9.3.4.1 [dcl.meaning.general] paragraph 2:
A static, thread_local, extern, mutable, friend, inline, virtual, constexpr, or typedef specifier or an explicit-specifier applies directly to each declarator-id in an init-declarator-list or member-declarator-list...
11.4.5.1 [class.ctor.general] paragraph 1:
...In a constructor declaration, each decl-specifier in the optional decl-specifier-seq shall be friend, inline, constexpr, or an explicit-specifier.
Proposed resolution, May, 2021:
Change 6.9.3.1 [basic.start.main] paragraph 3 as follows:
...A program that defines main as deleted or that declares main to be inline, static,orconstexpr, or consteval is ill-formed...
Change 9.3.4.1 [dcl.meaning.general] paragraph 4 as follows:
A static, thread_local, extern, mutable, friend, inline, virtual, constexpr, consteval, constinit, or typedef specifier or an explicit-specifier applies directly to each declarator-id in a declaration; the type specified for each declarator-id depends on both the decl-specifier-seq and its declarator.
Change 11.4.5.1 [class.ctor.general] paragraph 5 as follows:
...Constructors do not have names. In a constructor declaration, each decl-specifier in the optional decl-specifier-seq shall be friend, inline, constexpr, consteval, or an explicit-specifier.
[Accepted at the July, 2022 meeting.]
Consider:
template<class T> int main(T) {}
C++20 specified in 6.9.3.1 [basic.start.main] paragraph 2:
An implementation shall not predefine the main function. This function shall not be overloaded.
While it is unclear what "overloaded" means when multiple translation units are involved, it arguably disallowed function templates called main. This prohibition was removed with P1787R6 (Declarations and where to find them).
Proposed resolution (approved by CWG 2022-06-17):
Change in 6.9.3.1 [basic.start.main] paragraph 3 and add bullets as follows:
... A program that declaresis ill-formed. The name main is not otherwise reserved.
- a variable main that belongs to the global scope, or
that declaresa function main that belongs to the global scope and is attached to a named module, or- a function template main that belongs to the global scope, or
that declaresan entity named main with C language linkage (in any namespace)
[Accepted as a DR at the October, 2021 meeting.]
According to 7.3.7 [conv.prom] paragraphs 1-2,
A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (6.8.6 [conv.rank]) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.
A prvalue of type char16_t, char32_t, or wchar_t (6.8.2 [basic.fundamental]) can be converted to a prvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int. If none of the types in that list can represent all the values of its underlying type, a prvalue of type char16_t, char32_t, or wchar_t can be converted to a prvalue of its underlying type.
Because of its omission from the list of excluded types (perhaps as an oversight when it was added), char8_t is handled in the first paragraph. However, char16_t falls into the second paragraph, even though it is guaranteed to be convertible to int or unsigned int. This seems inconsistent, so perhaps char8_t should be moved to the second paragraph or char16_t moved to the first?
Notes from the August, 2021 teleconference:
char8_t should be handled by the second paragraph by including it in all three lists of types in the two paragraphs.
Proposed resolution (August, 2021):
Change 7.3.7 [conv.prom] paragraphs 1 and 2 as follows:
A prvalue of an integer type other than bool, char8_t, char16_t, char32_t, or wchar_t whose integer conversion rank (6.8.6 [conv.rank]) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.
A prvalue of type char8_t, char16_t, char32_t, or wchar_t (6.8.2 [basic.fundamental]) can be converted to a prvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int. If none of the types in that list can represent all the values of its underlying type, a prvalue of type char8_t, char16_t, char32_t, or wchar_t can be converted to a prvalue of its underlying type.
decltype(capture)
in a lambda's parameter-declaration-clause
[Accepted at the July, 2022 meeting as part of paper P2579R0 (Mitigation strategies for P2036 "Changing scope for lambda trailing-return-type").]
Paper P2036R3 disallowed using captures in the parameter-declaration-clause of a lambda, because it is not yet known at that point whether the lambda is going to be mutable, and thus the type of an expression referring to a capture may or may not be const. Such problematic uses of captures are now ill-formed. The paper was approved as a Defect Report, recommending to implementers to apply the change to all language modes back to C++11.
However, that broke legitimate uses in popular implementations of the standard library such as:
[local_end](decltype(local_end) it) { return it != local_end; };
As specified in 9.2.9.6 [dcl.type.decltype] bullet 1.3, decltype(local_end) does not depend on whether the lambda ends up being mutable or not:
- otherwise, if E is an unparenthesized id-expression or an unparenthesized class member access (7.6.1.5 [expr.ref]), decltype(E) is the type of the entity named by E. If there is no such entity, the program is ill-formed;
Possible approaches (not necessarily exclusive):
Suggested resolution (carves out an exception for decltype, sizeof, noexcept):
Change in 7.5.5.2 [expr.prim.id.unqual] paragraph 3 as follows:
An unqualified-id is mutable-agnostic if it is the operand of a decltype (9.2.9.6 [dcl.type.decltype]), sizeof (7.6.2.5 [expr.sizeof]), or noexcept (7.6.2.7 [expr.unary.noexcept]). If the unqualified-id appears in a lambda-expression at program point P and the entity is a local entity (6.1 [basic.pre]) or a variable declared by an init-capture (7.5.6.3 [expr.prim.lambda.capture]), then let S be the compound-statement of the innermost enclosing lambda-expression of P. If naming the entity from outside of an unevaluated operand within S would refer to an entity captured by copy in some intervening lambda-expression, then let E be the innermost such lambda-expression, and:...
- If the unqualified-id is mutable-agnostic or P is in E's function parameter scope but not its parameter-declaration-clause, then the type of the expression is the type of a class member access expression (7.6.1.5 [expr.ref]) naming the non-static data member that would be declared for such a capture in the object parameter (9.3.4.6 [dcl.fct]) of the function call operator of E. [Note 3: If E is not declared mutable, the type of such an identifier will typically be const qualified. —end note]
- Otherwise (if the unqualified-id is not mutable-agnostic and P either precedes E's function parameter scope or is in E's parameter-declaration-clause), the program is ill-formed.
[Example:
[=]<decltype(x) P>{}; // ok: P has type float [=]<decltype((x)) P>{}; // error: x refers to local entity but precedes the // lambda's function parameter scope [=](decltype((x)) y){}; // error: x refers to local entity but is in the lambda's // parameter-declaration-clause-- end example]
Suggested resolution (carves out an exception for decltype only):
Change in 7.5.5.2 [expr.prim.id.unqual] paragraph 3 as follows:
If the unqualified-id appears in a lambda-expression at program point P and the entity is a local entity (6.1 [basic.pre]) or a variable declared by an init-capture (7.5.6.3 [expr.prim.lambda.capture]), then let S be the compound-statement of the innermost enclosing lambda-expression of P. If naming the entity from outside of an unevaluated operand within S would refer to an entity captured by copy in some intervening lambda-expression, then let E be the innermost such lambda-expression, and:...
- If the unqualified-id is the operand of a decltype (9.2.9.6 [dcl.type.decltype]) or P is in E's function parameter scope but not its parameter-declaration-clause, then the type of the expression is the type of a class member access expression (7.6.1.5 [expr.ref]) naming the non-static data member that would be declared for such a capture in the object parameter (9.3.4.6 [dcl.fct]) of the function call operator of E. [Note 3: If E is not declared mutable, the type of such an identifier will typically be const qualified. —end note]
- Otherwise (if the unqualified-id is not the operand of a decltype and P either precedes E's function parameter scope or is in E's parameter-declaration-clause), the program is ill-formed.
[Example:
[=]<decltype(x) P>{}; // ok: P has type float [=]<decltype((x)) P>{}; // error: x refers to local entity but precedes the // lambda's function parameter scope [=](decltype((x)) y){}; // error: x refers to local entity but is in the lambda's // parameter-declaration-clause-- end example]
Additional notes (April, 2022):
Forwarded to EWG with paper issue 1227, by decision of the CWG chair.
See paper P2579R0 for a more detailed discussion of the issue.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Issue 2385 assumed a simple case where a conversion-type-id is an identifier. More complex cases need to be addressed as well. For example:
struct A { struct B; operator B B::*(); }; struct B; void f(A a) { a.operator B B::*(); } // first B is A::B. what is second B? void g(A a) { a.operator decltype(B()) B::*();} // what about the operand of decltype? void h(A a) { a.operator X<B>(); } // what is B here?
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
According to 7.5.6 [expr.prim.lambda] paragraph 7, names appearing in the compound-statement of a lambda-expression are looked up in the context of the lambda-expression, ignoring the fact that the compound-statement will be transformed into the body of the closure type's function operator. This leaves unspecified how the lambda-expression's parameters are found by name lookup. Presumably the parameters hide the corresponding names from the surrounding scope, but this needs to be specified.
[Adopted at the February, 2021 meeting as paper P1102R2.]
The grammar in 7.5.6 [expr.prim.lambda] paragraph 1 allows for omitting the the parameter list but only for a non-mutable lambda, i.e., it does not permit
auto lambda = [] mutable { };
This should be addressed, and the possibility of other abbreviated forms should be considered, such as:
[] -> float { return 42; } [] noexcept { foo(); }
(This is EWG issue 135.)
Proposed resolution (May, 2015):
Change the grammar in 7.5.6 [expr.prim.lambda] paragraph 1 as follows:
...
lambda-declarator:Change 7.5.6 [expr.prim.lambda] paragraph 4 as follows:
If a lambda-expression does not include a lambda-declarator, it is as if the lambda-declarator were ().The lambda return type...
Change 7.5.6 [expr.prim.lambda] paragraph 5 as follows:
The closure type for a non-generic lambda-expression has a public inline function call operator (12.4.4 [over.call]) whose parameters and return type are described by the lambda-expression's parameter-declaration-clause and trailing-return-type respectively. For a generic lambda... This function call operator or operator template is declared const (11.4.3 [class.mfct.non.static]) if and only if thelambda-expression's parameter-declaration-clause is not followed bylambda-declarator does not contain the keyword mutable. It is neither...
Notes from the October, 2015 meeting:
Additional wording is needed in the proposed resolution in paragraph 5 to handle the potential absence of the parameter declaration clause.
[Accepted at the February, 2022 meeting.]
(From editorial issue 2338.)
Use of decl-specifier-seq in the production for lambda-specifiers is too general and should be restricted.
Proposed resolution (December, 2021):
Change the grammar in 7.5.6.1 [expr.prim.lambda.general] as follows:
Change 7.5.6.1 [expr.prim.lambda.general] paragrap 3 as follows:
In the decl-specifier-seq of the lambda-declarator, each decl-specifier shall be one of mutable, constexpr, or consteval.A lambda-specifier-seq shall contain at most one of each lambda-specifier and shall not contain both constexpr and consteval. If the lambda-declarator contains an explicit object parameter (9.3.4.6 [dcl.fct]), then nodecllambda-specifier in thedecllambda-specifier-seq shall be mutable.
[Accepted as a DR at the October, 2021 meeting.]
Consider the following example:
void f(int i) { auto l1 = [i] { auto l2 = [&i] { ++i; // Well-formed? }; }; }
Because the l1 lambda is not marked as mutable, its operator() is const; however, it is not clear from the wording of 7.5.6 [expr.prim.lambda] paragraph 16 whether the captured member of the enclosing lambda is considered const or not.
Proposed resolution (August, 2021):
Change 7.5.6.3 [expr.prim.lambda.capture] paragraph 14 as follows:
If a lambda-expression m2 captures an entity and that entity is captured by an immediately enclosing lambda-expression m1, then m2's capture is transformed as follows:
ifIf m1 captures the entity by copy, m2 captures the corresponding non-static data member of m1's closure type; if m1 is not mutable, the non-static data member is considered to be const-qualified.
ifIf m1 captures the entity by reference, m2 captures the same entity captured by m1.
[Accepted at the July, 2022 meeting.]
The specification about the relative sequencing of multiple parameters of the subscripting operator is missing. Also, issue 2507 adds support for default arguments for user-defined subscripting operators, but the sequencing of these is unspecified, too.
Suggested resolution: [SUPERSEDED]
Add a new paragraph 4 at the end of 7.6.1.2 [expr.sub]:
If the subscript operator invokes an operator function, the sequencing restrictions of the corresponding function call expression apply (12.4.5 [over.sub], 7.6.1.3 [expr.call]).
Notes from the 2022-05-20 CWG telecon:
A wording approach amending 12.2.2.3 [over.match.oper] paragraph 2 instead would be preferred.
Possible resolution (2022-05-21): [SUPERSEDED]
Change in 12.2.2.3 [over.match.oper] paragraph 2 as follows:
Therefore, the operator notation is first transformed to the equivalent function-call notation as summarized in Table 17 (where @ denotes one of the operators covered in the specified subclause). However, except for the subscript operator (7.6.1.2 [expr.sub]), the operands are sequenced in the order prescribed for the built-in operator (7.6 [expr.compound]).
Notes from the 2022-06-03 CWG telecon:
Repeating the function call rules for the subscript operator in 7.6.1.2 [expr.sub] instead would be preferred, to avoid any impression of a special case.
Proposed resolution (2022-06-24, amended 2022-07-15, approved by CWG 2022-07-15):
Change in 7.6.1.2 [expr.sub] paragraph 1 as follows:
A subscript expression is a postfix expression followed by square brackets containing a possibly empty, comma-separated list of initializer-clauseswhichthat constitute the arguments to the subscript operator. The postfix-expression and the initialization of the object parameter of any applicable subscript operator function is sequenced before each expression in the expression-list and also before any default argument. The initialization of a non-object parameter of a subscript operator function S (12.4.5 [over.sub]), including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other non-object parameter of S.
[Accepted as a DR at the October, 2021 meeting.]
According to 7.6.1.3 [expr.call] paragraph 6,
Calling a function through an expression whose function type is different from the function type of the called function's definition results in undefined behavior.
This restriction should exempt calling a noexcept function where the function type of the expression is identical except that it is noexcept(false).
In addition, 7.6.1.9 [expr.static.cast] paragraph 7 currently forbids static_cast from converting a function pointer or member function pointer from noexcept(false) to noexcept:
The inverse of any standard conversion sequence (7.3 [conv]) not containing an lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), function-to-pointer (7.3.4 [conv.func]), null pointer (7.3.12 [conv.ptr]), null member pointer (7.3.13 [conv.mem]), boolean (7.3.15 [conv.bool]), or function pointer (7.3.14 [conv.fctptr]) conversion, can be performed explicitly using static_cast.
This restriction should also be relaxed, allowing binding a constexpr reference to the result of the reversed conversion.
Notes from the August, 2021 teleconference:
CWG agreed that it should be permitted to call a noexcept function via an expression that is noexcept(false); since the implicit conversion is allowed, the failure to allow the call is clearly just an oversight. The question of whether to allow the static_cast in the inverse direction, as well as whether to allow calling a noexcept(false) function via a noexcept expression (which would result in undefined behavior only if the function actually threw an exception) was deemed to be a matter for EWG and was thus split off into issue 2500.
Proposed resolution (September, 2021):
Change 7.6.1.3 [expr.call] paragraph 6 as follows:
Calling a function through an expression whose function type E is different from the function type F of the called function's definition results in undefined behavior unless the type “pointer to F” can be converted to the type “pointer to E” via a function pointer conversion (7.3.14 [conv.fctptr]). [Note: The exception applies when the expression has the type of a potentially-throwing function, but the called function has a non-throwing exception specification, and the function types are otherwise the same. —end note]
[Accepted as a DR at the June, 2021 meeting.]
Expressions denoting non-static member functions are currently classified as prvalues (7.5.5.3 [expr.prim.id.qual] paragraph 2; 7.6.1.5 [expr.ref] bullet 6.3.2; and 7.6.4 [expr.mptr.oper] paragraph 6). It would simplify the specification if such expressions were categorized as lvalues. (See also this pull request.)
Notes from the August, 2020 teleconference:
CWG preferred that the unbound case (i.e., &X::f) should be an lvalue, while the bound case should be a prvalue.
Proposed resolution (April, 2021):
Change 7.5.5.3 [expr.prim.id.qual] paragraph 5, converting the running text into a bulleted list, as follows:
The result of a qualified-id Q is the entity it denotes (6.5.5 [basic.lookup.qual]). The type of the expression is the type of the result. The result is an lvalue if the member is
a function other than a non-static member function,
a non-static member function if Q is the operand of a unary & operator,
a variable,
a structured binding (9.7 [dcl.struct.bind]), or
a static member function, ora data member,
and a prvalue otherwise.
Change 7.6.2.2 [expr.unary.op] paragraph 3 as follows:
The result of theThe operand of the unary & operator shall be an lvalue of some type T. The result is apointer to its operandprvalue.
If the operand is a qualified-id naming a non-static or variant member m of some class C
with type T, the result has type “pointer to member of class C of type T” andis a prvalue designatingdesignates C::m.Otherwise,
if the operand is an lvalue of type T,theresulting expression is a prvalue ofresult has type “pointer to T”whose result is a pointerand points to the designated object (6.7.1 [intro.memory]) or function (6.8.4 [basic.compound]). [Note 2: In particular, taking the address of a variable of type “cv T” yields a pointer of type “pointer to cv T”. —end note]
Otherwise, the program is ill-formed.
[Drafting note: neither 7.6.1.5 [expr.ref] bullet 6.3.2,
Otherwise (when E2 refers to a non-static member function), E1.E2 is a prvalue. The expression can be used only as the left-hand operand of a member function call (11.4.2 [class.mfct]). [Note 5: Any redundant set of parentheses surrounding the expression is ignored (7.5.4 [expr.prim.paren]). —end note]
nor 7.6.4 [expr.mptr.oper] paragraph 6,
...The result of a .* expression whose second operand is a pointer to a member function is a prvalue...
requires any change.]
[Accepted at the July, 2022 meeting.]
Subclause 7.6.1.5 [expr.ref] paragraph 3 defines the value category of a pseudo-destructor class member access expression to be an lvalue:
Abbreviating postfix-expression.id-expression as E1.E2, E1 is called the object expression. If the object expression is of scalar type, E2 shall name the pseudo-destructor of that same type (ignoring cv-qualifications) and E1.E2 is an lvalue of type “function of () returning void”.This is inconsistent with the analogous situation naming the destructor of a class. In that case, the class member access expression is a prvalue, not an lvalue, as specified in 7.6.1.5 [expr.ref] bullet 6.3 (see also issue 2458):
It also contradicts 7.2.1 [basic.lval] bullet 1.1:
- If E2 is an overload set, function overload resolution (12.2 [over.match]) is used to select the function to which E2 refers. The type of E1.E2 is the type of E2 and E1.E2 refers to the function referred to by E2.
- If E2 refers to a static member function, E1.E2 is an lvalue.
- Otherwise (when E2 refers to a non-static member function), E1.E2 is a prvalue. The expression can be used only as the left-hand operand of a member function call (11.4.2 [class.mfct]).
A pseudo-destructor does not have an identity.
- A glvalue is an expression whose evaluation determines the identity of an object or function.
Proposed resolution (approved by CWG 2022-04-08):
Change 7.6.1.5 [expr.ref] paragraph 3 as follows:
If the object expression is of scalar type, E2 shall name the pseudo-destructor of that same type (ignoring cv-qualifications) and E1.E2 isan lvaluea prvalue of type “function of () returning void”.
[Accepted at the July, 2022 meeting.]
The initialization of j ought to have undefined behavior, but the standard does not explicitly say so:
struct C { int m; }; int i = 0; int j = reinterpret_cast<C&>(i).m; // the same as int j = i ?
A related case for pointer-to-member expressions is covered by 7.6.4 [expr.mptr.oper] paragraph 4:
If the dynamic type of E1 does not contain the member to which E2 refers, the behavior is undefined.
The invocation of non-static member functions is covered by 11.4.3 [class.mfct.non.static] paragraph 2:
If a non-static member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined.
Proposed resolution (approved by CWG 2022-06-17):
(updated according to 2022-05-20 and 2022-06-03 CWG guidance)
Add a new paragraph after 7.6.1.5 [expr.ref] paragraph 7:
If E2 is a non-static
data member or a non-staticmemberfunction, the program is ill-formed if the class of which E2 is directly a member is an ambiguous base (6.5.2 [class.member.lookup]) of the naming class (11.8.3 [class.access.base]) of E2. [Note: The program is also ill-formed if the naming class is an ambiguous base of the class type of the object expression; see 11.8.3 [class.access.base]. —end note -- end note]If E2 is a non-static member and the result of E1 is an object whose type is not similar (7.3.6 [conv.qual]) to the type of E1, the behavior is undefined. [ Example:
struct A { int i; }; struct B { int j; }; struct D : A, B {}; void f() { D d; static_cast<B&>(d).j; // OK, object expression designates the B subobject of d reinterpret_cast<B&>(d).j; // undefined behavior }-- end example ]
Change in 7.6.4 [expr.mptr.oper] paragraph 4:
If the dynamic type of E1If the result of E1 is an object whose type is not similar to the type of E1, or whose most derived object does not contain the member to which E2 refers, the behavior is undefined.Otherwise, tThe expression E1 is sequenced before the expression E2.
Remove 11.4.3 [class.mfct.non.static] paragraph 2:
If a non-static member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined.
[Accepted at the July, 2022 meeting.]
Consider:
struct S { int a[5]; } s; int (*p)[] = reinterpret_cast<int(*)[]>(&s); int n = (*p)[0];
This ought to have defined behavior: a pointer to s and a pointer to s.a are pointer-interconvertible, so you should be able to navigate between them this way. But the cast as shown does not work, because the type of the pointer-interconvertible object is int[5], not int[].
Proposed resolution (approved by CWG 2022-07-01):
Change in 7.6.1.9 [expr.static.cast] paragraph 13 as follows:
... Otherwise, if the original pointer value points to an object a, and there is an object b of type similar to T(ignoring cv-qualification)that is pointer-interconvertible (6.8.4 [basic.compound]) with a, the result is a pointer to b. Otherwise, the pointer value is unchanged by the conversion.
[Accepted as a DR at the June, 2021 meeting.]
The description of co_await should not permit reordering the subexpressions constituting the evaluation of a co_await expression. For example, given
auto z = co_await coro + co_await coro;
the result may be different from the expected
auto x = co_await coro; auto y = co_await coro; auto z = x + y;
Suggested resolution:
Add the following as a new paragraph following 7.6.2.4 [expr.await] paragraph 5:
With respect to an indeterminately-sequenced function call, the operation of co_await is a single evaluation. [Note: Therefore a function call cannot intervene between the subexpressions constituting evaluation of a co_await expression. —end note]
[Example 1:...
Proposed resolution, May, 2021:
Change 6.9.1 [intro.execution] paragraph 11 as follows:
Whencallinginvoking a function (whether or not the function is inline), everyvalue computation and side effect associated with anyargument expression, or withand the postfix expression designating the called function, isare sequenced beforeexecution ofevery expression or statement in the body of the called function. For each function invocation or evaluation of an await-expression F,for everyeach evaluationAthatoccursdoes not occur within Fand every evaluation B that does not occur within Fbut is evaluated on the same thread and as part of the same signal handler (if any), either A is sequenced before B or B is sequenced before A.is either sequenced before all evaluations that occur within F or sequenced after all evaluations that occur within F; [Footnote: In other words, function executions do not interleave with each other. —end footnote] if F invokes or resumes a coroutine (7.6.2.4 [expr.await]), only evaluations subsequent to the previous suspension (if any) and prior to the next suspension (if any) are considered to occur within F. [Note 7: If A and B would not otherwise be sequenced then they are indeterminately sequenced. —end note]
Add the following note at the end of 7.6.2.4 [expr.await] paragraph 5:
The await-expression evaluates the (possibly-converted) o expression and the await-ready expression, then:
...
[Note: With respect to sequencing, an await-expression is indivisible (6.9.1 [intro.execution]). —end note]
Drafting note: No change is needed in 6.9.1 [intro.execution] paragraph 8:
...An expression X is said to be sequenced before an expression Y if every value computation and every side effect associated with the expression X is sequenced before every value computation and every side effect associated with the expression Y.
Additional note, May, 2021:
Note 7 in 6.9.1 [intro.execution] paragraph 11 refers to evaluations A and B, even though the edit to that paragraph above removes those names. This discrepancy was noticed only after CWG approved the change to the normative wording. Since it involves only the wording of a non-normative note, the problem will be addressed editorially. See editorial issue 4612.
[Accepted as a DR at the June, 2021 meeting.]
(From editorial issue 4305.)
According to 7.6.2.9 [expr.delete] paragraph 3,
In a single-object delete expression, if the static type of the object to be deleted is different from its dynamic type and the selected deallocation function (see below) is not a destroying operator delete, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined. In an array delete expression, if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.
Both the static type and the dynamic type include cv-qualification, and requiring agreement in qualification between the two for deletion is not intended. Perhaps the restriction should be to similar types instead of identical types?
Notes from the December, 2020 teleconference:
“Similar types” raises issues with arrays of unknown bounds, but a change to allow for differences in cv-qualification is needed.
Notes from the May 25, 2021 teleconference:
It was observed that current implementations store the total number of class objects in a multi-dimensional array in a “cookie” in the array allocation overhead, rather than the number of top-level array elements, and thus are able to invoke the destructors correctly even if the type being deleted is an array of unknown bound. Consequently, it was decided that use of the “similar” criterion was appropriate.
Proposed resolution, May, 2021:
Change 7.6.2.9 [expr.delete] paragraph 3 as follows:
In a single-object delete expression, if the static type of the object to be deleted isdifferent fromnot similar (7.3.6 [conv.qual]) to its dynamic type and the selected deallocation function (see below) is not a destroying operator delete, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined. In an array delete expression, if the dynamic type of the object to be deleteddiffers fromis not similar to its static type, the behavior is undefined.
[Accepted as a DR at the October, 2021 meeting.]
According to 7.7 [expr.const] paragraph 6,
For the purposes of determining whether an expression E is a core constant expression, the evaluation of a call to a member function of std::allocator<T> as defined in 20.2.10.2 [allocator.members], where T is a literal type, does not disqualify E from being a core constant expression, even if the actual evaluation of such a call would otherwise fail the requirements for a core constant expression. Similarly, the evaluation of a call to std::destroy_at, std::ranges::destroy_at, std::construct_at, or std::ranges::construct_at does not disqualify E from being a core constant expression unless:
for a call to std::construct_at or std::ranges::construct_at, the first argument, of type T*, does not point to storage allocated with std::allocator<T> or to an object whose lifetime began within the evaluation of E, or the evaluation of the underlying constructor call disqualifies E from being a core constant expression, or
for a call to std::destroy_at or std::ranges::destroy_at, the first argument, of type T*, does not point to storage allocated with std::allocator<T> or to an object whose lifetime began within the evaluation of E, or the evaluation of the underlying destructor call disqualifies E from being a core constant expression.
There are, however, no specific restrictions in 7.7 [expr.const] regarding destructor or pseudo-destructor calls. In particular, a constexpr destructor can be called for any object, regardless of how it was constructed or the start of its lifetime, and similarly for pseudo-destructor calls. This seems inconsistent.
If those restrictions are added, would the specific restrictions on library destruction facilities still be needed?
Notes from the August, 2021 teleconference:
CWG agreed that since trivial destructors and pseudo-destructors are now considered to end the lifetime of the object for which they are called, they should be prohibited from being invoked for a runtime object in a constant expression.
Proposed resolution (August, 2021):
Change 7.7 [expr.const] paragraph 5 as follows:
An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following:
...
a modification of an object (7.6.19 [expr.assign], 7.6.1.6 [expr.post.incr], 7.6.2.3 [expr.pre.incr]) unless it is applied to a non-volatile lvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;
an invocation of a destructor (11.4.7 [class.dtor]) or a function call whose postfix-expression names a pseudo-destructor (7.6.1.3 [expr.call]), in either case for an object whose lifetime did not begin within the evaluation of E;
a new-expression (7.6.2.8 [expr.new]), unless...
Change 7.7 [expr.const] paragraph 6 as follows, merging the single remaining bulleted item into the running text of the paragraph:
For the purposes of determining whether an expression E is a core constant expression, the evaluation of a call to a member function of std::allocator<T> as defined in 20.2.10.2 [allocator.members], where T is a literal type, does not disqualify E from being a core constant expression, even if the actual evaluation of such a call would otherwise fail the requirements for a core constant expression. Similarly, the evaluation of a call to
std::destroy_at, std::ranges::destroy_at,std::construct_at,or std::ranges::construct_at does not disqualify E from being a core constant expression unless:
for a call to std::construct_at or std::ranges::construct_at,the first argument, of type T*, does not point to storage allocated with std::allocator<T> or to an object whose lifetime began within the evaluation of E, or the evaluation of the underlying constructor call disqualifies E from being a core constant expression, or
for a call to std::destroy_at or std::ranges::destroy_at, the first argument, of type T*, does not point to storage allocated with std::allocator<T> or to an object whose lifetime began within the evaluation of E, or the evaluation of the underlying destructor call disqualifies E from being a core constant expression.
[Accepted at the November, 2020 meeting.]
There are two references to “flowing off the end of a coroutine”, specifically in 8.7.5 [stmt.return.coroutine] paragraph 3:
If p.return_void() is a valid expression, flowing off the end of a coroutine is equivalent to a co_return with no operand; otherwise flowing off the end of a coroutine results in undefined behavior.
and 9.6.4 [dcl.fct.def.coroutine] paragraph 11:
The coroutine state is destroyed when control flows off the end of the coroutine or...
These mean different things and should be clarified.
Proposed resolution (July, 2020):
Change 8.7.5 [stmt.return.coroutine] paragraph 3 as follows:
If p.return_void() is a valid expression, flowing off the end of a coroutine's function-body is equivalent to a co_return with no operand;otherwise flowing off the end of a coroutine's function-body results in undefined behavior.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
According to 8.10 [stmt.ambig] paragraph 3,
The disambiguation is purely syntactic; that is, the meaning of the names occurring in such a statement, beyond whether they are type-names or not, is not generally used in or changed by the disambiguation. Class templates are instantiated as necessary to determine if a qualified name is a type-name. Disambiguation precedes parsing, and a statement disambiguated as a declaration may be an ill-formed declaration. If, during parsing, a name in a template parameter is bound differently than it would be bound during a trial parse, the program is ill-formed. No diagnostic is required. [Note: This can occur only when the name is declared earlier in the declaration. —end note]
The statement about template parameters is confusing (and not helped by the fact that the example that follows illustrates the general rule for declarations and does not involve any template parameters). It is attempting to say that a program is ill-formed if a template argument of a class template specialization has a different value in the two parses. With decltype this can now apply to other kinds of templates as well, so the wording should be clarified and made more general.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The resolution of issue 482 allows a typedef to be redeclared in the same or a containing scope using a qualified declarator-id. This was not the principal goal of the issue and is not supported by current implementations. Should the prohibition of qualified declarator-ids be reinstated for typedefs?
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The resolution of issue 407 does not cover cases involving using-declarations. For example:
namespace A { struct S {}; } namespace B { // This is valid per issue 407 using A::S; typedef A::S S; struct S s; } namespace C { // The typedef does not redefine the name S in this // scope, so issue 407's resolution does not apply. typedef A::S S; using A::S; // The name lookup here isn't ambiguous, because it only finds one // entity, but it finds both a typedef-name and a non-typedef-name referring // to that entity, so the standard doesn't appear to say whether this is valid. struct S s; }
The same issue appears with using-directives:
namespace D { typedef A::S S; }
namespace E {
using namespace A;
using namespace D;
struct S s; // ok? issue 407 doesn't apply here either
}
One possibility might be to remove the rule that a typedef-name declaration redefines an already-defined name and instead rely on struct stat-style hiding, taking the non-typedef-name if name lookup finds both and they refer to the same type.
Notes from the June, 2014 meeting:
CWG felt that these examples should be well-formed.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
It is still unclear how typedefs and elaborated-type-specifiers interact in some cases. For example:
namespace A { struct S {}; } namespace B { typedef int S; } namespace C { using namespace A; using namespace B; struct S s; // clearly ambiguous, S names different entities } namespace D { using A::S; typedef struct S S; struct S s; // OK under issue 407: S could be used in an // elaborated-type-specifier before the typedef, so still can be } namespace E { typedef A::S S; using A::S; struct S s; // ??? the identifier S could not have been used in an // elaborated-type-specifier prior to the typedef, so is this lookup // ill-formed because it finds a typedef-name? } namespace F { typedef A::S S; } namespace G { using namespace A; using namespace F; struct S s; // ??? F::S could not have been used as an // elaborated-type-specifier before the typedef. is this ill-formed because // the lookup finds a typedef-name? } namespace H { using namespace F; using namespace A; struct S s; // some implementations give different answers for G and H }
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
According to 9.2.9.5 [dcl.type.elab] paragraph 1,
If an elaborated-type-specifier is the sole constituent of a declaration, the declaration is ill-formed unless it is an explicit specialization (13.9.4 [temp.expl.spec]), an explicit instantiation (13.9.3 [temp.explicit]) or it has one of the following forms:
class-key attribute-specifier-seqopt identifier ;
friend class-key ::opt identifier ;
friend class-key ::opt simple-template-id ;
friend class-key nested-name-specifier identifier ;
friend class-key nested-name-specifier templateopt simple-template-id ;
This implies that class template partial specializations cannot be forward-declared, which is probably unintentional.
Notes from the November, 2016 meeting:
CWG felt that forward declarations of partial specializations should be allowed.
The Standard does not explicitly address whether an example like the following is well-formed or not:
struct S { static int i; }; auto S::i = 23;
There is implementation divergence on the handling of this example.
Notes from the July, 2019 meeting
Editorially add the example as well-formed; see editorial issue 2979.
CWG 2019-09-16
Approved editorial change 3227.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting. See 8.9 [stmt.dcl] paragraph 2.]
It is not clear what, if anything, in the existing specification requires that the initialization of multiple init-declarators within a single declaration be performed in declaration order.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Issue 1477 assumes that a name declared only in a friend declaration can be defined outside its namespace using a qualified-id, but the normative passages in 9.3.4 [dcl.meaning] paragraph 1 and _N4868_.9.8.2.3 [namespace.memdef] paragraph 2 do not settle the question definitively, and there is implementation variance. A clearer statement of intent is needed.
[Accepted as a DR at the June, 2021 meeting.]
According to 9.3.4.5 [dcl.array] paragraph 1,
In a declaration T D where D has the form
D1 [ constant-expressionopt ] attribute-specifier-seqopt
and the type of the identifier in the declaration T D1 is “derived-declarator-type-list T”, then the type of the identifier of D is an array type; if the type of the identifier of D contains the auto type-specifier, the program is ill-formed.
This formulation forbids useful constructs like
int a[3]; auto (*p)[3] = &a;
(accepted by current implementations) and should be relaxed to accommodate such cases.
Notes from the February, 2019 meeting:
CWG agreed that the example should be accepted.
Notes from the May 25, 2021 teleconference:
It was observed that CWG rejected the same example as being "not a defect" in considering issue 1222. However, the use of auto has significantly expanded since that time and the prohibition of such declarations now seems inconsistent.
Proposed resolution, May, 2021:
Change 9.3.4.5 [dcl.array] paragraph 4 as follows:
U is called the array element type; this type shall not bea placeholder type (9.2.9.7 [dcl.spec.auto]),a reference type, a function type, an array of unknown bound, or cv void.
Change 9.3.4.6 [dcl.fct] paragraph 11 as follows:
The return type shall be a non-array object type, a reference type, or cv void. [Note: An array of placeholder type is considered an array type. —end note]
Change 9.2.9.7.2 [dcl.type.auto.deduct] paragraph 2 as follows:
A type T containing a placeholder type, and a corresponding initializer E, are determined as follows:
for a non-discarded return statement that occurs in a function declared with a return type that contains a placeholder type, T is the declared return type and E is the operand of the return statement. If the return statement has no operand, then E is void();
for a variable declared with a type that contains a placeholder type, T is the declared type of the variable and E is the initializer. If the initialization is direct-list-initialization, the initializer shall be a braced-init-list containing only a single assignment-expression and E is the assignment-expression;
for a non-type template parameter declared with a type that contains a placeholder type, T is the declared type of the non-type template parameter and E is the corresponding template argument.
T shall not be an array type. In the case of a return statement with no operand...
[Accepted as a DR at the June, 2021 meeting.]
According to 9.5.4 [dcl.init.ref] bullet 5.4.2, when a reference is initialized with a non-class value and the referenced type is not reference-related to the type of the initializer,
Otherwise, the initializer expression is implicitly converted to a prvalue of type “cv1 T1”. The temporary materialization conversion is applied and the reference is bound to the result.
According to 7.2.2 [expr.type] paragraph 2, the cv-qualification is discarded before invoking the temporary materialization conversion:
If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.
This results in a reference-to-const being bound to a non-const object, meaning that a const_cast of the reference to a reference-to-nonconst would allow a well-defined modification of the value:
constexpr const int &r = 42; const_cast<int &>(r) = 23; // Well-defined static_assert(r == 42); // Ill-formed, non-constant expression
This was different from the situation before the advent of the temporary materialization conversion in C++17, when the description of the reference binding created the temporary explicitly with the cv-qualified type:
If T1 is a non-class type, a temporary of type “cv1 T1” is created and copy-initialized (8.5) from the initializer expression. The reference is then bound to the temporary.
Presumably this difference was unintentional and should be reverted.
Proposed resolution, May, 2021:
Change 9.5.4 [dcl.init.ref] bullet 5.4.2 as follows:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
...
Otherwise:
If T1 or T2 is a class type and T1 is not reference-related to T2...
Otherwise, the initializer expression is implicitly converted to a prvalue of type
“cv1T1”. The temporary materialization conversion is applied, considering the type of the prvalue to be “cv1 T1”, and the reference is bound to the result.
[Accepted as a DR at the October, 2021 meeting.]
9.6.2 [dcl.fct.def.default] paragraph 1 specifies that an explicitly-defaulted function shall
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...
This allows an example like
struct A { A& operator=(A const&) && = default; };
but forbids
struct B { B&& operator=(B const&) && = default; };
which seems backward.
In addition, 11.4.5.3 [class.copy.ctor] paragraph 22 only specifies the return value for implicitly-declared copy/move assignment operators, not for explicitly-defaulted ones.
Proposed resolution (August, 2021):
Change 11.4.6 [class.copy.assign] paragraph 6 as follows:
The implicitly-declared copy/move assignment operator for class X has the return type X&; it returns the object for which the assignment operator is invoked, that is, the object assigned to. An implicitly-declared copy/move assignment operator is an inline public member of its class.
Add the following as a new paragraph following 11.4.6 [class.copy.assign] paragraph 13:
The implicitly-defined copy assignment operator for a union X copies the object representation (6.8 [basic.types]) of X. If the source and destination of the assignment are not the same object, then for each object nested within (6.7.2 [intro.object]) the object that is the source of the copy, a corresponding object o nested within the destination is created, and the lifetime of o begins before the copy is performed.
The implicitly-defined copy/move assignment operator for a class returns the object for which the assignment operator is invoked, that is, the object assigned to.
[Note: The first point in the issue, that of the relationship between the ref-qualifier and the return type, will be referred to EWG for consideration. The draft resolution above addresses only the second point of the issue.
Subclause 9.6.2 [dcl.fct.def.default] paragraph 1 specifies:
A function that is explicitly defaulted shall
- ...
- 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,
- ...
Therefore, the following code is ill-formed:
struct S
{
int i;
S(const S&) = default;
S(const volatile S&) = default; // ill-formed
};
volatile S s1;
S s2(s1);
However, C.7.7 [diff.class] paragraph 2 mentions the ability to default such a constructor:
If volatile semantics are required for the copy, a user-declared constructor or assignment must be provided. [ Note: This user-declared constructor may be explicitly defaulted. —end note]
Notes from the November, 2016 meeting:
This issue is to be resolved editorially and is placed in "review' status until the corresponding change appers in a working draft.
Additional note (February, 2022):
The change has been applied editorially with commit 219538.
[Accepted as a DR at the June, 2021 meeting.]
The resolution of issue 2436 (in P2107R0) deleted the sentence
A reference to a parameter in the function-body of the coroutine and in the call to the coroutine promise constructor is replaced by a reference to its copy.
replacing it with new wording in 7.5.5.2 [expr.prim.id.unqual] paragraph 1:
An identifier that names a coroutine parameter refers to the copy of the parameter (9.6.4 [dcl.fct.def.coroutine]).
This new approach no longer covers coroutine parameters passed to a promise constructor, since the constructor call is implicit, as described in 7.5.5.2 [expr.prim.id.unqual] paragraph 5.
Suggested resolution:
Change 7.5.5.2 [expr.prim.id.unqual] paragraph 4 as follows:
In the following, pi is an lvalue of type Pi, where p1 denotes *this and pi+1 denotes the ith function parameter for a non-static member function, and pi denotes the ith function parameter otherwise. Let qi be the corresponding parameter copy, as described below.
Change 7.5.5.2 [expr.prim.id.unqual] bullet 5.7 as follows:
A coroutine behaves as if its function-body were replaced by...
...
promise-constructor-arguments is determined as follows: overload resolution is performed on a promise constructor call created by assembling an argument list with lvalues
pq1 ...pqn. If a viable constructor is found (12.2.3 [over.match.viable]), then promise-constructor-arguments is (pq1, ... ,pqn), otherwise promise-constructor-arguments is empty.
Proposed resolution (April, 2021):
Change 9.6.4 [dcl.fct.def.coroutine] paragraph 4 as follows:
In the following, pi is an lvalue of type Pi, where p1 denotes *this and pi+1 denotes the ith function parameter for a non-static member function, and pi denotes the ith function parameter otherwise. For a non-static member function, q1 is an lvalue that denotes *this; any other qi is an lvalue that denotes the parameter copy corresponding to pi, as described below.
Change 9.6.4 [dcl.fct.def.coroutine] bullet 5.7 as follows:
A coroutine behaves as if its function-body were replaced by: ... where
...
promise-constructor-arguments is determined as follows: overload resolution is performed on a promise constructor call created by assembling an argument list
with lvalues p1 ... pnq1 ... qn. If a viable constructor is found (12.2.3 [over.match.viable]), then promise-constructor-arguments is(p1, ... , pn)(q1, ... , qn), otherwise promise-constructor-arguments is empty.
[Accepted at the July, 2022 meeting.]
Consider:
struct Allocator;
struct resumable::promise_type {
void* operator new(std::size_t sz, Allocator&);
// ...
};
resumable foo() {
co_return;
}
Subclause 9.6.4 [dcl.fct.def.coroutine] paragraph 9 specifies:
... The allocation function's name is looked up by searching for it in the scope of the promise type.If no viable function is found (12.2.3 [over.match.viable]), overload resolution is performed again on a function call created by passing just the amount of space required as an argument of type std::size_t.
- If any declarations are found, overload resolution is performed on a function call created by assembling an argument list. The first argument is the amount of space requested, and has type std::size_t. The lvalues p1 . . . pn are the succeeding arguments.
- Otherwise, a search is performed in the global scope.
Is the example ill-formed because resumable::promise_type is not viable, or is the example well-formed because the global operator new can be used? There is implementation divergence.
See also LLVM issue 54881.
Proposed resolution (approved by CWG 2022-06-17):
(updated according to 2022-05-20, 2022-06-03, and 2022-06-17 CWG guidance)
Change in 9.6.4 [dcl.fct.def.coroutine] paragraph 9 as follows:
... The allocation function's name is looked up by searching for it in the scope of the promise type.
- If the search finds any declarations
are found, overload resolution is performed on a function call created by assembling an argument list. The first argument is the amount of space requested, andhasis a prvalue of type std::size_t. The lvalues p1 ... pn are thesucceedingsuccessive arguments.Otherwise, a search is performed in the global scope.If no viable function is found (12.2.3 [over.match.viable]), overload resolution is performed again on a function call created by passing just the amount of space required asan argumenta prvalue of type std::size_t.- If the search finds no declarations, a search is performed in the global scope. Overload resolution is performed on a function call created by passing the amount of space required as a prvalue of type std::size_t.
[Accepted at the November, 2020 meeting.]
An example like the following is currently ill-formed:
struct A { mutable int n; }; void f() { const auto [a] = A(); a = 0; }
According to 9.7 [dcl.struct.bind] paragraph 4, the type of a is const int, since the implicitly-declared variable is const. This seems obviously wrong: the member n is mutable, so the member access expression e.n has type int, which should also be the type of a. (mutable should presumably be taken into account when forming the referenced type too, so that decltype(a) is int as would presumably be expected, rather than const int.)
Proposed resolution, March, 2018: [SUPERSEDED]
Change 9.7 [dcl.struct.bind] paragraph 4 as follows:
...Designating the non-static data members of E as m0, m1, m2, ... (in declaration order), each vi is the name of an lvalue that refers tothe member mi of e and whose type is cv Ti, where Ti is the declared type of that membere.mi; the referenced type iscv Tithe type of e.mi. The lvalue is a bit-field if...
Notes from the June, 2018 meeting:
It was observed that this resolution does not handle members with reference type correctly. The main problem seems to be the statement in 7.6.1.5 [expr.ref] paragraph 4, which directly handles members with reference type rather than allowing the type of the member to be the result type and relying on the general rule that turns reference-typed expressions into lvalues.
Proposed resolution (April, 2020):
Change 9.7 [dcl.struct.bind] paragraph 5 as follows:
...Designating the non-static data members of E as m0, m1, m2, ... (in declaration order), each vi is the name of an lvalue that refers to the member mi of e and whose type is
cv Ti, where Ti is the declared type of that memberthat of e.mi (7.6.1.5 [expr.ref]); the referenced type iscv Tithe declared type of mi if that type is a reference type, or the type of e.mi otherwise. The lvalue is a bit-field if that member is a bit-field.[Example 2:
struct S { mutable int x1 : 2; volatile double y1; }; S f(); const auto [ x, y ] = f();The type of the id-expression x is “
constint”, the type of the id-expression y is “const volatile double”. —end example]
[Accepted at the February, 2022 meeting.]
According to 9.7 [dcl.struct.bind] paragraph 1,
A structured binding declaration introduces the identifiers v0, v1, v2, ... of the identifier-list as names of structured bindings. Let cv denote the cv-qualifiers in the decl-specifier-seq and S consist of the storage-class-specifiers of the decl-specifier-seq (if any). A cv that includes volatile is deprecated; see D.4 [depr.volatile.type]. First, a variable with a unique name e is introduced. If the assignment-expression in the initializer has array type A and no ref-qualifier is present, e is defined by
attribute-specifier-seqopt S cv A e ;
and each element is copy-initialized or direct-initialized from the corresponding element of the assignment-expression as specified by the form of the initializer.
This means that in an example like
const int arr[1]{}; auto [i] = arr;
i is a reference to const int. Presumably the fact that the array is copied should drop the array's cv-qualification.
Proposed resolution (December, 2021):
Change 9.7 [dcl.struct.bind] paragraph 1 as follows:
If the assignment-expression in the initializer has array type cv1 A and no ref-qualifier is present, e is defined by...
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Section 9.10 [namespace.udecl] paragraph 8 says:
A using-declaration is a declaration and can therefore be used repeatedly where (and only where) multiple declarations are allowed.It contains the following example:
namespace A { int i; } namespace A1 { using A::i; using A::i; // OK: double declaration } void f() { using A::i; using A::i; // error: double declaration }However, if "using A::i;" is really a declaration, and not a definition, it is far from clear that repeating it should be an error in either context. Consider:
namespace A { int i; void g(); } void f() { using A::g; using A::g; }Surely the definition of f should be analogous to
void f() { void g(); void g(); }which is well-formed because "void g();" is a declaration and not a definition.
Indeed, if the double using-declaration for A::i is prohibited in f, why should it be allowed in namespace A1?
Proposed Resolution (04/99): Change the comment "// error: double declaration" to "// OK: double declaration". (This should be reviewed against existing practice.)
Notes from 04/00 meeting:
The core language working group was unable to come to consensus over what kind of declaration a using-declaration should emulate. In a straw poll, 7 members favored allowing using-declarations wherever a non-definition declaration could appear, while 4 preferred to allow multiple using-declarations only in namespace scope (the rationale being that the permission for multiple using-declarations is primarily to support its use in multiple header files, which are seldom included anywhere other than namespace scope). John Spicer pointed out that friend declarations can appear multiple times in class scope and asked if using-declarations would have the same property under the "like a declaration" resolution.
As a result of the lack of agreement, the issue was returned to "open" status.
See also issues 56, 85, and 138..
Additional notes (January, 2005):
Some related issues have been raised concerning the following example (modified from a C++ validation suite test):
struct A { int i; static int j; }; struct B : A { }; struct C : A { }; struct D : virtual B, virtual C { using B::i; using C::i; using B::j; using C::j; };
Currently, it appears that the using-declarations of i are ill-formed, on the basis of 9.10 [namespace.udecl] paragraph 10:
Since a using-declaration is a declaration, the restrictions on declarations of the same name in the same declarative region (6.4 [basic.scope]) also apply to using-declarations.
Because the using-declarations of i refer to different objects, declaring them in the same scope is not permitted under 6.4 [basic.scope]. It might, however, be preferable to treat this case as many other ambiguities are: allow the declaration but make the program ill-formed if a name reference resolves to the ambiguous declarations.
The status of the using-declarations of j, however, is less clear. They both declare the same entity and thus do not violate the rules of 6.4 [basic.scope]. This might (or might not) violate the restrictions of 11.4 [class.mem] paragraph 1:
Except when used to declare friends (11.8.4 [class.friend]) or to introduce the name of a member of a base class into a derived class (9.10 [namespace.udecl], _N3225_.11.3 [class.access.dcl]), member-declarations declare members of the class, and each such member-declaration shall declare at least one member name of the class. A member shall not be declared twice in the member-specification, except that a nested class or member class template can be declared and then later defined.
Do the using-declarations of j repeatedly declare the same member? Or is the preceding sentence an indication that a using-declaration is not a declaration of a member?
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The following came up recently on comp.lang.c++.moderated (edited for brevity):
namespace N1 { template<typename T> void f( T* x ) { // ... other stuff ... delete x; } } namespace N2 { using N1::f; template<> void f<int>( int* ); // A: ill-formed class Test { ~Test() { } friend void f<>( Test* x ); // B: ill-formed? }; }
I strongly suspect, but don't have standardese to prove, that the friend declaration in line B is ill-formed. Can someone show me the text that allows or disallows line B?
Here's my reasoning: Writing "using" to pull the name into namespace N2 merely allows code in N2 to use the name in a call without qualification (per 9.10 [namespace.udecl]). But just as declaring a specialization must be done in the namespace where the template really lives (hence line A is ill-formed), I suspect that declaring a specialization as a friend must likewise be done using the original namespace name, not obliquely through a "using". I see nothing in 9.10 [namespace.udecl] that would permit this use. Is there?
Andrey Tarasevich: 13.7.5 [temp.friend] paragraph 2 seems to get pretty close: "A friend declaration that is not a template declaration and in which the name of the friend is an unqualified 'template-id' shall refer to a specialization of a function template declared in the nearest enclosing namespace scope".
Herb Sutter: OK, thanks. Then the question in this is the word "declared" -- in particular, we already know we cannot declare a specialization of a template in any other namespace but the original one.
John Spicer: This seems like a simple question, but it isn't.
First of all, I don't think the standard comments on this usage one way or the other.
A similar example using a namespace qualified name is ill-formed based on 9.3.4 [dcl.meaning] paragraph 1:
namespace N1 { void f(); } namespace N2 { using N1::f; class A { friend void N2::f(); }; }
Core issue 138 deals with this example:
void foo(); namespace A{ using ::foo; class X{ friend void foo(); }; }
The proposed resolution (not yet approved) for issue 138 is that the friend declares a new foo that conflicts with the using-declaration and results in an error.
Your example is different than this though because the presence of the explicit argument list means that this is not declaring a new f but is instead using a previously declared f.
One reservation I have about allowing the example is the desire to have consistent rules for all of the "declaration like" uses of template functions. Issue 275 (in DR status) addresses the issue of unqualified names in explicit instantiation and explicit specialization declarations. It requires that such declarations refer to templates from the namespace containing the explicit instantiation or explicit specialization. I believe this rule is necessary for those directives but is not really required for friend declarations -- but there is the consistency issue.
Notes from April 2003 meeting:
This is related to issue 138. John Spicer is supposed to update his paper on this topic. This is a new case not covered in that paper. We agreed that the B line should be allowed.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The status of an example like the following is unclear in the current Standard:
struct B { void f(); }; template<typename T> struct S: T { using B::f; };
9.10 [namespace.udecl] does not deal explicitly with dependent base classes, but does say in paragraph 3,
In a using-declaration used as a member-declaration, the nested-name-specifier shall name a base class of the class being defined. If such a using-declaration names a constructor, the nested-name-specier shall name a direct base class of the class being defined; otherwise it introduces the set of declarations found by member name lookup (6.5.2 [class.member.lookup], 6.5.5.2 [class.qual]).
In the definition of S, B::f is not a dependent name but resolves to an apparently unrelated class. However, because S could be instantiated as S<B>, presumably 13.8 [temp.res] paragraph 8 would apply:
No diagnostic shall be issued for a template definition for which a valid specialization can be generated.
Note also the resolution of issue 515, which permitted a similar use of a dependent base class named with a non-dependent name.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The status of an example like the following is not clear:
void f(int, int); template<typename T> void g(T t) { f(t); } void f(int, int = 0); void h() { g(0); }
According to 13.8.4 [temp.dep.res] paragraph 1,
In resolving dependent names, names from the following sources are considered:
Declarations that are visible at the point of definition of the template.
- ...
If this is to be interpreted as meaning that only the declarations that are visible at the point of definition can be used in overload resolution for dependent calls, the call g(0) is ill-formed. If, however, it is the names, not the declarations, that are captured, then presumably the second declaration of f should be considered, making the call well-formed. There is implementation divergence for this example.
The resolution of issue 1551 recently clarified the requirements in similar cases involving using-declarations:
namespace N { void f(int, int); } using N::f; template<typename T> void g(T t) { f(t); } namespace N { void f(int, int = 0); } void h() { g(0); }
The note added to 9.10 [namespace.udecl] paragraph 11 makes clear that the call g(0) is well-formed in this example.
This outcome results in an unfortunate discrepancy between how default arguments and overloaded functions are treated, even though default arguments could conceptually be viewed as simply adding extra overloads for the additional arguments.
Notes from the June, 2014 meeting:
CWG was unable to come to consensus regarding the desired outcome, with an approximately equal split between desiring the first example to be well-formed or ill-formed. It was noted that the resolution of issue 1850 makes the corresponding case for non-dependent references ill-formed, with no diagnostic required. Similar questions also apply to completing an array type, which also involves a modification to an existing entity declaration in a given scope.
Notes from the February, 2016 meeting:
CWG determined that the case should be ill-formed, no diagnostic required, to allow implementations to continue to use either strategy.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
It is not clear whether some of the wording in 9.12 [dcl.link] that applies only to function types and names ought also to apply to object names. In particular, paragraph 3 says,
Every implementation shall provide for linkage to functions written in the C programming language, "C", and linkage to C++ functions, "C++".
Nothing is said about variable names, apparently meaning that implementations need not provide C (or even C++!) linkage for variable names. Also, paragraph 5 says,
Except for functions with C++ linkage, a function declaration without a linkage specification shall not precede the first linkage specification for that function. A function can be declared without a linkage specification after an explicit linkage specification has been seen; the linkage explicitly specified in the earlier declaration is not affected by such a function declaration.
There doesn't seem to be a good reason for these provisions not to apply to variable names, as well.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Does the language linkage of a block-scope declaration determine the language linkage of a subsequent declaration of the same name in a different scope? For example,
extern "C" void f() { void g(); // Implicitly extern "C" } void g() { } // Also extern "C" or linkage mismatch?
In other contexts, inheritance of linkage requires that the earlier declaration be visible, as in 6.6 [basic.link] paragraph 6:
The name of a function declared in block scope and the name of a variable declared by a block scope extern declaration have linkage. If there is a visible declaration of an entity with linkage having the same name and type, ignoring entities declared outside the innermost enclosing namespace scope, the block scope declaration declares that same entity and receives the linkage of the previous declaration.
The specification for language linkage in 9.12 [dcl.link] paragraph 5, however, makes no mention of visibility:
A function can be declared without a linkage specification after an explicit linkage specification has been seen; the linkage explicitly specified in the earlier declaration is not affected by such a function declaration.
[Accepted at the November, 2020 meeting.]
According to 13.7.5 [temp.friend] paragraph 9,
A non-template friend declaration with a requires-clause shall be a definition. A friend function template with a constraint that depends on a template parameter from an enclosing template shall be a definition. Such a constrained friend function or function template declaration does not declare the same function or function template as a declaration in any other scope.
However, this specification conflicts with the treatment of functions with C language linkage in 9.12 [dcl.link] paragraph 7:
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.
For example:
template <typename T> struct A { struct B; }; extern "C" { template <typename T> struct A<T>::B { friend void f(B *) requires true {} // C language linkage applies }; } namespace Q { extern "C" void f(); // ill-formed redeclaration? }
Proposed resolution (April, 2020):
Change 9.12 [dcl.link] paragraph 5 as follows:
...A C language linkage is ignored in determining the language linkage of the names of class members, the names of friend functions with a trailing requires-clause, and the function type of class member functions...
[Accepted at the July, 2022 meeting.]
Subclause 10.1 [module.unit] paragraph 7 implicitly attaches the replaceable global allocation or deallocation functions to the global module. Now that extern "C++" can be used to introduce declarations in the global module, even when in the purview of a named module, the provision seems superfluous.
Proposed resolution [SUPERSEDED]:
Change in 6.7.6.5.1 [basic.stc.dynamic.general] paragraph 2 as follows:
The library provides default definitions for the global allocation and deallocation functions. Some global allocation and deallocation functions are replaceable (17.6.3 [new.delete]). A C++ program shall provide at most one definition of a replaceable allocation or deallocation function. Any such function definition replaces the default version provided in the library (16.4.5.6 [replacement.functions]). The following allocation and deallocation functions (17.6 [support.dynamic]) are implicitly declared in global scope in each translation unit of a program and are attached to the global module (10.1 [module.unit]).
Change in 10.1 [module.unit] bullet 7.2 as follows:
- If the declaration is ...
- Otherwise, if the declaration
it is attached to the global module.
is a replaceable global allocation or deallocation function (17.6.3.2 [new.delete.single], 17.6.3.3 [new.delete.array]), or- is a namespace-definition with external linkage
,or- appears within a linkage-specification (9.12 [dcl.link])
,- Otherwise, ...
Additional notes (June, 2022):
Forwarded to EWG with paper issue 1273, by decision of the CWG chair.
Approved by EWG telecon 2022-07-07.
Proposed resolution (approved by CWG 2022-07-15):
Change in 6.7.6.5.1 [basic.stc.dynamic.general] paragraph 2 as follows:
The library provides default definitions for the global allocation and deallocation functions. Some global allocation and deallocation functions are replaceable (17.6.3 [new.delete]) ; these are attached to the global module 10.1 [module.unit]). A C++ program shall provide at most one definition of a replaceable allocation or deallocation function. Any such function definition replaces the default version provided in the library (16.4.5.6 [replacement.functions]). The following allocation and deallocation functions (17.6 [support.dynamic]) are implicitly declared in global scope in each translation unit of a program.
Change in 10.1 [module.unit] bullet 7.2 as follows:
- If the declaration is ...
- Otherwise, if the declaration
it is attached to the global module.
is a replaceable global allocation or deallocation function (17.6.3.2 [new.delete.single], 17.6.3.3 [new.delete.array]), or- is a namespace-definition with external linkage
,or- appears within a linkage-specification (9.12 [dcl.link])
,- Otherwise, ...
[Accepted as a DR at the October, 2021 meeting.]
According to 10.2 [module.interface] paragraph 6,
A redeclaration of an entity or typedef-name X is implicitly exported if X was introduced by an exported declaration; otherwise it shall not be exported. [Example 4:
export module M; struct S { int n; }; typedef S S; export typedef S S; // OK, does not redeclare an entity export struct S; // error: exported declaration follows non-exported declaration—end example]
The normative text says that exporting a typedef that was not exported on its first declaration is ill-formed, but the example does so and states that it is “OK”. This is a contradiction that was introduced by the changes in paper P1787R6; the previous normative text supported the usage in the example.
(See also editorial issue 4540.)
Proposed resolution, August, 2021:
Change 10.2 [module.interface] paragraph 6 as follows:
A redeclaration of an entityor typedef-nameX is implicitly exported if X was introduced by an exported declaration; otherwise it shall not be exported.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
11.4 [class.mem] paragraph 1 allows nested classes, class templates, and enumerations to be declared and then later defined in the class member-specification. There does not appear to be a restriction on using a qualified-id in that definition. Should such a restriction be added?
[ Resolved by P2448R2, applied in July 2022. ]
According to 11.4.5 [class.ctor] paragraph 6, a defaulted default constructor is constexpr if the corresponding user-written constructor would satisfy the constexpr requirements. However, the requirements apply to the definition of a constructor, and a defaulted constructor is defined only if it is odr-used, leaving it indeterminate at declaration time whether the defaulted constructor is constexpr or not.
(See also issue 1358.)
Additional notes (February, 2013):
As an example of this issue, consider:
struct S { int i = sizeof(S); };
You can't determine the value of the initializer, and thus whether the initializer is a constant expression, until the class is complete, but you can't complete the class without declaring the default constructor, and whether that constructor is constexpr or not depends on whether the member initializer is a constant expression.
A similar issue arises with the following example:
struct A { int x = 37; struct B { int x = 37; } b; B b2[2][3] = { { } }; };
This introduces an order dependency that is not specified in the current text: determining whether the default constructor of A is constexpr requires first determining the characteristics of the initializer of B::x and whether B::B() is constexpr or not.
The problem is exacerbated with class templates, since the current direction of CWG is to instantiate member initializers only when they are needed (see issue 1396). For a specific example:
struct S;
template<class T> struct X {
int i = T().i;
};
unsigned n = sizeof(X<S>); // Error?
struct S { int i; };
This also affects determining whether a class template specialization is a literal type or not; presumably getting the right answer to that requires instantiating the class and all its nonstatic data member initializers.
See also issues 1397 and 1594.
Notes from the September, 2013 meeting:
This issue should be resolved together with issue 1397.
Proposed resolution (May, 2014):
Change 11.4.5 [class.ctor] paragraphs 4-5 as follows:
A defaulted default constructor for class X is defined as deleted if:
...
any potentially constructed subobject has a type with a destructor that is deleted or inaccessible from the defaulted default constructor.
An implicitly-declared default constructor is constexpr if:
X has no virtual bases; and
for each non-variant non-static data member or base class subobject M, either M is initialized via brace-or-equal-initializer or default-initialization of M uses a constexpr constructor; and
if X is a union having variant members, or, if X is a non-union-class, for each anonymous union member having variant members, exactly one non-static data member is initialized via brace-or-equal-initializer.
A default constructor is trivial if it is not user-provided and if:
...
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 default constructor that is defaulted and not defined as deleted is implicitly defined when it is odr-used (6.3 [basic.def.odr]) to create an object of its class type (6.7.2 [intro.object]) or when it is explicitly defaulted after its first declaration. The implicitly-defined 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 (11.9.3 [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 (9.2.6 [dcl.constexpr]), the implicitly-defined default constructor is constexpr.Before the 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:...
Additional notes, May, 2014:
The proposed resolution inadvertently allows a defaulted default constructor of a class with virtual bases to be constexpr. It has been updated with a change addressing that oversight and returned to "review" status.
See also issue 1890.
[Accepted as a DR at the June, 2021 meeting.]
According to 11.4.5.3 [class.copy.ctor] paragraph 6,
If the class definition does not explicitly declare a copy constructor, a non-explicit one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted (9.6 [dcl.fct.def]).
However, this rule is contradicted by paragraph 10, which lists a number of other reasons why a defaulted copy constructor will be defined as deleted, rather than being “defined as defaulted,” as required by paragraph 6:
A defaulted copy/move constructor for a class X is defined as deleted (9.6.3 [dcl.fct.def.delete]) if X has:
...
A similar contradiction exists for copy assignment operators in 11.4.6 [class.copy.assign] paragraphs 2 and 7.
Proposed resolution (April, 2021):
Change 11.4.5.3 [class.copy.ctor] paragraph 6 as follows:
If the class definition does not explicitly declare a copy constructor, a non-explicit one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it isdefined asdefaulted (9.6 [dcl.fct.def]). The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor (D.6 [depr.impldec]).
Change 11.4.6 [class.copy.assign] paragraph 2 as follows:
If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy assignment operator is defined as deleted; otherwise, it isdefined asdefaulted (9.6 [dcl.fct.def]). The latter case is deprecated if the class has a user-declared copy constructor or a user-declared destructor (D.9). The implicitly-declared...
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Mark Mitchell raised a number of issues related to the resolution of issue 244 and of destructor lookup in general.
Issue 244 says:
... in a qualified-id of the form:::opt nested-name-specifieropt class-name :: ~ class-name
the second class-name is looked up in the same scope as the first.
But if the reference is "p->X::~X()", the first class-name is looked up in two places (normal lookup and a lookup in the class of p). Does the new wording mean:
This is a test case that illustrates the issue:
struct A { typedef A C; }; typedef A B; void f(B* bp) { bp->B::~B(); // okay B found by normal lookup bp->C::~C(); // okay C found by class lookup bp->B::~C(); // B found by normal lookup C by class -- okay? bp->C::~B(); // C found by class lookup B by normal -- okay? }
A second issue concerns destructor references when the class involved is a template class.
namespace N { template <typename T> struct S { ~S(); }; } void f(N::S<int>* s) { s->N::S<int>::~S(); }
The issue here is that the grammar uses "~class-name" for destructor names, but in this case S is a template name when looked up in N.
Finally, what about cases like:
template <typename T> void f () { typename T::B x; x.template A<T>::template B<T>::~B(); }
When parsing the template definition, what checks can be done on "~B"?
Sandor Mathe adds :
The standard correction for issue 244 (now in DR status) is still incomplete.
Paragraph 5 of 6.5.5 [basic.lookup.qual] is not applicable for p->T::~T since there is no nested-name-specifier. Section _N4868_.6.5.6 [basic.lookup.classref] describes the lookup of p->~T but p->T::~T is still not described. There are examples (which are non-normative) that illustrate this sort of lookup but they still leave questions unanswered. The examples imply that the name after ~ should be looked up in the same scope as the name before the :: but it is not stated. The problem is that the name to the left of the :: can be found in two different scopes. Consider the following:
struct S { struct C { ~C() { } }; }; typedef S::C D; int main() { D* p; p->C::~D(); // valid? }
Should the destructor call be valid? If there were a nested name specifier, then D should be looked for in the same scope as C. But here, C is looked for in 2 different ways. First, it is searched for in the type of the left hand side of -> and it is also looked for in the lexical context. It is found in one or if both, they must match. So, C is found in the scope of what p points at. Do you only look for D there? If so, this is invalid. If not, you would then look for D in the context of the expression and find it. They refer to the same underlying destructor so this is valid. The intended resolution of the original defect report of the standard was that the name before the :: did not imply a scope and you did not look for D inside of C. However, it was not made clear whether this was to be resolved by using the same lookup mechanism or by introducing a new form of lookup which is to look in the left hand side if that is where C was found, or in the context of the expression if that is where C was found. Of course, this begs the question of what should happen when it is found in both? Consider the modification to the above case when C is also found in the context of the expression. If you only look where you found C, is this now valid because it is in 1 of the two scopes or is it invalid because C was in both and D is only in 1?
struct S { struct C { ~C() { } }; }; typedef S::C D; typedef S::C C; int main() { D* p; p->C::~D(); // valid? }
I agree that the intention of the committee is that the original test case in this defect is broken. The standard committee clearly thinks that the last name before the last :: does not induce a new scope which is our current interpretation. However, how this is supposed to work is not defined. This needs clarification of the standard.
Martin Sebor adds this example (September 2003), along with errors produced by the EDG front end:
namespace N { struct A { typedef A NA; }; template <class T> struct B { typedef B NB; typedef T BT; }; template <template <class> class T> struct C { typedef C NC; typedef T<A> CA; }; } void foo (N::A *p) { p->~NA (); p->NA::~NA (); } template <class T> void foo (N::B<T> *p) { p->~NB (); p->NB::~NB (); } template <class T> void foo (typename N::B<T>::BT *p) { p->~BT (); p->BT::~BT (); } template <template <class> class T> void foo (N::C<T> *p) { p->~NC (); p->NC::~NC (); } template <template <class> class T> void foo (typename N::C<T>::CA *p) { p->~CA (); p->CA::~CA (); } Edison Design Group C/C++ Front End, version 3.3 (Sep 3 2003 11:54:55) Copyright 1988-2003 Edison Design Group, Inc. "t.cpp", line 16: error: invalid destructor name for type "N::B<T>" p->~NB (); ^ "t.cpp", line 17: error: qualifier of destructor name "N::B<T>::NB" does not match type "N::B<T>" p->NB::~NB (); ^ "t.cpp", line 30: error: invalid destructor name for type "N::C<T>" p->~NC (); ^ "t.cpp", line 31: error: qualifier of destructor name "N::C<T>::NC" does not match type "N::C<T>" p->NC::~NC (); ^ 4 errors detected in the compilation of "t.cpp".
John Spicer: The issue here is that we're unhappy with the destructor names when doing semantic analysis of the template definitions (not during an instantiation).
My personal feeling is that this is reasonable. After all, why would you call p->~NB for a class that you just named as N::B<T> and you could just say p->~B?
Additional note (September, 2004)
The resolution for issue 244 removed the discussion of p->N::~S, where N is a namespace-name. However, the resolution did not make this construct ill-formed; it simply left the semantics undefined. The meaning should either be defined or the construct made ill-formed.
Additional note, November, 2014:
Here are some additional examples that should be addressed by the resolution of this issue:
namespace N { template<typename T> struct E {}; typedef E<int> F; } namespace M { typedef N::F H; } void g(N::F f) { typedef N::F G; f.G::~E(); // #1 f.G::~F(); // #2 f.G::~G(); // #3 f.N::F::~E(); // #4 f.N::F::~F(); // #5 f.N::F::~G(); // #6 f.M::H::~E(); // #7 f.M::H::~F(); // #8 f.M::H::~G(); // #9 f.M::H::~H(); // #10 }
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting. See 11.4.7 [class.dtor] paragraph 1.]
There does not appear to be wording to exclude use of a name like ~S for entities other than destructors.
[Accepted at the February, 2022 meeting.]
Presumably the following example is intended to be ill-formed:
struct A {
(*operator int*());
};
A a;
int *x = a; // Ok?
It is not clear, however, which rule is supposed to reject such a member-declaration.
Proposed resolution (December, 2021):
Change 11.4.8.3 [class.conv.fct] paragraph 1 as follows, splitting the paragraph as indicated:
A member function of a class X with a name of the formconversion-function-id:
operator conversion-type-id
conversion-type-id:
type-specifier-seq conversion-declaratoropt
conversion-declarator:
ptr-operator conversion-declaratoropt
A declaration whose declarator-id has an unqualified-id that is a conversion-function-id declares a conversion function; its declarator shall be a function declarator (9.3.4.6 [dcl.fct]) of the form
ptr-declarator ( parameter-declaration-clause ) cv-qualifier-seqopt
ref-qualifier-seqopt noexcept-specifieropt attribute-specifier-seqopt
where the ptr-declarator consists solely of an id-expression, an optional attribute-specifier-seq, and optional surrounding parentheses, and the id-expression has one of the following forms:
in a member-declaration that belongs to the member-specification of a class or class template but is not a friend declaration (11.8.4 [class.friend]), the id-expression is a conversion-function-id;
otherwise, the id-expression is a qualified-id whose unqualified-id is a conversion-function-id.
A conversion function shall have no parameters and shall be a non-static member function of a class or class template X; it specifies a conversion from X to the type specified by the conversion-type-id, interpreted as a type-id (9.3.2 [dcl.name]).
Such functions are called conversion functions.A decl-specifier in the decl-specifier-seq of a conversion function (if any) shall not be
neithera defining-type-specifiernor static. The type of the conversion function(9.3.4.6 [dcl.fct])is “noexceptopt function taking no parameter cv-qualifier-seqopt ref-qualifieropt returning conversion-type-id”.A conversion function is never used to convert a (possibly cv-qualified) object to the (possibly cv-qualified) same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to it), or to cv void.102 [Example 1:...
[Accepted at the February, 2022 meeting.]
According to 11.4.10 [class.bit] paragraph 1,
A bit-field shall have integral or enumeration type
This apparently does not allow for cv-qualification in a bit-field type.
Notes from the December, 2021 meeting:
As of N4901, there is no longer an issue regarding the integral types; 6.8.2 [basic.fundamental] paragraph 11 says,
The character types, bool, the signed and unsigned integer types, and cv-qualified versions (6.8.5 [basic.type.qualifier]) thereof, are collectively termed integral types.
Proposed resolution (December, 2021):
A bit-field shall have integral or (possibly cv-qualified) enumeration type
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Paragraph 4 of 11.4.11 [class.free] speaks of looking up a deallocation function. While it is an error if a placement deallocation function alone is found by this lookup, there seems to be an assumption that a placement deallocation function and a usual deallocation function can both be declared in a given class scope without creating an ambiguity. The normal mechanism by which ambiguity is avoided when functions of the same name are declared in the same scope is overload resolution; however, there is no mention of overload resolution in the description of the lookup. In fact, there appears to be nothing in the current wording that handles this case. That is, the following example appears to be ill-formed, according to the current wording:
struct S { void operator delete(void*); void operator delete(void*, int); }; void f(S* p) { delete p; // ill-formed: ambiguous operator delete }
Suggested resolution (Mike Miller, March 2002):
I think you might get the right effect by replacing the last sentence of 11.4.11 [class.free] paragraph 4 with something like:
After removing all placement deallocation functions, the result of the lookup shall contain an unambiguous and accessible deallocation function.
Additional notes (October, 2012):
This issue should be reconsidered in list of paper N3396, as it would add additional overloads for allocation and deallocation functions.
The term “usual deallocation function” is defined in 6.7.6.5.3 [basic.stc.dynamic.deallocation] paragraph 2; perhaps it could be used to good effect in 7.6.2.9 [expr.delete] paragraph 7. The specifications in 11.4.11 [class.free] paragraphs 4 and 5 should probably also be moved into 7.6.2.9 [expr.delete].
[Accepted as a DR at the October, 2021 meeting.]
According to 11.7.3 [class.virtual] paragraph 2,
If a virtual member function F is declared in a class B, and, in a class D derived (directly or indirectly) from B, a declaration of a member function G corresponds (6.4.1 [basic.scope.scope]) to a declaration of F, ignoring trailing requires-clauses, then G overrides105 F.
This is different from C++20, where G was considered to hide, rather than to override, F if the ref-qualifiers of the declarations are different. This unintentional change could be addressed in one of two ways. To restore the C++20 behavior, the cited paragraph could be amended to:
...a declaration of a member function G corresponds (6.4.1 [basic.scope.scope]) to a declaration of F, ignoring trailing requires-clauses, and has the same ref-qualifier (if any), then G overrides105 F.
Alternatively, such a situation could be regarded as an ill-formed attempt to override the base class function, which could be specified by adding the following as a new paragraph preceding 11.7.3 [class.virtual] paragraph 7:
The ref-qualifier, or lack thereof, of an overriding function shall be the same as that of the overridden function.
The return type of an overriding function shall be either identical to the return type of the overridden function or covariant...
Notes from the August, 2021 teleconference:
CWG preferred the second option.
Proposed resolution, August, 2021:
Add the following as a new paragraph preceding 11.7.3 [class.virtual] paragraph 7:
The ref-qualifier, or lack thereof, of an overriding function shall be the same as that of the overridden function.
The return type of an overriding function shall be either identical to the return type of the overridden function or covariant...
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Referring to a private member of a class, 11.8 [class.access] paragraph 1 says,
its name can be used only by members and friends of the class in which it is declared.
That wording does not appear to reflect the intent of access control, however. Consider the following:
struct S {
void f(int);
private:
void f(double);
};
void g(S* sp) {
sp->f(2); // Ill-formed?
}
The statement from 11.8 [class.access] paragraph 1 says that the name f can be used only by members and friends of S. Function g is neither, and it clearly contains a use of the name f. That appears to make it ill-formed, in spite of the fact that overload resolution will select the public member.
A related question is whether the use of the term “name” in the description of the effect of access control means that it does not apply to constructors and destructors, which do not have names.
Mike Miller: The phrase “its name can be used” should be understood as “it can be referred to by name.” Paragraph 4, among other places, makes it clear that access control is applied after overload resolution. The “name” phrasing is there to indicate that access control does not apply where the name is not used (in a call via a pointer, for example).
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
I have heard a claim that the following code is valid, but I don't see why.
struct A { int foo (); }; struct B: A { private: using A::foo; }; int main () { return B ().foo (); }
It seems to me that the using declaration in B should hide the public foo in A. Then the call to B::foo should fail because B::foo is not accessible in main.
Am I missing something?
Steve Adamczyk: This is similar to the last example in 11.8.3 [class.access.base]. In prose, the rule is that if you have access to cast to a base class and you have access to the member in the base class, you are given access in the derived class. In this case, A is a public base class of B and foo is public in A, so you can access foo through a B object. The actual permission for this is in the fourth bullet in 11.8.3 [class.access.base] paragraph 4.
The wording changes for issue 9 make this clearer, but I believe even without them this example could be discerned to be valid.
See my paper J16/96-0034, WG21/N0852 on this topic.
Steve Clamage: But a using-declaration is a declaration (9.10 [namespace.udecl]). Compare with
struct B : A { private: int foo(); };
In this case, the call would certainly be invalid, even though your argument about casting B to an A would make it OK. Your argument basically says that an access adjustment to make something less accessible has no effect. That also doesn't sound right.
Steve Adamczyk: I agree that is strange. I do think that's what 11.8.3 [class.access.base] says, but perhaps that's not what we want it to say.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The access rules in 11.8.3 [class.access.base] do not appear to handle references in nested classes and outside of nonstatic member functions correctly. For example,
struct A { typedef int I; // public }; struct B: private A { }; struct C: B { void f() { I i1; // error: access violation } I i2; // OK struct D { I i3; // OK void g() { I i4; // OK } }; };
The reason for this discrepancy is that the naming class in the reference to I is different in these cases. According to 11.8.3 [class.access.base] paragraph 5,
The access to a member is affected by the class in which the member is named. This naming class is the class in which the member name was looked up and found.
In the case of i1, the reference to I is subject to the transformation described in 11.4.3 [class.mfct.non.static] paragraph 3:
Similarly during name lookup, when an unqualified-id (7.5 [expr.prim]) used in the definition of a member function for class X resolves to a static member, an enumerator or a nested type of class X or of a base class of X, the unqualified-id is transformed into a qualified-id (7.5 [expr.prim]) in which the nested-name-specifier names the class of the member function.
As a result, the reference to I in the declaration of i1 is transformed to C::I, so that the naming class is C, and I is inacessible in C. In the remaining cases, however, the transformation does not apply. Thus, the naming class of I in these references is A, and I is publicly accessible in A.
Presumably either the definition of “naming class” must be changed or the transformation of unqualified-ids must be broadened to include all uses within the scope of a class and not just within nonstatic member functions (and following the declarator-id in the definition of a static member, per 11.4.9 [class.static] paragraph 4).
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
In an example like,
struct Y {}; template <typename T> struct X : public virtual Y { }; template <typename T> class A : public X<T> { template <typename S> A (S) : S () { } }; template A<int>::A (Y);
Should S be found? (S is a dependent name, so if it resolves to a base class type in the instantiated template, it should satisfy the requirements.) All the compilers I tried allowed this example, but 11.9.3 [class.base.init] paragraph 2 says,
Names in a mem-initializer-id are looked up in the scope of the constructor's class and, if not found in that scope, are looked up in the scope containing the constructor's definition.
The name S is not declared in those scopes.
Mike Miller: Here's another example that is accepted by most/all compilers but not by the current wording:
namespace N { struct B { B(int); }; typedef B typedef_B; struct D: B { D(); }; } N::D::D(): typedef_B(0) { }
Except for the fact that the constructor function parameter names are ignored (see paragraph 7), what the compilers seem to be doing is essentially ordinary unqualified name lookup.
Notes from the October, 2009 meeting:
The eventual resolution of this issue should take into account the template parameter scope introduced by the resolution of issue 481.
[Accepted at the July, 2022 meeting.]
"Deducing this" allows to declare assignment and comparison operator functions as explicit object member functions.
However, such an assignment operator can never be a copy or move assignment operator, which means it always conflicts with the implicitly-defined one:
struct C {
C& operator=(this C&, C const&); // error: can't overload with the copy assignment operator
};
Similarly, operator== or operator<=> can be declared with an explicit object parameter, but they cannot be defaulted:
struct D {
bool operator==(this D const&, D const&) = default; // error: not a kind of comparison that can be defaulted
};
There seems to be no reason to disallow that, for people who prefer writing all of their members with explicit object parameters.
Suggested resolution:
Change in 11.4.6 [class.copy.assign] paragraph 1 as follows:
A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one non-object parameter of type X, X&, const X&, volatile X&, or const volatile X&.
Change in 11.4.6 [class.copy.assign] paragraph 3 as follows:
A user-declared move assignment operator X::operator= is a non-static non-template member function of class X with exactly one non-object parameter of type X&&, const X&&, volatile X&&, or const volatile X&&.
Change in 11.10.1 [class.compare.default] paragraph 1 as follows:
A defaulted comparison operator function (12.4.3 [over.binary]) for some class C shall be a non-template function that is
- a non-static
const non-volatilememberof C having one parameter of type const C& and either no ref-qualifier or the ref-qualifier &, oror friend of C anda friend of C havingeither has two parameters of type const C& or two parameters of type C , where the implicit object parameter (if any) is considered to be the first parameter..
Additional notes (May, 2022):
Forwarded to EWG with paper issue 1235, by decision of the CWG chair.
Approved by EWG telecon 2022-06-09 and EWG 2022-06 electronic poll.
See vote.
Additional notes (July, 2022):
The suggested resolution makes the following a copy assignment operator, suppressing the implicitly-declared one, which is surprising:
struct B {
B &operator =(this int, const B &); // copy assignment operator
};
Proposed resolution (approved by CWG 2022-07-15):
Change in 9.6.2 [dcl.fct.def.default] paragraph 2 as follows:
The type T1 of anAn explicitly defaulted special member functionFF1 with type T1 is allowed to differ from the corresponding special member function F2 with type T2it would have had if it werethat would have been implicitly declared, as follows:If T1 differs from T2 in
- T1 and T2 may have differing ref-qualifiers;
- if F2 has an implicit object parameter of type "reference to C", F1 may be an explicit object member function whose explicit object parameter is of type "reference to C";
- T1 and T2 may have differing exception specifications; and
- if
T2F2 has a non-object parameter of type const C&, the corresponding non-object parameter ofT1F1 may be of type C&.any othera way other than as allowed by the preceding rules, then:
- if
FF1 is an assignment operator, and the return type of T1 differs from the return type of T2 orT1F1's non-object parameter type is not a reference, the program is ill-formed;- otherwise, if
FF1 is explicitly defaulted on its first declaration, it is defined as deleted;- otherwise, the program is ill-formed.
Change in 11.4.6 [class.copy.assign] paragraph 1 as follows:
A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one non-object parameter of type X, X&, const X&, volatile X&, or const volatile X&.
Change in 11.4.6 [class.copy.assign] paragraph 3 as follows:
A user-declared move assignment operator X::operator= is a non-static non-template member function of class X with exactly one non-object parameter of type X&&, const X&&, volatile X&&, or const volatile X&&.
Change in 11.10.1 [class.compare.default] paragraph 1 as follows:
A defaulted comparison operator function (12.4.3 [over.binary]) for some class C shall be a non-template function that is
- a non-static
const non-volatilememberof C having one parameter of type const C& and either no ref-qualifier or the ref-qualifier &, oror friend of C anda friend of C havingeither has two parameters of type const C& or two parameters of type C , where the implicit object parameter (if any) is considered to be the first parameter..
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Consider an example like:
template<typename T> struct A { typename T::error e; }; template<typename T> struct B { }; B<A<void>> b1, &b2 = (b1 = b1);
If the assignment operator performs argument-dependent lookup, A<void> will be an associated class and will be instantiated, producing an error. Similar questions apply to the other member-only overloaded operators, operator-> and operator[]. Bullet 3.2 of 12.2.2.3 [over.match.oper] should be changed not to perform unqualified lookup for these operators.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
According to 12.2.4 [over.match.best] paragraph 4, the following program appears to be ill-formed:
void f(int, int=0); void f(int=0, int); void g() { f(); }
Though I do not expect this is the intent of this paragraph in the standard.
12.2.4 [over.match.best] paragraph 4:
If the best viable function resolves to a function for which multiple declarations were found, and if at least two of these declarations or the declarations they refer to in the case of using-declarations specify a default argument that made the function viable, the program is ill-formed. [Example:namespace A { extern "C" void f(int = 5); } namespace B { extern "C" void f(int = 5); } using A::f; using B::f; void use() { f(3); //OK, default argument was not used for viability f(); //Error: found default argument twice }end example]
[Accepted at the July, 2022 meeting.]
The intent of paper P2128R6, which permitted multiple parameters in overloaded subscript operators and was adopted at the October, 2021 plenary, was that overloaded operator[] should allow parameters with default arguments. However, the adopted wording did not address the following restriction from 12.4.1 [over.oper.general] paragraph 10:
An operator function cannot have default arguments (9.3.4.7 [dcl.fct.default]), except where explicitly stated below.
Similar wording to that of operator() should be added for operator[].
Proposed resolution (December, 2021):
Change 12.4.5 [over.sub] paragraph 1 as follows:
A subscripting operator function is a function named operator[] that is a non-static member function with an arbitrary number of parameters. It may have default arguments. For an expression...
Approved by EWG 2022-04-14.
Approved by CWG 2022-04-22.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
According to Clause 13 [temp] paragraph 5,
Except that a function template can be overloaded either by (non-template) functions with the same name or by other function templates with the same name (13.10.4 [temp.over] ), a template name declared in namespace scope or in class scope shall be unique in that scope._N4868_.6.4.10 [basic.scope.hiding] paragraph 2 agrees that only functions, not function templates, can hide a class name declared in the same scope:
A class name (11.3 [class.name] ) or enumeration name (9.8.1 [dcl.enum] ) can be hidden by the name of an object, function, or enumerator declared in the same scope.However, 6.4 [basic.scope] paragraph 4 treats functions and template functions together in this regard:
Given a set of declarations in a single declarative region, each of which specifies the same unqualified name,
- they shall all refer to the same entity, or all refer to functions and function templates; or
- exactly one declaration shall declare a class name or enumeration name that is not a typedef name and the other declarations shall all refer to the same object or enumerator, or all refer to functions and function templates; in this case the class name or enumeration name is hidden
John Spicer: You should be able to take an existing program and replace an existing function with a function template without breaking unrelated parts of the program. In addition, all of the compilers I tried allow this usage (EDG, Sun, egcs, Watcom, Microsoft, Borland). I would recommend that function templates be handled exactly like functions for purposes of name hiding.
Martin O'Riordan: I don't see any justification for extending the purview of what is decidedly a hack, just for the sake of consistency. In fact, I think we should go further and in the interest of consistency, we should deprecate the hack, scheduling its eventual removal from the C++ language standard.
The hack is there to allow old C programs and especially the 'stat.h' file to compile with minimum effort (also several other Posix and X headers). People changing such older programs have ample opportunity to "do it right". Indeed, if you are adding templates to an existing program, you should probably be placing your templates in a 'namespace', so the issue disappears anyway. The lookup rules should be able to provide the behaviour you need without further hacking.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
According to 13.3 [temp.names] paragraph 4,
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 object expression of the postfix-expression is type-dependent or the nested-name-specifier in the qualified-id refers to a dependent type, but the name is not a member of the current instantiation (13.8.3.2 [temp.dep.type]), the member template name must be prefixed by the keyword template.
In other words, the template keyword is only required when forming a template-id. However, current compilers reject an example like:
template<typename T, template<typename> class U = T::X> struct A;
and require the template keyword before X. Should the rule be amended to require the template keyword in cases like this?
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The relationship between declarations and definitions of variable templates is not clear. For example:
template<typename T> auto var0 = T(); // #1a. template<typename T> extern T var0; // #1b. template<typename T> T var1; // #2a. template<typename T> extern auto var1; // #2b. template<typename T> extern T var1; // #2c. template<typename T> T var1; // #2d.
Questions:
When is a variable template declaration a definition and when a non-defining declaration?
What declarations are valid?
Should auto declarations be allowed?
To what extent, if any, do these involve type matching?
How are types matched, especially in the presence of auto?
Proposed resolution (May, 2017):
This issue is resolved by the resolution of issue 1704.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
There does not appear to be a rule that two declarations of a class template must have compatible template parameter lists; e.g., it is not clear what makes the following ill-formed:
template <typename> struct A; template <unsigned> struct A;
[Accepted at the November, 2020 meeting as paper P2096R2.]
It appears that partial specializations of variable templates are intended to be supported, as 13.4.4 [temp.arg.template] paragraph 2 says,
Any partial specializations (13.7.6 [temp.spec.partial]) associated with the primary class template or primary variable template are considered when a specialization based on the template template-parameter is instantiated.
However, there is no explicit specification for how they are to be handled, and the wording in 13.7.6 [temp.spec.partial] and its subsections explicitly applies only to partial specializations of class templates.
Additional note, July, 2017:
The term “primary template” appears not to be defined in the current wording; the resolution of this issue might be a good opportunity to add such a definition.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The current wording of the Standard does not permit repeated alias template declarations within a scope, but some current implementations allow it, presumably by analogy with typedef declarations. Should the Standard be changed to permit this usage?
Notes from the November, 2014 meeting:
CWG agreed that the usage should be permitted, provided that the dependent types are equivalent. Note that this is a weaker requirement than the token-for-token identity of the ODR, since alias templates are not definitions per Clause 13 [temp] paragraph 1.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The “Down with typename!” paper, P0634R3, overlooked the case of a conversion-type-id in a conversion-function-id:
template<class T> struct S { operator typename T::X(); // typename is not helpful here. };
This context should be added to the list of contexts in which a qualified-id is assumed to name a type.
[Accepted at the November, 2020 meeting.]
Given the following example,
template <typename T> struct A {}; template <typename T> void f() requires (sizeof(A<T>)) {}
the current wording does not appear to allow diagnosis of the program as ill-formed. In particular, 13.8 [temp.res] bullet 8.2 says,
The program is ill-formed, no diagnostic required, if:
...
no substitution of template arguments into a type-constraint or requires-clause would result in a valid expression, or
...
However, substitution into the requires-clause in this case would result in a valid expression, but not one that is an atomic constraint that can be checked for satisfaction.
Proposed resolution (April, 2020):
Change bullet 8.2 of 13.8 [temp.res] as follows:
The program is ill-formed, no diagnostic required, if:
...
no substitution of template arguments into a type-constraint or requires-clause would result in a valid expressionany constraint-expression in the program, introduced or otherwise, has (in its normal form) an atomic constraint A where no satisfaction check of A could be well-formed and no satisfaction check of A is performed, or...
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
According to 13.8.2 [temp.local]paragraph 1,
Like normal (non-template) classes, class templates have an injected-class-name (Clause 11 [class]). The injected-class-name can be used as a template-name or a type-name. When it is used with a template-argument-list, as a template-argument for a template template-parameter, or as the final identifier in the elaborated-type-specifier of a friend class template declaration, it refers to the class template itself. Otherwise, it is equivalent to the template-name followed by the template-parameters of the class template enclosed in <>.
The intent is that a < following such an injected-class-name is to be interpreted as the start of a template-argument-list (and an error if the following tokens do not constitute a valid template-argument-list), but that is not said explicitly.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The resolution of issue 1321 changed the term “dependent name” to apply only to unqualified-ids, presumably on the basis that only unqualified-ids affect the lookup set. However, the rule from 13.7.7.2 [temp.over.link] paragraph 5,
For determining whether two dependent names (13.8.3 [temp.dep]) are equivalent, only the name itself is considered, not the result of name lookup in the context of the template. If multiple declarations of the same function template differ in the result of this name lookup, the result for the first declaration is used.
should apply to non-dependent qualified-ids naming functions called with dependent arguments, as well.
There should also be a statement that the name of a member of an unknown specialization is a dependent name and so should fall under the rules of 13.8.4 [temp.dep.res] and not _N4868_.13.8.4 [temp.nondep].
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The specification of dependent types in 13.8.3.2 [temp.dep.type] is given in terms of names. However, one might consider some unnamed types as dependent. Consider the following example:
template <typename T> struct A { struct { } obj; void foo() { bar(obj); // lookup for bar when/where? } }; void bar(...); int main() { A<int> a; a.foo(); // calls bar(...)? }
If the type of A::obj had a name, it would be dependent. However, the rationale for making nested types dependent is that they are subject to explicit specialization and thus not knowable at the point of the template definition. An unnamed type, as in this example, cannot be explicitly specialized and thus could be considered as a member of the current instantiation. Which treatment is intended?
Notes from the February, 2014 meeting:
There are other cases in which a named entity is dependnet, even though it cannot be explicitly specialized. CWG felt that the most consistent rule would be to make all nested classes dependent, whether named or not.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
According to 13.8.3.2 [temp.dep.type] paragraph 1, a name refers to the current instantiation if it is
in the definition of a partial specialization or a member of a partial specialization, the name of the class template followed by the template argument list of the partial specialization enclosed in <> (or an equivalent template alias specialization).
I don't think this works. How are the argument lists compared? If it's using the “equivalent” rules, this doesn't work because we make no provision for “functionally equivalent but not equivalent” here. If it's using 13.6 [temp.type] paragraph 1, that fails because it doesn't handle dependent template arguments at all.
The same issue would come up when defining members of a partial specialization out-of-line.
[Accepted at the November, 2020 meeting.]
Consider the following example:
template<typename ...T> auto f() {
using F = int(*)(int (...p)[sizeof(sizeof(T))]);
// ...
}
F is not covered in the list of cases in 13.8.3.2 [temp.dep.type] paragraph 9, because the types from which the function type is constructed are not dependent types. (The parameter pack p is of type int[sizeof(size_t)].) Similar situations arise with non-injective alias templates.
Proposed resolution (August, 2020):
Change 13.8.3.2 [temp.dep.type] paragraph 9 as follows:
A type is dependent if it is
...
an array type whose element type is dependent or whose bound (if any) is value-dependent,
a function type whose parameters include one or more function parameter packs,
a function type whose exception specification is value-dependent,
...
(We do have the relevant wording for pack expansions in simple-template-ids in bullet 9.8, so that similar case is already handled.)
[Accepted at the July, 2022 meeting.]
According to 13.8.3.3 [temp.dep.expr] paragraph 3,
...Expressions of the following forms are type-dependent only if the type specified by the type-id, simple-type-specifier or new-type-id is dependent, even if any subexpression is type-dependent:
simple-type-specifier ( expression-list )
::opt new new-placementopt new-type-id new-initializeropt
::opt new new-placementopt ( type-id ) new-initializeropt
dynamic_cast < type-id > ( expression )
static_cast < type-id > ( expression )
const_cast < type-id > ( expression )
reinterpret_cast < type-id > ( expression )
( type-id ) cast-expression
This list is missing cases for:
Proposed resolution (approved by CWG 2022-06-17):
Change in 13.8.3.3 [temp.dep.expr] paragraph 3 as follows:
Expressions of the following forms are type-dependent only if the type specified by the type-id, simple-type-specifier, typename-specifier, or new-type-id is dependent, even if any subexpression is type-dependent:simple-type-specifier ( expression-listopt ) simple-type-specifier braced-init-list typename-specifier ( expression-listopt ) typename-specifier braced-init-list ...
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
The current wording of 13.8.4 [temp.dep.res] seems to assume that dependent names can only appear in the definition of a template:
In resolving dependent names, names from the following sources are considered:
Declarations that are visible at the point of definition of the template.
Declarations from namespaces associated with the types of the function arguments both from the instantiation context (13.8.4.1 [temp.point]) and from the definition context.
However, dependent names can occur in non-defining declarations of the template as well; for instance,
template<typename T> T foo(T, decltype(bar(T())));
bar needs to be looked up, even though there is no definition of foo in the translation unit.
Additional note (February, 2011):
The resolution of this issue can't simply replace the word “definition” with the word “declaration,” mutatis mutandis, because there can be multiple declarations in a translation unit (which isn't true of “the definition”). As a result, the issue was moved back to "open" status for further consideration.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Consider the following example:
template<typename T> struct A { operator int() { return 0; } void f() { operator T(); } }; int main() { A<int> a; a.f(); }
One might expect this to call operator int when instantiating. But since operator T is a dependent name, it is looked up by unqualified lookup only in the definition context, where it will find no declaration. Argument-dependent lookup will not find anything in the instantiation context either, so this code is ill-formed. If we change operator int() to operator T(), which is a seemingly unrelated change, the code becomes well-formed.
There is implementation variability on this point.
[Accepted at the July, 2022 meeting.]
Subclause 13.10.2 [temp.arg.explicit] paragraph 4 specifies:
Trailing template arguments that can be deduced (13.10.3 [temp.deduct]) or obtained from default template-arguments may be omitted from the list of explicit template-arguments.
[Note 1: A trailing template parameter pack (13.7.4 [temp.variadic]) not otherwise deduced will be deduced as an empty sequence of template arguments. —end note]
If all of the template arguments can be deduced, they may all be omitted; in this case, the empty template argument list <> itself may also be omitted.
The wording does not allow omitting the empty template argument list <> if all of the template arguments have been obtained from default template-arguments. For example:
template<typename T = int> int f(); int x = f(); // ill-formed per the wording int (*y)() = f; // ditto
Proposed resolution (approved by CWG 2022-07-15):
Change in 13.10.2 [temp.arg.explicit] paragraph 4 as follows:
... If all of the template arguments can be deduced or obtained from default template-arguments, they may all be omitted; in this case, the empty template argument list <> itself may also be omitted.
[Accepted at the November, 2020 meeting as part of paper P1787R6 and moved to DR at the February, 2021 meeting.]
Nicolai Josuttis sent me an example like the following:
template <typename RET, typename T1, typename T2> const RET& min (const T1& a, const T2& b) { return (a < b ? a : b); } template const int& min<int>(const int&,const int&); // #1 template const int& min(const int&,const int&); // #2
Among the questions was whether explicit instantiation #2 is valid, where deduction is required to determine the type of RET.
The first thing I realized when researching this is that the standard does not really spell out the rules for deduction in declarative contexts (friend declarations, explicit specializations, and explicit instantiations). For explicit instantiations, 13.9.3 [temp.explicit] paragraph 2 does mention deduction, but it doesn't say which set of deduction rules from 13.10.3 [temp.deduct] should be applied.
Second, Nicolai pointed out that 13.9.3 [temp.explicit] paragraph 6 says
A trailing template-argument can be left unspecified in an explicit instantiation provided it can be deduced from the type of a function parameter (13.10.3 [temp.deduct]).
This prohibits cases like #2, but I believe this was not considered in the wording as there is no reason not to include the return type in the deduction process.
I think there may have been some confusion because the return type is excluded when doing deduction on a function call. But there are contexts where the return type is included in deduction, for example, when taking the address of a function template specialization.
Suggested resolution:
[Accepted as a DR at the October, 2021 meeting.]
According to 13.10.3 [temp.deduct] paragraph 8,
If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments.
Presumably the phrase “if written” refers to rewriting the template declaration in situ with the substituted arguments, rather than writing that type or expression at some arbitrary location, e.g.,
void g(double) = delete; template<class T> auto f(T t) -> decltype(g(t)); void g(int); void h() { typedef int T; T t = 42; g(t); // Ok (I “wrote the substituted arguments”, and it seems fine) f(42); // Presumably substitution is meant to fail. }
Perhaps a clearer formulation could be used?
Proposed resolution (August, 2021):
Change 13.10.3.1 [temp.deduct.general] paragraph 8 as follows:
If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written in the same context using the substituted arguments.
[Accepted at the November, 2020 meeting.]
The specification of template argument deduction in 13.10.3 [temp.deduct] paragraph 5 specifies the order of processing as:
substitute explicitly-specified template arguments throughout the template parameter list and type;
deduce template arguments from the resulting function signature;
check that non-dependent parameters can be initialized from their arguments;
substitute deduced template arguments into the template parameter list and particularly into any needed default arguments to form a complete template argument list;;
substitute resulting template arguments throughout the type;
check that the associated constraints are satisfied;
check that remaining parameters can be initialized from their arguments.
This ordering yields unexpected differences between concept and SFINAE implementations. For example:
template <typename T> struct static_assert_integral { static_assert(std::is_integral_v<T>); using type = T; }; struct fun { template <typename T, typename Requires = std::enable_if_t<std::is_integral_v<T>>> typename static_assert_integral<T>::type operator()(T) {} };
Here the substitution ordering guarantees are leveraged to prevent static_assert_integral<T> from being instantiated when the constraints are not satisfied. As a result, the following assertion holds:
static_assert(!std::is_invocable_v<fun, float>);
A version of this code written using constraints unexpectedly behaves differently:
struct fun { template <typename T> requires std::is_integral_v<T> typename static_assert_integral<T>::type operator()(T) {} };
or
struct fun {
template <typename T>
typename static_assert_integral<T>::type
operator()(T) requires std::is_integral_v<T> {}
};
static_assert(!std::is_invocable_v<fun, float>); // error: static assertion failed: std::is_integral_v<T>
Perhaps steps 5 and 6 should be interchanged.
Proposed resolution (August, 2020):
Delete paragraph 10 of 13.10.3.2 [temp.deduct.call]:
If deduction succeeds for all parameters that contain template-parameters that participate in template argument deduction, and all template arguments are explicitly specified, deduced, or obtained from default template arguments, remaining parameters are then compared with the corresponding arguments. For each remaining parameter P with a type that was non-dependent before substitution of any explicitly-specified template arguments, if the corresponding argument A cannot be implicitly converted to P, deduction fails. [Note 2: Parameters with dependent types in which no template-parameters participate in template argument deduction, and parameters that became non-dependent due to substitution of explicitly-specified template arguments, will be checked during overload resolution. —end note]
[Example 9:template <class T> struct Z { typedef typename T::x xx; }; template <class T> typename Z<T>::xx f(void *, T); // #1 template <class T> void f(int, T); // #2 struct A {} a; int main() { f(1, a); // OK, deduction fails for #1 because there is no conversion from int to void* }
—end example]
Change 13.10.3.1 [temp.deduct.general] paragraph 5 as follows:
...When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters in the template parameter list of the template
and the function typeare replaced with the corresponding deduced or default argument values. If the substitution results in an invalid type, as described above, type deduction fails. If the function template has associated constraints (13.5.3 [temp.constr.decl]), those constraints are checked for satisfaction (13.5.2 [temp.constr.constr]). If the constraints are not satisfied, type deduction fails. In the context of a function call, if type deduction has not yet failed, then for those function parameters for which the function call has arguments, each function parameter with a type that was non-dependent before substitution of any explicitly-specified template arguments is checked against its corresponding argument; if the corresponding argument cannot be implicitly converted to the parameter type, type deduction fails. [Note: Overload resolution will check the other parameters, including parameters with dependent types in which no template parameters participate in template argument deduction and parameters that became non-dependent due to substitution of explicitly-specified template arguments. —end note] If type deduction has not yet failed, then all uses of template parameters in the function type are replaced with the corresponding deduced or default argument values. If the substitution results in an invalid type, as described above, type deduction fails. [Example:template <class T> struct Z { typedef typename T::x xx; }; template <class T> concept C = requires { typename T::A; }; template <C T> typename Z<T>::xx f(void *, T); // #1 template <class T> void f(int, T); // #2 struct A {} a; struct ZZ { template <class T, class = typename Z<T>::xx> operator T *(); operator int(); }; int main() { ZZ zz; f(1, a); // OK, deduction fails for #1 because there is no conversion from int to void* f(zz, 42); // OK, deduction fails for #1 because C<int> is not satisfied }—end example]
[Accepted at the July, 2022 meeting.]
The list of deducible forms in 13.10.3.6 [temp.deduct.type] paragraph 8 does not include the ability to deduce the value of the constant in a noexcept-specifier, although implementations appear to allow it.
Notes from the April, 2018 teleconference:
Although this appears to be an obvious omission, CWG felt that EWG should weigh in on whether this capability should be supported or not.
EWG guidance (January, 2021):
Modify the Standard such that the value of a constant in a noexcept-specifier can be deduced. See vote.
Proposed resolution (June, 2022):
Change 13.10.3.6 [temp.deduct.type] paragraph 3 as follows:
A given type P can be composed from a number of other types, templates, and non-type values:
A function type includes the types of each of the function parameters
and, the return type, and its exception specification....
Add the following to Example 3 in 13.10.3.6 [temp.deduct.type] paragraph 7:
Here is an example where two template arguments are deduced from a single function parameter/argument pair...
Here is an example where the exception specification of a function type is deduced:
template <bool E> void f1(void (*)() noexcept(E)); template<bool> struct A { }; template<bool B> void f2(void (*)(A<B>) noexcept(B)); void g1(); void g2() noexcept; void g3(A<true>); void h() { f1(g1); // OK: E is false f1(g2); // OK: E is true f2(g3); // error: B deduced as both true and false }Here is an example where a qualification conversion applies...
Change 13.10.3.6 [temp.deduct.type] paragraph 8 as follows:
A template type argument T, a template template argument TT, or a template non-type argument i can be deduced if P and A have one of the following forms:
Tcvopt T
T*
T&
T&&
Topt[integer-constantiopt]
template-name<T> (where template-name refers to a class template)Topt(Topt) noexcept(iopt)
type(T)
T()
T type::*Topt Topt::*
type T::*
T (type::*)()
type (T::*)()
type (type::*)(T)
type (T::*)(T)
T (type::*)(T)
T (T::*)()
T (T::*)(T)type[i]TTopt<T>
template-name<i> (where template-name refers to a class template)
TTopt<i>
TTopt<TT>
TTopt<>
where
(T) represents a parameter-type-list (9.3.4.6 [dcl.fct]) where at least one parameter type contains a T, and () represents a parameter-type-list where no parameter type contains a T.
Topt represents a type or parameter-type-list that either satisfies these rules recursively, is a non-deduced context in P or A, or is the same non-dependent type in P and A,
TTopt represents either a class template or a template template parameter,
iopt represents an expression that either is an i, is value-dependent in P or A, or has the same constant value in P and A, and
noexcept(iopt) represents an exception specification (14.5 [except.spec]) in which the (possibly-implicit, see 9.3.4.6 [dcl.fct]) noexcept-specifier's operand satisfies the rules for an iopt above.
[Note: If a type matches such a form but contains no Ts, is, or TTs, deduction is not possible. —end note]
Similarly, <T> represents template argument lists where at least one argument contains a T, <i> represents template argument lists where at least one argument contains an i and <> represents template argument lists where no argument contains a T or an i.
Add the following as a new paragraph following 13.10.3.6 [temp.deduct.type] paragraph 14:
The type of N in the type T[N] is std::size_t. [Example 9: ... —end example]
The type of B in the noexcept-specifier noexcept(B) of a function type is bool.
[Example:template<bool> struct A { }; template<auto> struct B; template<auto X, void (*F)() noexcept(X)> struct B<F> { A<X> ax; }; void f_nothrow() noexcept; B<f_nothrow> bn; // OK: type of X deduced as bool—end example]
Change 13.10.3.6 [temp.deduct.type] paragraph 19 as follows:
If P has a form that contains <i>, and if the type of i differs from the type of the corresponding template parameter of the template named by the enclosing simple-template-id, deduction fails. If P has a form that contains [i], and if the type of i is not an integral type, deduction fails.131 If P has a form that includes noexcept(i) and the type of i is not bool, deduction fails.
[Example 12: ...
Add the following as a new section preceding C.2.7 [diff.cpp20.library]:
C.1.4 Clause 13: templates [diff.cpp20.temp] Affected subclause: 13.10.3.6 [temp.deduct.type]
Change: Deducing template arguments from exception specifications.
Rationale: Facilitate generic handling of throwing and non-throwing functions.
Effect on original feature: Valid ISO C++20 code may be ill-formed in this revision of C++.
[Example 1:
template<bool> struct A { }; template<bool B> void f(void (*)(A<B>) noexcept(B)); void g(A<false>) noexcept; void h() { f(g); // ill-formed; previously well-formed. }
—end example]
[Resolved at the October, 2021 meeting by paper P2314R4.]
When a string literal containing an extended character is stringized (15.7.3 [cpp.stringize]), the result contains a universal-character-name instead of the original extended character. The reason is that the extended character is translated to a universal-character-name in translation phase 1 (5.2 [lex.phases]), so that the string literal "@" (where @ represents an extended character) becomes "\uXXXX". Because the preprocessing token is a string literal, when the stringizing occurs in translation phase 4, the \ is doubled, and the resulting string literal is "\"\\uXXXX\"". As a result, the universal-character-name is not recognized as such when the translation to the execution character set occurs in translation phase 5. (Note that phase 5 translation does occur if the stringized extended character does not appear in a string literal.) Existing practice appears to ignore these rules and preserve extended characters in stringized string literals, however.
See also issue 578.
Additional note (August, 2013):
Implementations are granted substantial latitude in their handling of extended characters and universal-character-names in 5.2 [lex.phases] paragraph 1 phase 1, i.e.,
(An implementation may use any internal encoding, so long as an actual extended character encountered in the source file, and the same extended character expressed in the source file as a universal-character-name (i.e., using the \uXXXX notation), are handled equivalently except where this replacement is reverted in a raw string literal.)
However, this freedom is mostly nullified by the requirements of stringizing in 15.7.3 [cpp.stringize] paragraph 2:
If, in the replacement list, a parameter is immediately preceded by a # preprocessing token, both are replaced by a single character string literal preprocessing token that contains the spelling of the preprocessing token sequence for the corresponding argument.
This means that, in order to handle a construct like
#define STRINGIZE_LITERAL( X ) # X #define STRINGIZE( X ) STRINGIZE_LITERAL( X ) STRINGIZE( STRINGIZE( identifier_\u00fC\U000000Fc ) )
an implementation must recall the original spelling, including the form of UCN and the capitalization of any non-numeric hexadecimal digits, rather than simply translating the characters into a convenient internal representation.
To effect the freedom asserted in 5.2 [lex.phases], the description of stringizing should make the spelling of a universal-character-name implementation-defined.
Additional note (February, 2022):
P2314R4 Character sets and encodings (approved in October, 2021) effected changes so that extended characters are no longer translated to UCNs in phase 1.
According to 17.6.5 [ptr.launder], referring to std::launder,
An invocation of this function may be used in a core constant expression whenever the value of its argument may be used in a core constant expression.
It is not clear whether this wording is intended to permit an example like
#include <new> struct A { char x; }; union U { A a; A a2; }; constexpr A foo() { U u = {{42}}; A *ap = &u.a2; return *std::launder(ap); } extern constexpr A globA = foo();
In particular, is the wording intended to restrict use of std::launder in a constant expression to cases in which the function returns its argument unchanged? As a further example, consider
#include <new> struct A { char x; }; struct B { A a; }; struct BytesAndMore { unsigned char bytes[sizeof(A)]; unsigned char more; }; union U { BytesAndMore bytes; A a; B b; }; constexpr B foo() { U u; A *ap = &u.a; B *bp = &u.b; u.bytes.more = 0; std::launder(ap)->x = 42; return *std::launder(bp); } extern constexpr B globB = foo();
Notes from the December, 2020 teleconference:
See also LWG issue 3495.
[Resolved by paper P1272R4, adopted at the October, 2021 plenary.]
As currently specified, bit_cast from an indeterminate value produces an unspecified value rather than an indeterminate value. That means this can't be implemented by a simple load on some implementations, and instead will require some kind of removing-the-taint-of-an-uninitialized-value operation to be performed. (A similar concern applies to reading from padding bits.)
The intent is as follows:
bits of the input that don't have defined values result in the corresponding bit of the output being “bad”
if any part of a scalar object is “bad”, that object has an indeterminate value
Some examples:
struct A { char c; /* char padding : 8; */ short s; }; struct B { char x[4]; }; B one() { A a = {1, 2}; return std::bit_cast<B>(a); }
In one(), the second byte of the object representation of a is bad. That means that the second byte of the produced B object is bad, so x[1] in the produced B object is an indeterminate value. The above function, if declared constexpr, would be usable in constant expressions so long as you don't look at one().x[1].
A two() { B b; b.x[0] = 'a'; b.x[2] = 1; b.x[3] = 2; return std::bit_cast<A>(b); }
In two() , the second byte of the object representation of b is bad. But a bit_cast to A doesn't care because it never looks at that byte. The above function returns an A with a fully-defined value. If declared constexpr, it would produce a normal, fully-initialized value.
int three() { int n; return std::bit_cast<int>(n); }
In three(), the entirety of n is bad. A bit_cast from it produces an int whose value is indeterminate. And because we have an expression of non-byte-like type that produced an indeterminate value, the behavior is undefined.
B four() { int n; return std::bit_cast<B>(n); }
In four(), just like three(), the entirety of n is bad, so the scalar subobjects of B are bad too. But because they're of byte-like type, that's OK: we can copy them about and produce them from prvalue expressions.
Proposed resolution (May, 2021):
Change 22.11.3 [bit.cast] paragraph 2 as follows:
Returns: An object of type To. Implicitly creates objects nested within the result (6.7.2 [intro.object]). Each bit of the value representation of the result is equal to the corresponding bit in the object representation of from. Padding bits of the result are unspecified. For the result and each object created within it, if there is no value of the object's type corresponding to the value representation produced, the behavior is undefined. If there are multiple such values, which value is produced is unspecified. A bit in the value representation of the result is indeterminate if it does not correspond to a bit in the value representation of from or corresponds to a bit of an object that is not within its lifetime or has an indeterminate value (6.7.5 [basic.indet]). For each bit in the value representation of the result that is indeterminate, the smallest object containing that bit has an indeterminate value; the behavior is undefined unless that object is of unsigned ordinary character type or std::byte type. The result does not otherwise contain any indeterminate values.
The Lao character 0e0d should be 0e8d. 0e0d is both out of order and already used in the Thai characters.
Proposed resolution (10/99): As suggested.
A nested-name-specifier that names a class, optionally followed by the keyword template (13.10.2 [temp.arg.explicit] ), ...The use of the template keyword in this context is discussed in 13.3 [temp.names] , not 13.10.2 [temp.arg.explicit] .
From paper J16/99-0010 = WG21 N1187.
_N4567_.5.1.1 [expr.prim.general] paragraph 7 says that class-name::class-name names the constructor when both class-name refer to the same class. (Note the different perspective, at least, in 11.4.5 [class.ctor] paragraph 1, in which constructors have no names and are recognized by syntactic context rather than by name.)
This formulation does not address the case of classes in which a function template is declared as a constructor, for example:
template <class T> struct A { template <class T2> A(T2); }; template<> template<> A<int>::A<int>(int);
Here there is an ambiguity as to whether the second template argument list is for the injected class name or for the constructor.
Suggested resolution: restate the rule as a component of name lookup. Specifically, if when doing a qualified lookup in a given class you look up a name that is the same as the name of the class, the entity found is the constructor and not the injected class name. In all other cases, the name found is the injected class name. For example:
class B { }; class A: public B { A::B ab; // B is the inherited injected B A::A aa; // Error: A::A is the constructor };
Without this rule some very nasty backtracking is needed. For example, if the injected class name could be qualified by its own class name, the following code would be well-formed:
template <class T> struct A { template <class T2> A(T2); static A x; }; template<> A<int>::A<int>(A<int>::x);
Here the declarator for the definition of the static data member has redundant parentheses, and it's only after seeing the declarator that the parser can know that the second A<int> is the injected class name rather than the constructor.
Proposed resolution (10/00):
In Clause 11 [class] paragraph 2, change
The class-name is also inserted into the scope of the class itself. For purposes of access checking the inserted class name...
to
The class-name is also inserted into the scope of the class itself; this is known as the injected-class-name. For purposes of access checking, the injected-class-name...
Also, in 6.5.5.2 [class.qual], add the following before paragraph 2:
If the nested-name-specifier nominates a class C, and the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C ( Clause 11 [class]), the name is instead considered to name the constructor of class C. Such a constructor name shall only be used in the declarator-id of a constructor definition that appears outside of the class definition. [Example:struct A { A(); }; struct B: public A { B(); }; A::A() { } B::B() { } B::A ba; // object of type A A::A a; // error, A::A is not a type name—end example]
Also, change 6.5 [basic.lookup] paragraph 3 from
Because the name of a class is inserted in its class scope ( Clause 11 [class]), the name of a class is also considered a member of that class for the purposes of name hiding and lookup.
to
The injected-class-name of a class (Clause 11 [class]) is also considered to be a member of that class for the purposes of name hiding and lookup.
(See also issue 194.)
John Spicer: I believe the standard is not clear with respect to this example:
namespace N { template <class T> void f(T); namespace M { struct A { friend void f<int>(int); // okay - refers to N::f }; } }At issue is whether the friend declaration refers to N::f, or whether it is invalid.
A note in 6.4.2 [basic.scope.pdecl] paragraph 6 says
friend declarations refer to functions or classes that are members of the nearest enclosing namespace ...I believe it is intended to mean unqualified friend declarations. Certainly friend void A::B() need not refer to a member of the nearest enclosing namespace. Only when the declarator is unqualified (i.e., it is a declaration and not a reference) does this rule need to apply. The presence of an explicit template argument list requires that a previous declaration be visible and renders this a reference and not a declaration that is subject to this rule.
Mike Miller: _N4868_.9.8.2.3 [namespace.memdef] paragraph 3 says,
When looking for a prior declaration of a class or a function declared as a friend, scopes outside the innermost enclosing namespace scope are not considered.On the other hand, the friend declaration would be a syntax error if f weren't declared as a template name; it would seem very strange not to find the declaration that made the friend declaration syntactically correct. However, it also seems strange to treat this case differently from ordinary functions and from templates:
namespace N { template <class T> void f(T); void g(); namespace M { struct A { friend void f<int>(int); // N::f template <class T> friend void f(T); // M::f friend void g(); // M::g }; } }
John Spicer: This section refers to "looking for a prior declaration". This gets back to an earlier discussion we've had about the difference between matching two declarations of the same name and doing name lookup. I would maintain that in f<int> the f is looked up using a normal lookup. In practice, this is really how it has to be done because the declaration could actually be f<int>::x.
Proposed resolution (10/00):
In _N4868_.9.8.2.3 [namespace.memdef] paragraph 3, change
When looking for a prior declaration of a class or a function declared as a friend, scopes outside the innermost enclosing namespace scope are not considered.to
When looking for a prior declaration of a class or a function declared as a friend, and when the name of the friend class or function is neither a qualified name nor a template-id, scopes outside the innermost enclosing namespace scope are not considered.Also, change the example in that paragraph as follows:
void h(int); template <class T> void f2(T); namespace A { class X { friend void f(X); // A::f(X) is a friend friend void f2<>(int); // ::f2<>(int) is a friend ...
(See also issues 95, 136, 138, 139, 143, and 165.)
At what point are semantic constraints applied to uses of non-dependent names in template definitions? According to _N4868_.13.8.4 [temp.nondep] , such names are looked up and bound at the point at which they are used, i.e., the point of definition and not the point of instantiation. However, the text does not mention the checking of semantic constraints.
Contrast this omission with the treatment of names in default argument expressions given in 9.3.4.7 [dcl.fct.default] paragraph 5, where the treatment of semantic constraints is explicit:
The names in the expression are bound, and the semantic constraints are checked, at the point where the default argument expression appears.The following code is an example of where this distinction matters:
struct S; template <class T> struct Q { S s; // incomplete type if semantic constraints // are applied in the definition context }; struct S { }; // Point of instantiation of Q<int>; S is complete here Q<int> si;There is real-world code that depends on late checking of semantic constraints. The Standard should be explicit about whether this code is broken or not.
Proposed resolution (10/00):
In 13.8 [temp.res] paragraph 7, add the following immediately preceding the note:
If a type used in a non-dependent name is incomplete at the point at which a template is defined but is complete at the point at which an instantiation is done, and if the completeness of that type affects whether or not the program is well-formed or affects the semantics of the program, the program is ill-formed; no diagnostic is required.
28.3.4.2.2.3 [locale.ctype.virtuals] paragraph 13 states a constraint on the values of the characters representing the decimal digits in the execution character set:
for any digit character c, the expression (do_narrow( c, dfault)-'0') evaluates to the digit value of the character.This requirement is not reflected in the description of the execution character set (5.3.1 [lex.charset] paragraph 3) .
Proposed resolution (10/00):
In 5.3.1 [lex.charset] paragraph 3, after the sentence
For each basic execution character set, the values of the members shall be non-negative and distinct from one another.insert the following:
In both the source and execution basic character sets, the value of each character after 0 in the above list of decimal digits shall be one greater than the value of the previous.
Footnotes 26 and 29 both use the phrase "following the function declarator" incorrectly: the function declarator includes the parameter list, but the footnotes make clear that they intend what's said to apply to names inside the parameter list. Presumably the phrase should be "following the function declarator-id."
Proposed Resolution (04/99): Change the text in 6.5.3 [basic.lookup.unqual] paragraph 6 from:
A name used in the definition of a function [footnote: This refers to unqualified names following the function declarator; such a name may be used as a type or as a default argument name in the parameter-declaration-clause, or may be used in the function body. end footnote] that is ...to:
A name used in the definition of a function following the function's declarator-id [footnote: This refers to unqualified names that occur, for instance, in a type or default argument expression in the parameter-declaration-clause or used in the function body. end footnote] that is ...Change the text in 6.5.3 [basic.lookup.unqual] paragraph 8 from:
A name used in the definition of a function that is a member function (11.4.2 [class.mfct] ) [footnote: That is, an unqualified name following the function declarator; such a name may be used as a type or as a default argument name in the parameter-declaration-clause, or may be used in the function body, or, if the function is a constructor, may be used in the expression of a mem-initializer. end footnote] of class X shall be ...to:
A name used in the definition of a member function (11.4.2 [class.mfct] ) of class X following the function's declarator-id [footnote: That is, an unqualified name that occurs, for instance, in a type or default argument expression in the parameter-declaration-clause, in the function body, or in an expression of a mem-initializer in a constructor definition. end footnote] shall be ...
If an argument used for lookup is the address of a group of overloaded functions, are there any associated namespaces or classes? What if it's the address of a function template?
My inclination is to say no to both.
From Mike Miller:
We discussed this on the reflector a few weeks ago. I'll leave the template case for the Core III experts, but I'd find it surprising if the overload case weren't handled as the obvious generalization of the single-function case. For a single function, the associated namespaces are those of the types used in the parameters and return type; I would expect that using an overloaded function name would simply be the union of the namespaces from the members of the overload set. That would be the simplest and most intuitive, IMHO — is there an argument for doing it differently?
Proposed Resolution (04/99): In 6.5.4 [basic.lookup.argdep] paragraph 2, add following the last bullet in the list of associated classes and namespaces for various argument types (not a bullet itself because overload sets and templates do not have a type):
In addition, if the argument is the name or address of a set of overloaded functions and/or function templates, its associated classes and namespaces are the union of those associated with each of the members of the set: the namespace in which the function or function template is defined and the classes and namespaces associated with its (non-dependent) parameter types and return type.
Section 6.5.4 [basic.lookup.argdep] includes the following:
struct A { union U {}; friend void f(U); }; struct B { struct S {}; friend void f(S); }; int main() { A::U u; f(u); // okay: A is an associated class B::S s; f(s); // error: no matching f(), B is not an associated class }Certainly the enclosing class should also be an associated class for nested class types, shouldn't it?
Proposed Resolution (10/99): Change the two referenced bullets to read:
The description of Koenig lookup in 6.5.4 [basic.lookup.argdep] paragraph 1 says,
...other namespaces not considered during the usual unqualified lookup (6.5.3 [basic.lookup.unqual] ) may be searched.Does this mean that Koenig lookup does not search namespaces that were already searched during the usual unqualified lookup? The answer is academic except for the two-stage lookup during template instantiation. If a given namespace is searched in the context of the template definition, are declarations in that namespace in the instantiation context ignored during the Koenig lookup? For instance,
void f(int); template <class T> void g(T t) { f(t); } enum E { e }; void f(E); void h() { g(e); }In this example, the call f(t) in the template function will resolve to f(E) if Koenig lookup reexamines already-searched namespaces and to f(int) if not.
Proposed Resolution (10/00):
Immediately preceding the example at the end of 6.5.4 [basic.lookup.argdep] paragraph 2, add the following:
[Note: the namespaces and classes associated with the argument types can include namespaces and classes already considered by the ordinary unqualified lookup.]
In 6.5.6 [basic.lookup.elab] paragraph 3, there is the example
struct Base { // ... struct Data { /* ... */ }; // Defines nested Data struct Data; // OK: Redeclares nested Data };The final redeclaration is invalid according to 11.4 [class.mem] paragraph 1 last sentence.
Proposed resolution (10/00): Remove the line
struct Data; // OK: Redeclares nested Data
See also Core issue 36 and Core issue 56.
A reference is rebindable. This is surprising and unnatural. This can also cause subtle optimizer bugs.Example:
struct T { int& ri; T (int& r) : ri (r) { } }; void bar (T*); void foo () { int i; T x (i); x.ri = 3; // the optimizer understands that this is really i = 3 bar (&x); x.ri = 4; // optimizer assumes that this writes to i, but this is incorrect } int gi; void bar (T* p) { p->~T (); new (p) T (gi); }If we replace T& with T* const in the example then undefined behavior result and the optimizer is correct.Proposal: make T& equivalent to T* const by extending the scope of 6.7.4 [basic.life] paragraph 9 to references.
(See also J16/99-0005 = WG21 N1182, "Proposed Resolutions for Core Language Issues 6, 14, 20, 40, and 89")
In addition, Lisa Lippincott pointed out the following example:
void f( const bool * ); void g(); int main() { const bool *b = new const bool( false ); f(b); if (*b) g(); } void f( const bool *b ) { new ( const_cast<bool *>(b) ) const bool( true ); }
The proposed wording in the paper would still permit this usage and thus prevent an optimizer from eliminating the call to g().
Proposed Resolution (10/00):
Add a new bullet to the list of restrictions in 6.7.4 [basic.life] paragraph 7, following the second bullet ("the new object is of the same type..."):
The text of 6.7.4 [basic.life] paragraph 2 currently reads,
The phrase "an object of type" is obviously incorrect. I believe it should read "an object of POD type." Does anyone disagree?
Proposed Resolution (10/99): As suggested.
Can you use memcpy on non-member POD subobjects of non-POD objects?
In 6.8 [basic.types] paragraphs 2 and 3 we have:
For any complete POD object type T, whether or not the object holds a valid value of type T, the underlying bytes (6.7.1 [intro.memory] ) 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. [Example elided]Paragraph 3 doesn't repeat the restriction of paragraph 2. Should it be assumed? Otherwise only complete POD types are copyable to an array of char and back, but scribbling over subobjects is OK. (Or perhaps a "distinct T object" is a complete object...)*[Footnote: By using, for example, the library functions (16.4.2.3 [headers] ) memcpy or memmove. end footnote]For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2, if the value of obj1 is copied into obj2, using the memcpy library function, obj2 shall subsequently hold the same value as obj1.
Proposed Resolution (04/99): Change the text in 6.8 [basic.types] paragraph 2 from:
For any complete POD object type T, ...to:
For any object (other than a base class subobject) of POD type T, ...Change the text in 6.8 [basic.types] paragraph 3 from:
For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2,to:
For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base class subobject, ...
The Standard uses confusing terminology when referring to accessibility in connection with ambiguity. For instance:
7.3.12 [conv.ptr] paragraph 3:
If B is an inaccessible or ambiguous base ...7.6.1.7 [expr.dynamic.cast] paragraph 8:
... has an unambiguous public base ...11.7.3 [class.virtual] paragraph 5:
... is an unambiguous direct or indirect base ... and is accessible ...14.4 [except.handle] paragraph 3:
not involving conversions to pointers to private or protected or ambiguous classes
The phrase "unambiguous public base" is unfortunate as it could mean either "an unambiguous base not considering accessibility, which is public" or "an unambiguous base considering only the publicly accessible bases." I believe the former interpretation correct, as accessibility is applied after visibility (11.8 [class.access] paragraph 4) and ambiguity is described in terms of visibility (6.5.2 [class.member.lookup] paragraph 2) .
Suggested Resolution: Use the phrases "public and unambiguous," "accessible and unambiguous," "non-public or ambiguous," or "inaccessible or ambiguous" as appropriate.
Proposed resolution (10/00):
7.6.1.5 [expr.ref] paragraph 4 should make it clear that when a nonstatic member is referenced in a member selection operation, the type of the left operand is implicitly cast to the naming class of the member. This allows for the detection of access and ambiguity errors on that implicit cast.
Proposed Resolution (10/00):
In 11.8.3 [class.access.base] paragraph 4, remove the following from the second note:
If the member m is accessible when named in the naming class according to the rules below, the access to m is nonetheless ill-formed if the type of p cannot be implicitly converted to type T (for example, if T is an inaccessible base class of p's class).
Add the following as a new paragraph 5 of 11.8.3 [class.access.base]:
If a class member access operator, including an implicit "this->," is used to access a nonstatic data member or nonstatic member function, the reference is ill-formed if the left operand (considered as a pointer in the "." operator case) cannot be implicitly converted to a pointer to the naming class of the right operand. [Note: this requirement is in addition to the requirement that the member be accessible as named.]
In 11.8.3 [class.access.base] paragraph 4, fix a typographical error by adding the missing right parenthesis following the text
(including cases where an implicit "this->" is added
Add following the first sentence of 7.6.1.3 [expr.call] paragraph 4:
If the function is a nonstatic member function, the "this" parameter of the function (_N4868_.11.4.3.2 [class.this]) shall be initialized with a pointer to the object of the call, converted as if by an explicit type conversion (7.6.3 [expr.cast]). [Note: there is no access checking on this conversion; the access checking is done as part of the (possibly implicit) class member access operator. See 11.8.3 [class.access.base].]
Section 7.6.1.9 [expr.static.cast] paragraph 6 should make it clear that when any of the "inverse of any standard conversion sequence" static_casts are done, the operand undergoes the lvalue-to-rvalue conversions first.
Proposed Resolution (10/00):
In 7.6.1.9 [expr.static.cast] paragraph 6, change
can be performed explicitly using static_cast subject to the restriction that the explicit conversion does not cast away constness (7.6.1.11 [expr.const.cast]), ...
to
can be performed explicitly using static_cast. The lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) conversions are applied to the operand. Such a static_cast is subject to the restriction that it does not cast away constness (7.6.1.11 [expr.const.cast]), ...
According to 9.8.1 [dcl.enum] paragraph 9, it is permitted to convert from one enumeration type to another. However, neither 7.6.1.9 [expr.static.cast] nor 7.6.3 [expr.cast] allows this conversion.
Proposed resolution (10/00): Change the first two sentences of 7.6.1.9 [expr.static.cast] paragraph 7 to read
A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (9.8.1 [dcl.enum] ).
According to 7.6.1.9 [expr.static.cast] paragraph 10,
An rvalue of type "pointer to cv void" can be explicitly converted to a pointer to object type.No requirements are stated regarding the cv-qualification of the pointer to object type. Contrast this with the formula used in paragraphs 5, 8, and 9, where the treatment of cv-qualification is explicit, requiring that the target type be at least as cv-qualified as the source. There is an apparently general requirement on all forms of static_cast in 7.6.1.9 [expr.static.cast] paragraph 1 that it "shall not cast away constness." Assuming that this restriction applies to paragraph 10, since there is no explicit exception to the general rule, that still leaves open the question of whether one can "cast away volatility" in a conversion from volatile void* to a pointer to object type. Should 7.6.1.9 [expr.static.cast] paragraph 10 be rewritten to handle cv-qualification in the same way as paragraphs 5, 8, and 9?
Proposed resolution (10/00):
Change the first sentence of 7.6.1.9 [expr.static.cast] paragraph 10 to
An rvalue of type "pointer to cv1 void" can be converted to an rvalue of type "pointer to cv2 T", where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1.
7.6.2.8 [expr.new] paragraph 6 says:
The expression in a direct-new-declarator shall have integral type (6.8.2 [basic.fundamental] ) with a non-negative value.I assume the intent was to also allow enumeral types, as we do in 7.6.1.2 [expr.sub] ?
Proposed Resolution (10/99): Replace "integral type" by "integral or enumeration type" in 7.6.2.8 [expr.new] paragraph 6.
If a placement allocation function has default arguments for all its parameters except the first, it can be called using non-placement syntax. In such a case, it is not clear whether the deallocation function to be called if the constructor terminates by throwing an expression is determined on the basis of the syntax of the new-expression (i.e., a non-placement deallocation function) or the declaration of the selected (placement) allocation function. 7.6.2.8 [expr.new] paragraph 19 indicates that the deallocation function must match the declaration of the allocation function. However, 14.3 [except.ctor] says that the distinction is based on whether the new-expression contains a new-placement or not.
Proposed resolution (10/00):
In 14.3 [except.ctor] paragraph 2, replace
If the object or array was allocated in a new-expression and the new-expression does not contain a new-placement, the deallocation function (6.7.6.5.3 [basic.stc.dynamic.deallocation], 11.4.11 [class.free]) is called to free the storage occupied by the object; the deallocation function is chosen as specified in 7.6.2.8 [expr.new]. If the object or array was allocated in a new-expression and the new-expression contains a new-placement, the storage occupied by the object is deallocated only if an appropriate placement operator delete is found, as specified in 7.6.2.8 [expr.new].
with
If the object or array was allocated in a new-expression, the matching deallocation function (6.7.6.5.3 [basic.stc.dynamic.deallocation], 7.6.2.8 [expr.new], 11.4.11 [class.free]), if any, is called to free the storage occupied by the object.
See also issue 429.
7.6.6 [expr.add] paragraph 8 explicitly allows subtraction of two pointers to functions:
If two pointers point to the same object or function... and the two pointers are subtracted...However, 7.6.6 [expr.add] paragraph 2 requires that two pointers that are subtracted be pointers to an object type; function pointers are not allowed.
Being able to subtract two pointers to functions doesn't seem terribly useful, especially considering that subtracting two pointers to different functions appears to produce undefined behavior rather than simply a non-zero result, according to paragraph 6:
Unless both pointers point to elements of the same array object, or one past the last element of the array object, the behavior is undefined.
Proposed resolution (10/00):
Remove the words or function from paragraph 8.
Nathan Myers: In 7.6.10 [expr.eq] , we have:
Pointers to objects or functions of the same type (after pointer conversions) can be compared for equality. Two pointers of the same type compare equal if and only if they are both null, both point to the same object or function, or both point one past the end of the same array.What does this say, when we have
int i[1]; int j[1];about the expression (i+1 == j) ? It seems to require padding between i[0] and j[0] so that the comparison will come out false.
Mike Miller: I think this is reading more into the statement in 7.6.10 [expr.eq] paragraph 1 than is actually there. What does it mean for a pointer to "point to" an object? I can't find anything that definitively says that i+1 cannot "point to" j[0] (although it's obviously not required to do so). If i+1 is allowed to "point to" j[0], then i+1==j is allowed to be true, and there's no defect. There are places where aliasing is forbidden, but the N+1th element of an array doesn't appear to be one of them.
To put it another way, "points to" is undefined in the Standard. The only definition I can think of that encompasses the possible ways in which a pointer can get its value (e.g., the implementation-defined conversion of an arbitrary integer value to a pointer) is that it means "having the same value representation as would be produced by applying the (builtin) & operator to an lvalue expression designating that object". In other words, if the bits are right, it doesn't matter how you produced the value, as long as you didn't perform any operations that have undefined results. The expression i+1 is not undefined, so if the bits of i+1 are the same as those of &j[0], then i+1 "points to" j[0] and i+i==j is allowed to be true.
Tom MacDonald: C9X contains the following words for the "==" operator:
Two pointers compare equal if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function, both are pointers to one past the last element of the same array object, or one is a pointer to one past the end of one array object and the other is a pointer to the start of a different array object that happens to immediately follow the first array object in the address space.Matt Austern: I don't think there's anything wrong with saying that the result of
int x[1]; int y[1]; std::cout << (y == x + 1) << std::endl;is implementation defined, or even that it's undefined.
Mike Miller: A similar question could be raised about different objects that (sequentially) share the same storage. Consider the following:
struct B { virtual void f(); }; struct D1: B { }; struct D2: B { }; void g() { B* bp1 = new D1; B* bp2 = new (bp1) D2; bp1 == bp2; // ??? }Section 6.7.4 [basic.life] paragraph 5 does not list this kind of comparison among the pointer operations that cause undefined behavior, so presumably the comparison is allowed. However, 7.6.10 [expr.eq] paragraph 1 describes pointer comparison in terms of "[pointing] to the same object," which bp1 and bp2 clearly do not do. How should we describe the result of this comparison?
Jason Merrill: When you consider comparing pointers to void, this seems to suggest that no two objects can have the same address, depending on your interpretation of "point to the same object." This would cripple the empty base optimization.
6.8.4 [basic.compound] refers to 'pointers to void or objects or functions'. In that case, 7.6.10 [expr.eq] does not allow you to compare them; it only allows comparing pointers to objects and functions.
Proposed Resolution (10/00):
A valid value of an object pointer type represents either the address of a byte in memory (6.7.1 [intro.memory]) or a null pointer (7.3.12 [conv.ptr]). If an object of type T is located at an address A, a pointer of type cv T* whose value is the address A is said to point to that object, regardless of how the value was obtained. [Note: for instance, the address one past the end of an array (7.6.6 [expr.add]) would be considered to point to an unrelated object of the array's element type that might be located at that address.]
Two pointers of the same type compare equal if and only if they are both null, both point to the same function, or both represent the same address (6.8.4 [basic.compound]).
(See also paper J16/00-0011 = WG21 N1234.)
Given
char arr[100]; sizeof(0,arr);
What does the sizeof expression return? According to 7.6.20 [expr.comma] paragraph 1, the comma operator yields an lvalue if the second argument is an lvalue. Since 7.3.3 [conv.array] paragraph 1 says that the array-to-pointer conversion yields an rvalue, it seems that sizeof should see an array type and give the answer 100. If so, the value of the sizeof expression would be different from that of the corresponding expression in C, but there is nothing in Annex Clause Annex C [diff] to indicate that an incompatible change was intended.
Proposed resolution (10/00):
Add the following as paragraph 3 of C.7.4 [diff.expr]:
5.16, 5.17, 5.18
Change: The result of a conditional expression, an assignment expression, or a comma expression may be an lvalue.
Rationale: C++ is an object-oriented language, placing relatively more emphasis on lvalues. For example, functions may return lvalues.
Effect on original feature: Change to semantics of well-defined feature. Some C expressions that implicitly rely on lvalue-to-rvalue conversions will yield different results. For example,char arr[100]; sizeof(0, arr)yields 100 in C++ and sizeof(char*) in C.
Difficulty of converting: Programs must add explicit casts to the appropriate rvalue.
How widely used: Rare.
struct S { static const int c = 5; }; int a[S::c]; // error: S::c not in scopeIs this restriction intentional? If so, what was the rationale for the restriction?
Bjarne Stroustrup: I think that once you have said S::, c is in scope so that
int a[S::c];is ok.
Mike Miller: I'd like to think that's what it meant, but I don't believe that's what it said. According to 6.4 [basic.scope] paragraph 1, the scope of a name is the region "in which that name may be used as an unqualified name." You can, indeed, use a qualified name to refer to a name that is not in scope, but that only goes to reinforce my point that "S::c" is not in scope at the point where the expression containing it is used. I think the phrase "within its scope" is at best misleading and should be removed. (Unless there's a reason I'm missing for restricting the use of static member constants to their scope.)
As far as I can tell from 7.7 [expr.const] paragraph 2, "arithmetic constant expressions" (as distinct from "integral constant expressions") are used only in static initializers to distinguish between static and dynamic initialization. They include floating point types and exclude non-type template parameters, as well as the const variables and static data members.
There is a minor error in 7.7 [expr.const] paragraph 2. The first sentence says, "Other expressions are considered constant expressions only for the purpose of non-local static object initialization." However, 8.9 [stmt.dcl] paragraph 4 appears to rely on the same definition dealing with the initialization of local static objects. I think that the words "non-local" should be dropped and a cross reference to 8.9 [stmt.dcl] added.
I'm guessing that should be "non-static member," like the similar prohibition in 11.9.5 [class.cdtor] regarding out-of-lifetime access to members of non-POD class objects.
Proposed resolutions (10/00):
Remove the phrase "within its scope" in 11.4.9.3 [class.static.data] paragraph 4.
An arithmetic constant expression shall satisfy the requirements for an integral constant expression, except that
- floating literals need not be cast to integral or enumeration type, and
- conversions to floating point types are permitted.
This is not a defect; no change is required. The suggested wording would be more accurate, but since the effect on local initialization is unobservable the current wording is adequate.
Change the referenced sentence in 7.7 [expr.const] paragraph 4 to "An expression that designates the address of a subobject of a non-POD class object is not an address constant expression."
The wording of 8.5 [stmt.select] paragraph 1 is misleading. Instead of
The substatement in a selection-statement (both substatements, in the else form of the if statement) implicitly defines a local scope (6.4 [basic.scope]).
it should say
... each substatement, in the else form...
As is, one is left with the impression that both "then" and "else" clauses together form a single scope.
Proposed resolution (10/00): As suggested.
Mike Ball: I cannot find anything in the standard that tells me the meaning of a storage-class-specifier on a function template declaration. In particular, there is no indication what effect, if any, it has on the storage class of the instantiations.
There is an explicit prohibition of storage-class-specifiers on explicit specializations.
For example, if we have
template<class T> static int foo(T) { return sizeof(T); }does this generate static functions for all instantiations? By 9.2.2 [dcl.stc] the storage class applies to the name declared in the declarator, which is the template foo, not an instantiation of foo, which is named with a template-id. There is a statement in clause 14 that template names have linkage, which supports the contention that "static" applies to the template, not to instantiations.
So what does the specifier mean? Lacking a direct statement in the standard, I see the following posibilities, in my preference order.
From John Spicer
The standard does say that a namespace scope template has external linkage unless it is a function template declared "static". It doesn't explicitly say that the linkage of the template is also the linkage of the instantiations, but I believe that is the intent. For example, a storage class is prohibited on an explicit specialization to ensure that a specialization cannot be given a different storage class than the template on which it is based.
Mike: This makes sense, but I couldn't find much support in the document. Sounds like yet another interpretation to add to the list.The standard does not talk about the linkage of instantiations, because only "names" are considered to have linkage, and instances are not really names. So, from an implementation point of view, instances have linkage, but from a language point of view, only the template from which the instances are generated has linkage.John: Agreed.
Mike: Which is why I think it would be cleaner to eliminate storage class specifiers entirely and rely on the unnamed namespace. There is a statement that specializations go into the namespace of the template. No big deal, it's not something it says, so we live with what's there."export" is an additional attribute that is separate from linkage, but that can only be applied to templates with external linkage.John: That would mean prohibiting static function templates. I doubt those are common, but I don't really see much motivation for getting rid of them at this point.
Mike: I can't find that restriction in the standard, though there is one that templates in an unnamed namespace can't be exported. I'm pretty sure that we intended it, though.John: I can't find it either. The "inline" case seems to be addressed, but not static. Surely this is an error as, by definition, a static template can't be used from elsewhere.
Proposed resolution (10/00):
Change the text in Clause 13 [temp] paragraph 4 from:A template name may have linkage (6.6 [basic.link]).to:
A template name has linkage (6.6 [basic.link]). A non-member function template can have internal linkage; any other template name shall have external linkage. Entities generated from a template with internal linkage are distinct from all entities generated in other translation units.
Can a typedef redeclaration be done within a class?
class X { typedef int I; typedef int I; };See also 11.4 [class.mem] , Core issue 36, and Core issue 85.
Proposed Resolution (10/99): Change 9.2.4 [dcl.typedef] paragraph 2 from "In a given scope" to "In a given non-class scope."
The following code does not compile with the EDG compiler:
volatile const int a = 5; int b[a];The standard, 9.2.9.2 [dcl.type.cv] , says:
A variable of const-qualified integral or enumeration type initialized by an integral constant expression can be used in integral constant expressions.This doesn't say it can't be const volatile-qualified, although I think that was what was intended.
Proposed Resolution (10/99): Change the referenced text in paragraph 2 of 9.2.9.2 [dcl.type.cv] to read:
I can't find the answer to the following in the standard. Does anybody have a reference?
The syntax for elaborated type specifier is
class foo<int> // foo is a templateOn the other hand, a friend declaration seems to require this production,
An elaborated-type-specifier shall be used in a friend declaration for a class.*And in 13.7.5 [temp.friend] we find the example[Footnote: The class-key of the elaborated-type-specifier is required. —end footnote]
[Example:Is there some special dispensation somewhere to allow the syntax in this context? Is there something I've missed about elaborated-type-specifier? Is it just another bug in the standard?template<class T> class task; template<class T> task<T>* preempt(task<T>*); template<class T> class task { // ... friend void next_time(); friend void process(task<T>*); friend task<T>* preempt<T>(task<T>*); template<class C> friend int func(C); friend class task<int>; template<class P> friend class frd; // ... };
An additional problem was reported via comp.std.c++: the grammar does not allow the following example:
namespace A{ class B{}; }; namespace B{ class A{}; class C{ friend class ::A::B; }; };
Proposed resolution (10/00):
Change the grammar in 9.2.9.5 [dcl.type.elab] to read
(From J16/99-0005 = WG21 N1182, "Proposed Resolutions for Core Language Issues 6, 14, 20, 40, and 89")
There are two sub-issues. The first concerns the statement in 9.3.4 [dcl.meaning] paragraph 1,
The id-expression of a declarator-id shall be a simple identifier except for the declaration of some special functions (11.4.8 [class.conv] , 11.4.7 [class.dtor] , 12.4 [over.oper] ) and for the declaration of template specializations or partial specializations (13.9 [temp.spec] ).The second sub-issue is regarding another statement in the same paragraph:
A declarator-id shall not be qualified except for the definition of a member function (11.4.2 [class.mfct] ) or static data member (11.4.9 [class.static] ) or nested class (11.4.12 [class.nest] ) outside of its class, the definition or explicit instantiation of a function, variable or class member of a namespace outside of its namespace, or...Analysis
The problem in the first sub-issue is that the wrong syntactic non-terminal is mentioned. The relevant portions of the grammar are:
If an unqualified-id is used as the id-expression of a declarator-id, it shall be a simple identifier except...However, it does not appear that this restriction has any meaning; all of the possible cases of unqualified-ids are represented in the list of exceptions! Rather than recasting the sentence into a correct but useless form, it would be better to remove it altogether.
The second sub-issue deals with the conditions under which a qualified-id can be used in a declarator, including "the definition of a...nested class" and "the definition or explicit instantiation of a...class member of a namespace." However, the name in a class definition is not part of a declarator; these constructs do not belong in a list of declarator contexts.
Proposed Resolution for sub-issue 1 (04/99):
The suggested resolution for the first sub-issue overlooked the fact that the existing wording has the additional effect of prohibiting the use of the non-identifier syntax for declaring other than the listed entities. Thus the proposed wording for the first sub-issue is:
Change 9.3.4 [dcl.meaning] paragraph 1 from:
The id-expression of a declarator-id shall be a simple identifier except...to:
An unqualified-id occurring in a declarator-id shall be a simple identifier except...
Proposed Resolution for sub-issue 2 (10/99):
Change 9.3.4 [dcl.meaning] paragraph 1 from:
A declarator-id shall not be qualified except for the definition of a member function (11.4.2 [class.mfct] ) or static data member (11.4.9 [class.static] ) or nested class (11.4.12 [class.nest] ) outside of its class, the definition or explicit instantiation of a function, variable or class member of a namespace outside of its namespace, or...to
A declarator-id shall not be qualified except for the definition of a member function (11.4.2 [class.mfct] ) or static data member (11.4.9 [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...
9.3.4 [dcl.meaning] paragraph 1 says:
In the qualified declarator-id for a class or namespace member definition that appears outside of the member's class or namespace, the nested-name-specifier shall not name any of the namespaces that enclose the member's definition.This results in the following behavior:
namespace N { namespace M { void f(); void g(); } void M::f(){} // okay void N::M::g(){} // error }I was very surprised when this rule was pointed out to me. The change appears to have been introduced around the time of the first Santa Cruz meeting, but I don't recall discussion of it and could not find a motion related to it.
Regardless of where it came from, I also can't understand why it is there. Certainly it shouldn't matter how you name a given class or namespace.
For example, the standard permits:
namespace N { namespace M { void f(); void g(); } namespace X = M; namespace Y = N::M; void X::f(){} // okay void Y::g(){} // okay }So, it is okay to use an alias for N::M, but not to use N::M directly. Note that it is okay to use N::M in any other context at this point in the program (i.e., the rule is a specific restriction on declarator names, not a general rule on the use of qualified names).
Does anyone recall the intent of this rule or any rationale for its existence?
Notes from 04/00 meeting:
There was some question as to whether this issue actually constituted a defect in the Standard. John Spicer suggested that machine-generated source code would be likely to run afoul of this prohibition. Francis Glassborow expressed support for a rule that would allow full qualification, or qualification relative to the namespace containing the definition, but not qualification relative to a containing namespace. There was no consensus for moving forward with a DR at this point, so the issue was left in "review" status.
Proposed resolution (10/00):
Remove the last sentence of 9.3.4 [dcl.meaning] paragraph 1 (cited above) and the example that follows.
6.3 [basic.def.odr] paragraph 4 and 9.3.4.6 [dcl.fct] paragraph 6 indicate that the return type and parameter types must be complete in a function definition. However, when 11.4 [class.mem] paragraph 2 lists the contexts in a class member-specification in which the class is considered complete, the return type and parameter types of a member function defined in the class definition are not included. It thus appears that the following example is ill-formed:
struct S { S f() { return S(); } // error: incomplete return type void g(S) { } // error: incomplete parameter type };Jack Rouse: I suggest supplementing the text in 8.3.5p6 with something like:
The type of a parameter or the return type for a function definition shall not be an incomplete class type unless the function definition is nested in the member-specification for that class (including definitions in nested classes defined within the class).
Proposed resolution (10/00): Replace the last sentence of 9.3.4.6 [dcl.fct] paragraph 6 with
The type of a parameter or the return type for a function definition shall not be an incomplete class type (possibly cv-qualified) unless the function definition is nested within the member-specification for that class (including definitions in nested classes defined within the class).
6.4 [basic.scope] paragraph 4 says:
Given a set of declarations in a single declarative region, each of which specifies the same unqualified name,9.3.4.7 [dcl.fct.default] paragraph 9 says:
- they shall all refer to the same entity, or all refer to functions ...
When a declaration of a function is introduced by way of a using-declaration (9.10 [namespace.udecl]), any default argument information associated with the declaration is imported as well.This is not really clear regarding what happens in the following case:
namespace A { extern "C" void f(int = 5); } namespace B { extern "C" void f(int = 7); } using A::f; using B::f; f(); // ???Proposed resolution (10/00):
Add the following at the end of 12.2.4 [over.match.best]:
If the best viable function resolves to a function for which multiple declarations were found, and if at least two of these declarations — or the declarations they refer to in the case of using-declarations — specify a default argument that made the function viable, the program is ill-formed. [Example:
namespace A {
extern "C" void f(int = 5);
}
namespace B {
extern "C" void f(int = 5);
}using A::f;
using B::f;void use() {
f(3); // OK, default argument was not used for viability
f(); // Error: found default argument twice
}—end example]
Proposed Resolution (04/99): Change the text in the example of section 9.3.4.7 [dcl.fct.default] paragraph 5 from:
... g will be called with the value f(1).to:
... g will be called with the value f(2).
According to 9.3.4.7 [dcl.fct.default] paragraphs 4 and 6,
For non-template functions, default arguments can be added in later declarations of a function in the same scope.
The default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition.
This would appear to allow the following example, in which a default argument is added to a non-template member function of a class template:
template <class T> struct S { void foo (int); }; template <class T> void S<T>::foo (int = 0) { }
John Spicer: The wording "non-template functions" is somewhat unclear with respect to member functions of class templates, but I know that this was intended to include them because it originates from issue 3.13 of the template issues list that I maintained for several years.
Having said that, the rationale for this restriction has since been made obsolete, so this could (in theory) be changed in the standard if it is problematic for users.
(See also issue 205.)
Proposed resolution (10/00):
In 9.3.4.7 [dcl.fct.default] paragraph 6, replace
The default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition.
with
Except for member functions of class templates, the default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition. Default arguments for a member function of a class template must be specified on the initial declaration of the member function within the class template.
Given:
struct S1 { int x; }; struct S2 { int x; double y; }; struct S3 { int x; double y; string s; };Once upon a time, we went through a fairly protracted discussion to ensure that S1().x would be guaranteed to be 0. Note that if we declare
void f() { S1 s1; // ... }there is no guarantee of the value of s1.x, and that is intentional. But S1().x is different, because S1() is an rvalue, and unless all of its members are defined, the effect of copying it is undefined.
Similarly, S2().x and S2().y are also defined to be equal to zero, and here it really matters for many implementations, because if S2().y is just a bunch of random bits, it is entirely possible that trying to copy S2().y will yield a floating-point trap.
However, rather to my surprise, the standard does not define the value of S3().x or S3().y, because S3 is not a POD. It does define S3().s (by running the string constructor), but once a structure is no longer a POD, the values of uninitialized members are no longer guaranteed in expressions of the form T().
In my opinion, this definition is a mistake, and the committee's intention was to zero-initialize all members that do not have an explicitly defined constructor, whether or not the class is a POD.
See also paper J16/99-0014 = WG21 N1191.
[Note: this issue is resolved by the resolution of issue 178.]
In 6.9.3.2 [basic.start.static] paragraph 1 and 9.5 [dcl.init] paragraphs 5 and 6, the terms "memory" and "storage" are used in connection with zero-initialization. This is inaccurate; it is the variables that are zero-initialized, not the storage. (An all-zero bit pattern in the storage may, in fact, not correspond to the representation of zero converted to the appropriate type, and it is the latter that is being described.)
Suggested resolution: remove the words "storage" and "memory" in these contexts.
Proposed resolution (10/00):
Delete the words "The storage for" from the first sentence of 6.9.3.2 [basic.start.static] paragraph 1.
[Note: Revised wording in 9.5 [dcl.init] relating to this issue is also found in issue 178.]
When the Committee considered issue 35, another context in which value initialization might be relevant was overlooked: mem-initializers. It would seem reasonable that if T() as an expression invokes value initialization, that the same syntactic construct in a mem-initializer-list would do the same, and the usefulness of value initialization in that context is at least as great as the standalone case.
Proposed resolution (10/00):
[Note: this resolution supersedes the resolution to issue 35.]
In 7.6.1.4 [expr.type.conv] paragraph 2, replace "whose value is determined by default-initialization" by "which is value-initialized".
In 7.6.2.8 [expr.new] paragraph 15,
Replace 9.5 [dcl.init] paragraph 5 by:
To zero-initialize an object of type T means:
- if T is a scalar type (6.8 [basic.types]), the object is set to the value of 0 (zero) converted to T;
- if T is a non-union class type, each non-static data member and each base-class subobject is zero-initialized;
- if T is a union type, the object's first named data member [Footnote: This member must not be static, by virtue of the requirements in 11.5 [class.union]. end footnote] is zero-initialized;
- if T is an array type, each element is zero-initialized;
- if T is a reference type, no initialization is performed.
To default-initialize an object of type T means:
- if T is a non-POD class type (Clause 11 [class]), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
- if T is an array type, each element is default-initialized;
- otherwise, the object is zero-initialized.
To value-initialize an object of type T means:
- if T is a class type (Clause 11 [class]) with a user-declared constructor (11.4.5 [class.ctor]), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
- if T is a non-union class type without a user-declared constructor, then every non-static data member and base-class component of T is value-initialized;
- if T is an array type, then each element is value-initialized;
- otherwise, the object is zero-initialized.
A program that calls for default-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.
In 9.5 [dcl.init] paragraph 6, change "The memory occupied by any" to "Every".
In 9.5 [dcl.init] paragraph 7, replace "default-initialized" by "value-initialized".
In 9.5.2 [dcl.init.aggr] paragraph 7, replace "default-initialized" by "value-initialized".
In 11.4.8.2 [class.conv.ctor] paragraph 2, insert "or value-initialization" after the first occurrence of "default-initialization".
In 11.9 [class.init] paragraph 1, replace the note by "The object is default-initialized if there is no initializer, or value-initialized if the initializer is ()" [i.e., replace the non-normative note by different, normative text].
In 11.9.2 [class.expl.init] paragraph 2, replace "default-initialized" by "value-initialized".
In 11.9.3 [class.base.init] paragraph 3, replace "default-initialized" by "value-initialized" in the first bulleted item.
In 11.9.3 [class.base.init] paragraph 4, replace "default-initialized, nor initialized" by "default-initialized, nor value-initialized, nor assigned".
Another glitch in the TC1/core issue 178 definition of value-initialization: it's no longer an error to value-initialize a reference. That makes an example like
typedef struct { int &r; } S; int main() { S(); // Error in C++98, okay in TC1! }valid, which has got to be wrong. See 9.5 [dcl.init] paragraph 5, where there is wording that forbids default-initialization of a reference, but not value-initialization thereof. As noted in issue 302, if the default constructor were required to be generated when a value-initialization is done, that would force an error.
Proposed resolution (10/01):
Add the indicated wording to the indicated sentence in 9.5 [dcl.init] paragraph 5:
A program that calls for default-initialization or value-initialization of an entity of reference type is ill-formed.
9.5.2 [dcl.init.aggr] paragraph 2 says,
When an aggregate is initialized the initializer can be an initializer-clause consisting of a brace-enclosed, comma-separated list of initializers for the members of the aggregate.Neither of these uses of the syntactic nonterminal initializer corresponds to the grammar:
Proposed resolution (10/99): replace the quoted words with:
When an aggregate is initialized the initializer can contain an initializer-clause consisting of a brace-enclosed, comma-separated list of initializer-clauses for the members of the aggregate.
9.9 [basic.namespace] paragraph 2 says:
A name declared outside all named namespaces, blocks (8.4 [stmt.block] ) and classes (Clause 11 [class] ) has global namespace scope (6.4.6 [basic.scope.namespace] ).But 6.4.6 [basic.scope.namespace] paragraph 3 says:
A name declared outside all named or unnamed namespaces (9.9 [basic.namespace] ), blocks (8.4 [stmt.block] ), function declarations (9.3.4.6 [dcl.fct] ), function definitions (9.6 [dcl.fct.def] ) and classes (Clause 11 [class] ) has global namespace scope (also called global scope).9.9 [basic.namespace] should evidently be changed to match the wording in 6.4.6 [basic.scope.namespace] — the unnamed namespace is not global scope.
Proposed resolution (10/00):
Replace the first sentence of 6.4.6 [basic.scope.namespace] paragraph 3 with
The outermost declarative region of a translation unit is also a namespace, called the global namespace. A name declared in the global namespace has global namespace scope (also called global scope).
In the last sentence of the same paragraph, change "Names declared in the global namespace scope" to "Names with global namespace scope."
Replace 9.9 [basic.namespace] paragraph 2 with
The outermost declarative region of a translation unit is a namespace; see 6.4.6 [basic.scope.namespace].
Section 9.9.4 [namespace.udir] paragraph 3 uses the term extended-namespace-definition three times:
If a namespace is extended by an extended-namespace-definition after a using-directive for that namespace is given, the additional members of the extended namespace and the members of namespaces nominated by using-directives in the extended-namespace-definition can be used after the extended-namespace-definition.I think the intent is clear, but unfortunately I cannot find any other mention (or definition) of this term.
Mike Miller: True enough; in Section 9.9.2 [namespace.def] [the grammar] it's called an extension-namespace-definition.
Proposed Resolution (10/99): Systematically replace "extended-namespace-definition" by "extension-namespace-definition".
Consider the following:
extern "C" void f(); namespace N { extern "C" void f(); } using N::f;According to 9.10 [namespace.udecl] paragraph 11, the using-declaration is an error:
If a function declaration in namespace scope or block scope has the same name and the same parameter types as a function introduced by a using-declaration, the program is ill-formed.Based on the context (9.10 [namespace.udecl] paragraph 10 simply reiterates the requirements of 6.4 [basic.scope] ), one might wonder if the failure to exempt extern "C" functions was intentional or an oversight. After all, there is only one function f() involved, because it's extern "C", so ambiguity is not a reason to prohibit the using-declaration.
This also breaks the relatively strong parallel between extern "C" functions and typedefs established in our discussion of Core issue 14 in Santa Cruz. There the question was for using-directives:
typedef unsigned int size_t; extern "C" int f(); namespace N { typedef unsigned int size_t; extern "C" int f(); } using namespace N; int i = f(); // ambiguous "f"? size_t x; // ambiguous "size_t"?We decided for both that there was no ambiguity because each pair of declarations declares the same entity. (According to 6.1 [basic.pre] paragraph 3, a typedef name is not an entity, but a type is; thus the declarations of size_t declare the same entity "unsigned int".)
In the context of using-declarations, there is no explicit extension of the restrictions in 6.4 [basic.scope] paragraph 4 except as noted above for function declarations; thus the parallel scenario for a typedef is not ill-formed:
typedef unsigned int size_t; namespace N { typedef unsigned int size_t; }; using N::size_t; // okay, both declarations // refer to the same entityI think the first sentence of 9.10 [namespace.udecl] paragraph 11 ought to be rewritten as:
If a function declaration in namespace scope or block scope has the same name and the same parameter types as a function introduced by a using-declaration, and the declarations do not declare the same function, the program is ill-formed.
Proposed Resolution (10/99): As suggested.
6.8 [basic.types] paragraph 10 defines pointer to member types to be scalar types. It also defines scalar types to be one of the POD types.
Clause 11 [class] paragraph 4 defines a POD struct as an aggregate class with no non-static data members of type pointer to member.
It seems contradictory that a type can be POD, yet a class containing that type is non-POD.
Suggested resolution: Alter Clause 11 [class] paragraph 4 to allow pointer to member objects as non-static data members of POD class.
Proposed resolution (10/00):
In Clause 11 [class] paragraph 4, remove all occurrences of "pointer to member."
There is some controversy about whether class name injection applies to class templates. If it does apply, what is injected? Is a class name injected or is the thing that is injected actually a template?
Clause 11 [class] paragraph 2 says,
The class-name is also inserted into the scope of the class itself.In general, clause 9 applies to both classes and class templates, so I would take this to mean that class name imjection does indeed apply to class templates. One problem with this is that clause 9 uses the syntactic term class-name, which I would take to imply that the inserted name is always a class. This is clearly unacceptable for class templates as it makes the template itself unusable from with the template. For example:
template <class T> struct A { A<T*> ptr; // Invalid: A refers to a class };
Clearly the injected name must be usable as both a class and a class template. This kind of magic already exists in the standard. In 13.8.2 [temp.local] it says,
Within the scope of a class template, when the name of the template is neither qualified nor followed by <, it is equivalent to the name of the template followed by the template-parameters enclosed in <>.
The proposal here is that we clarify that name injection does indeed apply to class templates, and that it is the injected name that has the special property of being usable as both a class and a template name (as described in 13.8.2 [temp.local] ). This would eliminate the need for special wording regarding the qualification of the name, but would achieve the same result. This would also make this "special" name available to a derived class of a class template — something which is necessary if the benefits of class name injection are to be made uniformly available for class templates, too.
template <class T> struct Base { Base* p; Base<T*>* p2; ::Base* p3; // Error: only injected name usable as class }; template <class T> struct Derived: public Base<T> { Base* p; // Now okay Base<T*>* p2; // Still okay Derived::Base* p3; // Now okayNote that by giving the special attribute of being usable as both a class and a template to the injected name it is now clear where this attribute can and cannot be used.
(See paper J16/99-0010 = WG21 N1187.)
Proposed resolution (10/00):
[Note: these changes depend on the resolution for issue 147.]
Replace 13.8.2 [temp.local] paragraphs 1 and 2 with the following:
Like normal (non-template) classes, class templates have an injected-class-name (Clause 11 [class]). The injected-class-name can be used with or without a template-argument-list. When it is used without a template-argument-list, it is equivalent to the injected-class-name followed by the template-parameters of the class template enclosed in <>. When it is used with a template-argument-list, it refers to the specified class template specialization, which could be the current specialization or another specialization.
Within the scope of a class template specialization or partial specialization, when the injected-class-name is not followed by a <, it is equivalent to the injected-class-name followed by the template-arguments of the class template specialization or partial specialization enclosed in <>. [Example:
template<class T> class Y; template<> class Y<int> { Y* p; // meaning Y<int> Y<char>* q; // meaning Y<char> };—end example]
The injected-class-name of a class template or class template specialization can be used either with or without a template-argument-list wherever it is in scope. [Example:
template <class T> struct Base { Base* p; }; template <class T> struct Derived: public Base<T> { typename Derived::Base* p; // meaning Derived::Base<T> };—end example]
A lookup that finds an injected-class-name (6.5.2 [class.member.lookup]) can result in an ambiguity in certain cases (for example, if it is found in more than one base class). If all of the injected-class-names that are found refer to specializations of the same class template, and if the name is followed by a template-argument-list, the reference refers to the class template itself and not a specialization thereof, and is not ambiguous. [Example:
template <class T> struct Base { }; template <class T> struct Derived: Base<int>, Base<char> { typename Derived::Base b; // error: ambiguous typename Derived::Base<double> d; // OK };—end example]
When the normal name of the template (i.e., the name from the enclosing scope, not the injected-class-name) is used without a template-argument-list, it refers to the class template itself and not a specialization of the template. [Example:
template <class T> class X { X* p; // meaning X<T> X<T>* p2; X<int>* p3; ::X* p4; // error: missing template argument list // ::X does not refer to the injected-class-name };—end example]
The standard says, in 11.4 [class.mem] paragraph 4:
A member-declarator can contain a constant-initializer only if it declares a static member (11.4.9 [class.static] ) of integral or enumeration type, see 11.4.9.3 [class.static.data] .But later, in the section on static class data member initialization, 11.4.9.3 [class.static.data] paragraph 4, it says:
If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (7.7 [expr.const] ). In that case, the member can appear in integral constant expressions within its scope.The first paragraph should be modified to make it clear that it is not possible to initialize a static data member in-line with a constant-initializer if that data member is of integral (or enumeration) type, and yet not const.
Proposed Resolution (10/99): Change the sentence in 11.4 [class.mem] paragraph 4 to read:
A member-declarator can contain a constant-initializer only if it declares a static member (11.4.9 [class.static] ) of const integral or const enumeration type, see 11.4.9.3 [class.static.data] .
Between the May '96 and September '96 working papers, the text in 11.4 [class.mem] paragraph 13:
If T is the name of a class, then each of the following shall have a name different from T:was changed by removing the word 'static'. Looking over the meeting minutes from Stockholm, none of the proposals seem to include this change, which breaks C compatibility and is not mentioned in the compatibility annex. Was this change actually voted in by the committee?
- every static data member of class T;
Specifically, this breaks /usr/include/netinet/in.h under Linux, in which "struct ip_opts" shares its name with one of its members.
Proposed resolution (10/00):
In addition, if class T has a user-declared constructor (11.4.5 [class.ctor] ), every nonstatic data member of class T shall have a name different from T.
The definition of layout-compatible POD-struct types in 11.4 [class.mem] paragraph 14 requires that the two types
have the same number of members, and corresponding members (in order) have layout-compatible types (3.9).There does not appear to be any reason for including member functions and static data members in this requirement. It would be more logical to require only that the non-static data members of the two types must match.
The characteristics of layout-compatible types are not well described in the current wording, either. Apart from their use in 11.4 [class.mem] paragraph 16 to define the term "common initial sequence," there appears to be nothing said about which operations are possible between objects of layout-compatible types. For example, 6.8 [basic.types] paragraphs 2-3 give certain guarantees regarding use of memcpy on objects of the same type; it might be reasonable to assume that the same kinds of guarantees might apply to objects of layout-compatible types, but that is not said. Similarly, 7.2.1 [basic.lval] paragraph 15 describes permissible "type punning" but does not mention layout-compatible types.
Proposed resolution (10/00):
In 11.4 [class.mem] paragraphs 14 and 15, change all occurrences of "members" to "nonstatic data members."
According to 11.4.5 [class.ctor] paragraph 1, the syntax used in declaring a constructor allows at most one function-specifier. It is thus not permitted to declare a constructor both inline and explicit. This seems overly restrictive.
On a related note, there doesn't seem to be any explicit prohibition against member functions with the same name as the class. (Such a prohibition might reasonably be expected to occur in 11.4 [class.mem] paragraph 13, but member functions are not listed there.)
One possible interpretation would be that such member functions would violate the restrictions in 6.4.7 [basic.scope.class] paragraph 1, because the class name would refer to the class at some points in the class scope and to the member function at others. However, this seems a bit tenuous. Is an explicit prohibition needed?
(See also issue 147.)
Proposed resolution (10/00):
Add to 11.4 [class.mem] paragraph 13
- every member function of class T [Note: this restriction does not apply to constructors, which do not have names (11.4.5 [class.ctor]). ];
immediately following the line
- every data member of class T;
Change 11.4.5 [class.ctor] paragraph 1 from
A special declarator syntax using an optional function-specifier (9.2.3 [dcl.fct.spec])...
to
A special declarator syntax using an optional sequence of function-specifiers (9.2.3 [dcl.fct.spec])...
Issue 1
11.4.5.3 [class.copy.ctor] (From J16/99-0005 = WG21 N1182, "Proposed Resolutions for Core Language Issues 6, 14, 20, 40, and 89")
There are three related sub-issues in this issue, all dealing with the elision of copy constructors as described in 11.4.5.3 [class.copy.ctor] paragraph 15:
After discussion in Santa Cruz, the core group decided that sub-issue #1 required no change; the necessity of an accessible and unambiguous copy constructor is made clear in 6.7.7 [class.temporary] paragraph 1 and need not be repeated in this text. The remaining two sub-issues appear to be valid criticisms and should be addressed.
Proposed Resolution (10/99):
[Note: a small portion of this wording is superseded by the resolution of issue 185.]
The paragraph in question should be rewritten as follows. In addition, references to this section should be added to the index under "temporary, elimination of," "elimination of temporary," and "copy, constructor elision."
in a return statement in a function with a class return type, where the expression is the name of a non-volatile automatic object with the same cv-unqualified type as the function return type, the copy operation can be omitted by constructing the automatic object directly into the function's return value
class Thing { public: Thing(); ~Thing(); Thing(const Thing&); }; Thing f() { Thing t; return t; } Thing t2 = f();Here the criteria for elision can be combined to eliminate two calls to the copy constructor of class Thing: the copying of the local automatic object t into the temporary object for the return value of function f() and the copying of that temporary object into object t2. Effectively, the construction of the local object t can be viewed as directly initializing the global object t2, and that object's destruction will occur at program exit. —end example]
11.4.5.3 [class.copy.ctor] paragraph 15 refers only to "temporary class objects." It needs to be made clear that these provisions do not apply to temporaries that have been bound to references. For instance,
struct A { mutable int value; explicit A(int i) : value(i) {} void mutate(int i) const { value = i; } }; int foo() { A const& t = A(1); A n(t); // can this copy be elided? t.mutate(2); return n.value; // can this return 2? }The current wording seems to allow an implementation not to perform the copy in A N(t) because the source object is a temporary (created explicitly by A(1)).
Proposed resolution (10/00):
Change the wording proposed in the resolution of issue 20 from
- when a temporary class object (6.7.7 [class.temporary]) would be copied to a class object...
to
- when a temporary class object that has not been bound to a reference (6.7.7 [class.temporary]) would be copied to a class object...
The Standard is not clear whether automatic objects in a destructor are destroyed before or after the destruction of the class's base and member subobjects. That is, given
struct S { ~S(); }; struct T { S x; ~T() { S y; }; };
which will be destroyed first, x or y?
Proposed resolution (10/00):
In 11.4.7 [class.dtor] paragraph 6, change
A destructor for class X calls the destructors for X's direct members, ...to
After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X's direct members, ...
Can a copy-constructor declared as explicit be used to copy class values implicitly? For example,
struct X { X(); explicit X(const X&); }; void f(X); int main() { X x; f(x); }According to 11.4.8.2 [class.conv.ctor] paragraphs 2-3,
An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (9.5 [dcl.init] ) or where casts (7.6.1.9 [expr.static.cast] , 7.6.3 [expr.cast] ) are explicitly used... A copy-constructor (11.4.5.3 [class.copy.ctor] ) is a converting constructor. An implicitly-declared copy constructor is not an explicit constructor; it may be called for implicit type conversions.This passage would appear to indicate that the call in the example is ill-formed, since it uses neither the direct-initialization syntax nor an explicit cast. The last sentences are especially interesting in this regard, indicating that explicit and non-explicit copy constructors are handled differently.
On the other hand, 9.5 [dcl.init] paragraph 14, bullet 4, sub-bullet 2 says,
If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination... [the] applicable constructors are enumerated (12.2.2.4 [over.match.ctor] )...The cited passage says that
The candidate functions are all the constructors of the class of the object being initialized.
Notes from 04/01 meeting:
After the issue was accepted as a DR with the proposed resolution to change 12.2.2.4 [over.match.ctor] paragraph 1 as described below, it was noticed that 11.4.8.2 [class.conv.ctor] paragraph 3 states that:
A copy-constructor (11.4.5.3 [class.copy.ctor]) is a converting constructor.
In addition to making the proposed resolution for this issue ineffectual, the wording of paragraph 3 also contradicts that of paragraph 1:
A constructor declared without the function-specifier explicit that can be called with a single parameter specifies a conversion from the type of its first parameter to the type of its class. Such a constructor is called a converting constructor.
These considerations led to the addition of the second point of the proposed resolution.
Proposed resolution (04/01):
Change the first two sentences of 12.2.2.4 [over.match.ctor] paragraph 1 to
When objects of class type are direct-initialized (9.5 [dcl.init]), or copy-initialized from an expression of the same or a derived class type (9.5 [dcl.init]), overload resolution selects the constructor. For direct-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization, the candidate functions are all the converting constructors (11.4.8.2 [class.conv.ctor] ) of that class.
Change the first sentence of 11.4.8.2 [class.conv.ctor] paragraph 3 to read:
A non-explicit copy constructor (11.4.5.3 [class.copy.ctor]) is a converting constructor.
Paragraph 2 says that "the object-expression is always evaluated" when the class member syntax is used to refer to a static member. This presumably should say that the object expression is evaluated if the member access is performed, i.e., not if the overall expression is the operand of sizeof or the unevaluated branch of ?:, ||, or &&.
Proposed Resolution (10/99): Replace "is always evaluated" by "is evaluated" in 11.4.9 [class.static] paragraph 2.
Also see section: 6.3 [basic.def.odr] .
Originally, all static data members still had to be defined outside the class whether they were used or not.
But that restriction was supposed to be lifted so that static data members need not be defined outside the class unless they are used in a manner which requires their definition, in the same manner as namespace-scope variables. In particular, if an integral/enum const static data member is initialized within the class, and its address is never taken, we agreed that no namespace-scope definition was required.
For example:
struct A { static const int size = 10; int array[size]; }; int main() { A a; return 0; }However, 11.4.9.3 [class.static.data] paragraph 4 says:
The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.A narrow interpreration of "used" in this rule would make the example ill-formed because there is no namespace-scope definition of "size". A better wording for this rule would be:
The member shall still be defined in a namespace scope if it is used in the program in the manner described in 6.3 [basic.def.odr] . The namespace scope definition shall not contain an initializer.Also, the wording in 6.3 [basic.def.odr] paragraph 2:
An expression is potentially evaluated unless either it is the operand of the sizeof operator (7.6.2.5 [expr.sizeof] ), or it is the operand of the typeid operator and does not designate an lvalue of polymorphic class type (7.6.1.8 [expr.typeid] ).is incomplete because it does not mention the use of a compile-time constant as an array bound or template argument. It should say something like:
An expression is potentially evaluated unless it is the operand of the sizeof operator (7.6.2.5 [expr.sizeof] ), the operand of the typeid operator, an integral constant-expression used as an array bound or an integral constant-expression used as a template-argument for a non-reference template-parameter; and the expression does not designate an lvalue of polymorphic class type (7.6.1.8 [expr.typeid] ).
Proposed Resolution (04/99): Change the first sentence of 6.3 [basic.def.odr] paragraph 2 from:
An expression is potentially evaluated unless either it is the operand of the sizeof operator (7.6.2.5 [expr.sizeof] ), or it is the operand of the typeid operator and does not designate an lvalue of polymorphic class type (7.6.1.8 [expr.typeid] ).to:
An expression is potentially evaluated unless it appears where an integral constant expression is required (see 7.7 [expr.const] ), is the operand of the sizeof operator (7.6.2.5 [expr.sizeof] ), or is the operand of the typeid operator and the expression does not designate an lvalue of polymorphic class type (7.6.1.8 [expr.typeid] ).
In the example in paragraph 3 of 11.8.3 [class.access.base] , all the references to B in DD::f() should be replaced by ::B. The reason is that the class name B is private in D and thus inaccessible in DD. (The example was probably not updated when class name injection was added.)
Proposed resolution (10/00):
Replace the example in 11.8.3 [class.access.base] paragraph 3 with:
class B { public: int mi; // nonstatic member static int si; // static member }; class D: private B { }; class DD: public D { void f(); }; void DD::f() { mi = 3; // error: mi is private in D si = 3; // error: si is private in D ::B b; b.mi = 3; // OK (b.mi is different from this->mi) b.si = 3; // OK (b.si is different from this->si) ::B::si = 3; // OK ::B* bp1 = this; // error: B is a private base class ::B* bp2 = (::B*)this; // OK with cast bp2->mi = 3; // OK: access through a pointer to B }
11.8.5 [class.protected] paragraph 1 begins:
When a friend or a member function of a derived class references a protected nonstatic member of a base class, an access check applies in addition to those described earlier in 11.8 [class.access] .
This was intended to refer to nonstatic member functions and nonstatic data members. However, a protected nested type declared in a base class is, by some definition of the word, a "nonstatic" member, and therefore subject to this additional access check.
Proposed resolution (10/99): change "protected nonstatic member" in the above to "protected nonstatic member function or protected nonstatic data member" to make the intent clear.
In 11.9.3 [class.base.init] paragraph 4 we read:
After the call to a constructor for class X has completed, if a member of X is neither specified in the constructor's mem-initializers, nor default-initialized, nor initialized during execution of the body of the constructor, the member has indeterminate value.
Using the term "initialized" to describe setting the value of a member inside the body of a constructor is a misuse of the term: only by use of a placement new expression can a member be initialized "during the execution of the body of the constructor."
Suggested resolution: Change "initialized" to "given a value."
Proposed resolution (10/00): As suggested.
Sections 12.2.2.5 [over.match.copy] and 12.2.2.6 [over.match.conv] should be clarified regarding the treatment of conversion functions which return reference types.
Proposed resolution (10/99):
In 12.2.2.5 [over.match.copy] paragraph 1, change
Conversion functions that return "reference to T" return lvalues of type T and are therefore considered to yield T for this process of selecting candidate functions.to
Conversion functions that return "reference to X" return lvalues of type X and are therefore considered to yield X for this process of selecting candidate functions.In 12.2.2.6 [over.match.conv] paragraph 1, change
Conversion functions that return "reference to T" return lvalues of type T and are therefore considered to yield T for this process of selecting candidate functions.to
Conversion functions that return "reference to cv2 X" return lvalues of type "cv2 X" and are therefore considered to yield X for this process of selecting candidate functions.
In 12.2.4 [over.match.best] paragraph 1, bullet 4 of the second set of bullets, there is a cross-reference to 9.5 [dcl.init] and 12.2.2.6 [over.match.conv] . I believe it should also reference 12.2.2.7 [over.match.ref] . I think the phrase "initialization by user-defined conversion" was intended to refer to all initializations using user-defined conversions, and not just the case in 12.2.2.6 [over.match.conv] . Referring to only 12.2.2.6 [over.match.conv] suggests a narrower meaning of the phrase.
12.2.2.5 [over.match.copy] , although it does deal with initialization by user-defined conversion, does not need to be referenced because it deals with class —> class cases, and therefore there are no standard conversions involved that could be compared.
By the letter of the standard, the conversions required to make auto_ptr work should be accepted.
However, there's good reason to wonder if there isn't a bug in the standard here. Here's the issue: line 16 in the example below comes down to
copy-initialize an auto_ptr<Base> from an auto_ptr<Derived> rvalueTo do that, we first look to see whether we can convert an auto_ptr<Derived> to an auto_ptr<Base>, by enumerating the constructors of auto_ptr<Base> and the conversion functions of auto_ptr<Derived>. There's a single possible way to do the conversion, namely the conversion function
auto_ptr<Derived>::operator auto_ptr<Base>()(generated from the template). (The constructor auto_ptr<Base>(auto_ptr_ref<Base>) doesn't work because it requires a user-defined conversion on the argument.)
So far, so good. Now, we do the copy step:
direct-initialize an auto_ptr<Base> from an auto_ptr<Base> rvalueThis, as we've gone to great lengths to set up, is done by calling the conversion function
auto_ptr<Base>::operator auto_ptr_ref<Base>()(generated from the template), and then the constructor
auto_ptr<Base>(auto_ptr_ref<Base>)(generated from the template).
The problem with this interpretation is that it violates the long-standing common-law rule that only a single user-defined conversion will be called to do an implicit conversion. I find that pretty disturbing. (In fact, the full operation involves two conversion functions and two constructors, but "copy" constructors are generally considered not to be conversions.)
The direct-initialization second step of a copy-initialization was intended to be a simple copy — you've made a temporary, and now you use a copy constructor to copy it. Because it is defined in terms of direct initialization, however, it can exploit the loophole that auto_ptr is based on.
To switch to personal opinion for a second, I think it's bad enough that auto_ptr has to exploit a really arcane loophole of overload resolution, but in this case it seems like it's exploiting a loophole on a loophole.
struct Base { // 2 static void sink(auto_ptr<Base>); // 3 }; // 4 struct Derived : Base { // 5 static void sink(auto_ptr<Derived>); // 6 }; // 7 auto_ptr<Derived> source() { // 8 auto_ptr<Derived> p(source()); // 9 auto_ptr<Derived> pp(p); // 10 Derived::sink(source()); // 11 p = pp; // 12 p = source(); // 13 auto_ptr<Base> q(source()); // 14 auto_ptr<Base> qp(p); // 15 Base::sink(source()); // 16 q = pp; // 17 q = source(); // 18 return p; // 19 return source(); }Derek Inglis:
It seems clear to me that the result of this direct initilization must be the second standard conversion sequence in a user defined conversion sequence. Otherwise the resulting conversion sequence is not an implicit conversion sequence. By the letter of the standard, the sequence of conversions making up a copy-initialization must be an implicit conversion sequence.
Paragraph 3 of 7.3 [conv]:
An expression e can be implicitly converted to a type T if and only if the declaration "T t=e;" is well-formed, for some invented temporary variable t (9.5 [dcl.init]).
Paragraph 1 of 12.2.4.2 [over.best.ics]:
An implicit conversion sequence is a sequence of conversions used to convert an argument in a function call to the type of the corresponding parameter of the function being called. The sequence of conversions is an implicit conversion as defined in 7.3 [conv], which means it is governed by the rules for initialization of an object or reference by a single expression (9.5 [dcl.init], 9.5.4 [dcl.init.ref]).Sentence 1 of paragraph 12 of 9.5 [dcl.init]:
The initialization that occurs in argument passing ... is called copy-initialization and is equivalent to the formT x = a;
For me, these sentences imply that all sequences of conversions permitted on a function argument must be valid implicit conversion sequences.
The 'loophole' can be closed by adding a sentence (or note) to the section describing the 'direct initialization second step of a copy initialization' stating that the copy initialization is ill-formed if the conversion sequence resulting from the direct initialization is not a standard conversion sequence.
(See also issue 177 and paper J16/00-0009 = WG21 N1232.)
Proposed resolution (10/00):
Change 12.2.4.2 [over.best.ics] paragraphs 3 and 4 from
Except in the context of an initialization by user-defined conversion (12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv]), a well-formed implicit conversion sequence is one of the following forms:
- a standard conversion sequence (12.2.4.2.2 [over.ics.scs]),
- a user-defined conversion sequence (12.2.4.2.3 [over.ics.user]), or
- an ellipsis conversion sequence (12.2.4.2.4 [over.ics.ellipsis])
In the context of an initialization by user-defined conversion (i.e., when considering the argument of a user-defined conversion function; see 12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv]), only standard conversion sequences and ellipsis conversion sequences are allowed.
to
A well-formed implicit conversion sequence is one of the following forms:
- a standard conversion sequence (12.2.4.2.2 [over.ics.scs]),
- a user-defined conversion sequence (12.2.4.2.3 [over.ics.user]), or
- an ellipsis conversion sequence (12.2.4.2.4 [over.ics.ellipsis])
However, when considering the argument of a user-defined conversion function that is a candidate by 12.2.2.4 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, or by 12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv], or 12.2.2.7 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are allowed.
In 12.2.4.3 [over.ics.rank] , we have
int f(const int *); int f(int *); int i; int j = f(&i); // Calls f(int *)—end example] or, if not that,
void f(char *); void f(const char *); f("abc");The two conversion sequences differ only in their qualification conversions, and the destination types are similar. The cv-qualification signature of "char *", is a proper subset of the cv-qualification signature of "const char *", so f(char *) is chosen, which is wrong. The rule should be like the one for conversion to bool — the deprecated conversion should be worse than another exact match that is not the deprecated conversion.
Proposed resolution (10/00):
Change 12.2.4.3 [over.ics.rank] bullet 3.1 sub-bullet 3 from
S1 and S2 differ only in their qualification conversion and yield similar types T1 and T2 (7.3.6 [conv.qual] ), respectively, and the cv-qualification signature of type T1 is a proper subset of the cv-qualification signature of type T2.to
S1 and S2 differ only in their qualification conversion and yield similar types T1 and T2 (7.3.6 [conv.qual] ), respectively, and the cv-qualification signature of type T1 is a proper subset of the cv-qualification signature of type T2, and S1 is not the deprecated string literal array-to-pointer conversion (7.3.3 [conv.array] ).
12.2.4.3 [over.ics.rank] bullet 3.1 sub-bullet 2 says,
the rank of S1 is better than the rank of S2 (by the rules defined below)...This wording is confusing. The word "below" refers to paragraph 4 (which may not be clear), and the bulk of paragraph 4 deals with comparing conversion sequences whose "rank" is the same.
Proposed resolution (10/00):
In 12.2.4.3 [over.ics.rank] paragraph 3, change
the rank of S1 is better than the rank of S2 (by the rules defined below)to
the rank of S1 is better than the rank of S2, or S1 and S2 have the same rank and are distinguishable by the rules in the paragraph below
12.3 [over.over] paragraph 1 contains a supposedly exhaustive list of contexts in which the name of an overloaded function can be used without an argument list ("...shall not be used without arguments in contexts other than those listed"). However, 13.4.3 [temp.arg.nontype] paragraph 5, bullet 4 gives another context: as a template nontype argument.
Suggested resolution: Add the missing case to 12.3 [over.over].
Proposed resolution (10/00):
Add as the final bullet in 12.3 [over.over] paragraph 1:
- a non-type template-parameter (13.4.3 [temp.arg.nontype]).
and adjust the "or" and final period on the preceding two bullets.
12.3 [over.over] paragraph 2 says,
If the name is a function template, template argument deduction is done (13.10.3.3 [temp.deduct.funcaddr]), and if the argument deduction succeeds, the deduced template arguments are used to generate a single template function, which is added to the set of overloaded functions considered.
It is not clear whether this formulation allows explicit specification of non-deduced template arguments. For instance,
template <int I> void f(double x[]); typedef void (*FPtr)(double x[]); FPtr fp = &f<3>;
If only deduced arguments can be used, this example is ill-formed.
Suggested resolution: Clarify 12.3 [over.over] paragraph 2 to allow both deduced and explicitly-specified template arguments to be used to determine the function template specialization to be added to the overload set.
(See also issues 115 and 214.)
Proposed resolution (10/00):
In 12.3 [over.over] paragraph 2, change
...if the argument deduction succeeds, the deduced template arguments are used to generate a single template function...
to
...if the argument deduction succeeds, the resulting template argument list is used to generate a single function template specialization...
Section Clause 13 [temp] paragraph 8 says:
A non-exported template that is neither explicitly specialized nor explicitly instantiated must be defined in every translation unit in which it is implicitly instantiated (13.9.2 [temp.inst] ) or explicitly instantiated (13.9.3 [temp.explicit] ); no diagnostic is required.Shouldn't the first underlined phrase be omitted to avoid conflict with the second underlined phrase?
From John Spicer:
The first "explicitly instantiated" is intended to mean "explicitly instantiated in some other translation unit".
Proposed Resolution (04/99): Change the text in Clause 13 [temp] paragraph 8 from:
A non-exported template that is neither explicitly specialized nor explicitly instantiated must be defined in every translation unit in which it is implicitly instantiated (13.9.2 [temp.inst] ) or explicitly instantiated (13.9.3 [temp.explicit] ); no diagnostic is required.to:
A non-exported template must be defined in every translation unit in which it is implicitly instantiated (13.9.2 [temp.inst] ), unless the corresponding specialization is explicitly instantiated (13.9.3 [temp.explicit] ) in some translation unit; no diagnostic is required. [Note: See also 13.9.3 [temp.explicit] ]
The phrase "template function" is sometimes used to refer to a template (e.g., in Clause 13 [temp] paragraph 8) and sometimes to refer to a function generated from a template (e.g., 12.3 [over.over] paragraph 4) .
Suggested Resolution:
The phrase should mean "a function generated from a template" (or might perhaps include explicit specializations).
Proposed resolution (10/00):
In Clause 3 [intro.defs] “signature,” replace "template function specialization" by "function template specialization".
In 11.4.2 [class.mfct] paragraph 2, replace "template member functions" by "member functions of class templates and member function templates."
In 12.2.2 [over.match.funcs] paragraph 7 and footnote, replace all instances of "template functions" by "function template specializations."
In 12.2.4 [over.match.best] paragraph 1, fourth bullet (counting all bullets in that paragraph), replace "template function specialization" by "function template specialization". In the fifth bullet, replace "template functions" by "function template specializations."
In 12.3 [over.over] paragraph 2, replace "template function" by "function template specialization."
Change 12.3 [over.over] paragraph 4 from:
If more than one function is selected, any template functions in the set are eliminated if the set also contains a non-template function, and any given template function is eliminated if the set contains a second template function that is more specialized than the first according to the partial ordering rules of 13.7.7.3 [temp.func.order]. After such eliminations, if any, there shall remain exactly one selected function.to:
If more than one function is selected, any function template specializations in the set are eliminated if the set also contains a non-template function, and any given function template specialization F1 is eliminated if the set contains a second function template specialization whose function template is more specialized than the function template of F1 according to the partial ordering rules of 13.7.7.3 [temp.func.order]. After such eliminations, if any, there shall remain exactly one selected function.
Change text in section Clause 13 [temp] paragraph 8 from:
A template function declared both exported and inline is just inline and not exported.to:
A function template declared both exported and inline is just inline and not exported.
In 13.7.5 [temp.friend] paragraph 1, third bullet, replace "template function" by "function template" and "function specialization" by "function template specialization."
In footnote 130 (13.7.7 [temp.fct] paragraph 2), replace "template functions" by "function template specializations."
In 13.7.7.3 [temp.func.order] paragraph 1, third bullet change "template function specialization" to "function template specialization".
In 13.10.3 [temp.deduct] paragraph 1, change "template function specialization" to "function template specialization".
In _N4567_.17.3 [definitions] “component” change "non-member template functions that operate" to "non-member function templates that operate".
In _N4567_.17.3 [definitions] “traits class” change "template classes and template functions" to "class templates and function templates".
In 22.2 [utility] paragraph 1 change:
This subclause contains some basic template functions and classes that are used throughout the rest of the library.to:
This subclause contains some basic function and class templates that are used throughout the rest of the library.
In 22.3 [pairs] paragrah 1 change "template function" to "function template".
In footnote 215 (_N3225_.20.8.11 [function.pointer.adaptors] paragraph 6) change "template functions" to "function templates".
In 28.3.3.1 [locale] paragraph 4 change "template function" to "function template".
In 24.3 [iterator.requirements] paragraph 2 change "template function" to "function template".
In 24.4.2 [std.iterator.tags] paragraph 1, change "template function" to "function template specialization."
In 24.4.3 [iterator.operations] paragraph 1 change "template function" to "function template", and "These functions use" to "These function templates use".
In the section heading of 31.7.6.3.4 [ostream.inserters.character] change "template functions" to "function templates".
In 16.3.2.3 [structure.requirements] paragraph 2 change "template class name char_traits" to "class template char_traits".
In the section heading of 17.3.5 [numeric.limits] change "Template class" to "Class template".
In 16.4.4.6 [allocator.requirements] paragraph 3 change "template class member rebind" to "member class template rebind" and change "template typedef" to "typedef template".
In the section heading of _N4140_.D.9.1 [depr.lib.binder.1st] change "Template class" to "Class template".
In the section heading of _N4140_.D.9.3 [depr.lib.binder.2nd] change "Template class" to "Class template".
In the section heading of _N4140_.D.10.1 [auto.ptr] change "Template class" to "Class template".
In the section heading of 27.4.3 [basic.string] change "Template class" to "Class template".
In 27.4.3 [basic.string] paragraphs 1 and 2 change "template class basic_string" to "class template basic_string".
In the section heading of 28.3.4.2.2 [locale.ctype] change "Template class" to "Class template".
In the section heading of 28.3.4.2.3 [locale.ctype.byname] change "Template class" to "Class template".
In the section heading of 28.3.4.2.5 [locale.codecvt] change "Template class" to "Class template".
In the section heading of 28.3.4.2.6 [locale.codecvt.byname] change "Template class" to "Class template".
In the section heading of 28.3.4.3.2 [locale.num.get] change "Template class" to "Class template".
In the section heading of 28.3.4.3.3 [locale.nm.put] change "Template class" to "Class template".
In the section heading of 28.3.4.4.1 [locale.numpunct] change "Template class" to "Class template".
In the section heading of 28.3.4.4.2 [locale.numpunct.byname] change "Template class" to "Class template".
In the section heading of 28.3.4.5.1 [locale.collate] change "Template class" to "Class template".
In the section heading of 28.3.4.5.2 [locale.collate.byname] change "Template class" to "Class template".
In the section heading of 28.3.4.6.2 [locale.time.get] change "Template class" to "Class template".
In the section heading of 28.3.4.6.3 [locale.time.get.byname] change "Template class" to "Class template".
In the section heading of 28.3.4.6.4 [locale.time.put] change "Template class" to "Class template".
In the section heading of 28.3.4.6.5 [locale.time.put.byname] change "Template class" to "Class template".
In the section heading of 28.3.4.7.2 [locale.money.get] change "Template class" to "Class template".
In the section heading of 28.3.4.7.3 [locale.money.put] change "Template class" to "Class template".
In the section heading of 28.3.4.7.4 [locale.moneypunct] change "Template class" to "Class template".
In the section heading of 28.3.4.7.5 [locale.moneypunct.byname] change "Template class" to "Class template".
In the section heading of 28.3.4.8.2 [locale.messages] change "Template class" to "Class template".
In the section heading of 28.3.4.8.3 [locale.messages.byname] change "Template class" to "Class template".
In the section heading of 23.3.5 [deque] change "Template class" to "Class template".
In the section heading of 23.3.11 [list] change "Template class" to "Class template".
In the section heading of 23.6.3 [queue] change "Template class" to "Class template".
In the section heading of 23.6.4 [priority.queue] change "Template class" to "Class template".
In the section heading of 23.6.6 [stack] change "Template class" to "Class template".
In the section heading of 23.3.13 [vector] change "Template class" to "Class template".
In the section heading of 23.4.3 [map] change "Template class" to "Class template".
In the section heading of 23.4.4 [multimap] change "Template class" to "Class template".
In the section heading of 23.4.6 [set] change "Template class" to "Class template".
In the section heading of 23.4.7 [multiset] change "Template class" to "Class template".
In the section heading of 22.9.2 [template.bitset] change "Template class" to "Class template".
In 22.9.2 [template.bitset] paragraph 1, change "template class" to "class template".
In the section heading of 24.5.1.2 [reverse.iterator] change "Template class" to "Class template".
In the section heading of 24.5.2.2 [back.insert.iterator] change "Template class" to "Class template".
In the section heading of 24.5.2.3 [front.insert.iterator] change "Template class" to "Class template".
In the section heading of 24.5.2.4 [insert.iterator] change "Template class" to "Class template".
In 24.6 [stream.iterators] paragraph 1, change "template classes" to "class templates".
In the section heading of 24.6.2 [istream.iterator] change "Template class" to "Class template".
In the section heading of 24.6.3 [ostream.iterator] [lib.ostream.iterator] change "Template class" to "Class template".
In the section heading of 24.6.4 [istreambuf.iterator] change "Template class" to "Class template".
In 24.6.4 [istreambuf.iterator] paragraph 1, change "template class" to "class template".
In the section heading of 24.6.4.2 [istreambuf.iterator.proxy] change "Template class" to "Class template".
In the section heading of 24.6.5 [ostreambuf.iterator] change "Template class" to "Class template".
In 24.6.5 [ostreambuf.iterator] paragraph 1, change "template class" to "class template".
In 29.4 [eq:rand.dist.bern.bernoulli] paragraph 1, change "template class" to "class template".
In the section heading of 29.4.3 [complex] change "Template class" to "Class template".
In 29.6.1 [valarray.syn] paragraph 1, change "template classes" to "class templates" and change "function signatures" to "function templates".
In the section heading of 29.6.2 [template.valarray] change "Template class" to "Class template".
In the section heading of 29.6.5 [template.slice.array] change "Template class" to "Class template".
In the section heading of 29.6.7 [template.gslice.array] change "Template class" to "Class template".
In the section heading of 29.6.8 [template.mask.array] change "Template class" to "Class template".
In the section heading of 29.6.9 [template.indirect.array] change "Template class" to "Class template".
In 31.3 [iostream.forward] [lib.iostream.forward] paragraphs 3 to 7, change "template classes" to "class templates". [Note: Some editorial changes were made in paragraphs 2 to 8 when these changes were applied in September 2001.]
In the section heading of 31.5.3 [fpos] change "Template class" to "Class template".
In the section heading of 31.5.4 [ios] change "Template class" to "Class template".
In the section heading of 31.6.3 [streambuf] change "Template class" to "Class template".
In 31.6.3 [streambuf] paragraphs 2 and 3, change "template class" to "class template".
In the section heading of 31.7.5.2 [istream] change "Template class" to "Class template".
In the section heading of 31.7.5.7 [iostreamclass] change "Template class" to "Class template".
In the section heading of 31.7.6.2 [ostream] change "Template class" to "Class template".
In 31.8 [string.streams] paragraph 1 change "template classes" to "class templates".
In the section heading of 31.8.2 [stringbuf] change "Template class" to "Class template".
In the section heading of 31.8.3 [istringstream] change "Template class" to "Class template".
In the section heading of 31.8.5 [stringstream] change "Template class" to "Class template".
In the section heading of 31.10.3 [filebuf] change "Template class" to "Class template".
In the section heading of 31.10.4 [ifstream] change "Template class" to "Class template".
In the section heading of 31.10.5 [ofstream] change "Template class" to "Class template".
In the section heading of 31.10.6 [fstream] change "Template class" to "Class template".
Clause 13 [temp] paragraph 2 says,
[Note: in a class template declaration, if the declarator-id is a template-id, the declaration declares a class template partial specialization (13.7.6 [temp.spec.partial] ). ]There is no declarator-id in a class template declaration (cf paragraph 3).
Proposed resolution (10/00):
Replace the phrase "if the declarator-id is a template-id" with "if the class name is a template-id."
13.2 [temp.param] paragraph 10 says:
The set of default template-arguments available for use with a template declaration or definition is obtained by merging the default arguments from the definition (if in scope) and all declarations in scope in the same way as default function arguments are (9.3.4.7 [dcl.fct.default] )."Can a default argument for a template argument appear in a friend declaration? If so, when is this default argument considered for template instantiations?
For example,
template<class T1, class T2 = int> class A; class B { template<class T1 = int, class T2> friend class A; };Is this well-formed? If it is, should the IS say when the default argument for T1 is considered for instantiations of class A?
Proposed resolution (10/00): Add to the end of 13.2 [temp.param] paragraph 9,
A default template-argument shall not be specified in a friend template declaration.
(See also issue 136.)
The example in 13.2 [temp.param] paragraph 8 is:
template<int* a> struct R { /*...*/ }; int* p; R<p> w;There was a French comment was that this is an error, and there was general agreement with that.
I've been looking for the verbiage that specifies that this is an error and haven't found it. In particular, nothing in 13.2 [temp.param] ("Template parameters") nor 13.4.3 [temp.arg.nontype] ("Template non-type arguments") appears to rule out this case. (13.4.3 [temp.arg.nontype] paragraph 1 allows an argument to be "the name of an object or function with external linkage," with no limitation on the kinds of parameters such a name can match; "p" is, in fact, such a name.)
Should the resolution of the French comment include beefing up one or both of these sections to cover the applicable rules explicitly?
Proposed Resolution (04/99): Change the example in 13.2 [temp.param] paragraph 8 from:
template<int *a> struct R { /* ... */ }; template<int b[5]> struct S { /* ... */ }; int *p; R<p> w; // OK S<p> x; // OK due to parameter adjustment int v[5]; R<v> y; // OK due to implicit argument conversion S<v> z; // OK due to both adjustment and conversionto:
template<int *a> struct R { /* ... */ }; template<int b[5]> struct S { /* ... */ }; int p; R<&p> w; // OK S<&p> x; // OK due to parameter adjustment int v[5]; R<v> y; // OK due to implicit argument conversion S<v> z; // OK due to both adjustment and conversionFurthermore, in 13.4.3 [temp.arg.nontype] paragraph 1:
At the Dublin meeting (04/99), the Committee proposed to resolve issue 22 by simply changing the wording to make clear that a template parameter cannot be used in its own default argument. This creates a third treatment of this kind of situation, in addition to 6.4.2 [basic.scope.pdecl] paragraph 1, where declarators are in scope and can be used in their initializers, and paragraph 3, where an enumerator is not in scope until after its complete enumerator-definition. The Dublin resolution is for the template parameter to be in scope in its default argument but not usable. It would be more consistent to treat template parameters like enumerators: simply not in scope until the entire template-parameter declaration is seen.
On a related note, 13.2 [temp.param] paragraph 14 should be rewritten to connect the prohibition with visibility rules; otherwise, it sounds as if the following example is not permitted:
const int Z = 1; template <int X = Z, int Z> class A {};
Notes from 04/00 meeting:
The core working group did not reach consensus on the suggested approach to issue 22. However, it was agreed that the intent expressed in the earlier resolution would be better served by different wording.
Proposed resolution (10/00):
[Note: This resolution supersedes the resolution to issue 22.]
Replace 13.2 [temp.param] paragraph 14 as follows:
A template parameter shall not be used in its own default argument.
I have a request for clarification regarding a issue similar to John Wiegley's, but wrt. the ::template syntax. More precisely, where is
X::template Yallowed? (It is required for dependent X where Y is a template-id, I believe, but it doesn't seem to be disallowed elsewhere.)
The question also holds for '.template' and '->template'.
Proposed Resolution (04/99): Append to 13.3 [temp.names] paragraph 5:
Furthermore, names of member templates shall not be prefixed by the keyword template if the postfix-expression or qualified-id does not appear in the scope of a template. [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. ]
It appears from the grammar that explicit template arguments cannot be specified for overloaded operator names. Does this mean that template operators can never be friends?
But assuming that I read things wrong, then I should be able to specify a global template 'operator +' by writing:
friend A::B operator + <>(char&);John Spicer:
You should be able to have explicit template arguments on operator functions, but the grammar does seem to prohibit it (unless I'm reading it incorrectly). This is an error in the grammar, they should be permitted.
Proposed resolution (10/00):
Change the grammar specified in 12.4 [over.oper] paragraph 1
from
The explanation in 13.4.3 [temp.arg.nontype] paragraph 2 of why a string literal cannot be used as a template argument leaves something to be desired:
...because a string literal is an object with internal linkage.I can't find anything that says that a string literal has internal linkage. In fact, I'd be pretty surprised if I did, since linkage is defined (in 6.6 [basic.link] ) strictly in terms of names, and a string literal doesn't have a name. Actually, I think that it's the namelessness of a string literal that prevents it from being a template argument; only the third and fourth bullets of 13.4.3 [temp.arg.nontype] paragraph 1 could conceivably apply, and both of those require that the entity have a name (i.e., that they be given as an id-expression).
Proposed Resolution (10/99): In 13.4.3 [temp.arg.nontype] paragraph 2, change
[Note: a string literal (5.13.5 [lex.string] ) is not an acceptable template-argument because a string literal is an object with internal linkage.to
[Note: a string literal (5.13.5 [lex.string] ) does not satisfy the requirements of any of these categories and thus is not an acceptable template-argument.
The phrase "member function template" is used in 6.3 [basic.def.odr] paragraph 5 in the list of entities whose definitions can appear more than once in a program, with a cross-reference to 13.7.2.2 [temp.mem.func]. The title of that section is "Member functions of class templates," and paragraph 1 of that section says,
A member function template may be defined outside of the class template in which it is declared.
The example in that paragraph shows a non-template member function of a class template being defined. This gives the impression that the phrase "member function template" is intended to refer to a member function of a class template.
If this usage were intended, much of the rest of the Standard would be unintelligible: objects of class template specializations could not be copied (11.4.5.3 [class.copy.ctor] paragraph 3), member functions of class templates could not be declared virtual (13.7.3 [temp.mem] paragraph 3), etc.
Suggested resolution:
Change "member function template" to "member function of a class template" in both 6.3 [basic.def.odr] paragraph 5 and 13.7.2.2 [temp.mem.func] paragraph 1.
(See also issue 205.)
Proposed resolution (10/00): As suggested.
13.7.7.2 [temp.over.link] , paragraphs 5 and 6, describes equivalence and functional equivalence for expressions involving template parameters. As a note in paragraph 5 points out, such expressions may involve type parameters as well as non-type parameters.
Paragraph 7, however, describes the equivalence of function templates only with respect to non-type template parameters. It appears to be unspecified how to determine the equivalence of template functions whose types involve expressions that use template type parameters.
template <int I> struct S { }; // The following two declarations are equivalent: template <int I> void f(S<I>); template <int J> void f(S<J>); // The IS doesn't say whether these are equivalent: template <class T> void f(S<sizeof(T)>); template <class T> void f(S<sizeof(T)>);
Proposed resolution (10/99): Remove the three uses of the words "non-type" in 13.7.7.2 [temp.over.link] paragraph 7.
In 13.8 [temp.res] , references to the nonexistent syntactic non-terminal qualified-name occur twice in paragraph 3, twice in paragraph 4, and once in paragraph 5. There is also a reference in 13.2 [temp.param] paragraph 2.
Proposed resolution (10/99): Change the reference in all these cases to qualified-id.
The wording in 13.8 [temp.res] paragraph 3:
A qualified-name that refers to a type and that depends on a template-parameter (13.8.3 [temp.dep] ) shall be prefixed by the keyword typename to indicate that the qualified-name denotes a type, forming an elaborated-type-specifier (9.2.9.5 [dcl.type.elab] ).was intended to say:
A qualified-id that refers to a type and in which the nested-name-specifier depends on a template-parameter (13.8.3 [temp.dep] ) shall ...in much the same vein as 13.8.3.2 [temp.dep.type], second bullet, first half.
Proposed resolution (10/00): As suggested.
John Spicer: In 13.8 [temp.res] paragraph 5, the standard says
The keyword typename shall only be used in template declarations and definitions...My understanding of the intent of this restriction is to say that typename is only allowed in contexts in which template dependent names can be found, but the wording leaves open to interpretation whether typename is allowed in an explicit specialization, such as:
template <class T> struct A {}; template <class T> struct B { typedef int X; }; template <> struct A<int> { typename B<int>::X x; };My understanding is that such usage is not permitted. This should be clarified one way or the other.
Mike Miller: I agree with your understanding that you are not allowed to use typename in an explicit specialization. However, I think the standard already says that — an explicit specialization is not a template declaration. According to the grammar in Clause 13 [temp] paragraph 1, a template-declaration must have a non-empty template-parameter-list.
Nathan Myers: Is there any actual reason for this restriction? Its only apparent effect is to make it harder to specialize templates, with no corresponding benefit.
Proposed resolution (10/00):
In 13.8 [temp.res] paragraph 5, replace
The keyword typename shall only be applied to qualified names, but those names need not be dependent.
with
The keyword typename shall be applied only to qualified names, but those names need not be dependent. The keyword typename shall be used only in contexts in which dependent names can be used. This includes template declarations and definitions but excludes explicit specialization declarations and explicit instantiation declarations.
Paragraphs 3-4 of 13.8.3 [temp.dep] say, in part,
if a base class of [a class] template depends on a template-parameter, the base class scope is not examined during name lookup until the class template is instantiated... If a base class is a dependent type, a member of that class cannot hide a name declared within a template, or a name from the template's enclosing scope.
John Spicer: The wording in paragraph 4 seems particularly odd to me. It essentially changes the order in which scopes are considered. If a scope outside of the template declares a given name, that declaration hides entities of the same name from template dependent base classes (but not from nondependent base classes).
In the following example, the calls of f and g are handled differently because B::f cannot hide ::f, but B::g doesn't try to hide anything, so it can be called.
extern "C" int printf(char *, ...); template <class T> struct A : T { void h(T t) { f(t); // calls ::f(B) g(t); // calls B::g } }; struct B { void f(B){printf("%s", "in B::f\n");} void g(B){printf("%s", "in B::g\n");} }; void f(B){printf("%s", "in ::f\n");} int main() { A<B> ab; B b; ab.h(b); }
I don't think the current wording in the standard provides a useful facility. The author of class A can't be sure that a given call is going to call a base class function unless the base class is explicitly specified. Adding a new global function could cause the program to suddenly change meaning.
What I thought the rule was is, "If a base class is a dependent type a member of that class is not found by unqualified lookup".
Derek Inglis: My understanding is the same except that I'd remove the word "qualified" from your sentence.
Erwin Unruh: My interpretation is based on 13.8.4 [temp.dep.res] and especially 13.8.4.2 [temp.dep.candidate] (and largely on my memory of the discussions). For all unqualified names you do something like the following algorithm:
Regarding names from base classes you cannot find them in 2) because you don't know what base class you have. You cannot find them in 3) because members of classes are not found by Koenig lookup (only namespaces are considered). So you don't find them at all (for unqualified names).
For a qualified name, you start lookup for each 'part' of the qualification. Once you reach a dependent part, you stop and continue lookup at the instantiation point. For example:
namespace A { namepace B { template <class T> class C { template <class U> class D { typedef int E; // ... }; }; }; }; template <class T> class F : public T { typename A::B::C<int>::D<T>::E var1; typename A::B::C<T>::D<int>::E var2; typename F::T::X var3; }
For var1 you do lookup for A::B::C<int>::D at definition time, for var2 you only do lookup for A::B::C. The rest of the lookup is done at instantiation time since specialisations could change part of the lookup. Similarly the lookup for var3 stops after F::T at definition time.
My impression was that an unqualified name never refers to a name in a dependent base class.
(See also issue 197.)
Proposed resolution (10/00):
In 13.8.3 [temp.dep] paragraph 3, replace
In the definition of a class template or in the definition of a member of such a template that appears outside of the template definition, if a base class of this template depends on a template-parameter, the base class scope is not examined during name lookup until the class template is instantiated.
with
In the definition of a class template or a member of a class template, if a base class of the class template depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.
Remove from 13.8.3 [temp.dep] paragraph 4:
If a base class is a dependent type, a member of that class cannot hide a name declared within a template, or a name from the template's enclosing scopes.
Mark Mitchell (via John Spicer): Given:
template <class T> struct S { struct I1 { typedef int X; }; struct I2 : public I1 { X x; }; };
Is this legal? The question really boils down to asking whether or not I1 is a dependent type. On the one hand, it doesn't seem to fit any of the qualifications in 13.8.3.2 [temp.dep.type] . On the other, 13.9.4 [temp.expl.spec] allows explicit specialization of a member class of a class template, so something like:
template <> struct S<double>::I1 { int X; };
is apparently legal. But, then, `X' no longer refers to a type name. So, it seems like `I1' should be classified as dependent. What am I missing?
Erwin Unruh: I wrote that particular piece of text and I just missed the problem above. It is intended to be a dependent type. The reasoning is that I1 is just a shorthand for S<T>::I1 which clearly is dependent.
Suggested Resolution: (Erwin Unruh)
I think the list of what is a dependent type should be extended to cover "a type declared and used within the same template" modulo of phrasing.
(See also paper J16/00-0009 = WG21 N1231. This issue is also somewhat related to issue 205: classes nested inside template classes are, in some sense, "templates," just as non-template member functions of class templates and static data members of class templates are "templates.")
Proposed resolution (10/00):
Add after 13.8.2 [temp.local] paragraph 2:
Within the scope of a class template, when the unqualified name of a nested class of the class template is referred to, it is equivalent to the name of the nested class qualified by the name of the enclosing class template. [Example:template <class T> struct A { class B {}; // B is equivalent to A::B, which is equivalent to A<T>::B, // which is dependent. class C : B { }; };—end example]
13.2 [temp.param] paragraph 13 says:
The scope of a template-parameter extends from its point of declaration until the end of its template. In particular, a template-parameter can be used in the declaration of subsequent template-parameters and their default arguments.Is the following well-formed?
template<class U = U> class X { ... };
[Note: this issue is resolved by the resolution of issue 187.]
Problem Description: At least four of the examples in 13.9.4 [temp.expl.spec] have errors.
Proposed Resolution (10/99):
1. Change the example in paragraph 8 from:
[Example:to:// file #1 #include <vector> // Primary class template vector export template<class T> void f(t) { vector<T> vec; // should match the specialization /* ... */ } // file #2 #include <vector> class B { }; // Explicit specialization of vector for vector<B> template<class T> class vector<B> { /* ... */ } template<class T> void f(T); void g(B b) { f(b); // ill formed: // f<B> should refer to vector<B>, but the // specialization was not declared with the // definition of f in file #1 }—end example]
[Example:// file #1 #include <vector> // Primary class template vector export template<class T> void f(T) { std::vector<T> vec; // should match the specialization /* ... */ }; // file #2 #include <vector> class B { }; // Explicit specialization of vector for vector<B> namespace std { template<> class vector<B> { /* ... */ }; } template<class T> void f(T); void g(B b) { f(b); // ill formed: // f<B> should refer to vector<B>, but the // specialization was not declared with the // definition of f in file #1 }—end example]
2. The example in paragraph 16 as it appears in the IS:
[Example:The word 'partial' in the third comment in the example should be removed because this example does not illustrate partial specialization. Also, the two specializations of template<> template<> void A<int>::g(int, char); violate 13.9 [temp.spec] , paragraph 5, which reads:template<class T> struct A { void f(T); template<class X> void g(T, X); void h(T) { } }; // specialization template<> void A<int>::f(int); // out of class member template definition template<class T> template<class X> void A<T>::g(T,X) { } // member template partial specialization template<> template<class X> void A<int>::g(int, X); // member template specialization template<> template<> void A<int>::g(int, char); // X deduced as char template<> template<> void A<int>::g<char>(int, char); // X specified as char // member specialization even if defined in class definition template<> void A<int>::h(int) { }—end example]
No program shall explicitly instantiate any template more than once, both explicitly instantiate and explicitly specialize a template, or specialize a template more than once for a given set of template-arguments. An implementation is not required to diagnose a violation of this rule.Proposed resolution (10/99):
[Example:template<class T> struct A { void f(T); template<class X1> void g1(T, X1); template<class X2> void g2(T, X2); void h(T) { } }; // specialization template<> void A<int>::f(int); // out of class member template definition template<class T> template<class X1> void A<T>::g1(T,X1) { } // member template specialization template<> template<class X1> void A<int>::g1(int, X1); // member template specialization template<> template<> void A<int>::g1(int, char); // X1 deduced as char template<> template<> void A<int>::g2<char>(int, char); // X2 specified as char // member specialization even if defined in class definition template<> void A<int>::h(int) { }—end example]
3. Remove the spurious semicolon (or the curly brackets) from the end of the last line in the example in paragraph 17. This is the example as it appears in the IS:
[Example:Proposed resolution (10/99):template<class T1> class A { template<class T2> class B { void mf(); }; }; template<> template<> A<int>::B<double> { }; template<> template<> void A<char>::B<char>::mf() {};—end example]
[Example:template<class T1> class A { template<class T2> class B { void mf(); }; }; template<> template<> A<int>::B<double>; template<> template<> void A<char>::B<char>::mf();—end example]
Note (Steve Adamczyk, March 2002): that's still incorrect. The missing "class" was added editorially when TC1 was prepared.
4. Remove spurious semicolons (or curly brackets) from the specializations of mf1 and mf2 in the example in paragraph 18. This is the text of the example as it appears in the IS:
[Example:Proposed resolution (10/99):template<class T1> class A { template<class T2> class B { template<class T3> void mf1(T3); void mf2(); }; }; template<> template<class X> class A<int>::B { }; template<> template<> template<class T> void A<int>::B<double>::mf1(T t) { }; template<class Y> template<> void A<Y>::B<double>::mf2() { }; // ill-formed; B<double> is specialized but // its enclosing class template A is not—end example]
[Example:template<class T1> class A { template<class T2> class B { template<class T3> void mf1(T3); void mf2(); }; }; template<> template<class X> class A<int>::B { }; template<> template<> template<class T> void A<int>::B<double>::mf1(T t) { } template<class Y> template<> void A<Y>::B<double>::mf2() { } // ill-formed; B<double> is specialized but // its enclosing class template A is not—end example]
Note (Steve Adamczyk, March 2002): that's still incorrect. See issue 336.
Paragraph 12 should address partial ordering. It wasn't updated when that change was made and conflicts with 13.7.7.3 [temp.func.order] paragraph 1.
Proposed resolution (10/00):
Remove 13.9.4 [temp.expl.spec] paragraph 12 and the example that follows.
13.10.2 [temp.arg.explicit] paragraph 6 contains the following example:
namespace A { struct B { }; template<int X> void f(); } namespace C { template<class T> void f(T t); } void g(A::B b) { f<3>(b); // ill-formed: not a function call A::f<3>(b); // well-formed C::f<3>(b); // ill-formed; argument dependent lookup // only applies to unqualified names using C::f; f<3>(b); // well-formed because C::f is visible; then // A::f is found by argument dependent lookup }
A::f() should have a parameter of type A::B.
Proposed resolution (10/00):
In the example in 13.10.2 [temp.arg.explicit] paragraph 6, change the third line from
template <int X> void f();
to
template <int X> void f(B);
13.10.3.6 [temp.deduct.type] paragraph 18 uses incorrect syntax. Instead of
template <template X<class T> > struct A { }; template <template X<class T> > void f(A<X>) { }it should be
template <template <class T> class X> struct A { }; template <template <class T> class X> void f(A<X>) { }
Proposed resolution (10/00): As suggested.
[Note: this section was numbered 14.8.2.4 in ISO/IEC 14882:2003.]
At the top of clause 15, in paragraph 2, it says:
A goto, break, return, or continue statement can be used to transfer control out of a try block or handler, but not into one.What about switch statements?
switch ( f() ) { case 1: try { g(); case 2: h(); } catch (...) { // handler } break; }Daveed Vandevoorde:
Consider:
void f() { try { label: ; } catch(...) { goto label; } }Now the phrase "try block" (without a hyphen) is used in paragraph 1 in a way that causes me to think that it is not intended to include the corresponding handlers. On the other hand, the grammar entity "try-block" (with hyphen) does include the handlers. So is the intent to prohibit the above or not?
Proposed resolution (10/00:
Change text in Clause 14 [except] paragraph 2 from:
A goto, break, return, or continue statement can be used to transfer control out of a try block or handler, but not into one.to:
A goto or switch statement shall not be used to transfer control into a try block or into a handler.
[ Example:void f() {—end example ]
goto l1; // Ill-formed
goto l2; // Ill-formed
try {
goto l1; // OK
goto l2; // Ill-formed
l1: ;
} catch (...) {
l2: ;
goto l1; // Ill-formed
goto l2; // OK
}
}
A goto, break, return, or continue statement can be used to transfer control out of a try block or handler.
(See also issue 246.)
14.4 [except.handle] paragraph 3 says,
A handler is a match for a throw-expression with an object of type E...
This wording leaves it unclear whether it is the dynamic type of the object being thrown or the static type of the expression that determines whether a handler is a match for a given exception. For instance,
struct B { B(); virtual ~B(); }; struct D : B { D(); }; void toss(const B* b) { throw *b; } void f() { const D d; toss(&d); }
In this code, presumably the type to be matched is B and not const D (14.2 [except.throw]).
Suggested resolution: Replace the cited wording as follows:
A handler is a match for a throw-expression which initialized a temporary (14.2 [except.throw]) of type E...
Proposed resolution (10/00):
Change 14.2 [except.throw] paragraph 3 from
A throw-expression initializes a temporary object, the type of which is determined...
to
A throw-expression initializes a temporary object, called the exception object, the type of which is determined...
Change 14.4 [except.handle] paragraph 3 from
A handler is a match for a throw-expression with an object of type E if...
to
A handler is a match for an exception object of type E if...
14.5 [except.spec] paragraph 3 should say what happens when two pointers to members with different exception specifications are assigned to each other, initialized with one another, etc.
Proposed Resolution (04/99): Change the text in 14.5 [except.spec] paragraph 3 from:
Similarly, any function or pointer to function assigned to, or initializing, a pointer to function shall only allow exceptions that are allowed by the pointer or function being assigned to or initialized.to:
A similar restriction applies to assignment to and initialization of pointers to functions, pointers to member functions, and references to functions: the target entity shall allow at least the exceptions allowed by the source value in the assignment or initialization.
The standard is inconsistent about constness inside exception specifications.
struct X {}; struct Y:X {}; const Y bar() {return Y();} void foo()throw(const X) { throw bar(); }It is unclear whether calling foo will result in a call to std::unexpected. According to 14.5 [except.spec] paragraph 7, only two cases are treated specially with regard to inheritance: If "class X" appears in the type-id-list, or if "class X*" appears in the type-id-list. Neither is the case here, so foo only allows exceptions of the same type (const X). As a result, std::unexpected should be called.
On the other hand, the intent of exception specification appears to allow an implementation of this example as
void foo() try{ throw bar(); }catch(const X){ throw; }catch(...){ std::unexpected(); }According to 14.4 [except.handle] , this replacement code would catch the exception, so std::unexpected would not be called.
Suggested resolution: Change 14.5 [except.spec] paragraph 7 to read
A function is said to allow all exception objects of all types E for which one of the types T in the type-id-list would be a handler, according to 14.4 [except.handle] .
Proposed resolution (10/00):
Replace 14.5 [except.spec] paragraph 7 with the following:
A function is said to allow an exception of type E if its exception-specification contains a type T for which a handler of type T would be a match (14.4 [except.handle]) for an exception of type E.
D.6 [depr.impldec] indicates that use of the postfix ++ with a bool operand is deprecated. Annex Clause Annex D [depr] says nothing about prefix ++. However, this use of prefix ++ is also deprecated, according to 7.6.2.3 [expr.pre.incr] paragraph 1. Presumably D.6 [depr.impldec] should be expanded to cover prefix ++, or another section should be added to Annex Clause Annex D [depr].
Proposed resolution (10/00):
Change the entire section D.6 [depr.impldec], including its heading, to read as follows:
D.1 Increment operator with bool operand [depr.incr.bool] The use of an operand of type bool with the ++ operator is deprecated (see 7.6.2.3 [expr.pre.incr] and 7.6.1.6 [expr.post.incr]).
[Voted into the WP at the November, 2010 meeting as paper N3146.]
The list of identifier characters specified in the C++ standard annex _N2691_.E [extendid] and the C99 standard annex D are different. The C99 standard includes more characters.
The C++ standard says that the characters are from "ISO/IEC PDTR 10176" while the C99 standard says "ISO/IEC TR 10176". I'm guessing that the PDTR is an earlier draft of the TR.
Should the list in the C++ standard be updated?
Tom Plum: In my opinion, the "identifier character" issue has not been resolved with certainty within SC22.
One critical difference in C99 was the decision to allow a compiler to accept more characters than are given in the annex. This allows for future expansion.
The broader issue concerns the venue in which the "identifier character" issue will receive ongoing resolution.
Notes from 10/00 meeting:
The core language working group expressed a strong preference (13/0/5 in favor/opposed/abstaining) that the list of identifier characters should be extensible, as is the case in C99. However, the fact that this topic is under active discussion by other bodies was deemed sufficient reason to defer any changes to the C++ specification until the situation is more stable.
Notes from October, 2005 meeting:
The working group expressed interest in the kind of approach taken by XML 1.1, in which the definition of an identifier character is done by excluding large ranges of the Unicode character set and accepting any character outside those ranges, rather than by affirmatively designating each identifier character in each language. As noted above, consideration of this issue was previously deferred pending other related standardization efforts. Clark Nelson will investigate whether these have reached a point at which progress on this issue in C++ is now possible.
Additional note (May, 2008):
Issue 663 also deals with this appendix, and the proposed resolution there is to update the table to reflect the newest available technical report, ISO/IEC TR 10176:2003. That resolution might be seen as sufficient for this issue, as well. However, that approach does not address several of the concerns mentioned in the discussion above: coordination with WG14, the extensibility of the list of identifiers, the alternative approach used in the XML specification, etc.
[Voted into the WP at the March, 2011 meeting as part of paper N3272.]
There are some kinds of declarations that can appear in a derived class and hide names from a base class, but for which the syntax does not permit a [[hiding]] attribute. For example:
struct B1 { int N; int M; }; struct B2 { int M; }; struct [[base_check]] D: B1, B2 { enum { N }; // hides B1::N but cannot take an attribute using B1::M; // hides B2::M but cannot take an attribute };
Additional note (October, 2010):
alias-declarations should also be considered in this regard.
Notes from the November, 2010 meeting:
Paper N3206 did not address these cases; in fact, it introduced additional member declarations that cannot be annotated as hiding a base class member (function-definitions and template-declarations), because the new virt-specifier applies to a member-declarator and none of these member-declarations uses a member-declarator.
Additional note (November, 2010):
The injected-class-name can also hide a name from a base class but cannot be annotated with new.
[Voted into the WP at the November, 2010 meeting as part of paper N3206.]
The meaning of the [[base_check]] and [[hiding]] attributes is defined in terms of hiding as described in _N4868_.6.4.10 [basic.scope.hiding]. In that section, however, hiding is orthogonal to overriding: practically by definition, a function that overrides a base class virtual function also hides it. According to the current specification, the [[override]] and [[hiding]] attributes would always need to be specified together on every overriding function in a [[base_check]] class. This is presumably unintended, so the current wording should be amended so that [[override]] implies [[hiding]] or some such.
[Voted into the WP at the November, 2010 meeting in paper N3206.]
N3092 comment US 44The facility for checking hiding and overriding of base class members should not use the attribute syntax but should use keywords instead. Concerns about breaking code by changing current identifiers into keywords can be addressed by using contextual keywords, i.e., by putting the keywords into syntactic locations where identifiers cannot appear and thus continuing to allow their use as ordinary identifiers in other contexts.
Notes from the August, 2010 meeting:
CWG expressed a preference for non-contextual keywords for these features.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 56Access declarations were deprecated in the 1998 standard and have no benefits over using-declarations. They should be removed in C++0x.
Proposed resolution (August, 2010):
Delete _N3225_.11.3 [class.access.dcl].
Delete _N3225_.D.3 [depr.access.dcl].
Delete the following production from the grammar in 11.4 [class.mem] paragraph 1:
...Except when used to declare friends (11.4) or to introduce the name of a member of a base class into a derived class (7.3.3, 11.3), member-declarations declare members of the class...
Delete 9.10 [namespace.udecl] paragraph 19:
[Note: use of access-declarations (_N3225_.11.3 [class.access.dcl]) is deprecated; member using-declarations provide a better alternative. —end note]
(Moved from issue 760.)
Although it was considered and rejected as part of issue 643, more recent developments may argue in favor of allowing the use of this in a late-specified return type. In particular, declaring the return type for a forwarding function in a derived class template that invokes a member function of a dependent base class is difficult without this facility. For example:
template <typename T> struct derived: base<T> { auto invoke() -> decltype(this->base_func()) { return this->base_func(); } };
(See also issue 1207 for another potential motivation for a change to this rule.)
Additional note (October, 2010):
The question should also be considered for parameter types; for example,
class comparable { public: bool is_equal(decltype(*this) other) { // should be X& return /*...*/; } };
Proposed resolution (March, 2011):
This issue is resolved by the resolution of issues 1017 and 1207 in document N3282.
[Voted into the WP at the March, 2011 meeting.]
N3092 comment US 23According to _N4868_.6.5.6 [basic.lookup.classref] paragraph 1,
In a class member access expression (7.6.1.5 [expr.ref]), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (13.3 [temp.names]) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a class template. If the lookup in the class of the object expression finds a template, the name is also looked up in the context of the entire postfix-expression and
if the name is not found, the name found in the class of the object expression is used, otherwise
if the name is found in the context of the entire postfix-expression and does not name a class template, the name found in the class of the object expression is used, otherwise
if the name found is a class template, it shall refer to the same entity as the one found in the class of the object expression, otherwise the program is ill-formed.
This makes the following ill-formed:
#include <set> using std::set; struct X { template <typename T> void set(const T& value); }; void foo() { X x; x.set<double>(3.2); }
That's confusing and unnecessary. The compiler has already done the lookup in X's scope, and the obviously-correct resolution is that one, not the identifier from the postfix-expression's scope. Issue 305 fixed a similar issue for destructor names but missed member functions.
Suggested resolution: Delete the end of paragraph 1, starting with “If the lookup in the class...” and including all three bullets.
Proposed resolution (November, 2010):
Change 6.5.5.2 [class.qual] bullet 1.2 as follows:
a conversion-type-id of an
conversion-function-id is looked up both in the scope of
the class and in the context in which the entire
postfix-expression occurs and shall refer to the same type in
both contexts in the same manner as a
conversion-type-id in a class member access (see _N4868_.6.5.6 [basic.lookup.classref]);
Change _N4868_.6.5.6 [basic.lookup.classref] paragraph 1 as follows:
In a class member access expression (7.6.1.5 [expr.ref]), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (13.3 [temp.names]) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a class template.
If the lookup in the class of the object expression finds a template, the name is also looked up in the context of the entire postfix-expression and
if the name is not found, the name found in the class of the object expression is used, otherwise
if the name is found in the context of the entire postfix-expression and does not name a class template, the name found in the class of the object expression is used, otherwise
if the name found is a class template, it shall refer to the same entity as the one found in the class of the object expression, otherwise the program is ill-formed.
Change _N4868_.6.5.6 [basic.lookup.classref] paragraph 4 as follows:
If the id-expression in a class member access is a qualified-id of the form
class-name-or-namespace-name::...the class-name-or-namespace-name following the . or -> operator is
looked up both in the context of the entire postfix-expression and in the scope of the class of the object expression. If the name is found only in the scope of the class of the object expression, the name shall refer to a class-name. If the name is found only in the context of the entire postfix-expression, the name shall refer to a class-name or namespace-name. If the name is found in both contexts, the class-name-or-namespace-name shall refer to the same entity.first looked up in the class of the object expression and the name, if found, is used. Otherwise it is looked up in the context of the entire postfix-expression. [Note: See 6.5.5 [basic.lookup.qual], which describes the lookup of a name before ::, which will only find a type or namespace name. —end note]
Change _N4868_.6.5.6 [basic.lookup.classref] paragraph 7 as follows:
If the id-expression is a conversion-function-id, its conversion-type-id
shall denote the same type in both the context in which the entire postfix-expression occurs and in the context of the class of the object expression (or the class pointed to by the pointer expression).is first looked up in the class of the object expression and the name, if found and denotes a type, is used. Otherwise it is looked up in the context of the entire postfix-expression and the name shall denote a type. [Example:struct A { }; namespace N { struct A { void g() { } template <class T> operator T(); }; } int main() { N::A a; a.operator A(); // calls N::A::operator N::A }
—end example]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The resolution of issue 1111 changes _N4868_.6.5.6 [basic.lookup.classref] paragraph 7 to read,
[A] conversion-type-id is first looked up in the class of the object expression and the name, if found and denotes a type, is used. Otherwise it is looked up in the context of the entire postfix-expression and the name shall denote a type.
The result of this specification is that a non-type member declaration in the class scope of the object expression will not be found (although it will hide a base class type member of the same name), but a non-type declaration in the context of the expression will be found (and make the program ill-formed).
This is inconsistent with the way other lookups are handled when they occur in a context that requires a type. For example, the lookup for a nested-name-specifier “considers only namespaces, types, and templates whose specializations are types” (6.5.5 [basic.lookup.qual] paragraph 1); the lookup for a name appearing in an elaborated-type-specifier is done “ignoring any non-type names that have been declared” (6.5.6 [basic.lookup.elab] paragraph 2); and in the lookup for a name in a base-type-specifier, “non-type names are ignored” (11.7 [class.derived] paragraph 2). The lookup for a conversion-type-id should be similar, and the wording in _N4868_.6.5.6 [basic.lookup.classref] paragraph 7 adjusted accordingly.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
_N4885_6.7.5.5.4 [basic.stc.dynamic.safety] paragraph 4 only prohibits the dereferencing and deallocation of non-safely-derived pointers. This is insufficient. Explicit deallocation of storage is described as rendering invalid all pointers to that storage, with the result that all operations on such a pointer value causes undefined behavior (6.7.6.5.3 [basic.stc.dynamic.deallocation] paragraph 4). The same should be true if the storage pointed to by a non-safely-derived pointer is garbage collected. In particular, the promise of objects having distinct addresses (6.7.2 [intro.object] paragraph 6) should not apply if one of those objects is designated by a non-safely-derived pointer.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change _N4885_6.7.5.5.4 [basic.stc.dynamic.safety] paragraph 4 as follows:
...Alternatively, an implementation may have strict pointer safety, in which case,ifa pointer value that is not a safely-derived pointer value isdereferenced or deallocated, andan invalid pointer value, unless the referenced complete object is of dynamic storage duration and hasnotpreviously been declared reachable (_N4700_.23.11.2 [util.smartptr]), the behavior is undefined. [Note:thisThe effect of using an invalid pointer value (including passing it to a deallocation function) is undefined, see 6.7.6.5.3 [basic.stc.dynamic.deallocation]. 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...
[Voted into WP at August, 2010 meeting.]
See also issue 37.
Given this piece of code and S having a user-defined ctor, at precisely which point must std::uncaught_exception() return true and where false?
try { S s0; throw s0; } catch (S s2) { }
My understanding of the semantics of the code is as follows:
Is my understanding correct?
14.2 [except.throw] paragraph 3 talks about “the exception object” when describing the semantics of the throw-expression:
a throw-expression initializes a temporary object, called the exception object...
However, 14.6.2 [except.terminate] paragraph 1 talks about “the expression to be thrown” when enumerating the conditions under which terminate() is called:
when the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught (14.2 [except.throw]), calls a user function that exits via an uncaught exception...
And, _N5001_.14.6.3 [except.uncaught] paragraph 1 refers to “the object to be thrown” in the description of uncaught_exception():
The function std::uncaught_exception() returns true after completing evaluation of the object to be thrown...
Are all these objects one and the same? I believe the answer is important in case the construction of the temporary exception object throws another exception.
Suppose they are the same. Then uncaught_exception() invoked from the copy ctor for s1 (from the example [above]) must return false and a new exception (e.g., bad_alloc) may be thrown and caught by a matching handler (i.e., without calling terminate()).
But if they are not the same, then uncaught_exception() invoked from the copy ctor for s1 must return true and throwing another exception would end up calling terminate(). This would, IMO, have pretty severe consequences on writing exception safe exception classes.
As in the first case, different compilers behave differently, with most compilers not calling terminate() when the ctor for the temporary exception object throws. Unfortunately, the two compilers that I trust the most do call terminate().
FWIW, my feeling is that it should be possible for the copy ctor invoked to initialize the temporary exception object to safely exit by throwing another exception, and that the new exception should be allowed to be caught without calling terminate.
Mike Miller: The way I see this, a throw-expression has an assignment-expression as an operand. This expression is “the expression to be thrown.” Evaluation of this expression yields an object; this object is “the object to be thrown.” This object is then copied to the exception object.
Martin Sebor: Here's a survey of the return value from uncaught_exception() in the various stages of exception handling, implemented by current compilers:
expr | temp | unwind | handlr | 2nd ex | |
---|---|---|---|---|---|
HP aCC 6 | 0 | 0 | 1 | 0 | OK |
Compaq C++ 6.5 | 0 | 0 | 1 | 1 | ABRT |
EDG eccp 3.4 | 0 | 1 | 1 | 1 | ABRT |
g++ 3.4.2 | 0 | 0 | 1 | 0 | OK |
Intel C++ 7.0 | 0 | 0 | 1 | 0 | OK |
MIPSpro 7.4.1 | 0 | 0 | 1 | 1 | ABRT |
MSVC 7.0 | 0 | 0 | 1 | 0 | OK |
SunPro 5.5 | 1 | 1 | 1 | 0 | OK |
VisualAge 6.0 | 0 | 1 | 1 | 1 | OK |
In the table above:
expr | is the evaluation of the assignment-expression in the throw-expression |
temp | is the invocation of the copy ctor for the unnamed temporary exception object created by the runtime. |
unwind | is stack unwinding. |
handlr | is the invocation of the copy ctor in the exception-declaration in the catch handler. |
2nd ex | describes the behavior of the implementation when the invocation of the copy ctor for the unnamed temporary exception object [temp] throws another exception. |
Proposed resolution (October, 2004):
Change 14.2 [except.throw] paragraph 3 as follows:
A throw-expression initializes a temporary object, called the exception object,theby copying the thrown object (i.e., the result of evaluating its assignment-expression operand) to it. The type ofwhichthe exception object is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T,” respectively. [Note: the temporary object createdforby a throw-expressionthatwhose operand is a string literal is never of type char* or wchar_t*; that is, the special conversions for string literals from the types “array of const char” and “array of const wchar_t” to the types “pointer to char” and “pointer to wchar_t,” respectively (7.3.3 [conv.array]), are never applied to the operand of a throw-expression. —end note] The temporary is an lvalue and is used to initialize the variable named in the matching handler (14.4 [except.handle]). The type of the operand of a throw-expression shall not be an incomplete type, or a pointer to an incomplete type other than (possibly cv-qualified) void. [...]
Change the note in 14.4 [except.handle] paragraph 3 as follows:
[Note: a throw-expression operand thatwhichis an integral constant expression of integer type that evaluates to zero does not match a handler of pointer type; that is, the null pointer constant conversions (7.3.12 [conv.ptr], 7.3.13 [conv.mem]) do not apply. —end note]
Change 14.6.2 [except.terminate] bullet 1.1 as follows:
when the exception handling mechanism, after completing evaluation of theexpression to be thrownoperand of throw but before the exception is caught (14.2 [except.throw]), calls a user function that exits via an uncaught exception,
Change _N5001_.14.6.3 [except.uncaught] paragraph 1 as follows:
The function std::uncaught_exception() returns true after completing evaluation of theobject to be thrownoperand of throw until completing the initialization of the exception-declaration in the matching handler (_N4140_.18.8.4 [uncaught]).
Change _N4140_.18.8.4 [uncaught] paragraph 1 by adding the indicated words:
Returns: true after completing evaluation of the operand of a throw-expression until either completing initialization of the exception-declaration in the matching handler or entering unexpected() due to the throw; or after entering terminate() for any reason other than an explicit call to terminate(). [Note: This includes stack unwinding (14.3 [except.ctor]). —end note]
Notes from the April, 2005 meeting:
The CWG discussed this resolution both within the group and with other interested parties. Among the points that were made:
Martin Sebor pointed to a posting in which he argues that writing copy constructors is more difficult if an exception during the copy to the exception object will result in a call to std::terminate().
In response to a question about why the copy to the exception object is different from the copy from the exception object to the object in the exception-declaration, it was observed that the writer of the handler can avoid the second copy (by using a reference declaration), but the first copy is unavoidable.
John Spicer observed that not exiting via exception should be a design constraint for copy constructors in exception objects, regardless of whether std::terminate() is called or not.
Adopting the position that uncaught_exception() returns false during the copy to the exception object would reduce the differences between the case where that copy is elided and the case where it is performed.
Jason Merrill observed that making uncaught_exception() return false during the copy to the exception object would simplify the code generated by g++; as it currently stands, the compiler must generate code to catch exceptions during that copy so std::terminate() can be called.
Bjarne Stroustrup worried that allowing the copy constructor to throw an exception during the copy to the exception object could result in a serious and specific exception being silently transformed into a more trivial and generic one (although the CWG later noted that this risk already exists if something in the expression being thrown throws an exception before the expression completes).
The CWG felt that more input from a wider audience was necessary before a decision could be made on the appropriate resolution.
Notes from the April, 2006 meeting:
The CWG agreed with the position that std::uncaught_exception() should return false during the copy to the exception object and that std::terminate() should not be called if that constructor exits with an exception. The issue was returned to “drafting” status for rewording to reflect this position.
Additional notes (September, 2007):
Although this issue deals primarily with when std::uncaught_exception() begins to return true, the specification of when it begins to return false is also problematic. There are two parallel sections that define the meaning of std::uncaught_exception() and each has a different problem. _N5001_.14.6.3 [except.uncaught] reads,
The function std::uncaught_exception() returns true after completing evaluation of the object to be thrown until completing the initialization of the exception-declaration in the matching handler (_N4140_.18.8.4 [uncaught]).
The problem here is that whether an exception is considered caught (the underlying condition tested by the function) is here presented in terms of having initialized the exception-declaration, while in other places it is specified by having an active handler for the exception, e.g., 14.2 [except.throw] paragraph 6:
An exception is considered caught when a handler for that exception becomes active (14.4 [except.handle]).
This distinction is important because of 14.4 [except.handle] paragraph 3:
A handler is considered active when initialization is complete for the formal parameter (if any) of the catch clause. [Note: the stack will have been unwound at that point. —end note] Also, an implicit handler is considered active when std::terminate() or std::unexpected() is entered due to a throw.
Note that there is no exception-declaration to be initialized for the std::terminate() and std::unexpected() cases; nevertheless, according to _N4140_.18.8.4 [uncaught], std::uncaught_exception() is supposed to return false when one of those two functions is entered.
The specification in _N4140_.18.8.4 [uncaught] is not well phrased, however, and is open to misinterpretation. It reads,
Returns: true after completing evaluation of a throw-expression until either completing initialization of the exception-declaration in the matching handler or entering unexpected() due to the throw; or after entering terminate() for any reason other than an explicit call to terminate().
The problem here is lack of parallelism: does “after entering terminate” refer to the condition for returning true or false? This would be better phrased along the lines of
Returns: true after completing evaluation of a throw-expression until a handler for the exception becomes active (14.4 [except.handle]).
Proposed resolution (March, 2010):
Change 14.6.2 [except.terminate] bullet 1.1 as follows:
In the following situations exception handling must be abandoned for less subtle error handling techniques:
when the exception handling mechanism, after completing
evaluation of the expression to be thrownthe initialization of the exception object but before theexception is caughtactivation of a handler for the exception (14.2 [except.throw]), calls a function that exits via an uncaught exception, [Footnote: For example, if the object being thrown is of a class with a copy constructor, std::terminate() will be called if that copy constructor exits with an exception duringa throwthe initialization of the formal parameter of a catch clause. —end footnote]...
Change _N5001_.14.6.3 [except.uncaught] paragraph 1 as follows:
The function std::uncaught_exception() returns true after completingevaluation of the object to be thrownthe initialization of the exception object (14.2 [except.throw]) until completing theinitialization of the exception-declaration in the matching handleractivation of a handler for the exception (14.4 [except.handle], _N4140_.18.8.4 [uncaught])...
Change _N4140_.18.8.4 [uncaught] paragraph 1 as follows:
Returns: true aftercompleting evaluation of a throw-expressioninitializing an exception object 14.2 [except.throw] untileither completing initialization of the exception-declaration in the matching handler or entering unexpected() due to the throw; or after entering terminate() for any reason other than an explicit call to terminate()a handler for the exception (including unexpected() or terminate()) is activated (14.4 [except.handle]). [Note: This includes stack unwinding (14.3 [except.ctor]). —end note]
[Voted into the WP at the March, 2011 meeting as document N3288.]
N2800 comment UK 6There should be a list of incompatibilities between the current and previous Standards, as in ISO/IEC TR 10176 4.1.1 paragraph 9.
(See document N2733 for an initial list of this information.)
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 13“Raw” strings are still only Pittsburgh-rare strings: the reversion in phase 3 only applies to an r-char-sequence. It should apply to the entire raw string literal.
Proposed resolution (August, 2010):
Change 5.2 [lex.phases] paragraph 1 phase 1 as follows:
...(An implementation may use any internal encoding, so long as an actual extended character encountered in the source file, and the same extended character expressed in the source file as a universal-character-name (i.e., using the \uXXXX notation), are handled equivalently except where this replacement is reverted in a raw string literal.).)
Change 5.2 [lex.phases] paragraph 1 phase 3 as follows:
...[Example: see the handling of < within a #include preprocessing directive. —end example]Within the r-char-sequence of a raw string literal, any transformations performed in phases 1 and 2 (trigraphs, universal-character-names, and line splicing) are reverted.
Change 5.2 [lex.phases] paragraph 1 phase 5 as follows:
Each source character set memberand universal-character-namein a character literal or a string literal, as well as each escape sequence and universal-character-name in a character literal or a non-raw string literal, is converted to the corresponding member of the execution character set (5.13.3 [lex.ccon], 5.13.5 [lex.string]); if there is no corresponding member, it is converted to an implementation-defined member other than the null (wide) character.
Change 5.3.1 [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 0x000x1F or 0x7F0x9F, both inclusive) or to a character in the basic source character set, the program is ill-formed. [Footnote: A sequence of characters resembling a universal-character-name in an r-char-sequence (5.13.5 [lex.string]) does not form a universal-character-name. —end footnote]
Change 5.5 [lex.pptoken] paragraph 3 as follows:
If the input stream has been parsed into preprocessing tokens up to a given character:
ifIf the next character begins a sequence of characters that could be the prefix and initial double quote of a raw string literal, such as R", the next preprocessing token shall be a raw string literal;. Between the initial and final double quote characters of the raw string, any transformations performed in phases 1 and 2 (trigraphs, universal-character-names, and line splicing) are reverted; this reversion shall apply before any d-char, r-char, or delimiting parenthesis is identified. The raw string literal is defined as the shortest sequence of characters that matches the raw-string patternencoding-prefixopt R raw-string
otherwiseOtherwise, 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.
Delete footnote 24 in 5.13.5 [lex.string] paragraph 2:
Use of characters with trigraph equivalents in a d-char-sequence may produce unintended results.
Insert the following examples after 5.13.5 [lex.string] paragraph 4:
[Example: The raw string
R"a( )\ a" )a"is equivalent to "\n)\\\na\"\n". The raw string
R"(??)"is equivalent to "\?\?". The raw string
R"#( )??=" )#"is equivalent to "\n)\?\?=\"\n". —end example]
5.12 [lex.key] paragraph 2 says,
Furthermore, the alternative representations shown in Table 4 for certain operators and punctuators (5.9 [lex.digraph]) are reserved and shall not be used otherwise:
Also, 5.9 [lex.digraph] paragraph 2 says,
In all respects of the language, each alternative token behaves the same, respectively, as its primary token, except for its spelling.
It is not clear whether the following example is well-formed:
#define STR2(x) #x #define STR(x) STR2(x) int main() { return sizeof STR('\0'bitor 0) - sizeof STR('\0'bitor 0); }
In this example, bitor is not the | operator but the identifier in a user-defined-character-literal. Does this violate the restrictions of 5.12 [lex.key] and 5.9 [lex.digraph]?
Proposed resolution (March, 2011):
This issue is resolved by the resolution of issue 1239 in document N3262, since literal suffix identifiers must begin with an underscore.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 15Passing a name qualified by the global scope operator :: as a template argument can inadvertently trigger recognition of the <: digraph, causing a syntax error. This should be handled by a lexical rule similar to the special treament given to >> so that <:: would be recognized as an open angle-bracket followed by the scope-resolution operator.
Proposed resolution (August, 2010):
Insert a bullet into the list in 5.5 [lex.pptoken] paragraph 3 as follows:
If the input stream has been parsed into preprocessing tokens up to a given character:
if the next character begins a sequence of characters that could be the prefix and initial double quote of a raw string literal, such as R", the next preprocessing token shall be a raw string literal;
otherwise, if the next three characters are <:: and the subsequent character is neither : nor >, the < is treated as a preprocessor token by itself (and not as the first character of the alternative token <:);
otherwise, the next preprocessing token is the longest sequence...
[Voted into the WP at the November, 2010 meeting as paper N3146.]
N3092 comment CA 24“Combining characters should not appear as the first character of an identifier.” [Reference: ISO/IEC TR 10176:2003 (Annex A)] This is not reflected in FCD.
Restrictions on the first character of an identifier are not observed as recommended in TR 10176:2003. The inclusion of digits (outside of those in the basic character set) under identifer-nondigit is implied by FCD.
It is implied that only the “main listing” from Annex A is included for C++. That is, the list ends with the Special Characters section. This is not made explicit in FCD. Existing practice in C++03 as well as WG 14 (C, as of N1425) and WG 4 (COBOL, as of N4315) is to include a list in a normative Annex.
Specify width sensitivity as implied by C++03: \uFF21 is not the same as A. Case sensitivity is already stated in 5.11 [lex.name].
Notes from the August, 2010 meeting:
CWG expressed interest in an approach by which all characters except for those in specifically-excluded categories would be acceptable identifier characters. This suggestion will be brought up with WG14 as a liaison matter in hopes of agreeing on a uniform approach between C and C++.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment DE 3It is not sufficiently clear that std::nullptr_t is a distinct type and neither a pointer type nor a pointer-to-member type. Add a note in 5.13.8 [lex.nullptr] stating that, preferably with cross-references to the normative statements in 6.8 [basic.types].
Proposed resolution (September, 2010):
Change 5.13.8 [lex.nullptr] paragraph 1 as follows:
The pointer literal is the keyword nullptr. It is a prvalue of type std::nullptr_t. [Note: std::nullptr_t is a distinct type that is neither a pointer type nor a pointer to member type; rather, a prvalue of this type is a null pointer constant and can be converted to a null pointer value or null member pointer value. See 7.3.12 [conv.ptr] and 7.3.13 [conv.mem]. —end note]
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 17In general, the parameter type of a literal operator must be the same as the argument passed to it. That is not the case for a user-defined-character-literal, where the argument could inadvertently match a literal operator intended for use with user-defined-integer-literals:
typedef unsigned long long ULL; int operator "" X(ULL); int i = 'c'X; // operator"" X(ULL('c'))
Suggested resolution: Add the following phrase to the description in paragraph 6:
S shall contain a literal operator whose parameter type is the same as the type of ch.
Proposed resolution (August, 2010):
Change 5.13.9 [lex.ext] paragraph 6 as follows:
If L is a user-defined-character-literal, let ch be the literal without its ud-suffix.TheS shall contain a literal operator (12.6 [over.literal]) whose only parameter has the type of ch and the literal L is treated as a call of the formoperator "" X (ch)
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
A user-defined literal like 0x123DZ could be parsed either as a hexadecimal-literal of 0x123 and a ud-suffix of DZ or as a hexadecimal-literal of 0x123D and a ud-suffix of Z. There does not appear to be a rule that disambiguates the two possible parses.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 5.13.9 [lex.ext] paragraph 1 as follows:
If a token matches both user-defined-literal and another literal kind, it is treated as the latter. [Example: 123_km, 1.2LL, "Hello"s are all user-defined-literals, but 12LL is an integer-literal. —end example] The syntactic nonterminal preceding the ud-suffix in a user-defined-literal is taken to be the longest sequence of characters that could match that nonterminal. [Example: The ud-suffix in 1.0e0X is X, not e0X; in 0x1DZ, the ud-suffix is Z, not DZ. —end example]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
5.11 [lex.name] paragraph 3 says,
In addition, some identifiers are reserved for use by C++ implementations and standard libraries (_N4140_.17.6.4.3.2 [global.names]) and shall not be used otherwise; no diagnostic is required.
There is no corresponding mention in 5.13.9 [lex.ext] of the restrictions on user-defined literal suffixes in 16.4.5.3.6 [usrlit.suffix]. Furthermore, considering the likelihood of adding hexadecimal floating-point literals, whose syntax overlaps that of user-defined literals except for that restriction, it would be a good idea to require a diagnostic for a violation of that rule.
[Voted into WP at August, 2010 meeting.]
6.2 [basic.def] makes statements about declarations that do not appear to apply to static_assert-declarations. For example, paragraph 1 says,
A declaration (9.1 [dcl.pre]) 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 (March, 2010):
Change 6.2 [basic.def] paragraphs 1-2 as follows:
A declaration (9.1 [dcl.pre]) may introduce
sone or more names into a translation unit or redeclaresnames introduced by previous declarations.AIf so, the declaration specifies the interpretation and attributes of these names. A declaration may also have effects including
a static assertion (9.1 [dcl.pre]),
controlling template instantiation (13.9.3 [temp.explicit],
use of attributes (9.1 [dcl.pre], 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 (9.6 [dcl.fct.def]), it contains the extern specifier (9.2.2 [dcl.stc]) or a linkage-specification25 (9.12 [dcl.link]) and neither an initializer nor a function-body, it declares a static data member in a class definition (11.4.9 [class.static]), it is a class name declaration (11.3 [class.name]), it is an opaque-enum-declaration (9.8.1 [dcl.enum]), or it is a typedef declaration (9.2.4 [dcl.typedef]), a using-declaration (9.10 [namespace.udecl]), a static_assert-declaration (9.1 [dcl.pre]), an attribute-declaration (9.1 [dcl.pre]), an empty-declaration (9.1 [dcl.pre]), or a using-directive (9.9.4 [namespace.udir]). [Example:...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
N2800 comment UK 23The list of declarations that are not definitions given in 6.2 [basic.def] paragraph 2 does not mention several plausible candidates: parameter declarations in non-defining function declarations, non-static data members, and template parameters. It might be argued that none of these are declarations (paragraph 1 does not use the syntactic non-terminal declaration but does explicitly refer to 9.1 [dcl.pre], where that non-terminal is defined). However, the list in paragraph 2 does mention static member declarations, which also are not declarations, so the intent is not clear.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 6.2 [basic.def] paragraph 2 as follows:
A declaration is a definition unless it declares a function without specifying the function's body (9.6 [dcl.fct.def]), it contains the extern specifier (9.2.2 [dcl.stc]) or a linkage-specification25 (9.12 [dcl.link]) and neither an initializer nor a function-body, it declares a static data member in a class definition (11.4 [class.mem], 11.4.9 [class.static]), it is a class name declaration (11.3 [class.name]), it is an opaque-enum-declaration (9.8.1 [dcl.enum]), it is a template-parameter (13.2 [temp.param]), it is a parameter-declaration (9.3.4.6 [dcl.fct]) in a function declaration that is not a definition, or it is a typedef declaration (9.2.4 [dcl.typedef]), an alias-declaration (9.2.4 [dcl.typedef]), a using-declaration (9.10 [namespace.udecl]), a static_assert-declaration (9.1 [dcl.pre]), an attribute-declaration (9.1 [dcl.pre]), an empty-declaration (9.1 [dcl.pre]), or a using-directive (9.9.4 [namespace.udir]).
[Voted into the WP at the March, 2011 meeting.]
According to 6.3 [basic.def.odr] paragraph 2,
A declaration is a definition unless it declares a function without specifying the function's body (9.6 [dcl.fct.def]), it contains the extern specifier (9.2.2 [dcl.stc]) or a linkage-specification25 (9.12 [dcl.link]) and neither an initializer nor a function-body...
Because = delete and = default are not forms of function-body, this description does not cover defaulted and deleted functions, even though these declarations are elsewhere referred to as being definitions.
Proposed resolution (January, 2011):
Change the grammar in 9.6.1 [dcl.fct.def.general] paragraph 1 as follows:
[Voted into WP at August, 2010 meeting.]
I thought this case would result in undefined behavior according to 6.3 [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 (March, 2010):
Add a new bullet following 6.3 [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 6.5 [basic.lookup], shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution (12.2 [over.match]) and after matching of partial template specialization (13.10.4 [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 (7.7 [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 shall have the same language linkage; and
...
[Voted into the WP at the November, 2010 meeting as paper N3214.]
N3092 comment US 19It is not always clear when an occurrence of the word “use” is intended as an implicit reference to 6.3 [basic.def.odr] and when it is simply the ordinary English meaning. This could be addressed by:
Replacing all the non-technical appearances of the word “use” with synonyms such as “occurrence” or “appearance” or “reference to.”
Following each occurrence of the word “use” that is intended in the technical sense with a cross-reference to 6.3 [basic.def.odr].
Replacing all the technical occurrences of the word “use” with a different word or phrase, such as “odr-use,” that would unambiguously indicate the intent.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 6.3 [basic.def.odr] paragraph 2,
A variable or non-overloaded function whose name appears as a potentially-evaluated expression is used... A virtual member function is used if it is not pure.
However, that does not adequately address when a pure virtual function is used or not used. For example,
struct S { virtual void pure1() = 0; virtual void pure2() = 0; }; void f(S* p) { p->pure1(); p->S::pure2(); };
Both pure1 and pure2 satisfy the criterion that their name appears in a potentially-evaluated expression, but pure1 should not be considered “used” (which would require that it be defined); pure2 is “used” and must be defined.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 6.3 [basic.def.odr] paragraph 2 as follows:
...A variableor non-overloaded functionwhose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (7.7 [expr.const]) and the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is immediately applied... A virtual member function is odr-used if it is not pure. A non-overloaded function whose name appears as a potentially-evaluated expression or a member of a set of candidate functionsis odr-usedifit isselected by overload resolution when referred to from a potentially-evaluated expression, are odr-used, unless the function is pure and its name is not explicitly qualified. [Note:...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Issue 678 added a bullet to the list in 6.3 [basic.def.odr] paragraph 5, inadvertently removing the second bullet from the reach of the part of that paragraph that reads,
If D is a template and is defined in more than one translation unit, then the last four requirements from the list above shall apply to names from the template's enclosing scope used in the template definition (_N4868_.13.8.4 [temp.nondep]),
In fixing this error, the wording should be recast to be more robust in the face of possible further edits to the list (i.e., not just changing “four” to “five”).
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 6.3 [basic.def.odr] paragraph 5 as follows:
...If D is a template and is defined in more than one translation unit, then thelast fourpreceding requirementsfrom the list aboveshall apply both to names from the template's enclosing scope used in the template definition (_N4868_.13.8.4 [temp.nondep]), and also to dependent names at the point of instantiation (13.8.3 [temp.dep])...
[Voted into the WP at the March, 2011 meeting.]
The current wording of 6.4.2 [basic.scope.pdecl] does not specify the point of declaration for an alias-declaration (although it does do so in paragraph 3 for a template alias: “The point of declaration of a template alias immediately follows the identifier for the alias being declared”). One might assume that an alias-declaration would be the same, but it's not clear that that is the right resolution (for either declaration, but especially for the alias-declaration).
An alias-declaration is intended to be essentially a different syntactic form of a typedef declaration (9.2.4 [dcl.typedef] paragraph 2). Placing the point of declaration at the trailing semicolon instead of following the name of the alias would allow more compatibility with the capabilities of typedefs, for instance:
struct S { }; namespace N { using S = S; }
Notes from the November, 2010 meeting:
The CWG agreed that the point of declaration for both template and non-template cases should be at the semicolon.
Proposed resolution (January, 2011):
Change 6.4.2 [basic.scope.pdecl] paragraph 3 as follows:
...The point of declaration ofa templatean alias or alias template immediately followsthe identifier for the alias being declaredthe type-id to which the alias refers.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 6.4.2 [basic.scope.pdecl] paragraph 6,
for an elaborated-type-specifier of the form
class-key identifier
if the elaborated-type-specifier is used in the decl-specifier-seq or parameter-declaration-clause of a function defined in namespace scope, the identifier is declared as a class-name in the namespace that contains the declaration; otherwise, except as a friend declaration, the identifier is declared in the smallest non-class, non-function-prototype scope that contains the declaration.
This should have been, but was not, updated when enumeration scope (6.4.8 [basic.scope.enum]) was added:
enum class E { e = sizeof((struct S*)0) };
Presumably the name S belongs to the same scope as E, not the enumeration scope of E.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
6.5.4 [basic.lookup.argdep] paragraph 2 excludes dependent parameter types and return types from consideration in determining the associated classes and namespaces of a function template. Presumably this means that an example like
namespace N { template<class T> struct A { }; void f(void (*)()); } template <class T> void g(T, N::A<T>); void g(); int main() { f(g); }
is ill-formed because the second parameter of the function template g does not add namespace N to the list of associated namespaces. This was probably unintentional.
See also issue 1015.
Notes from the November, 2010 meeting:
The CWG agreed that the rules should be changed to make this example well-formed.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 6.5.4 [basic.lookup.argdep] paragraph 2 as follows:
...In addition, if the argument is the name or address of a set of overloaded functions and/or function templates, its associated classes and namespaces are the union of those associated with each of the members of the set, i.e., the classes and namespaces associated with its(non-dependent)parameter types and return type. Additionally, if the aforementioned set of overloaded functions is named with a template-id, its associated classes and namespaces are those of its type template-arguments and its template template-arguments.
This resolution also resolves issue 1015.
[Drafting note: It's not clear that we need the inserted text above, because for the example in issue 1015, the type N::S is already represented in the type of the function address, so there is no need to pull it from template arguments. For cases where template parameters are not represented in the function type, it's not clear that we want ADL to reach further.]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Currently, according to 6.5.4 [basic.lookup.argdep] paragraph 2, explicit template arguments in a function argument do not contribute to the associated namespaces in a function call, although they plausibly should in an example like the following:
namespace N {
struct S { };
void f(void (*)(S));
};
template<typename T> void g(T);
void h() {
f(g<N::S>); // Should find N::f
}
See also issue 997.
Proposed resolution (November, 2010) [SUPERSEDED]:
This issue is resolved by the resolution of issue 997.
[Voted into WP at August, 2010 meeting.]
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 6.5.7 [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 6.5.7 [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 6.5.7 [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 6.5.7 [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.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 24One of the critieria for giving a name internal linkage is “a variable that is explicitly declared const and neither explicitly declared extern nor previously declared to have external linkage.” This should presumably apply to variables declared constexpr as well.
Proposed resolution (August, 2010):
Change 6.6 [basic.link] bullet 3.2 as follows:
a variable that is explicitly declared const or constexpr and neither explicitly declared extern nor previously declared to have external linkage; or
[Voted into the WP at the November, 2010 meeting.]
N3092 comment DE 4It is odd that, in the following example, N has no linkage but g has external linkage:
namespace { namespace N // has no linkage { void g(); // has external linkage } }
Proposed resolution (August, 2010):
Change 6.6 [basic.link] paragraph 4 as follows:
An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage. All other namespaces have external linkage. A name having namespace scope that has not been given internal linkage above hasexternal linkagethe same linkage as the enclosing namespace if it is the name of
a variable
, unless it has internal linkage; ora function
, unless it has internal linkage; ora named class (Clause 11 [class]), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes (9.2.4 [dcl.typedef]); or
a named enumeration (9.8.1 [dcl.enum]), or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes (9.2.4 [dcl.typedef]); or
an enumerator belonging to an enumeration with
externallinkage; ora template
, unless it is a function template that has internal linkage (Clause 13 [temp]); or.
a namespace (9.9 [basic.namespace]), unless it is declared within an unnamed namespace.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
6.7.2 [intro.object] paragraph 6 says,
Two distinct objects that are neither bit-fields nor base class subobjects of zero size shall have distinct addresses.
This formulation leaves open the possibility that two base class subobjects of the same type could have the same address (because one or both might be zero-length base class subobjects).
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 6.7.2 [intro.object] paragraph 6 as follows:
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 areneithernot bit-fieldsnor base class subobjects of zero sizeshall have distinct addresses, if both have the same type or if not both are base class subobjects of zero size...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The current wording of the Standard does not recognize the fact that the alignment of a complete object of a given type may be different from its alignment as a subobject. This arises in particular with virtual base classes. For example,
struct B { long double d; }; struct D: virtual B { char c; };
When D is a complete object, it will have a subobject of type B, which must be aligned appropriately for a long double. On the other hand, if D appears as a subobject of another object, the B subobject might be part of a different subobject, reducing the alignment requirement on the D subobject.
The Standard should make clear that it is the complete-object alignment that is being described, in parallel with the distinction between the size of a complete object and a subobject of the same type.
[Voted into the WP at the November, 2010 meeting in document N3190.]
N3092 comment US 25The C and C++ approaches to alignment are incompatible. See document PL22.16 10-0083 = WG21 N3093 for details.
Notes from the August, 2010 meeting:
CWG agreed that the alignment specifier should be a keyword instead of an attributes.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Now that alignment can be applied directly to class types, the current wording of the note at the end of 6.7.3 [basic.align] paragraph 3 is no longer correct:
[Note: every over-aligned type is or contains a class type with a non-static data member to which an extended alignment has been applied. —end note]
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 6.7.3 [basic.align] paragraph 3 as follows:
[Note: every over-aligned type isor containsa class typewith a non-static data member to which an extended alignment has been appliedto which extended alignment applies (possibly through a non-static data member). —end note]
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 18The example in 6.7.4 [basic.life] paragraph 9 reads,
struct B {
B();
~B();
};
const B b;
void h() {
b.~B();
new (&b) const B; // undefined behavior
}
Assuming that the placement new is intended to use the operator defined in the Standard library, the new-expression is ill-formed, because there is no implicit conversion from “pointer to const B” to void*.
Proposed resolution (August, 2010):
Change the example in 6.7.4 [basic.life] paragraph 9 as follows:
...
new (const_cast<B *>(&b)) const B; // undefined behavior
...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
An implicit declaration of a copy assignment operator is deprecated if the class has a user-declared copy constructor or a user-declared destructor. However, the example in 6.7.7 [class.temporary] relies on such an implicit declaration; an explicit declaration for the copy assignment operator for class X should be provided:
class X {
public:
X(int);
X(const X&);
~X();
};
class Y {
public:
Y(int);
Y(Y&&);
~Y();
};
X f(X);
Y g(Y);
void h() {
X a(1);
X b = f(X(2));
Y c = g(Y(3));
a = f(a); // relies on implicitly-declared X::operator=(const X&)
}
[Voted into WP at August, 2010 meeting.]
Is the following example well-formed?
struct S { static char a[5]; }; char S::a[]; // Unspecified bound in definition
6.6 [basic.link] paragraph 10 certainly makes allowance for declarations to differ in the presence or absence of a major array bound. However, 6.2 [basic.def] paragraph 5 says that
A program is ill-formed if the definition of any object gives the object an incomplete type (6.8 [basic.types]).
6.8 [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 9.3.4.5 [dcl.array] paragraph 1 as follows:
...IfExcept 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 9.3.4.5 [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 arrayscanmay be omittedonly 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 (9.3.4.6 [dcl.fct]).The first constant-expression canAn array bound may also be omitted when the declarator is followed by an initializer (9.5 [dcl.init]). In this case the bound is calculated from the number of initial elements (say, N) supplied (9.5.2 [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 (March, 2010):
Change 9.3.4.5 [dcl.array] paragraph 1 as follows:
...IfExcept 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 9.3.4.5 [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
canmay be omittedonly 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 some cases in the declaration of a function parameter (9.3.4.6 [dcl.fct]).The first constant-expression canAn array bound may also be omitted when the declarator is followed by an initializer (9.5 [dcl.init]). In this case the bound is calculated from the number of initial elements (say, N) supplied (9.5.2 [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]
[Voted into the WP at the March, 2011 meeting.]
6.8 [basic.types] paragraph 10 requires that a class have at least one constexpr constructor other than the copy constructor in order to be considered a literal type. However, a constexpr constructor template might be instantiated in such a way that the constexpr specifier is ignored (9.2.6 [dcl.constexpr] paragraph 5) . It is therefore not known whether a class with a constexpr constructor template is a literal type or not until the constructor template is specialized, which could mean that an example like
struct IntValue { template<typename T> constexpr IntValue(T t) : val(t) { } constexpr intmax_t get_value() { return val; } private: intmax_t val; };
is ill-formed, because it is an error to declare a member function (like get_value()) of a non-literal class to be constexpr (9.2.6 [dcl.constexpr] paragraph 6).
6.8 [basic.types] paragraph 10 should be revised so that either a constexpr constructor or constexpr constructor template allows a class to be a literal type.
Proposed resolution (November, 2010):
Change 6.8 [basic.types] paragraph 10 as follows:
A type is a literal type if it is:
a scalar type; or
a class type (Clause 11 [class])
withthat
a trivial copy constructor,
no non-trivial move constructor,has a trivial destructor,
a trivial default constructor oris an aggregate type (9.5.2 [dcl.init.aggr]) or has at least one constexpr constructorother than theor constructor template that is not a copy or move constructor, andhas all non-static data members and base classes of literal types; or
an array of literal type.
This resolution also resolves issues 1071 and 1198.
[Voted into the WP at the March, 2011 meeting.]
According to 6.8 [basic.types] paragraph 10, one of the requirements for a literal class type is
a trivial default constructor or at least one constexpr constructor other than the copy or move constructor
This rule has unfortunate consequences. For example, in
struct A { int x; }; struct B: A { int y; };
B is a literal type, even though it is impossible to initialize a constant of that type. Conversely, in
struct C { int a, b; constexpr C(int x, int y): a(x), b(y) { } }; struct D { int x; C c; };
D is not a literal type, even though it could be initialized as an aggregate.
It would be an improvement to replace the requirement for a trivial default constructor with a requirement that the class be an aggregate.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 981.
[Voted into the WP at the March, 2011 meeting.]
The current draft uses the term “built-in type” several times, but it is not defined anywhere. The Index appears to make it synonymous with “fundamental type,” but the implication of 7.3 [conv] paragraph 1 is that compound types like pointers should also be considered as “built-in.”
Proposed resolution (January, 2011):
Change 6.7.2 [intro.object] paragraph 7 as follows:
[Note: C++ provides a variety ofbuilt-infundamental types and several ways of composing new types from existing types (6.8 [basic.types]). —end note]
Change 5.5 [lex.pptoken] as follows:
[Example: The program fragment x+++++y is parsed as x ++ ++ + y, which, if x and yare of built-inhave integral types, violates a constraint on increment operators, even though the parse x ++ + ++ y might yield a correct expression. —end example]
Change 17.3.5.2 [numeric.limits.members] paragraph 58 as follows:
True if the set of values representable by the type is finite.220 [Note: Allbuilt-infundamental types (6.8.2 [basic.fundamental]) are bounded. This member would be false for arbitrary precision types. —end note]
Change 24.3.1 [iterator.requirements.general] paragraph 1 as follows:
...All input iterators i support the expression *i, resulting in a value of someclass, enumeration, or built-inobject type T, called the value type of the iterator...
Change 24.3.5.3 [input.iterators] paragraph 1 as follows:
A class or abuilt-inpointer type X satisfies the requirements of an input iterator for the value type T if X satisfies the Iterator (24.3.5.2 [iterator.iterators]) and EqualityComparable (Table 33) requirements and the expressions in Table 107 are valid and have the indicated semantics.
Change 24.3.5.4 [output.iterators] paragraph 1 as follows:
A class or abuilt-inpointer type X satisfies the requirements of an output iteratorif Xif X satisfies the Iterator requirements (24.3.5.2 [iterator.iterators]) and the expressions in Table 108 are valid and have the indicated semantics.
Change 24.3.5.5 [forward.iterators] paragraph 1 as follows:
A class or abuilt-inpointer type X satisfies the requirements of a forward iterator if...
Change 24.3.5.6 [bidirectional.iterators] paragraph 1 as follows:
A class or abuilt-inpointer type X satisfies the requirements of a bidirectional iterator if, in addition to satisfying the requirements for forward iterators, the following expressions are valid as shown in Table 110.
Change 24.3.5.7 [random.access.iterators] paragraph 1 as follows:
A class or abuilt-inpointer type X satisfies the requirements of a random access iterator if, in addition to satisfying the requirements for bidirectional iterators, the following expressions are valid as shown in Table 111.
Change C.7.3 [diff.basic] section 3.1 as follows:
Rationale: This avoids having different initialization rules forbuilt-infundamental types and user-defined types.
Change C.7.7 [diff.class] section 9.1 as follows:
...This new name space definition provides important notational conveniences to C++ programmers and helps making the use of the user-defined types as similar as possible to the use ofbuilt-infundamental types...
Delete the index entry, “built-in type; see fundamental type
.”(Note: This resolution assumes that the resolution for issue 572 has been applied, removing “built-in type” from 7.3 [conv] paragraph 1.)
[Voted into the WP at the March, 2011 meeting.]
According to 6.8 [basic.types] paragraph 10, a literal class type has
a trivial copy constructor,
no non-trivial move constructor,
...
Is this intended to mean that
struct A { A(const A&) = default; A(A&); };
is a literal class because it does have a trivial copy constructor even though it also has a non-trivial one? That seems inconsistent with the prohibition of non-trivial move constructors.
My preference would be to resolve this inconsistency by dropping the restriction on non-trivial move constructors. It seems to me that having a trivial copy or move constructor is sufficient, we don't need to prohibit additional non-trivial ones. Actually, it's not clear to me that we need the first condition either; a literal type could be used for singleton variables even if it can't be copied.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 981.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The current treatment of constexpr constructors and constant expressions does not deal with the initializers for non-static data members, which should also be required to be constant expressions.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 6.9.3.2 [basic.start.static] paragraph 2 as follows:
if an object with static or thread storage duration is initialized by a constructor call, if the constructor is a constexpr constructor, if all constructor arguments are constant expressions (including conversions), and if, after function invocation substitution (9.2.6 [dcl.constexpr]), every constructor call and full-expression in the mem-initializers and in the brace-or-equal-initializers for non-static data members is a constant expression
Change 6.8 [basic.types] paragraph 10 as follows (wording assumes the proposed resolution of issue 981)
A type is a literal type if it is:
a scalar type; or
a class type (Clause 11 [class]) that has all of the following properties:
it has a trivial destructor,
every constructor call and full-expression in the brace-or-equal-initializers for non-static data members (if any) is a constant expression (7.7 [expr.const]),
it is an aggregate type (9.5.2 [dcl.init.aggr]) or has at least one constexpr constructor or constructor template that is not a copy or move constructor, and
it has all non-static data members and base classes of literal types; or
an array of literal type.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 6.8.2 [basic.fundamental] paragraph 9,
Any expression can be explicitly converted to type cv void (7.6.3 [expr.cast]). An expression of type void shall be used only as an expression statement (8.3 [stmt.expr]), as an operand of a comma expression (7.6.20 [expr.comma]), as a second or third operand of ?: (7.6.16 [expr.cond]), as the operand of typeid, or as the expression in a return statement (8.7.4 [stmt.return]) for a function with the return type void.
First, this is self-contradictory: if “any expression” can be converted to void, why is such a conversion not listed among the acceptable uses of an expression of type void?
Second, presumably an expression of type void can be used as an operand of decltype, but this use is not listed.
Finally, there are several places in the Standard that speak of expressions having a cv-qualified void type (7.6.16 [expr.cond] paragraph 2, 8.7.4 [stmt.return] paragraph 3) . However, an expression of type void is a non-class prvalue, and there are no cv-qualified non-class prvalues (7.2.1 [basic.lval] paragraph 4).
Proposed resolution (February, 2011) [SUPERSEDED]:
Change 6.8.2 [basic.fundamental] paragraph 9 as follows:
...Any expression can be explicitly converted to type cv void (7.6.3 [expr.cast]). An expression of type void shall be used only as an expression statement (8.3 [stmt.expr]), as an operand of a comma expression (7.6.20 [expr.comma]), as a second or third operand of ?: (7.6.16 [expr.cond]), as the operand of typeid or decltype,oras the expression in a return statement (8.7.4 [stmt.return]) for a function with the return type void, or as the operand of an explicit conversion to type cv void.
Change 7.6.16 [expr.cond] paragraph 2 as follows:
If either the second or the third operand has type(possibly cv-qualified)void, then...
Change 8.7.4 [stmt.return] paragraph 3 as follows:
A return statement with an expression of type“cvvoid”can be used only in functions with a return type of cv void; the expression is evaluated just before the function returns to its caller.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 6There are core issues surrounding the undefined behavior of dereferencing a null pointer. It appears the intent is that dereferencing is well defined, but using the result of the dereference will yield undefined behavior. This topic is too confused to be the reference example of undefined behavior, or should be stated more precisely if it is to be retained.
(See also issue 232.)
Proposed resolution (September, 2010):
Change 6.9.1 [intro.execution] paragraph 4 as follows:
Certain other operations are described in this International Standard as undefined (for example, the effect ofdereferencing the null pointerattempting to modify a const object). [Note:...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 7The current wording of 6.9.1 [intro.execution] paragraph 6 could be read as saying that any signal would leave the program in an unspecified state after completing.
Proposed resolution (August, 2010):
Change 6.9.1 [intro.execution] paragraph 6 as follows:
When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects which are neither
of type volatile std::sig_atomic_t nor
lock-free atomic objects (32.5.5 [atomics.lockfree])
are unspecified during the execution of the signal handler, and the value of any object not in either of these two categories that is modified by the handler becomes undefined.
[Voted into the WP at the November, 2010 meeting as part of paper N3196.]
N3092 comment CA 12The current wording of the standard suggests that release sequences are maximal with respect to sequence inclusion, i.e. that if there are two release operations in the modification order,
mod mod rel1----->rel2----->w
then [rel1;rel2;w] is the only release sequence, as the other candidate [rel2;w] is included in it. This interpretation precludes synchronizing with releases which have other releases sequenced-before them. We believe that the intention is actually to define the maximal release sequence from a particular release operation, which would admit both [rel1;rel2;w] and [rel2;w].
Proposed resolution (August, 2010):
Change 6.9.2 [intro.multithread] paragraph 6 as follows:
A release sequence from a release operation A on an atomic object M is a maximal contiguous sub-sequence of side effects in the modification order of M, where the first operation is
a releaseA, and every subsequent operation
is performed by the same thread that performed the release, or
is an atomic read-modify-write operation.
[Voted into the WP at the November, 2010 meeting as part of paper N3196.]
N3092 comment CA 15The current draft has release/acquire synchronize-with edges only between a release on one thread and an acquire on a different thread, whereas the definition of dependency-ordered-before permits the release and consume to be on the same thread; it seems odd to permit the latter. (At the moment function arguments can't race or sync with each other, but they can be dependency ordered before each other.)
We don't currently have an example in which this makes a real difference, but for symmetry could suggest changing the definition of dependency-ordered-before in 6.9.2 [intro.multithread].
Proposed resolution (August, 2010):
Change 6.9.2 [intro.multithread] paragraph 9 as follows:
An evaluation A is dependency-ordered before an evaluation B if
A performs a release operation on an atomic object M, and, on another thread, B performs a consume operation on M and reads a value written by any side effect in the release sequence headed by A, or
for some evaluation X, A is dependency-ordered before X and X carries a dependency to B.
[Note:...
[Voted into the WP at the March, 2011 meeting.]
The note in 6.9.3.2 [basic.start.static] paragraph 3 contains the following example:
inline double fd() { return 1.0; } extern double d1; double d2 = d1; // unspecified: // may be statically initialized to 0.0 or // dynamically initialized to 1.0 double d1 = fd(); // may be initialized statically to 1.0
The comment for d2 overlooks the third possibility: if both d1 and d2 are dynamically initialized, d2 will be initialized to 0.
Proposed resolution (November, 2010):
Change the comments in the example in 6.9.3.2 [basic.start.static] paragraph 3 as follows:
inline double fd() { return 1.0; } extern double d1; double d2 = d1; // unspecified: // may be statically initialized to 0.0 or // dynamically initializedto1.0to 0.0 if d1 is dynamically initialized, or 1.0 otherwise double d1 = fd(); // may be initialized statically or dynamically to 1.0
(The note should also be in running text following the bulleted list instead of appearing as a bulleted item, as well.)
[Voted into the WP at the November, 2010 meeting.]
N3092 comment JP 1One of the bullets in the note in Clause 7 [expr] paragraph 6 says that an expression is an xvalue if it is:
a class member access expression designating a non-static data member in which the object expression is an xvalue
This is incorrect if the type of the non-static data member is a reference type (cf 7.6.1.5 [expr.ref] paragraph 4.)
Proposed resolution (September, 2010):
Change Clause 7 [expr] bullet 6.3 as follows:
a class member access expression designating a non-static data member of non-reference type in which the object expression is an xvalue, or
[Voted into the WP at the November, 2010 meeting.]
7.2.1 [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 (9.5.4 [dcl.init.ref]).
Proposed resolution (October, 2009):
Change 7.2.1 [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 9.5.4 [dcl.init.ref]),the lvalueit is converted to an rvalue; see 7.3.2 [conv.lval], 7.3.3 [conv.array], and 7.3.4 [conv.func].
Notes from the March, 2010 meeting:
This resolution needs to be reconsidered in light of the new expression taxonomy.
Proposed resolution (September, 2010):
Change 7.2.1 [basic.lval] paragraph 2 as follows:
Whenever a glvalue appears in a context where a prvalue is expected, the glvalue is converted to a prvalue; see 7.3.2 [conv.lval], 7.3.3 [conv.array], and 7.3.4 [conv.func]. [Note: An attempt to bind an rvalue reference to an lvalue is not such a context; see 9.5.4 [dcl.init.ref]. —end note]
[Voted into the WP at the March, 2011 meeting.]
7.3 [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 7.3 [conv] paragraph 1 as follows:
Standard conversions are implicit conversionsdefined for built-in typeswith built-in meaning...
[Voted into the WP at the November, 2010 meeting.]
7.5.6 [expr.prim.lambda] bullet 4.1 says,
if the compound-statement if [sic] of the form
{ return attribute-specifieropt expression ; }
the type of the returned expression...
The problem (besides the typo “if”) is that the attribute-specifier for a return statement precedes, rather than following, the keyword (Clause 8 [stmt] paragraph 1).
Proposed resolution (September, 2010):
Change 7.5.6 [expr.prim.lambda] bullet 4.1 as follows:
if the compound-statement is of the form
the type of the returned expression after lvalue-to-rvalue conversion (7.3.2 [conv.lval]), array-to-pointer conversion (7.3.3 [conv.array]), and function-to-pointer conversion (7.3.4 [conv.func]);
[Voted into the WP at the November, 2010 meeting.]
N3092 comment CH 4The adoption of paper N3067 at the March, 2010 meeting moved the position of the optional attribute-specifier in a function declarator from immediately following the parameter-declaration-clause to after the exception-specification. However, the grammar in 7.5.6 [expr.prim.lambda] paragraph 1 and the verbal description in paragraph 5 still have the attribute-specifier in a lambda-declarator at its old position. These should be updated to reflect the new function declarator syntax.
Proposed resolution (August, 2010):
Change the grammar in 7.5.6 [expr.prim.lambda] paragraph 1 as follows:
Change 7.5.6 [expr.prim.lambda] paragraph 5 as follows:
...Any attribute-specifiers appearing immediately after the lambda-expression's parameter-declaration-clause appertainAn attribute-specifier in a lambda-declarator appertains to the type of the corresponding function call operator...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
N2800 comment UK 537.6.1.2 [expr.sub] paragraph 2 deals with one particular aspect of the overloaded operator[], which seems out of place. Either 7.6.1.2 [expr.sub] should be augmented to discuss the overloaded operator[] in general or the information in paragraph 2 should be moved into 12.4.5 [over.sub].
[Voted into the WP at the November, 2010 meeting.]
7.6.1.3 [expr.call] paragraph 7 should mention a non-trivial move constructor as well, for consistency with “trivially copyable.”
Proposed resolution (September, 2010):
Change 7.6.1.3 [expr.call] paragraph 7 as follows:
...Passing a potentially-evaluated argument of class type ( Clause 11 [class])withhaving a non-trivial copy constructor, a non-trivial move constructor, or a non-trivial destructor, with no corresponding parameter is conditionally-supported,with implementation-defined semantics. If the argument...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 32According to 7.6.1.5 [expr.ref] paragraph 5,
If E2 is a non-static data member or a non-static member function, the program is ill-formed if the class of which E2 is directly a member is an ambiguous base (6.5.2 [class.member.lookup]) of the naming class (11.8.3 [class.access.base]) of E2.
This does not cover the following case:
struct A { int i; }; struct B: A { }; struct C: A, B { }; void f(C* p) { p->A::i; // Should be ambiguous }
Notes (August, 2010):
The example in the FCD National Body comment is incorrect: it is missing the A:: in the next-to-last line.
The ambiguity actually is covered in the Standard but in an unexpected location: 11.8.3 [class.access.base] paragraph 6:
If a class member access operator, including an implicit “this->,” is used to access a non-static data member or non-static member function, the reference is ill-formed if the left operand (considered as a pointer in the “.” operator case) cannot be implicitly converted to a pointer to the naming class of the right operand.
An explanatory note, including a cross-reference to 11.8.3 [class.access.base], should be added to 7.6.1.5 [expr.ref] paragraph 6.
Proposed resolution (September, 2010):
Change 7.6.1.5 [expr.ref] paragraph 5 as follows:
If E2 is a non-static data member or a non-static member function, the program is ill-formed if the class of which E2 is directly a member is an ambiguous base (6.5.2 [class.member.lookup]) of the naming class (11.8.3 [class.access.base]) of E2. [Note: The program is also ill-formed if the naming class is an ambiguous base of the class type of the object expression; see 11.8.3 [class.access.base]. —end note]
[Voted into the WP at the November, 2010 meeting.]
According to 7.6.1.9 [expr.static.cast] paragraph 7, static_cast can be used to perform the inverse of any standard conversion sequence except the lvalue-to-rvalue, array-to-pointer, function-to-pointer, and boolean conversions. The null pointer and null pointer-to-member conversions should also be listed — it should not be permitted to cast a pointer or pointer-to-member to either integral type or std::nullptr_t.
Proposed resolution (October, 2010):
Change 7.3.12 [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 type. Such a conversion is called a null pointer conversion. Two null pointer values...
Change 7.3.13 [conv.mem] paragraph 1 as follows:
A null pointer constant (7.3.12 [conv.ptr]) can be converted to a pointer to member type; the result is the null member pointer value of that type and is distinguishable from any pointer to member not created from a null pointer constant. Such a conversion is called a null member pointer conversion. Two null member pointer values...
Change 7.6.1.9 [expr.static.cast] paragraph 7 as follows:
The inverse of any standard conversion sequence (7.3 [conv]), other than thenot containing an lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), function-to-pointer (7.3.4 [conv.func]),andnull pointer (7.3.12 [conv.ptr]), null member pointer (7.3.13 [conv.mem]), or boolean (7.3.14 [conv.fctptr]) conversions, can be performed explicitly using static_cast. A program is ill-formed...
[Voted into the WP at the March, 2011 meeting.]
According to 9.8.1 [dcl.enum] paragraph 10,
An expression of arithmetic or enumeration type can be converted to an enumeration type explicitly.
However, 7.6.1.9 [expr.static.cast] paragraph 10 says only,
A value of integral or enumeration type can be explicitly converted to an enumeration type.
This omits floating-point values. Presumably unscoped enumeration types are covered by paragraph 7,
The inverse of any standard conversion sequence ( 7.3 [conv]), other than the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to- pointer (7.3.3 [conv.array]), function-to-pointer (7.3.4 [conv.func]), and boolean (7.3.14 [conv.fctptr]) conversions, can be performed explicitly using static_cast.
because 7.3.11 [conv.fpint] paragraph 2 allows an unscoped enumeration value to be implicitly converted to a floating point type. (Although that also covers the integral types, so it's not clear why they would be mentioned specifically in 7.6.1.9 [expr.static.cast] paragraph 10.) However, this should presumably say “arithmetic” instead of “integral” to match the statement in 9.8.1 [dcl.enum] paragraph 10.
Proposed resolution (November, 2010):
Change 7.6.1.9 [expr.static.cast] paragraph 10 as follows:
A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (9.8.1 [dcl.enum]). Otherwise, the resultingenumerationvalue is unspecified (and might not be in that range). A value of floating-point type can also be converted to an enumeration type. The resulting value is the same as converting the original value to the underlying type of the enumeration (7.3.11 [conv.fpint]), and subsequently to the enumeration type.
Add the following footnote to the end of 9.8.1 [dcl.enum] paragraph 7:
...If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0. [Footnote: This set of values is used to define promotion and conversion semantics for the enumeration type; it does not exclude an expression of enumeration type from having a value that falls outside this range. —end footnote]
Delete 9.8.1 [dcl.enum] paragraph 10:
An expression of arithmetic or enumeration type can be converted to an enumeration type explicitly. The value is unchanged if it is in the range of enumeration values of the enumeration type; otherwise the resulting enumeration value is unspecified.
[Voted into the WP at the March, 2011 meeting.]
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” (6.8 [basic.types] paragraph 9). The wording should be amended to allow conversion to and from void* types.
Proposed resolution (November, 2010):
Change _N4885_6.7.5.5.4 [basic.stc.dynamic.safety] paragraphs 1-2 as follows:
A traceable pointer object is
an object of
pointer-to-objectan object pointer type (6.8.4 [basic.compound]), oran 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
thatthose of somepointer-to-objectobject pointer type.A pointer value is a safely-derived pointer to a dynamic object only if it has
pointer-to-objectan object pointer type and it is...
Change 6.8.4 [basic.compound] paragraphs 3-4 as follows:
The type of a pointer to void or a pointer to an object type is called an object pointer type. [Note: A pointer to void does not have a pointer-to-object type, however, because void is not an object type. —end note] 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 (6.8.5 [basic.type.qualifier]) or cv-unqualified type void* (pointer to void),A pointer to cv-qualified (6.8.5 [basic.type.qualifier]) 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.A cv-qualified or cv-unqualified (6.8.5 [basic.type.qualifier])An object of type cv void* shall have the same representation and alignment requirements asa cv-qualified or cv-unqualifiedcv char*.
Change 7.3.12 [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 ofpointer to object or pointer to functionobject pointer or function pointer type...
Change 7.3.13 [conv.mem] paragraph 2 footnote 58 as follows:
...Note that a pointer to member is nota pointer to object or a pointer to functionan object pointer or a function pointer and...
Change 7.6.1.10 [expr.reinterpret.cast] paragraphs 6-8 as follows:
A
pointer to afunction pointer can be explicitly converted to apointer to afunction pointer of a different type...
A pointer to anAn object pointer can be explicitly converted toa pointer to a different object typean object pointer of a different type...Converting a
pointer to a function into a pointer to an objectfunction pointer to an object pointer type or vice versa is conditionally-supported...
Change the note in 9.3.4.6 [dcl.fct] paragraph 6 as follows:
[Note: function types are checked during the assignments and initializations ofpointer-to-functions, reference-to-functions, and pointer-to-member-functionspointers to functions, references to functions, and pointers to member functions. —end note]
In the “Index of Implementation-defined Behavior,” change the following item as indicated:
convertingpointer to function into pointer to objectfunction pointer to object pointer and vice versa
[Drafting note: 7.6.2.9 [expr.delete] paragraph 1 was not changed, so the operand of delete still cannot be a void*. 12.5 [over.built] paragraph 14 was not changed, so void* pointers still do not get overloads for operator-. 13.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.]
(See also issue 1120.)
[Voted into the WP at the March, 2011 meeting.]
N3092 comment GB 22It is not permitted to use reinterpret_cast to convert between pointers to object type and pointers to void.
See also issue 573.
Proposed resolution (August, 2010):
Change 7.6.1.10 [expr.reinterpret.cast] paragraph 7 as follows:
A pointer to an objectAn object pointer can be explicitly converted toa pointer to a different object typean object pointer of a different type.70 When a prvalue v of type “pointer to T1” is converted to the type “pointer to cv T2”, the result is static_cast<cv T2*>(static_cast<cv void*>(v)) if both T1 and T2 are standard-layout types (6.8 [basic.types]) and the alignment requirements of T2 are no stricter than those of T1, or if either type is void. Converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types 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 any other such pointer conversion is unspecified.
(Note: this resolution depends on that of issue 573.)
[Voted into the WP at the November, 2010 meeting.]
7.6.1.11 [expr.const.cast] paragraph 1 refers to casting to an rvalue reference to function type. This was an accidental edit in the application of the value categories and should be removed.
Proposed resolution (October, 2010):
Change 7.6.1.11 [expr.const.cast] paragraph 1 as follows:
If T is an lvalue reference to object type[Drafting note: with this change to the wording, an attempt to cast a function lvalue to either an lvalue or rvalue reference will be ill-formed because the function-to-pointer conversion will be applied to the operand and the resulting operand base type will be incompatible with the target base type.]or an rvalue reference to function type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue and the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the expression v...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 33The resolution of issue 983 restored an error, inadvertently removed by the resolution of issue 39, for the formation of a member of an ambiguous base class. For example:
struct B { int i; }; struct I1: B { }; struct I2: B { }; struct D: I1, I2 { }; int B::* pm = &D::i; // Originally and again ambiguous
This error is not necessary, because the result of taking the address of a member of an ambiguous base class is a pointer to a member of that class; an actual ambiguity would occur only if that pointer to a base class member is converted to a pointer to a member of the derived class. (See issue 203, which suggests changing the result of taking the address of a member to reflect the naming class of the member instead of the class of which it is directly a member; if that change were made, the ambiguity error would be needed.)
The resolution of issue 983 should be reverted.
Proposed resolution (August, 2010):
Change 7.6.2.2 [expr.unary.op] paragraph 3 as follows:
...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 a prvalue designating C::m; the program is ill formed if C is an ambiguous base (10.2) of the class designated by the nested-name-specifier of the qualified-id. Otherwise...
Change 6.5.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 (7.3.13 [conv.mem], 7.6.1.5 [expr.ref],7.6.2.2 [expr.unary.op],11.8.3 [class.access.base]). —end note]...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 24The return type of the sizeof operator is defined as being of type std::size_t, defined in library 17.2 [support.types]. This, in turn, says that size_t is defined in the C standard, which in turn says that size_t is defined as the type of the result of the sizeof operator!
The C definition of sizeof returns an implementation-defined unsigned integer type, recommended not to have “an integer conversion rank greater than signed long int, unless the implementation supports objects large enough to make this necessary.”
Proposed resolution (September, 2010):
Add the following three paragraphs after 17.2 [support.types] paragraph 4:
The type ptrdiff_t is an implementation-defined signed integer type that can hold the difference of two subscripts in an array object, as described in 7.6.6 [expr.add].
The type size_t is an implementation-defined unsigned integer type that is large enough to contain the size in bytes of any object.
[Note: It is recommended that implementations choose types for ptrdiff_t and size_t whose integer conversion ranks (7.3.15 [conv.bool]) are no greater than that of signed long int unless a larger size is necessary to contain all the possible values. —end note]
[Voted into the WP at the November, 2010 meeting as paper N3204.]
N3092 comment FI 17Destructors should by default be noexcept. Such a rule should be obeyed even for cases where a destructor is defaulted. Then a throwing destructor would need to be declared noexcept(false), and the resulting code breakage is acceptable.
Notes from the August, 2010 meeting:
CWG agreed with the suggested direction.
(Duplicate of issue 1147.)
[Voted into the WP at the November, 2010 meeting.]
Recent changes have added the requirement (7.6.2.8 [expr.new] paragraph 7) ,
If the value of that expression is such that the size of the allocated object would exceed the implementation-defined limit, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (14.4 [except.handle]) of type std::bad_array_new_length (17.6.4.2 [new.badlength]).
Given this checking, is there any current reason for the statement in the preceding paragraph,
If the value of the expression is negative, the behavior is undefined.
Presumably for most negative expressions on most platforms, a negative value would result in a too-large request anyway, and even if not the check could easily be expanded to look explicitly for a negative value in addition to a too-large request.
Proposed resolution (September, 2010):
Change 7.6.2.8 [expr.new] paragraphs 6 and 7 as follows:
...
If the value of the expression is negative, the behavior is undefined.[Example: given the definition int n = 42, new float[n][5] is well-formed (because n is the expression of a noptr-new-declarator), but new float[5][n] is ill-formed (because n is not a constant expression).If n is negative, the effect of new float[n][5] is undefined.—end example]When the value of the expression in a noptr-new-declarator is zero, the allocation function is called to allocate an array with no elements. If the value of that expression is less than zero or such that the size of the allocated object would exceed the implementation-defined limit, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (14.4 [except.handle]) of type std::bad_array_new_length (17.6.4.2 [new.badlength]).
Change 17.6.4.2 [new.badlength] paragraph 1 as follows:
The class bad_array_new_length defines the type of objects thrown as exceptions by the implementation to report an attempt to allocate an array of size less than zero or greater than an implementation-defined limit (7.6.2.8 [expr.new]).
[Voted into the WP at the November, 2010 meeting.]
According to 7.6.2.9 [expr.delete] paragraph 2,
...in the first alternative (delete object), the value of the operand of delete shall be a pointer to a non-array object or a pointer to a subobject (6.7.2 [intro.object]) representing a base class of such an object (11.7 [class.derived]). If not, the behavior is undefined. In the second alternative (delete array), the value of the operand of delete shall be the pointer value which resulted from a previous array new-expression.79 If not, the behavior is undefined.
The second part of this specification makes it clear that an array object being deleted must have been allocated via new. However, the first part, for the non-array object, completely omits this vital requirement, requiring only that it not be an array.
The corresponding requirement for an argument to a deallocation function is found in 6.7.6.5.3 [basic.stc.dynamic.deallocation] paragraph 3:
...the value supplied to operator delete(void*) in the standard library shall be one of the values returned by a previous invocation of either operator new(std::size_t) or operator new(std::size_t, const std::nothrow_t&) in the standard library, and the value supplied to operator delete[](void*) in the standard library shall be one of the values returned by a previous invocation of either operator new[](std::size_t) or operator new[](std::size_t, const std::nothrow_t&) in the standard library.
This correctly states the required provenance of the pointer, but it does so using “shall,” which is inappropriate for a runtime requirement. This wording should be recast in terms of undefined behavior if the requirement is not met.
Proposed resolution (October, 2010):
Change 7.6.2.9 [expr.delete] paragraph 2 as follows:
If the operand has a class type, the operand is converted to a pointer type by calling the above-mentioned conversion function, and the converted operand is used in place of the original operand for the remainder of this section.In either alternative, the value of the operand of delete may be a null pointer value. If it is not a null pointer value, inIn the first alternative (delete object), the value of the operand of deleteshallmay be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject (6.7.2 [intro.object]) representing a base class of such an object ( 11.7 [class.derived]). If not, the behavior is undefined. In the second alternative (delete array), the value of the operand of deleteshallmay bethea null pointer value or a pointer valuewhichthat resulted from a previous array new-expression.79 If not, the behavior is undefined. [Note: this means that the syntax of the delete-expression must match the type of the object allocated bynewnew, not the syntax of the new-expression. —end note]...
Change 6.7.6.5.3 [basic.stc.dynamic.deallocation] paragraph 3 as follows:
...Otherwise, the behavior is undefined if the value supplied to operator delete(void*) in the standard libraryshall beis not one of the values returned by a previous invocation of either operator new(std::size_t) or operator new(std::size_t, const std::nothrow_t&) in the standard library, and the behavior is undefined if the value supplied to operator delete[](void*) in the standard libraryshall beis not one of the values returned by a previous invocation of either operator new[](std::size_t) or operator new[](std::size_t, const std::nothrow_t&) in the standard library.
[Voted into the WP at the March, 2011 meeting.]
The description of class member access expressions in 7.6.1.5 [expr.ref] paragraph 2 defines the terms “object expression” and “pointer expression:”
For the first option (dot) the type of the first expression (the object expression) shall be “class object” (of a complete 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).
(Note in passing that the phrase “class object” seems very odd when describing a type.) The rest of that section is based on the equivalence of the expression E1->E2 to (*(E1)).E2 and thus is phrased only in terms of “object expression.” This terminology appears to have been misapplied in other parts of the Standard, using the term “object expression” to refer both to class types and pointers to class types. The most egregious of these is 7.6.4 [expr.mptr.oper] paragraph 4, describing the operands of a pointer-to-member expression:
The first operand is called the object expression. If the dynamic type of the object expression does not contain the member to which the pointer refers, the behavior is undefined.
The dynamic type of the first operand in the ->* case is a pointer type, not a class type, so it cannot “contain the member to which the pointer refers.” Another example is _N4868_.6.5.6 [basic.lookup.classref], describing the lookup in a class member access expression. The first paragraph uses the term consistently with its use in 7.6.1.5 [expr.ref], but paragraph 2 reads:
If the id-expression in a class member access (7.6.1.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.
Paragraph 7 gets it right:
...in the context of the class of the object expression (or the class pointed to by the pointer expression).
Another misapplication of the term occurs in 7.6.1.3 [expr.call] paragraph 1:
...the call is as a member of the object pointed to or referred to by the object expression (7.6.1.5 [expr.ref], 7.6.4 [expr.mptr.oper])... its final overrider (11.7.3 [class.virtual]) in the dynamic type of the object expression is called. [Note: the dynamic type is the type of the object pointed or referred to by the current value of the object expression...
Here again we have the idea that an object expression can “point to” an object.
Another minor complication is that Clause 7 [expr] paragraph 7 has a separate definition for the (hyphenated) term “object-expression:”
An expression designating an object is called an object-expression.
This term is used several times in the Standard, apparently interchangeably with the non-hyphenated version defined in 7.6.1.5 [expr.ref]; for example, _N4567_.5.1.1 [expr.prim.general] bullet 10.1 mentions
a class member access (7.6.1.5 [expr.ref]) in which the object-expression refers to the member's class
using the term defined in Clause 7 [expr] paragraph 7 but linking it with 7.6.1.5 [expr.ref].
These uses of “object expression” and “object-expression” need to be made consistent, especially the reference in 7.6.4 [expr.mptr.oper] that implies that the dynamic type of a pointer is that of the complete object to which it points.
Proposed resolution (February, 2011):
Change _N4868_.6.5.6 [basic.lookup.classref] paragraph 2 as follows:
...If the type of the object expression is of pointer to scalar typeFor a pseudo-destructor call (_N4778_.7.6.1.4 [expr.pseudo]), the unqualified-id is looked up in the context of the complete postfix-expression.
Delete Clause 7 [expr] paragraph 7
An expression designating an object is called an object-expression.
Change _N4567_.5.1.1 [expr.prim.general] paragraph 10 as follows:
An id-expression that denotes a non-static data member or non-static member function of a class can only be used:
as part of a class member access (7.6.1.5 [expr.ref]) in which the
object-expressionobject expression refers to the member's class or a class derived from that class, or...
Change 7.6.1.3 [expr.call] paragraph 1 as follows:
...For a member function call, the postfix expression shall be an implicit (11.4.3 [class.mfct.non.static], 11.4.9 [class.static]) or explicit class member access (7.6.1.5 [expr.ref]) whose id-expression is a function member name, or a pointer-to-member expression (7.6.4 [expr.mptr.oper]) selecting a function member; the call is as a member of the class objectpointed to orreferred to by the object expression(7.6.1.5 [expr.ref], 7.6.4 [expr.mptr.oper])... [Note: the dynamic type is the type of the objectpointed orreferred to by the current value of the object expression. 11.9.5 [class.cdtor] describes the behavior of virtual function calls when theobject-expressionobject expression refers to an object under construction or destruction. —end note]
Change 7.6.1.5 [expr.ref] paragraph 2 as follows:
For the first option (dot)the type ofthe first expression(the object expression)shallbe “class object” (of a complete type)have complete class type. For the second option (arrow)the type ofthe first expression(the pointer expression)shallbe “pointer to class object” (of a complete type)have pointer to complete class type. The expression E1->E2 is converted to the equivalent form (*(E1)).E2; the remainder of 7.6.1.5 [expr.ref] will address only the first option (dot) [Footnote: Note that (*(E1)) is an lvalue. —end footnote]. Inthese caseseither case, the id-expression shall name a member of the class or of one of its base classes...
Change 7.6.1.5 [expr.ref] paragraph 3 as follows:
If E1 has the type “pointer to class X,” then the expression E1->E2 is converted to the equivalent form (*(E1)).E2; the remainder of 7.6.1.5 [expr.ref] will address only the first option (dot)66.Abbreviatingobject-expressionpostfix-expression.id-expression as E1.E2,then theE1 is called the object expression. The type and value category ofthis expressionE1.E2 are determined as follows...
Change 7.6.4 [expr.mptr.oper] paragraph 3 as follows:
...The result is an object or a function of the type specified by the second operand.The expression E1->*E2 is converted into the equivalent form (*(E1)).*E2.
Change 7.6.4 [expr.mptr.oper] paragraph 4 as follows:
The first operandAbbreviating pm-expression.*cast-expression as E1.*E2, E1 is called the object expression. If the dynamic type ofthe object expressionE1 does not contain the member to whichthe pointerE2 refers, the behavior is undefined.
Change 7.6.4 [expr.mptr.oper] paragraph 6 as follows:
...In a .* expression whose object expression is an rvalue, the program is ill-formed if the second operand is a pointer to member function with ref-qualifier &. In a->* expression or in a.* expression whose object expression is an lvalue, the program is ill-formed if the second operand is a pointer to member function with ref-qualifier &&. The result of a .* expression whose second operand is a pointer to a data member is of the same value category (7.2.1 [basic.lval]) as its first operand. The result of a .* expression whose second operand is a pointer to a member function is a prvalue.The result of an ->* expression is an lvalue if its second operand is a pointer to data member and a prvalue otherwise.If the second operand is the null pointer to member value (7.3.13 [conv.mem]), the behavior is undefined.
Change 11.4.9 [class.static] paragraph 2 as follows:
...A static member may be referred to using the class member access syntax, in which case theobject-expressionobject expression is evaluated...
Change 11.9.5 [class.cdtor] paragraph 4 as follows:
...If the virtual function call uses an explicit class member access (5.2.5) and theobject-expressionobject expression refers to the object under construction...
Change 13.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 objector pointerexpression of the postfix-expression or...
[Note: although the current text of _N4868_.6.5.6 [basic.lookup.classref] paragraph 7 mentions the phrase “pointer expression,” that wording will be replaced by issue 1111 or issue 1220 and is thus not addressed here.]
[Voted into the WP at the March, 2011 meeting as part of paper N3260.]
According to 7.7 [expr.const] paragraph 3,
A constant expression is an integral constant expression if it is of integral or enumeration type. [Note: such expressions may be used as array bounds (9.3.4.5 [dcl.array], 7.6.2.8 [expr.new]), as case expressions (8.5.3 [stmt.switch]), as bit-field lengths (11.4.10 [class.bit]), as enumerator initializers (9.8.1 [dcl.enum]), and as integral or enumeration non-type template arguments (13.4 [temp.arg]). —end note]
Although there is conceptually a conversion from enumeration type to integral type involved in using an enumerator as an array bound or bit-field length, the normative wording for those uses does not explicitly mention it and simply requires an integral constant expression. Consequently, the current wording permits uses like the following:
enum class E { e = 10; }; struct S { int arr[E::e]; int i: E::e; };
This seems surprising.
Proposed resolution (February, 2011):
This issue is resolved by the resolution of issue 1197.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
One of the bullets in 7.7 [expr.const] paragraph 2 says,
a type conversion from a pointer or pointer-to-member type to a literal type
This appears to prohibit conversion from one pointer type to another; for example,
int x;
constexpr void* p = &x; // ill-formed
This seems excessive and probably unintentional.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 7.7 [expr.const] paragraph 2 as follows:
a type conversion from a pointer or
pointer-to-member type to a literal an
integral type [Note: a user-defined conversion
invokes a function —end note];
[Note: the proposed resolution of issue 1188 edits this bullet in an incompatible fashion.]
[Voted into the WP at the March, 2011 meeting.]
It is not clear what happens when a program violates the limits on constexpr function recursion in a context that does not require a constant expression. For example,
constexpr int f(int i) { return f(i); }
const int i = f(1); // error, undefined behavior, or dynamic initialization?
(Presumably the “within its resource limits” caveat of 4.1 [intro.compliance] paragraph 2 would effectively result in undefined behavior in a context that required a constant expression.)
Notes from the November, 2010 meeting:
The CWG was of mixed opinion as to whether an infinite recursion in a constexpr function should be ill-formed or simply render an expression non-constant.
Proposed resolution (January, 2011):
Add the following bullet in 7.7 [expr.const] paragraph 2:
an invocation of a constexpr function or a constexpr constructor that would exceed the implementation-defined recursion limit (see annex Clause Annex B [implimits]);
[Voted into the WP at the March, 2011 meeting as part of paper N3260.]
According to 13.4.3 [temp.arg.nontype] paragraph 1, one of the possibilities for a template-argument for a non-type, non-template template-parameter is
an integral constant expression (including a constant expression of literal class type that can be used as an integral constant expression as described in 7.7 [expr.const])
However, the requirement for such a literal class type is (7.7 [expr.const] paragraph 5):
...that class type shall have a single non-explicit conversion function to an integral or enumeration type and that conversion function shall be constexpr.
Note that this normative requirement for a single conversion function is contradicted by the example in that paragraph, which reads in significant part,
struct A {
constexpr A(int i) : val(i) { }
constexpr operator int() { return val; }
constexpr operator long() { return 43; }
private:
int val;
};
template<int> struct X { };
constexpr A a = 42;
X<a> x; // OK: unique conversion to int
Proposed resolution (February, 2011):
This issue is resolved by the resolution of issue 1197.
[Voted into the WP at the November, 2010 meeting as paper N3218.]
N3092 comment DE 8In the definition of “potential constant expression” in 7.7 [expr.const] paragraph 6, it is unclear how “arbitrary” the substitution of the function parameters is. Does it mean “there exists a value for which the result is a constant expression” or does it mean “for all possible values, the result needs to be a constant expression?” Example:
constexpr int f(int x){ return x + 1; }
is a constant expression under the first interpretation, but not under the second (because overflow occurs for x == INT_MAX, cf 7.7 [expr.const] paragraph 2 bullet 5). The answer also affects expressions such as:
constexpr int f2(bool v) { return v ? throw 0 : 0; } constexpr int f3(bool v) { return v && (throw 0, 0); }
See also issue 1129.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 25It does not appear to be clearly enough stated that the example
constexpr int f() { return 42 + 84; } const int sz = f(); int a[sz];
is equivalent to
const int sz = 42 + 84; int a[sz];
Proposed resolution (August, 2010):
Change 7.7 [expr.const] paragraph 1 as follows:
Certain contexts require expressions that satisfy additional requirements as detailed in this sub-clause; other contexts have different semantics depending on whether or not an expression satisfies these requirements.Such expressionsExpressions that satisfy these requirements are called constant expressions. [Note:ThoseConstant expressions can be evaluated during translation. —end note]
[Voted into the WP at the November, 2010 meeting as paper N3218.]
N3092 comment GB 26It is not clear how overload resolution is performed inside the body of a constexpr function. In particular, if the function is invoked with parameters such that an expression evaluates to an integral 0, does that make the expression eligible for the null pointer conversion and thus potentially select a different overloaded function than in invocations in which the expression has a non-zero value? (Suggested answer: no.)
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The status of the following example is not clear:
union U { float f; unsigned long u; }; constexpr U u1 = { 1.0 }; constexpr unsigned long u2 = u1.u;
This might be ill-formed because the aliasing causes undefined behavior, which should make the expression not a constant expression. However, a similar example using a permitted aliasing would presumably be acceptable:
union U { unsigned char c[sizeof(double)]; double d; }; constexpr U c1u = { 0x12, 0x34 /* etc. */ }; constexpr double c1 = c1u.d;
One suggestion was that unions should not be considered literal types, but some in the ensuing discussion deemed that excessive. That also would not address similar examples using reinterpret_cast, which is currently also permitted in constant expressions.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 7.7 [expr.const] paragraph 2 as follows:
an lvalue-to-rvalue conversion...
an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) that is applied to a glvalue that refers to a non-active member of a union or a subobject thereof;
...
a type conversion from a pointer or pointer-to-member type to a
literal type [Note: a user-defined conversion invokes a function
—end note] a reinterpret_cast
(7.6.1.10 [expr.reinterpret.cast]);
[Note: the proposed resolution of issue 1098 edits this bullet in an incompatible fashion.]
[Voted into the WP at the March, 2011 meeting.]
It's not clear whether the current rules for constant expressions allow indirect calls of constexpr functions and constexpr member functions; for example,
constexpr bool is_negative(int x) { return x < 0; } constexpr bool check(int x, bool (*p)(int)) { return p(x); } static_assert(check(-2, is_negative), "Error");
If this is to be permitted, there does not seem to be a reason to prohibit equality comparison of pointers to functions or pointers to objects of static storage duration -- these can be tracked as is already done for non-type template parameters.
Proposed resolution (November, 2010):
Change 7.7 [expr.const] bullet 2.19 as follows:
a relational (7.6.9 [expr.rel]) or equality
(7.6.10 [expr.eq]) operator where at least one of the
operands is a pointer the result is
unspecified;
[Voted into the WP at the March, 2011 meeting as part of paper N3260.]
The requirement in 7.7 [expr.const] that a constant expression cannot contain
an array-to-pointer conversion (7.3.3 [conv.array]) that is applied to a glvalue that does not designate an object with static storage duration
effectively eliminates the use of automatic constexpr arrays such as
void f() { constexpr int ar[] = { 1, 2 }; constexpr int i = ar[1]; }
There does not seem to be a problem with this kind of usage.
Proposed resolution (February, 2011):
The proposed resolution will be submitted as a separate document.
[Voted into the WP at the March, 2011 meeting.]
C and C++ differ in the treatment of an expression statement, in particular with regard to whether a volatile lvalue is fetched. For example,
volatile int x; void f() { x; // Fetches x in C, not in C++ }
The reason C++ is different in this regard is principally due to the fact that an assignment expression is an lvalue in C++ but not in C. If the lvalue-to-rvalue conversion were applied to expression statements, a statement like
x = 5;
would write to x and then immediately read it.
It is not clear that the current approach to dealing with the difference in assignment expressions is the only or best approach; it might be possible to avoid the unwanted fetch on the result of an assignment statement without giving up the fetch for a variable appearing by itself in an expression statement.
Proposed resolution (January, 2011):
Add a new paragraph after Clause 7 [expr] paragraph 10:
In some contexts, an expression only appears for its side-effects. Such an expression is called a discarded-value expression. The expression is evaluated and its value is discarded. The array-to-pointer (7.3.3 [conv.array]) and function-to-pointer (7.3.4 [conv.func]) standard conversions are not applied. The lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is applied only if the expression is an lvalue of volatile-qualified type and it has one of the following forms:
id-expression (_N4567_.5.1.1 [expr.prim.general]),
subscripting (7.6.1.2 [expr.sub]),
class member access (7.6.1.5 [expr.ref]),
indirection (7.6.2.2 [expr.unary.op]),
pointer-to-member operation (7.6.4 [expr.mptr.oper]),
conditional expression (7.6.16 [expr.cond]) where both the second and the third operand are one of the above, or
comma expression (7.6.20 [expr.comma]) where the right operand is one of the above.
Change 7.6.1.9 [expr.static.cast] paragraph 6 as follows:
Any expression can be explicitly converted to type cv void, in which case it becomes a discarded-value expression (Clause 7 [expr]).The expression value is discarded.[Note: however, if the value is in a temporary object (6.7.7 [class.temporary]), the destructor for that object is not executed until the usual time, and the value of the object is preserved for the purpose of executing the destructor. —end note]The lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are not applied to the expression.
Change 7.6.20 [expr.comma] paragraph 1 as follows:
...A pair of expressions separated by a comma is evaluated left-to-right;and the value ofthe left expression isdiscardeda discarded-value expression (Clause 7 [expr]).83The lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are not applied to the left expression.Every value computation...
Change 8.3 [stmt.expr] paragraph 1 as follows:
...The expression isevaluated and its value is discardeda discarded-value expression (Clause 7 [expr]).The lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are not applied to the expression.All side effects...
[Voted into WP at August, 2010 meeting.]
The grammar for condition in 8.5 [stmt.select] paragraph 1 does not allow for the constexpr specifier. This was not intended by the original proposal.
Proposed resolution (March, 2010):
Change the definition of condition in 8.5 [stmt.select] paragraph 1 as follows:
Insert the following text as a new paragraph at the end of 8.5 [stmt.select]:
In the decl-specifier-seq of a condition, each decl-specifier shall be either a type-specifier or constexpr.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
It seems unfortunate that the beginning of a C-style for loop can look like
whereas the beginning of a range-based for loop looks like
So that we don't know what constraints we are trying to apply to the specifiers until we see, or don't see, a :. The inconsistency between decl-specifier-seq and type-specifier-seq seems gratuitous and inconvenient.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change the grammar 8.6 [stmt.iter] paragraph 1 as follows:
Add the following as a new paragraph at the end of 8.6.5 [stmt.ranged]:
The the decl-specifier-seq of a for-range-declaration, each decl-specifier shall be either a type-specifier or constexpr.
[Voted into WP at August, 2010 meeting.]
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 8.6.5 [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 9.2.9.7 [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, 9.2.9.7 [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 (9.5.5 [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 (June, 2010):
Change 8.6 [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 :
expressionfor-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 8.6.5 [stmt.ranged] paragraph 1 as follows:
TheFor a range-based for statement of the formfor ( for-range-declaration : expression ) statement
let range-init be equivalent to the expression surrounded by parentheses:
( 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, ...
Note to editor:
The formatting in the preceding change for range-init follows that of the existing text for begin-expr and end-expr. However, CWG is concerned that this style makes all of these elements look too much like grammar nonterminals and asks that the editor consider some other formatting convention.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The grammar for declarations includes the following two nonterminals:
An attribute-specifier followed by a semicolon could thus be parsed as either an attribute-declaration or as a simple-declaration that omits the optional decl-specifier-seq and init-declarator-list, and the current wording does not disambiguate the two. (There doesn't seem to be a compelling need for attribute-declaration as a separate nonterminal, given that simple-declaration can accommodate that form.)
Proposed resolution (February, 2011) [SUPERSEDED]:
Change 9.1 [dcl.pre] paragraph 1 as follows:
...Theoptionalattribute-specifier-seq in a simple-declaration appertains to each of the entities declared by the declarators; it shall not appear if the optionalof the init-declarator-listis omitted...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The grammar for an alias-declaration does not have a place for an attribute-specifier, although a typedef declaration does. Since an alias-declaration is essentially a different syntactic form of a typedef declaration (9.2.4 [dcl.typedef] paragraph 2), this could be surprising.
Proposed resolution (February, 2011) [SUPERSEDED]:
Change the grammar in 9.1 [dcl.pre] paragraph 1 as follows:
Change 9.2.4 [dcl.typedef] paragraph 2 as follows:
A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that typedef-name. It has the same semantics...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 39The current wording of 9.2 [dcl.spec] paragraph 1 says,
The optional attribute-specifier in a decl-specifier-seq appertains to the type determined by the decl-specifier-seq (9.3.4 [dcl.meaning]).
However, decl-specifier-seq is a recursive production. The intent is that the attribute-specifier appertains to the type determined by the top-level decl-specifier-seq, but this makes it sound as if it appertains to the one that directly contains the attribute-specifier.
Proposed resolution (August, 2010):
Change 9.2 [dcl.spec] paragraph 1 as follows:
The optional attribute-specifier in a decl-specifier-seq appertains to the type determined by thedecl-specifier-seqpreceding decl-specifiers (9.3.4 [dcl.meaning]).
[Voted into the WP at the March, 2011 meeting.]
Here's an example:
typedef struct S { ... } S; void fs(S *x) { ... }
The big question is, to what declaration does the reference to identifier S actually refer? Is it the S that's declared as a typedef name, or the S that's declared as a class name (or in C terms, as a struct tag)? (In either case, there's clearly only one type to which it could refer, since a typedef declaration does not introduce a new type. But the debugger apparently cares about more than just the identity of the type.)
Here's a classical, closely related example:
struct stat { ... }; int stat(); ... stat( ... ) ...
Does the identifier stat refer to the class or the function? Obviously, in C, you can't refer to the struct tag without using the struct keyword, because it is in a different name space, so the reference must be to the function. In C++, the reference is also to the function, but for a completely different reason.
Now in C, typedef names and function names are in the same name space, so the natural extrapolation would be that, in the first example, S refers to the typedef declaration, as it would in C. But C++ is not C. For the purposes of this discussion, there are two important differences between C and C++
The first difference is that, in C++, typedef names and class names are not in separate name spaces. On the other hand, according to section _N4868_.6.4.10 [basic.scope.hiding] (Name hiding), paragraph 2:
A class name (9.1) or enumeration name (7.2) can be hidden by the name of an object, function, or enumerator declared in the same scope. If a class or enumeration name and an object, function, or enumerator are declared in the same scope (in any order) with the same name, the class or enumeration name is hidden wherever the object, function, or enumerator name is visible.
Please consider carefully the phrase I have highlighted, and the fact that a typedef name is not the name of an object, function or enumerator. As a result, this example:
struct stat { ... }; typedef int stat;
Which would be perfectly legal in C, is disallowed in C++, both implicitly (see the above quote) and explicitly (see section 9.2.4 [dcl.typedef] (The typedef specifier), paragraph 3):
In a given scope, a typedef specifier shall not be used to redefine the name of any type declared in that scope to refer to a different type. Similarly, in a given scope, a class or enumeration shall not be declared with the same name as a typedef-name that is declared in that scope and refers to a type other than the class or enumeration itself.
From which we can conclude that in C++ typedef names do not hide class names declared in the same scope. If they did, the above example would be legal.
The second difference is that, in C++, a typedef name that refers to a class is a class-name; see 9.2.4 [dcl.typedef] paragraph 4:
A typedef-name that names a class is a class-name(9.1). If a typedef-name is used following the class-key in an elaborated-type-specifier (7.1.5.3) or in the class-head of a class declaration (9), or is used as the identifier in the declarator for a constructor or destructor declaration (12.1, 12.4), the program is ill-formed.
This implies, for instance, that a typedef-name referring to a class can be used in a nested-name-specifier (i.e. before :: in a qualified name) or following ~ to refer to a destructor. Note that using a typedef-name as a class-name in an elaborated-type-specifier is not allowed. For example:
struct X { }; typedef struct X X2; X x; // legal X2 x2; // legal struct X sx; // legal struct X2 sx2; // illegal
The final relevant piece of the standard is 9.2.4 [dcl.typedef] paragraph 2:
In a given scope, a typedef specifier can be used to redefine the name of any type declared in that scope to refer to the type to which it already refers.
This of course is what allows the original example, to which let us now return:
typedef struct S { ... } S; void fs(S *x) { ... }
The question, again is, to which declaration of S does the reference actually refer? In C, it would clearly be to the second, since the first would be accessible only by using the struct keyword. In C++, if typedef names hid class names declared in the same scope, the answer would be the same. But we've already seen that typedef names do not hide class names declared in the same scope.
So to which declaration does the reference to S refer? The answer is that it doesn't matter. The second declaration of S, which appears to be a declaration of a typedef name, is actually a declaration of a class name (9.2.4 [dcl.typedef] paragraph 4), and as such is simply a redeclaration. Consider the following example:
typedef int I, I; extern int x, x; void f(), f();
To which declaration would a reference to I, x or f refer? It doesn't matter, because the second declaration of each is really just a redeclaration of the thing declared in the first declaration. So to save time, effort and complexity, the second declaration of each doesn't add any entry to the compiler's symbol table.
Note (March, 2005):
Matt Austern: Is this legal?
struct A { }; typedef struct A A; struct A* p;
Am I right in reading the standard [to say that this is ill-formed]? On the one hand it's a nice uniform rule. On the other hand, it seems likely to confuse users. Most people are probably used to thinking that 'typedef struct A A' is a null operation, and, if this code really is illegal, it would seem to be a gratuitous C/C++ incompatibility.
Mike Miller: I think you're right. 9.2.4 [dcl.typedef] paragraph 1:
A name declared with the typedef specifier becomes a typedef-name.
9.2.4 [dcl.typedef] paragraph 2:
In a given non-class scope, a typedef specifier can be used to redefine the name of any type declared in that scope to refer to the type to which it already refers.
After the typedef declaration in the example, the name X has been “redefined” — it is no longer just a class-name, it has been “redefined” to be a typedef-name (that, by virtue of the fact that it refers to a class type, is also a class-name).
John Spicer: In C, and originally in C++, an elaborated-type-specifier did not consider typedef names, so “struct X* x” would find the class and not the typedef.
When C++ was changed to make typedefs visible to elaborated-type-specifier lookups, I believe this issue was overlooked and inadvertantly made ill-formed.
I suspect we need add text saying that if a given scope contains both a class/enum and a typedef, that an elaborated type specifier lookup finds the class/enum.
Mike Miller: I'm a little uncomfortable with this approach. The model we have for declaring a typedef in the same scope as a class/enum is redefinition, not hiding (like the “struct stat” hack). This approach seems to assume that the typedef hides the class/enum, which can then be found by an elaborated-type-specifier, just as if it were hidden by a variable, function, or enumerator.
Also, this approach reduces but doesn't eliminate the incompatibility with C. For example:
struct S { }; { typedef struct S S; struct S* p; // still ill-formed }
My preference would be for something following the basic principle that declaring a typedef-name T in a scope where T already names the type designated by the typedef should have no effect on whether an elaborated-type-specifier in that or a nested scope is well-formed or not. Another way of saying that is that a typedef-name that designates a same-named class or enumeration in the same or a containing scope is transparent with respect to elaborated-type-specifiers.
John Spicer: This strikes me as being a rather complicated solution. When we made the change to make typedefs visible to elaborated-type-specifiers we did so knowing it would make some C cases ill-formed, so this does not bother me. We've lived with the C incompatibility for many years now, so I don't personally feel a need to undo it. I also don't like the fact that you have to essentially do the old-style elaborated-type-specifier lookup to check the result of the lookup that found the typedef.
I continue to prefer the direction I described earlier where if a given scope contains both a class/enum and a typedef, that an elaborated-type-specifier lookup finds the class/enum.
Notes from the April, 2005 meeting:
The CWG agreed with John Spicer's approach, i.e., permitting a typedef-name to be used in an elaborated-type-specifier only if it is declared in the same scope as the class or enumeration it names.
Proposed resolution (January, 2011):
Add the following new paragraph after 9.2.4 [dcl.typedef] paragraph 4:
If a typedef specifier is used to redefine in a given scope an entity that can be referenced using an elaborated-type-specifier, the entity can continue to be referenced by an elaborated-type-specifier or as an enumeration or class name in an enumeration or class definition respectively. [Example:
struct S; typedef struct S S; int main() { struct S* p; // OK } struct S {}; // OK—end example]
[Voted into WP at August, 2010 meeting.]
9.2.6 [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 13.8 [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 9.2.6 [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 ignoredthat 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 rendered not constexpr by a non-dependent construct. —end note]
[Voted into the WP at the March, 2011 meeting as part of paper N3268.]
The body of a constexpr function is required by 9.2.6 [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 (March, 2010):
Change 8.7.4 [stmt.return] paragraph 2 as follows:
A return statementwithout an expressionwith neither an expression nor a braced-init-list can be used only in functions that do not return a value...
Change 9.2.6 [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 return value
(8.7.4 [stmt.return], 9.5 [dcl.init])
shall be one of those allowed in a constant expression
(7.7 [expr.const]).
Notes from the March, 2010 meeting:
The new wording added in 7.7 [expr.const] in support of reference parameters for constexpr functions should also be considered to see whether additional changes are needed.
[Voted into WP at August, 2010 meeting.]
9.2.6 [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 (11.4.3 [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 9.2.6 [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 (11.4.3 [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 (6.8 [basic.types]). [Example:...
[Voted into WP at August, 2010 meeting.]
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 9.2.6 [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 or a reference to 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 (11.9.3 [class.base.init])
every constructor involved in initializing non-static data members and base class sub-objects
invoked by a mem-initializershall 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.
[Voted into the WP at the March, 2011 meeting as part of paper N3268.]
According to 9.2.6 [dcl.constexpr] paragraph 3, no declarations are permitted in the body of a constexpr function. This seems overly restrictive. At least three kinds of declarations would seem to be helpful in writing such functions: static_assert, typedef and alias declarations, and local automatic variables initialized with constant expressions. (Similar considerations apply to lambdas in which the lambda-return-type-clause is omitted.)
Rationale (July, 2009):
This suggestion needs a proposal and analysis by EWG before it can be considered by CWG.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 29A constexpr function is not permitted to return via an exception. This should be recognised, and a function declared constexpr without an explicit exception specification should be treated as if declared noexcept(true) rather than the usual noexcept(false). For a function template declared constexpr without an explicit exception specification, it should be considered noexcept(true) if and only if the constexpr keyword is respected on a given instantiation.
See also issue 1125.
Notes from the August, 2010 meeting:
The premise is not correct: an exception is forbidden only when a constexpr function is invoked in a context that requires a constant expression. Used as an ordinary function, it can throw.
Proposed resolution (August, 2010):
Change 7.6.2.7 [expr.unary.noexcept] bullet 3.1 as follows:
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
9.2.6 [dcl.constexpr] paragraph 5 says,
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, 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 is rendered not constexpr by a non-dependent construct. —end note]
A non-dependent error in a function template renders it ill-formed with no diagnostic required (13.8 [temp.res] paragraph 8). A similar approach is being taken in the proposed resolution of issue 1125 with respect to constexpr functions. It would be more consistent to treat constexpr function templates in the same way, along the lines of
If no specialization of the template would be constexpr, the program is ill-formed, no diagnostic required.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 9.2.6 [dcl.constexpr] paragraph 6 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, 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 is rendered not constexpr by a non-dependent construct.—end note] If no specialization of the template would yield a constexpr function or constexpr constructor, the program is ill-formed; no diagnostic required.
[Voted into the WP at the March, 2011 meeting as part of paper N3277.]
9.2.6 [dcl.constexpr] restricts the constexpr specifier to object and function declarations. Especially given the support for reference types in constexpr functions, and considering that constexpr pointer declarations are permitted, there does not seem to be a good reason for excluding constexpr references.
(See also issue 1195.)
Proposed resolution (November. 2010):
Change 9.2.6 [dcl.constexpr] paragraph 1 as follows:
The constexpr specifier shall be applied only to the definition ofan objecta variable, the declaration of a function...
Change 9.2.6 [dcl.constexpr] paragraph 8 as follows:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall be initialized. If it is initialized by a constructor call, the constructor shall be a constexpr constructor and every argument to the constructor shall be a constant expression. Otherwise, or if a constexpr specifier is used in a reference declaration, every full-expression that appears in its initializer shall be a constant expression...
[Voted into the WP at the March, 2011 meeting as part of paper N3277.]
9.2.6 [dcl.constexpr] paragraph 3 is overly restrictive in requiring that reference parameter and return types of a constexpr function or constructor must refer to a literal type. 7.7 [expr.const] paragraph 2 already prevents any problematic uses of lvalues of non-literal types, and it permits use of pointers to non-literal types as address constants. The same should be permitted via reference parameters and return types of constexpr functions.
(See also issue 1194.)
Proposed resolution (November, 2010):
Change 6.8 [basic.types] paragraph 10 as follows:
A type is a literal type if it is:
a scalar type; or
a reference type; or
...
Change 9.2.6 [dcl.constexpr] paragraph 3 as follows:
The definition of a constexpr function shall satisfy the following constraints:
it shall not be virtual (11.7.3 [class.virtual])
its return type shall be a literal type
or a reference to literal typeeach of its parameter types shall be a literal type
or a reference to literal type...
Change 9.2.6 [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
or a reference to literal type;...
[Voted into the WP at the March, 2011 meeting as part of paper N3277.]
The current requirements for constexpr functions do not permit a deleted constexpr function because the definition does not consist of a compound-statement containing just a return statement. However, it could be useful to allow this form in a case where a single piece of code is used in multiple configurations, in some of which the function is constexpr and others deleted; having to update all declarations of the function to remove the constexpr specifier is unnecessarily onerous.
Proposed resolution (January, 2011):
Change 9.2.6 [dcl.constexpr] paragraph 3 as follows:
...
its function-body shall be = delete or a compound-statement of the form
{ return expression ; }...
Change 9.2.6 [dcl.constexpr] paragraph 4 as follows:
The definition of a constexpr constructorIn the definition of a constexpr constructor, each of the parameter types shall be a literal type or a reference to a literal type. In addition, either its function-body shall be = delete or it shall satisfy the following constraints:
each of its parameter types shall be a literal type or a reference to literal type;...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
A class with a virtual base should not be allowed to have a constexpr constructor.
Proposed resolution (November, 2010) [SUPERSEDED]:
Add the following bullet to the list in 9.2.6 [dcl.constexpr] paragraph 4:
the class shall not have virtual base classes;
[Voted into the WP at the November, 2010 meeting.]
According to 13.3 [temp.names] paragraph 7,
A template-id that names a template alias specialization is a type-name.
However, the grammar for type-name in 9.2.9.3 [dcl.type.simple] does not include a production for simple-template-id.
Proposed resolution (September, 2010):
Change the definition of type-name in 9.2.9.3 [dcl.type.simple] paragraph 1 as follows:
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 40The description of decltype does not specify whether the type of a parameter is the declared type or the type as adjusted in 9.3.4.6 [dcl.fct] paragraph 5:
auto f(int a[])->decltype(a); // ill-formed or int*? auto g(const int i)->decltype(i); // int or const int?
Suggested resolution: Clarify the wording to indicate that the type of a parameter is after the array- and function-to-pointer decay but before the removal of cv-qualification.
Proposed resolution (August, 2010):
Change 9.3.4.6 [dcl.fct] paragraph 5 as follows:
...After producing the list of parameter types,several transformations take place upon these types to determine the function type. Anyany top-level cv-qualifiers modifying a parameter typeisare deleted when forming the function type.[Example: the type void(*)(const int) becomes void(*)(int) —end example] Such cv-qualifiers affect only the definition of the parameter within the body of the function; they do not affect the function type. If a storage-class-specifier modifies a parameter type, the specifier is deleted. [Example: register char* becomes char* —end example] Such storage-class-specifiers affect only the definition of the parameter within the body of the function; they do not affect the function type.The resulting list of transformed parameter types and the presence or absence of the ellipsis or a function parameter pack is the function's parameter-type-list. [Note: This transformation does not affect the types of the parameters. For example, int(*)(const int p, decltype(p)*) and int(*)(int, const int*) are identical types. —end note]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Given
int&& f(); int i;
it is surprising that decltype(f()) and decltype(static_cast<int&&>(i)) are not the same type.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 9.2.9.3 [dcl.type.simple] paragraph 4 as follows:
The type denoted by decltype(e) is defined as follows:
if e is an unparenthesized id-expression or a class member access (7.6.1.5 [expr.ref]), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;
otherwise, if e is
a function call (7.6.1.3 [expr.call]) or an invocation of an overloaded operator (parentheses around e are ignored), decltype(e) is the return type of the statically chosen functionan xvalue, decltype(e) is T&&, where T is the type of e;otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
otherwise, decltype(e) is the type of e.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 41The current wording disallows use of typedef-names in elaborated-type-specifiers. This prohibition should also apply to template aliases:
struct A { }; template<typename T> using X = A; struct X<int>* p2; // ill-formed
Proposed resolution (August, 2010):
Change 9.2.9.5 [dcl.type.elab] paragraph 2 as follows:
...If the identifier resolves to a typedef-name or the simple-template-id resolves to an alias template specialization, the elaborated-type-specifier is ill-formed. [Note:...
[Note: this wording assumes the change from “template alias” to “alias template” requested by comment FI 11 on FCD N3092.]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The footnote for 9.3 [dcl.decl] paragraph 3 reads,
A declaration with several declarators is usually equivalent to the corresponding sequence of declarations each with a single declarator... The exception occurs when a name introduced by one of the declarators hides a type name used by the decl-specifiers, so that when the same decl-specifiers are used in a subsequent declaration, they do not have the same meaning...
A more important exception to the rule has been added in C++0x, specifically with the auto specifier when the deduced type is not the same for all declarators, which renders the declaration ill-formed. The footnote should be updated accordingly.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The ellipsis for a parameter pack enters the normal declarator grammar as part of the declarator-id nonterminal. In contrast, however, the abstract-declarator grammar has no counterpart to declarator-id; instead, the ellipsis is one of the productions for the abstract-declarator nonterminal itself. It is thus impossible to declare a parameter pack for a pointer or reference using an abstract declarator, e.g.,
template<typename... T> void f(T& ...t); // t is a parameter pack template<typename... T> void f(T& ...); // equivalent to void f(T&, ...)
[Voted into the WP at the March, 2011 meeting.]
Issue 1199 proposes to add the capability of defining a constexpr special function as deleted. It would be similarly useful to be able to mark a defaulted constructor as constexpr. (It should be noted that the existing text of 11.4.5 [class.ctor] and the proposed resolution of issue 1224 already allow for implicitly-defined constructors to be implicitly constexpr; this issue simply proposes allowing the explicit use of the constexpr specifier.)
Proposed resolution (February, 2011):
Change 9.2.6 [dcl.constexpr] paragraph 3 as follows:
...
its function-body shall be = delete, = default, or a compound-statement of the form
{ return expression ; }...
Change 9.2.6 [dcl.constexpr] paragraph 4 as follows:
In the definition of a constexpr constructor, each of the parameter types shall be a literal type or a reference to a literal type. In addition, either its function-body shall be = delete or = default or it shall satisfy the following constraints:
...
A trivial copy/move constructor is also a constexpr constructor.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
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, 9.3.4.6 [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 11.4.2 [class.mfct] paragraph 9). The issue is thus being left open for renewed discussion at the next meeting.
Proposed resolution (June, 2008) [SUPERSEDED]:
Change 9.3.4.6 [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,orthe 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,orthe 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...
[Voted into the WP at the November, 2010 meeting.]
It seems strange that it is possible to call a function with an explict argument of {} but that it is not possible to specify that same argument as a default in a function declaration.
Rationale (August, 2010):
This was previously considered and rejected by EWG.
Note (October, 2010):
Additional discussion has indicated a potential willingness to revisit this question.
Proposed resolution (November, 2010):
See paper N3217.
[Voted into the WP at the November, 2010 meeting.]
In 9.3.4.6 [dcl.fct] paragraph 2, the type of a function declarator with a trailing-return-type is said to be
“function of (parameter-declaration-clause) cv-qualifier-seqopt ref-qualifieropt returning type-id”.
This formulation incorrectly omits the derived-declarator-type-list modifier for the type, and it should refer to “the trailing-type-specifier-seq of the trailing-return-type” as the return type instead of type-id (which is left over from before the introduction of trailing-return-type).
Proposed resolution (September, 2010):
Change 9.3.4.6 [dcl.fct] paragraph 2 as follows:
...T shall be the single type-specifier auto. The type of the declarator-id in D is “derived-declarator-type-list function of (parameter-declaration-clause) cv-qualifier-seqopt ref-qualifieropt returningtype-idtrailing-return-type”. The optional...
[Voted into the WP at the March, 2011 meeting as part of paper N3270.]
9.3.4.6 [dcl.fct] paragraph 13 says,
The type T of the declarator-id of the function parameter pack shall contain a template parameter pack; each template parameter pack in T is expanded by the function parameter pack.
I think that's incorrect. For example, I think
template<class... P> void f(int (* ...p)[sizeof...(P)]);
should be an error, and that the function parameter pack p does not expand the template parameter pack P in this case.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 778.
[Voted into WP at August, 2010 meeting.]
According to the definition of value initialization (9.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:
11.4.7 [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?”)
14.3 [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...”
6.7.4 [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 (11.4.5 [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 9.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 9.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.
[Voted into WP at August, 2010 meeting.]
9.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 9.5 [dcl.init] paragraph 2 as follows:
Automatic, register, static, and external variables of namespace scopeVariables 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 9.5 [dcl.init] paragraph 2 as follows:
Automatic, register, thread_local, static, and namespace-scoped external variables can be initialized byExcept for objects declared with the constexpr specifier, for which see 9.2.6 [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:...
[Voted into the WP at the November, 2010 meeting.]
The C committee is considering changing the definition of zero-initialization of unions to guarantee that the bytes of the entire union are set to zero before assigning 0, converted to the appropriate type, to the first member. The argument (summarized here) is for backward compatibility. The C++ Committee may want to consider the same change.
Proposed resolution (August, 2008):
Change bullet 3 of 9.5 [dcl.init] paragraph 5 (in the first list, dealing with zero-initialization) as follows:
[Drafting notes: Ask a C liaison about the progress of WG14 paper N1311, which deals with this issue. Since the adoption of WG21 paper N2544, unions may have static data members, hence the change to refer to the first non-static data member and the deletion of the footnote.]
Notes from the September, 2008 meeting:
It was observed that padding bytes in structs are zero-initialized in C, so if we are changing the treatment of unions in this way we should consider adding the C behavior for padding bytes at the same time. In particular, using memcmp to compare structs only works reliably if the padding bytes are zero-initialized.
Additional notes (February, 2010):
The C Committee has changed its approach to this question and is no longer using a two-phase process; in C, zero-initialization is specific to program start-up and thus is not appropriate for use with thread-local storage. See C paper N1387.
Proposed resolution (October, 2010):
Change 9.5 [dcl.init] paragraph 5 as follows:
To zero-initialize an object or reference of type T means:
if T is a scalar type (6.8 [basic.types]), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;103
if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized, and padding is initialized to zero bits;
if T is a (possibly cv-qualified) union type, the object's first non-static named data member is zero-initialized, and padding is initialized to zero bits;
if T is an array type, each element is zero-initialized;
if T is a reference type, no initialization is performed.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
9.5 [dcl.init] paragraph 16 describes three kinds of initializers: a single expression, a braced-init-list, and a parenthesized list of expressions. It is not clear which, if any, of these categories is the appropriate description for an initializer like
T t( { 1, 2 } )
and thus not clear which of the bullets in the list applies.
[Voted into the WP at the March, 2011 meeting.]
9.5.2 [dcl.init.aggr] paragraph 4 says,
An initializer-list is ill-formed if the number of initializer-clauses exceeds the number of members or elements to initialize.
However, in a new-expression, the number of elements to be initialized is potentially unknown at compile time. How should an overly-long initializer-list in a new-expression be treated?
Notes from the August, 2010 meeting:
The consensus of the CWG was that this case should throw an exception at runtime.
Proposed resolution (January, 2011):
Change 7.6.2.8 [expr.new] paragraph 7 as follows:
When the value of the expression in a noptr-new-declarator is zero, the allocation function is called to allocate an array with no elements. If the value of that expression is less than zero or such that the size of the allocated object would exceed the implementation-defined limit, or if the new-initializer is a braced-init-list for which the number of initializer-clauses exceeds the number of elements to initialize, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (14.4 [except.handle]) of type std::bad_array_new_length (17.6.4.2 [new.badlength]).
[Voted into the WP at the March, 2011 meeting.]
The ordering imposed by 9.5.2 [dcl.init.aggr] paragraph 17 applies only to “the full-expressions in an initializer-clause” (i.e., what follows an = in an aggregate initializer); this leaves unspecified the order in which the expressions in an initializer-list (the term used by the braced-init-list form of initializer, with no =) are evaluated.
Notes from the November, 2010 meeting:
The CWG favored guaranteeing the order of evaluation of initializer-clauses appearing in a braced-init-list, regardless of whether the braced-init-list is an aggregate initialization or constructor call.
Proposed resolution (January, 2011):
Delete 9.5.2 [dcl.init.aggr] paragraph 17:
The full-expressions in an initializer-clause are evaluated in the order in which they appear.
Insert the following as a new paragraph between paragraphs 3 and 4 of 9.5.5 [dcl.init.list]
Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (13.7.4 [temp.variadic]), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list. [Note: This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of the initializer-list are interpreted as arguments of a constructor call, even though ordinarily there are no sequencing constraints on the arguments of a call. —end note]
[Voted into the WP at the November, 2010 meeting.]
Issue 990 added the following text to 9.5.5 [dcl.init.list] paragraph 3:Otherwise, if the initializer list has no elements and T is an aggregate, each of the members of T is initialized from an empty initializer list. [Example:...
A better way to handle this would be to delete that bullet and change 9.5.2 [dcl.init.aggr] paragraph 7 as follows:
If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall bevalue-initialized (9.5 [dcl.init])initialized from an empty initializer list (9.5.5 [dcl.init.list]).
This makes { } less of a special case and makes the following example work:
struct A { A(std::initializer_list<int>); }; struct B { int i; A a; }; B b = { 1 };
Proposed resolution (August, 2010):
Delete 9.5.5 [dcl.init.list] bullet 3.2, includeing the example:
Change 9.5.2 [dcl.init.aggr] paragraph 7 as follows:
If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall bevalue-initialized (9.5 [dcl.init])initialized from an empty initializer list (9.5.5 [dcl.init.list]).
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 48The requirement that an rvalue reference must be bound to an rvalue is found in 9.5.4 [dcl.init.ref] bullet 5.2:
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference and the initializer expression shall be an rvalue or have a function type.
This is not quite correct, as it is phrased in terms of the value category of the initializer expression itself rather than that of the result of any conversions applied to the initializer. It should be permitted to bind an rvalue reference to a temporary created from an lvalue, for instance, or to the rvalue result of a conversion function for an lvalue object of class type. Also, it should not be permitted to bind an rvalue reference to the lvalue result of a conversion function for a class rvalue.
Proposed resolution (August, 2010):
Change 9.5.4 [dcl.init.ref] paragraph 5 as follows:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
If the reference is an lvalue reference and the initializer expression
is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an lvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3”105 (this conversion is selected by enumerating the applicable conversion functions (12.2.2.7 [over.match.ref]) and choosing the best one through overload resolution (12.2 [over.match])),
then the reference is bound to the initializer expression lvalue in the first case and to the lvalue result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object). [Note: the usual lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are not needed, and therefore are suppressed, when such direct bindings to lvalues are done. —end note]
[Example:
double d = 2.0; double& rd = d; // rd refers to d const double& rcd = d; // rcd refers to d struct A { }; struct B : A { operator int&(); } b; A& ra = b; // ra refers to A subobject in b const A& rca = b; // rca refers to A subobject in b int& ir = B(); // ir refers to the result of B::operator int&—end example]
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference
and the initializer expression shall be an rvalue or have a function type. [Example:double& rd2 = 2.0; // error: not an lvalue and reference not const int i = 2; double& rd3 = i; // error: type mismatch and reference not constdouble&& rd4 = i; // error: rvalue reference cannot bind to lvalue—end example]
If the initializer expression
is an xvalue, class prvalue, array prvalue or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an xvalue, class prvalue, or function lvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3”,
then the reference is bound to the value of the initializer expression in the first case and to the result of the conversion in the second case (or, in either case, to an appropriate base class subobject). In the second case, if the reference is an rvalue reference and the second standard conversion sequence of the user-defined conversion sequence includes an lvalue-to-rvalue conversion, the program is ill-formed.
If T1 is a function type, then
if T2 is the same type as T1, the reference is bound to the initializer expression lvalue;
if T2 is a class type and the initializer expression can be implicitly converted to an lvalue of type T1 (this conversion is selected by enumerating the applicable conversion functions (12.2.2.7 [over.match.ref]) and choosing the best one through overload resolution (12.2 [over.match])), the reference is bound to the function lvalue that is the result of the conversion;
otherwise, the program is ill-formed.
Otherwise, if T2 is a class type and
the initializer expression is an rvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
T1 is not reference-related to T2 and the initializer expression can be implicitly converted to an rvalue of type “cv3 T3” (this conversion is selected by enumerating the applicable conversion functions (12.2.2.7 [over.match.ref]) and choosing the best one through overload resolution (12.2 [over.match])),
then the reference is bound to the initializer expression rvalue in the first case and to the object that is the result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object).[Example:
struct A { }; struct B : A { } b; extern B f(); const A& rca = f(); // bound to the A subobject of the B rvalue. A&&rcbrra = f(); // same as above struct X { operator B(); operator int&(); } x; const A& r = x; // bound to the A subobject of the result of the conversion int&& rri = static_cast<int&&>(i); // bound directly to i B&& rrb = x; // bound directly to the result of operator B int&& rri2 = X(); // error: lvalue-to-rvalue conversion applied to result of operator int&—end example]
If the initializer expression is an rvalue, with T2 an array type, and “cv1 T1” is reference-compatible with “cv2 T2,” the reference is bound to the object represented by the rvalue (see 7.2.1 [basic.lval]).Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy-initialization (9.5 [dcl.init]). The reference is then bound to the temporary. If T1 is reference-related to T2, cv1
mustshall be the same cv-qualification as, or greater cv-qualification than, cv2; otherwise, the program is ill-formed. If T1 is reference-related to T2 and the reference is an rvalue reference, the initializer expression shall not be an lvalue. [Example:const double& rcd2 = 2; // rcd2 refers to temporary with value 2.0 double&&rcd3rrd = 2; //rcd3rrd refers to temporary with value 2.0 const volatile int cvi = 1; const int& r = cvi; // error: type qualifiers dropped double&& rrd2 = d; // error: copying lvalue of related type double&& rrd3 = i; // rrd3 refers to temporary with value 2.0—end example]
In all cases except the last (i.e., creating and initializing a temporary from the initializer expression), the reference is said to bind directly to the initializer expression.
This resolution also resolves issue 1139.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 49The current wording of 9.5.4 [dcl.init.ref] paragraph 5 does not specify direct binding of an rvalue reference to a scalar xvalue; instead, it requires creation of a temporary and binding the rvalue reference to the temporary.
Proposed resolution (August, 2010):
This issue is resolved by the resolution of 1138.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The examples in 9.5.4 [dcl.init.ref] paragraph 5 are not consistent as to whether earlier code fragments are assumed to be part of the example or not. For instance, the variables i and d are used as initializers without declaration in later fragments, presumably intended to refer to the declarations introduced in the first couple. However, the third fragment declares rca, which is an incompatible redeclaration of a name declared in the first fragment.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The current wording of the WP appears not to allow for list-initialization of a reference like the following:
int i; int& ir{i};
First, 9.5 [dcl.init] bullet 16.1 reads,
If the initializer is a braced-init-list, the object is list-initialized (8.5.4).
A reference is not an object, so this does not appear to apply; however, the second bullet sends reference initialization off to 9.5.4 [dcl.init.ref], which does not cover braced-init-lists: paragraph 5 of that section deals only with initilizer expressions, and a braced-init-list is not an expression.
Assuming that the use of “object” in the first bullet is just an oversight, 9.5.5 [dcl.init.list] also does not cover the case of a reference to a scalar type whose initalizer is a braced-init-list with a single element. Bullet 7 of paragraph 3 reads,
Otherwise, if the initializer list has a single element, the object is initialized from that element
and would cover this case except that, again, a reference is not an object. As a result, such an initialization would end up in the last bullet and consequently be ill-formed.
Presumably all that is needed is to add “or reference” to the appropriate bullets of 9.5 [dcl.init] paragraph 16 and 9.5.5 [dcl.init.list] paragraph 3.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 9.5 [dcl.init] bullet 16.1 as follows:
If the initializer is a braced-init-list, the object or reference is list-initialized (9.5.5 [dcl.init.list]).
Change 9.5.5 [dcl.init.list] bullet 3.7 as follows:
Otherwise, if the initializer list has a single element, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
By analogy with the variable definition
int arr[3] = {1, 2, 3};
it should be possible to write something like
void f(const int(&)[3]); void g() { f({1, 2, 3}); }
There are currently at least two problems with the latter usage. First, the variable initializer relies on brace elision, which appears to be defined only for variable declarations (9.5.2 [dcl.init.aggr] paragraph 11) , and possibly only for certain forms of variable declarations.
Second, the call would require creation of an array temporary to which the parameter reference would be bound, and the current Standard does not describe array temporaries (although rvalue arrays can occur as members of class rvalues). This is also contrary to the direction established by CWG in considering 1058.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 479.6.2 [dcl.fct.def.default] paragraph 4 says,
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...
The second sentence should say “user-declared” instead of “user-provided.”
Proposed resolution (September, 2010):
Change 9.6.2 [dcl.fct.def.default] paragraph 4 as follows:
...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 (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [Note:...[Drafting note: the suggestion in the NB comment is incorrect. The proposed resolution clarifies the intent.]
[Voted into the WP at the March, 2011 meeting.]
N3092 comment FI 1It should be allowed to explicitly default a non-public special member function on its first declaration. It is very likely that users will want to default protected/private constructors and copy constructors without having to write such defaulting outside the class.
Proposed resolution (November, 2010):
Change 9.6.2 [dcl.fct.def.default] paragraphs 1-5 as follows:
A function definition of the form:
attribute-specifieropt decl-specifier-seqopt declarator = default ;
is called an explicitly-defaulted definition. 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, and
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, and may have an explicit exception-specification only if it is compatible (14.5 [except.spec]) with the exception-specification on the implicit declaration. If
ita function 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 be constexpr if the implicit declaration would be,
it is implicitly considered to have the same exception-specification as if it had been implicitly declared (14.5 [except.spec]), and
in the case of a copy constructor, move constructor, copy assignment operator, or move 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 { constexpr S() = default; //ill-formed: implicit S() is not constexpr S(int a = 0) = default; // ill-formed: default argument void operator=(const S&) = default; // ill-formed: non-matching return type ~S() throw(int) = default; // ill-formed: exception specification doesn't match private: int i; 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 (11.4.5 [class.ctor] 11.4.7 [class.dtor], 11.4.5.3 [class.copy.ctor]), which might mean defining them as deleted. A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [Note:
while an implicitly-declared special member function is inline (11.4.4 [special]), an explicitly-defaulted definition may be non-inline. Non-inline definitions are user-provided, and hence non-trivial (11.4.5 [class.ctor], 11.4.7 [class.dtor], 11.4.5.3 [class.copy.ctor]). This rule enablesDeclaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. —end note][Example:
struct trivial { trivial() = default; trivial(const trivial&) = default; trivial(trivial&&) = default; trivial& operator=(const trivial&) = default; trivial& operator=(trivial&&) = default; ~trivial() = default; }; struct nontrivial1 { nontrivial1(); }; nontrivial1::nontrivial1() = default; // notinlinefirst declarationstruct nontrivial2 { nontrivial2(); }; inline nontrivial2::nontrivial2() = default; // not first declaration struct nontrivial3 { virtual ~nontrivial3() = 0; // virtual }; inline nontrivial3::~nontrivial3() = default; // not first declaration—end example]
Change 11.4.5 [class.ctor] paragraph 5 as follows:
Change 11.4.7 [class.dtor] paragraph 3 as follows:
...A destructor is trivial if it isneithernot user-providednor deletedand...
Change 11.4.5.3 [class.copy.ctor] paragraph 13 as follows:
A copy/move constructor for class X is trivial if it isneithernot user-providednor deletedand...
Change 11.4.5.3 [class.copy.ctor] paragraph 27 as follows:
A copy/move assignment operator for class X istrivialtrivial if it isneithernot user-providednor deletedand...
This resolution also resolves issues 1136, 1137, 1140, 1145, 1149, and 1208.
[Voted into the WP at the March, 2011 meeting.]
N3092 comment FI 2It should be allowed to explicitly default an explicit special member function on its first declaration. It is very likely that users will want to default explicit copy constructors without having to write such defaulting outside of the class.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 1135.
[Voted into the WP at the March, 2011 meeting.]
N3092 comment FI 3It should be allowed to explicitly default a virtual special member function on its first declaration. It is very likely that users will want to default virtual copy assignment operators and destructors without having to write such defaulting outside of the class.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 1135.
[Voted into the WP at the March, 2011 meeting.]
N3092 comment US 31According to 9.8.1 [dcl.enum] paragraph 10,
An expression of arithmetic or enumeration type can be converted to an enumeration type explicitly. The value is unchanged if it is in the range of enumeration values of the enumeration type; otherwise the resulting enumeration value is unspecified.
(There is similar wording in 7.6.1.9 [expr.static.cast].) Does the phrase “resulting enumeration value” mean that the result, although unspecified, must lie within the range of enumeration values of the enumeration type? Existing practice seems to allow out-of-range values to be preserved if the underlying type is large enough to represent the value. This freedom is important both for efficiency (to avoid having to mask values while storing and/or fetching) and to prevent optimizers from removing code that tests for out-of-range values.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 1094.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment FI 6Although 9.9.2.2 [namespace.unnamed] states that the use of the static keyword for declaring variables in namespace scope is deprecated because the unnamed namespace provides a superior alternative, it is unlikely that the feature will be removed at any point in the foreseeable future, especially in light of C compatibility concerns. The Committee should consider removing the deprecation.
Proposed resolution (August, 2010):
Delete 9.9.2.2 [namespace.unnamed] paragraph 2:
The use of the static keyword is deprecated when declaring variables in a namespace scope (see annex Clause Annex D [depr]); the unnamed-namespace provides a superior alternative.
Delete _N3225_.D.2 [depr.static]:
D.2 static keyword[depr.static]
The use of the static keyword is deprecated when declaring objects in namespace scope (see 6.4.6 [basic.scope.namespace]).
[Voted into the WP at the November, 2010 meeting.]
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 (6.3 [basic.def.odr]) or the extern "C" section (9.12 [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 9.12 [dcl.link] paragraph 6 and 6.6 [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 9.12 [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 (6.1 [basic.pre]) 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 (6.3 [basic.def.odr]), onlyOnly one definition for a function or object with C linkage may appear in the program (see 6.3 [basic.def.odr]); thatis,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 9.12 [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 9.12 [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 (6.3 [basic.def.odr]), onlyOnly one definition fora function or objectan entity with C linkage may appear in the program (see 6.3 [basic.def.odr]); thatis,implies that sucha function or objectan 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
noteexample]
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.
Proposed resolution (August, 2010):
Change 9.12 [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 objecta variable with C language linkage with the same name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the sameobjectvariable. 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 entity; no diagnostic is required if the declarations appear in different translation units. A variable with C language linkage shall not be declared with the same name as a function with C language linkage (ignoring the namespace names that qualify the respective names); no diagnostic is required if the declarations appear in different translation units. [Note:because of the one definition rule (6.3 [basic.def.odr]), onlyOnly one definition fora function or objectan entity with a given name with C language linkage may appear in the program (see 6.3 [basic.def.odr]); thatis,implies sucha function or objectan 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
noteexample]
[Note to editor: please consider reformatting the comments in the example.]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The current wording of 9.12 [dcl.link] paragraph 4 is:
...A C language linkage is ignored for the names of class members and the member function type of class member functions...
This has two problems. First, it sounds as if a class member function has a “member function type,” while in fact the type of a class member function is an ordinary function type (cf 11.4 [class.mem] paragraph 11).
Second, even if that problem is fixed, it is not accurate to say that a C language linkage is “ignored” for the type of a member function. It does not affect the language linkage of the type of the member function, but it does affect the language linkage of any function declarators appearing in the parameter and return types of the function and thus the type of the function.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 9.12 [dcl.link] paragraph 4 as follows:
...A C language linkage is ignoredforin determining the language linkage of the names of class members and thememberfunction type of class member functions...
[Voted into the WP at the November, 2010 meeting as part of paper N3190.
The current syntax requires that multiple attributes that appertain to the same entity be grouped into a single attribute-specifier. The migration from existing vendor-specific attributes would be easier if the syntax allowed multiple attribute-specifiers at each location where an attribute-specifier currently appears.
Proposed resolution (October, 2010):
Replace every occurrence of attribute-specifier with attribute-specifier-seq except in 9.13.1 [dcl.attr.grammar] paragraphs 1, 3, and 6 (but including paragraph 4).
Insert the following production at the beginning of the grammar of 9.13.1 [dcl.attr.grammar] paragraph 1:
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
An attribute-argument-clause should be allowed to consist solely of (), i.e., with no balanced-token-seq. Furthermore, the grammar for balanced-token should make the balanced-token-seq optional. Both of these goals could be accomplished by making the balanced-token optional in the first production of the rule for balanced-token-seq.
Proposed resolution (February, 2011) [SUPERSEDED]:
Change the grammar of 9.13.1 [dcl.attr.grammar] paragraph 1 as follows:
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 9.13.2 [dcl.align] paragraph 5,
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 entity being declared.
“...would otherwise be required” could be read as referring to the alignment set by another declaration of the entity. However, it was intended to prevent specifying an alignment smaller than the natural alignment the entity would have in the absence of an align attribute. The wording should be changed to make that clearer.
Proposed resolution (February, 2011) [SUPERSEDED]:
Change 9.13.2 [dcl.align] paragraph 5 as follows:
The combined effect of all alignment-specifiers in a declaration shall not specify an alignment that is less strict than the alignment that wouldotherwisebe required for the entity being declared if all alignment-specifiers were ignored (including those in other declarations).
[Voted into the WP at the November, 2010 meeting.]
The Standard explicitly bans alignment attributes for function parameters (9.13.2 [dcl.align] paragraph 1), but it is silent regarding the “parameter” of an exception handler. This should be clarified one way or the other.
Proposed resolution (October, 2010):
Change 9.13.2 [dcl.align] paragraph 1 as follows:
...The attribute may be followed by an ellipsis. The attribute may be applied to a variablethat is neither a function parameter nor declared with the register storage class specifier and to a class data member that is not a bit-fieldor to a class data member, but it shall not be applied to a bit-field, a function parameter, the formal parameter of a catch clause (14.4 [except.handle]), or a variable declared with the register storage class specifier. The attribute may also be applied to the declaration of a class or enumeration type.
[Voted into the WP at the March, 2011 meeting as paper N3259.]
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 (February, 2011):
The proposed resolution will be submitted as a separate document.
[Voted into the WP at the March, 2011 meeting.]
N3092 comment US 50The class
struct A { const int i; };
was a POD in C++98, but is not a POD under the FCD rules because it does not have a trivial default constructor. C++0x POD was intended to be a superset of C++98 POD.
Suggested resolution: Change POD to be standard layout and trivially copyable.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 1135.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The definition of a POD struct in Clause 11 [class] paragraph 9 is not (but should be) restricted to non-union class types.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change Clause 11 [class] paragraph 9 as follows:
A POD struct109 is a non-union class that is both a trivial class and a standard-layout class...
[Voted into WP at August, 2010 meeting.]
The grammar for member-declaration in 11.4 [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 11.4 [class.mem], add the indicated production to the definition of member-declaration:
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 11.4 [class.mem] paragraph 6,
The decl-specifier-seq is omitted in constructor, destructor, and conversion function declarations only.
This is incorrect, as some decl-specifiers (explicit, virtual, inline, constexpr) are permitted in these declarations. Conversely, C.7.6 [diff.dcl], “Banning implicit int,” says,
In C++ a decl-specifier-seq must contain a type-specifier.
This is also incorrect for these declarations.
Proposed resolution (February, 2011) [SUPERSEDED]:
Change 11.4 [class.mem] paragraph 7 as follows:
The decl-specifier-seqismay be omitted in constructor, destructor, and conversion function declarations only; when declaring another kind of member the decl-specifier-seq shall contain a type-specifier that is not a cv-qualifier. The member-declarator-list can be omitted...
Change C.7.6 [diff.dcl], “Banning implicit int,” as follows:
In C++ a decl-specifier-seq must contain a type-specifier, unless it is followed by a declarator for a constructor, a destructor, or a conversion function. In the following example...
[Voted into the WP at the November, 2010 meeting.]
11.4 [class.mem] paragraph 13 requires that all enumerators of a member enumeration type have names different from that of the containing class. This is only necessary for an unscoped enumeration; scoped enumerators are not in the scope of the containing class.
Proposed resolution (September, 2010):
Change 11.4 [class.mem] bullet 14.4 as follows:
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 52The current wording of 11.4.2 [class.mfct] paragraph 7 allows friend declarations to name member functions “after their class has been defined.” This appears to prohibit a friend declaration in a nested class defined inside its containing class that names a member function of the containing class, because the containing class is still considered to be incomplete at that point.
Proposed resolution (September, 2010):
Change 11.4.2 [class.mfct] paragraph 7 as follows:
MemberPreviously-declared member functions may be mentioned in friend declarationsafter their class has been defined.
[Voted into the WP at the March, 2011 meeting as part of paper N3282.]
The following innocuous-appearing code is currently ill-formed:
struct A { int a; }; struct B { void f() { decltype(A::a) i; // ill-formed } };
The reason is that, according to 11.4.3 [class.mfct.non.static] paragraph 3, the reference to A::a is transformed into (*this).A::a, and there is no A subobject of B. It would seem reasonable to suppress this transformation in unevaluated operands, where a reference to a non-static member is permitted without an object expression.
(See also issue 1005.)
Notes from the November, 2010 meeting:
The CWG agreed that the resolution of issue 515 was ill-advised and should be reversed.[Voted into the WP at the March, 2011 meeting as part of paper N3282.]
Consider the following example:
struct vector { struct iterator { }; struct const_iterator { }; iterator begin(); const_iterator begin() const; }; class block { vector v; auto end() const -> decltype(v.begin()) { return v.begin(); } };
Because the transformation of a member name into a class member access expression (11.4.3 [class.mfct.non.static] paragraph 3) only occurs inside the body of a non-static member function, the type of v in the trailing-return-type is non-const but is const in the return expression, resulting in a type mismatch between the return expression and the return type of the function.
One possibility would be to include the trailing-return-type as being subject to the transformation in 11.4.3 [class.mfct.non.static]. Note, however, that this is currently not in scope at that point (see issue 945).
Notes from the November, 2010 meeting:
The CWG felt that, because this is effectively an implicit parameter, the best approach would be to model its usability on the visibility of parameters: it could be named wherever a parameter of the function is in scope.
Proposed resolution (February, 2011):
Change _N4567_.5.1.1 [expr.prim.general] paragraph 2 as follows, adding three new paragraphs:
The keyword this names a pointer to the object for which a non-static member function (_N4868_.11.4.3.2 [class.this]) is invoked or a non-static data member's initializer (11.4 [class.mem]) is evaluated.
The keyword this shall be used only inside the body of a non-static member function (11.4.2 [class.mfct]) of the nearest enclosing class or in a brace-or-equal-initializer for a non-static data member (11.4 [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 a prvalue.If a function-definition or member-declarator declares a member function of a class X, the expression this is a prvalue of type “pointer to cv-qualifier-seq X” between the optional cv-qualifier-seq and the end of the function-definition or member-declarator. It shall not appear before the optional cv-qualifier-seq and it shall not appear within the declaration of a static member function (although its type and value category is defined within a static member function as it is within a non-static member function). [Note: the type and value category is defined even for the case of a static member function because declaration matching does not occur until the complete declarator is known, and this may be used in the trailing-return-type of the declarator. —end note]
Otherwise, if a member-declarator declares a non-static data member (11.4 [class.mem]) of a class X, the expression this is a prvalue of type “pointer to X” within the optional brace-or-equal-initializer. It shall not appear elsewhere in the member-declarator.
The expression this shall not appear in any other context.
[Example:...
Change _N4567_.5.1.1 [expr.prim.general] paragraph 10 as follows:
An id-expression that denotes a non-static data member or non-static member function of a class can only be used:
...
in the body ofbeyond the optional cv-qualifier-seq in the member-declarator or function-definition that declares a non-static member function of that class or of a class derived from that class (11.4.3 [class.mfct.non.static]), or...
Change 11.4.3 [class.mfct.non.static] paragraph 3 as follows:
When an id-expression (7.5 [expr.prim]) that is not part of a class member access syntax (7.6.1.5 [expr.ref]) and not used to form a pointer to member (7.6.2.2 [expr.unary.op]) is used in thebodydeclaration of anon-staticmember function of class X, if name lookup (6.5 [basic.lookup]) resolves the name...
[Voted into the WP at the March, 2011 meeting.]
It is currently not permitted to specify an exception-specification in a defaulted definition. It would be nice to be able to do so (providing the explicit specification matches the one that would be implicitly supplied) for documentation purposes.
Proposed resolution (November, 2010) [SUPERSEDED]:
This issue is resolved by the resolution of issue 1135.
[Voted into WP at August, 2010 meeting.]
According to 11.4.5 [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 11.4.5 [class.ctor] paragraph 1 but perhaps should have done so.)
Proposed resolution (February, 2010):
Change 11.4.5 [class.ctor] paragraph 1 as follows:
Constructors do not have names. A special declarator syntax
using an optional sequence of function-specifiers (9.2.3 [dcl.fct.spec]) followed by the constructor's class name followed by a parameter listis used to declare or define the constructor. The syntax uses
an optional 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:...
[Voted into the WP at the March, 2011 meeting.]
N3092 comment FI 4What effect does defaulting have on triviality? Related to issue 1135, non-public special members defaulted on their first declaration should retain triviality, because they shouldn't be considered user-provided. Related to issue 1137, defaulted member functions that are virtual should not be considered trivial, but there's no reason why non-virtuals could not be.
Furthermore, a class with a non-public explicitly-defaulted constructor isn't ever trivially constructible under the current rules. If such a class is used as a subobject, the constructor of the aggregating class should be trivial if it can access the non-public explicitly defaulted constructor of a subobject.
Suggested resolution: Change the triviality rules so that a class can have a trivial default constructor if the class has access to the default constructors of its subobjects and the default constructors of the subobjects are explicitly defaulted on first declaration, even if said defaulted constructors are non-public.
See also issue 1149.
Rationale (August, 2010):
The consensus of the CWG was that this change should not be made at this point in the standardization process, but that it might be considered at a later date.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 1135.
[Voted into the WP at the March, 2011 meeting.]
Consider the following example:
struct A { A(); ~A() = delete; }; struct B: A { }; B* b = new B;
Under the current rules, B() is not deleted, but is ill-formed because it calls the deleted ~A::A() if it exits via an exception after the completion of the construction of A. A deleted subobject destructor should be added to the list of reasons for implicit deletion in 11.4.5 [class.ctor] and 11.4.5.3 [class.copy.ctor].
Notes from the November, 2010 meeting:
The CWG agreed that a change was needed, but only if one or more base and/or member constructors are non-trivial.
Proposed resolution (January, 2011):
Add a new bullet to 11.4.5 [class.ctor] paragraph 5 as follows:
...A defaulted default constructor for class X is defined as deleted if:
...
X is a non-union class and all members of any anonymous union member are of const-qualified type (or array thereof),
orany direct or virtual base class, or non-static data member with no brace-or-equal-initializer, has class type M (or array thereof) and either M has no default constructor or overload resolution (12.2 [over.match]) as applied to M's default constructor results in an ambiguity or in a function that is deleted or inaccessible from the defaulted default constructor
., orany direct or virtual base class or non-static data member has a type with a destructor that is deleted or inaccessible from the defaulted default constructor.
Add a new bullet to 11.4.5.3 [class.copy.ctor] paragraph 12 as follows:
...A defaulted copy/move constructor for a class X is defined as deleted (9.6.3 [dcl.fct.def.delete]) if X has:
a variant member with a non-trivial corresponding constructor and X is a union-like class,
a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to M's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,
ora direct or virtual base class B that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to B's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,
orany direct or virtual base class or non-static data member of a type with a destructor that is deleted or inaccessible from the defaulted constructor,
for the copy constructor, a non-static data member of rvalue reference type, or
for the move constructor, a non-static data member or direct or virtual base class with a type that does not have a move constructor and is not trivially copyable.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment FI 19It is not clear whether the current specification allows a defaulted copy constructor to call an explicit constructor to copy a base or member subobject, and if so, whether that is desirable or not. See also issues 535 and 1118.
Proposed resolution (August, 2010):
This issue is resolved by the resolution of issue 1051.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 62The new wording describing generated copy constructors (11.4.5.3 [class.copy.ctor] paragraph 16) does not describe the initialization of members with reference type.
See also issue 992.
Proposed resolution (October, 2010):
Change 11.4.5.3 [class.copy.ctor] paragraph 12 as follows:
An implicitly-declared copy/move constructor is an inline public member of its class. A defaulted copy/move constructor for a class X is defined as deleted (9.6.3 [dcl.fct.def.delete]) if X has:
a variant member with a non-trivial corresponding constructor and X is a union-like class,
a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to M's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor, or
a direct or virtual base class B that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to B's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor, or
for the copy constructor, a non-static data member of rvalue reference type, or
for the move constructor, a non-static data member or direct or virtual base class with a type that does not have a move constructor and is not trivially copyable.
Change 11.4.5.3 [class.copy.ctor] paragraph 16 as follows:
The implicitly-defined copy/move constructor for a non-union class X performs a memberwise copy/move of its
subobjectsbases and members. [Note: brace-or-equal-initializers of non-static data members are ignored. See also the example in 11.9.3 [class.base.init]. —end note] The order ofcopyinginitialization is the same as the order of initialization of bases and members in a user-defined constructor (see 11.9.3 [class.base.init]). Let x be either the parameter of the constructor or, for the move constructor, an xvalue referring to the parameter. Eachsubobjectbase or non-static data member is copied/moved in the manner appropriate to its type:
if the subobject is of class type, the copy constructor for the class is used;if the
subobjectmember is an array, each element iscopied, in the manner appropriate to the element typedirect-initialized with the corresponding subobject of x;if a member m has rvalue reference type T&&, it is direct-initialized with static_cast<T&&>(x.m);
otherwise, the base or member is direct-initialized with the corresponding base or member of x.
if the subobject is of scalar type, the built-in assignment operator is used.Virtual base class subobjects shall be
copiedinitialized only once by the implicitly-defined copy/move constructor (see 11.9.3 [class.base.init]).
Delete 11.4.5.3 [class.copy.ctor] paragraph 17:
The implicitly-defined move constructor for a non-union class X performs a memberwise move of its subobjects. [Note: brace-or-equal-initializers of non-static data members are ignored. See also the example in 11.9.3 [class.base.init]. —end note] The order of moving is the same as the order of initialization of bases and members in a user-defined constructor (see 11.9.3 [class.base.init]). Given a parameter named x, each base or non-static data member is moved in the manner appropriate to its type:
a named member m of reference or class type T is direct-initialized with the expression static_cast<T&&>(x.m);
a base class B is direct-initialized with the expression static_cast<B&&>(x);
an array is initialized by moving each element in the manner appropriate to the element type;
a scalar type is initialized with the built-in assignment operator.
Virtual base class subobjects shall be moved only once by the implicitly-defined move constructor (see 11.9.3 [class.base.init]).
Change 11.4.5.3 [class.copy.ctor] paragraph 18 as follows:
The implicitly-defined copy/move constructor for a union X copies the object representation (6.8 [basic.types]) of X.
Change 11.4.5.3 [class.copy.ctor] paragraph 28 as follows:
A copy/move assignment operator that is defaulted and not defined as deleted is implicitly defined whenis assigned a value of its class type or a value of a class type derived from its class typeit is used (6.3 [basic.def.odr]) (e.g., when it is selected by overload resolution to assign to an object of its class type) or when it is explicitly defaulted after its first declaration.
Change 11.4.5.3 [class.copy.ctor] paragraph 30 as follows:
The implicitly-defined copy/move assignment operator for a non-union class X performs memberwise copy/move assignment of its subobjects. The direct base classes of X are assigned first, in the order of their declaration in the base-specifier-list, and then the immediate non-static data members of X are assigned, in the order in which they were declared in the class definition. Let x be either the parameter of the function or, for the move assignment operator, an xvalue referring to the parameter. 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 is usedas if by a call to operator= with the subobject as the object expression and the corresponding subobject of x as a single function argument (as if by explicit qualification; that is, ignoring any possible virtual overriding functions in more derived classes);if the subobject is an array, each element is assigned, in the manner appropriate to the element type;
if the subobject is of scalar type, the built-in assignment operator is used.
It is unspecified whether subobjects representing virtual base classes are assigned more than once by the implicitly-defined copy assignment operator. [Example:
struct V { }; struct A : virtual V { }; struct B : virtual V { }; struct C : B, A { };It is unspecified whether the virtual base class subobject V is assigned twice by the implicitly-defined copy assignment operator for C. —end example] [Note: This does not apply to move assignment, as a defaulted move assignment operator is deleted if the class has virtual bases. —end note]
Delete 11.4.5.3 [class.copy.ctor] paragraph 31:
The implicitly-defined move assignment operator for a non-union class X performs memberwise assignment of its subobjects. The direct base classes of X are assigned first, in the order of their declaration in the base-specifier-list, and then the immediate non-static data members of X are assigned, in the order in which they were declared in the class definition. Given a parameter named x, each subobject is assigned in the manner appropriate to its type:
if the subobject is a named member c of class type C, as if by the expression this->c = static_cast<C&&>(x.c);
if the subobject is a direct base class B, as if by the expression this->B::operator=(static_cast<B&&>(x));
if the subobject is an array, each element is moved, in the manner appropriate to the element type;
if the subobject is of scalar type, the built-in assignment operator is used.
This resolution also resolves issues 1020, 1064 and 1066.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 63The new wording in 11.4.5.3 [class.copy.ctor] specifies the behavior of an implicitly-defined copy constructor for a non-union class (paragraph 16), an implicitly-defined move constructor for a non-union class (paragraph 17), and an implicitly-defined copy constructor for a union (paragraph 18), but not an implicitly-defined move constructor for a union.
Proposed resolution (August, 2010):
This issue is resolved by the resolution of issue 1051.
[Voted into the WP at the March, 2011 meeting.]
11.4.5.3 [class.copy.ctor] paragraphs 6-7 currently read,
A declaration of a constructor for a class X is ill-formed if its first parameter is of type (optionally cv-qualified) X and either there are no other parameters or else all other parameters have default arguments.
A member function template is never instantiated to perform the copy of a class object to an object of its class type. [Example:
struct S { template<typename T> S(T); template<typename T> S(T&&); S(); }; S f(); const S g; void h() { S a( f() ); // does not instantiate member template; // uses the implicitly generated move constructor S a(g); // does not instantiate the member template; // uses the implicitly generated copy constructor }
These paragraphs were previously a single paragraph, and the second sentence was intended to mean that
template <class T> A(T):
will never be instantiated to produce A(A). It should not have been split and the example should not have been amended to include move construction.
Lawrence Crowl: I suggest something along the lines of
A member function template is never instantiated to match the signature of an ill-formed constructor.
Proposed resolution (November, 2010):
Merge 11.4.5.3 [class.copy.ctor] paragraphs 6 and 7 and change the text as follows:
A declaration of a constructor for a class X is ill-formed if its first parameter is of type (optionally cv-qualified) X and either there are no other parameters or else all other parameters have default arguments. A member function template is never instantiated to
perform the copy of a class object to an object of its class typeproduce such a constructor signature. [Example:struct S { template<typename T> S(T);template<typename T> S(T&&);S(); };S f();constS g; void h() {S a( f() ); // does not instantiate member template; // uses the implicitly generated move constructorS a(g); // does not instantiate the member template to produce S::S<S>(S); // uses the implicitlygenerateddeclared copy constructor }
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
It seems odd to have an implicitly declared copy constructor (and the same for the copy assignment operator) if one of the subobjects does not have one. For example,
struct A { A(); A(A&&); }; struct B: A { }; B b; B b2(b); // error when implicitly defining B(B&), should not be declared
If we don't declare it in that case, we need to decide what happens if one base has only a move constructor and another has only a copy constructor.
Notes from the November, 2010 meeting:
The consensus of the CWG was to change the behavior so that all classes have a declaration of a copy constructor, but that it is defined as deleted in the cases where the declaration is omitted by the current rules.
[Voted into the WP at the March, 2011 meeting.]
N3092 comment FI 5A class with a non-public explicitly-defaulted copy constructor isn't ever trivially copyable under the current rules. If such a class is used as a subobject, the copy constructor of the aggregating class should be trivial if it can access the non-public explicitly defaulted copy constructor of a subobject.
See also issue 1145.
Rationale (August, 2010):
The consensus of the CWG was that this change should not be made at this point in the standardization process, but that it might be considered at a later date.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 1135.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
11.4.5 [class.ctor] allows for a defaulted default constructor to be constexpr, but 11.4.5.3 [class.copy.ctor] does not do the same for a defaulted copy constructor. This seems wrong.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 11.4.5.3 [class.copy.ctor] paragraph 14 as follows:
A copy/move constructor that is defaulted and not defined as deleted is implicitly defined if it is odr-used (6.3 [basic.def.odr]) 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 type123 or when it is explicitly defaulted after its first declaration. [Note: the copy/move constructor is implicitly defined even if the implementation elided its odr-use (6.3 [basic.def.odr], 6.7.7 [class.temporary]). —end note] If the implicitly-defined constructor would satisfy the requirements of a constexpr constructor (9.2.6 [dcl.constexpr]), the implicitly-defined constructor is constexpr.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 64According to 11.4.5.3 [class.copy.ctor] paragraph 28,
A copy/move 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.
This sounds as if any assignment to a class object, regardless of whether it is a copy or a move assignment, defines both the copy and move operators. Presumably an assignment should only define the assignment operator chosen by overload resolution for the operation. (Compare the corresponding wording in paragraph 14 for the copy/move constructors: “...implicitly defined if it is used to initialize an object of its class type...”)
Proposed resolution (August, 2010):
This issue is resolved by the resolution of issue 1051.
[Voted into the WP at the November, 2010 meeting.]
The Standard does not define the type of a destructor call. Although that is not of any practical importance, it should do so as a matter of completeness. (_N4778_.7.6.1.4 [expr.pseudo] paragraph 1 defines the type of a pseudo-destructor call as void.)
Proposed resolution (September, 2010):
Change 7.6.1.3 [expr.call] paragraph 3 as follows:
TheIf the postfix-expression designates a destructor (11.4.7 [class.dtor]), the type of the function call expression is void; otherwise, the type of the function call expression is the return type of the statically chosen function (i.e., ignoring the virtual keyword), even if the type of the function actually called is different. This type shall be a complete object type, a reference type or the type void.
[Voted into the WP at the March, 2011 meeting.]
A defaulted destructor should be implicitly defined as deleted if operator delete is deleted or inaccessible.
Proposed resolution (November, 2010):
Change 11.4.7 [class.dtor] paragraph 3 as follows:
...A 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 a deleted destructor or a destructor that is inaccessible from the defaulted destructor,
orany direct or virtual base class has a deleted destructor or a destructor that is inaccessible from the defaulted destructor
.,or, for a virtual destructor, lookup of the non-array deallocation function results in an ambiguity or in a function that is deleted or inaccessible from the defaulted destructor.
A destructor is trivial if...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 39The note in 11.4.7 [class.dtor] paragraph 4 says,
An explictly defaulted definition has no implicit exception-specification.
There are similar notes in 11.4.5.3 [class.copy.ctor] paragraphs 15 and 29.
However, 9.6.2 [dcl.fct.def.default] bullet 2.4 says that a special member function that is explicitly defaulted on its first declaration
is implicitly considered to have the same exception-specification as if it had been implicitly declared (14.5 [except.spec])
The notes are incorrect.
Proposed resolution (August, 2010):
Change 11.4.7 [class.dtor] paragraph 4 as follows:
[Note: an implicitly- declared destructor has an exception-specification (15.4).An explictly defaulted definition has no implicit exception-specification.—end note]
Change 11.4.5.3 [class.copy.ctor] paragraph 15 as follows:
[Note: an implicitly-declared copy/move constructor has an exception-specification (15.4).An explicitly-defaulted definition (8.4.2) has no implicit exception-specification.—end note]
Change 11.4.5.3 [class.copy.ctor] paragraph 29 as follows:
[Note: An implicitly-declared copy/move assignment operator has an exception- specification (15.4).An explicitly-defaulted definition has no implicit exception-specification.—end note]
[Voted into the WP at the November, 2010 meeting as paper N3204.]
N3092 comment GB 40A user-declared destructor that does not supply an exception specification should be considered as if declared noexcept(true) rather than noexcept(false).
(Duplicate of issue 1123.)
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The current wording of 11.4.7 [class.dtor] paragraph 7 says,
After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X's direct non-variant members...
This is incorrect; it is only the non-static members that are destroyed.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 11.4.9.3 [class.static.data] paragraph 3,
If a static data member is of const literal type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [Note: In both these cases, the member may appear in constant expressions. —end note]
However, 7.7 [expr.const] bullet 2.7 allows only integral and enumeration types in constant expressions for the const case; the other types require constexpr to be considered constant expressions.
Proposed resolution (November, 2010):
Change 11.4.9.3 [class.static.data] paragraph 3 as follows:
If a non-volatile const static data member is ofconst literalintegral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression (7.7 [expr.const]). A static data member of literal type can be declared in the class definition with the constexpr specifier...
[Voted into WP at August, 2010 meeting.]
The type long long is missing from the list of bit-field types in 11.4.10 [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 7.3.7 [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 11.4.10 [class.bit] paragraph 3 as follows:
...It is implementation-defined whether a plain (neither explicitly signed nor unsigned) char, short, intor, long, or long long bit-field is signed or unsigned...
[Voted into the WP at the March, 2011 meeting.]
According to 11.6 [class.local] paragraph 1,
Declarations in a local class can use only type names, static variables, extern variables and functions, and enumerators from the enclosing scope.
This would presumably make both of the members of S2 below ill-formed:
void test () { const int local_const = 7; struct S2 { int member:local_const; void f() { int j = local_const; } }; }
Should there be an exception to this rule for constant values? Current implementations seem to accept the reference to local_const in the bit-field declaration but not in the member function definition. Should they be the same or different?
Notes from the September, 2008 meeting:
The CWG agreed that both uses of local_const in the example above should be accepted. The intent of the restriction was to avoid the need to pass a frame pointer into local class member functions, so uses of local const variables as values should be permitted.
Notes from the October, 2009 meeting:
There was interest in an approach that would allow explicitly-captured constants to appear in constant expressions but also to be “used.” Another suggestion was to have variables captured if they appear in either “use” or “non-use” contexts.
Proposed resolution (February, 2011):
Change 7.5.6 [expr.prim.lambda] paragraph 17 as follows:
Every id-expression that is an odr-use (6.3 [basic.def.odr]) of an entity captured by copy is transformed into an access to the corresponding unnamed data member of the closure type. [Note: an id-expression that is not an odr-use refers to the original entity, never to a member of the closure type. Furthermore, such an id-expression does not cause the implicit capture of the entity. —end note] If this is captured, each odr-use of this is transformed into an access to the corresponding unnamed data member of the closure type, cast (7.6.3 [expr.cast]) to the type of this. [Note: the cast ensures that the transformed expression is a prvalue. —end note] [Example:
void f(const int*); void g() { const int N = 10; [=] { int arr[N]; // OK: not an odr-use, refers to automatic variable f(&N); // OK: causes N to be captured; &N points to the // corresponding member of the closure type } }—end example]
...Declarations in a local class
can use only type names, static variables, extern variables and functions, and enumerators from theshall not odr-use (6.3 [basic.def.odr]) a variable with automatic storage duration from an enclosing scope. [Example:int x; void f() { static int s ; int x; const int N = 5; extern intgq(); struct local { int g() { return x; } // error: odr-use of automatic variable xhas automatic storage durationint h() { return s; } // OK int k() { return ::x; } // OK int l() { returngq(); } // OK int m() { return N; } // OK: not an odr-use int* n() { return &N; } // error: odr-use of automatic variable N }; } local* p = 0; // error: local not in scope—end example]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
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) [SUPERSEDED]:
Change 11.8 [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) [SUPERSEDED]:
Change 11.8 [class.access] paragraphs 6-7 as follows:
All access controls in 11.8 [class.access] affect the ability to access a class member name from a declaration of a particular
scopeentity, 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 gis a friendand 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]
[Voted into WP at August, 2010 meeting.]
The changes for delegating constructors overlooked the need to change 11.9.3 [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 9.5 [dcl.init]);
otherwise, the subobject indicated by mem-initializer-id is direct-initialized using expression-list as the initializer (see 9.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 11.9.3 [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 areA 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 9.5 [dcl.init]);
otherwise, the subobject indicated by mem-initializer-id is direct-initialized using expression-list as the initializer (see 9.5 [dcl.init]).
...
The initialization
of each base and memberperformed 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 11.9.3 [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-iddesignated subobject (or, in the case of a delegating constructor, the complete class object) according to the initialization rules of 9.5 [dcl.init] for direct-initialization.[Example: ...
—end example] The initialization
of each base and memberperformed by each mem-initializer constitutes a full-expression...
Change 11.9.3 [class.base.init] paragraph 8 as follows:
IfIn a non-delegating constructor, if a given non-static data member or base class is not named by a mem-initializer-id...
Change 11.9.3 [class.base.init] paragraph 10 as follows:
InitializationIn a non-delegating constructor, initialization proceeds in the following order:
Change 11.9.3 [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 9.5.5 [dcl.init.list] paragraph 1 as follows:
as a base-or-member initializer in a
mem-initializer (11.9.3 [class.base.init])
References to non-static data members inside the body of a non-static member function (which includes the mem-initializers of a constructor definition) are implicitly transformed to member access expressions using (*this) (11.4.3 [class.mfct.non.static] paragraph 3) . Although _N4567_.5.1.1 [expr.prim.general] paragraph 3 permits use of this in a brace-or-equal-initializer for a non-static data member, 11.9.3 [class.base.init] does not give details about the value of this in that context, and there is no parallel to the transformation of member references into class member access expressions. This leaves use of non-static data members in this context underspecified.
Proposed resolution (March, 2011):
This issue is resolved by the resolution of issues 1017 and 1207 in document N3282.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The current wording of 11.9.3 [class.base.init] paragraph 5 says,
A ctor-initializer may initialize the member of an anonymous union that is a member of the constructor's class.
The wording “the member” is strange; furthermore, this should be restricted to non-static data members. That could be accomplished by using the existing term “variant members,” which is defined in 11.5 [class.union] paragraph 8 to be “the non-static data members of all anonymous unions that are members of” the class (which by definition must be non-static data members, since a storage class specifier is not allowed on an anonymous union in class scope).
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 11.9.5 [class.cdtor] paragraph 4,
Member functions, including virtual functions (11.7.3 [class.virtual]), can be called during construction or destruction (11.9.3 [class.base.init]). When a virtual function is called directly or indirectly from a constructor (including the mem-initializer or brace-or-equal-initializer for a non-static data member) or from a destructor, and the object to which the call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor's own class or in one of its bases, but not a function overriding it in a class derived from the constructor or destructor's class, or overriding it in one of the other base classes of the most derived object (6.7.2 [intro.object]).
This is clear regarding virtual functions called during the initialization of a class's members, but it does not specifically address the polymorphic behavior of the class during the destruction of the members. Presumably the behavior during destruction should be the exact inverse of that of the constructor, i.e., the class's virtual functions should still be called during member destruction.
In addition, the wording
If the virtual function call uses an explicit class member access (7.6.1.5 [expr.ref]) and the object-expression refers to the object under construction or destruction but its type is neither the constructor or destructor's own class or one of its bases, the result of the call is undefined.
should be clarified that “refers to the object under construction” does not include referring to member subobjects but only to base or more-derived classes of the class under construction or destruction.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment DE 11It is unclear whether copy elision is permitted when returning a parameter of class type. If not, it should still be possible to move, rather than copy, the return value.
Suggested resolution: Amend paragraph 34 to explicitly exclude function parameters from copy elision. Amend paragraph 35 to include function parameters as eligible for move-construction.
Proposed resolution (September, 2010):
Change 11.4.5.3 [class.copy.ctor] paragraph 34 bullets 1 and 2 as follows:
in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function's return value
in a throw-expression, when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object (14.2 [except.throw]) can be omitted by constructing the automatic object directly into the exception object
Change 11.4.5.3 [class.copy.ctor] paragraph 35 as follows:
When the criteria for elision of a copy operation are met, or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution...
[Voted into the WP at the November, 2010 meeting.]
According to Clause 12 [over] paragraph 1,
Only function declarations can be overloaded; object and type declarations cannot be overloaded.
There are two problems with this statement. First, it does not allow for overloading function templates. (There may be other places in the Standard that refer to “functions” but should include function templates, as well.)
Second, the restriction on “object” declarations should presumably be on “variable” declarations instead, since one can also not overload reference declarations.
Proposed resolution (September, 2010):
Change Clause 12 [over] paragraph 1 as follows:
...Only function and function template declarations can be overloaded;objectvariable and type declarations cannot be overloaded.
[Voted into the WP at the November, 2010 meeting.]
The resolution of issue 899 needs to be extended to cover move constructors and template constructors as well.
Proposed resolution (August, 2010):
When the type of the initializer expression is a class type
“cv S”, the non-explicit conversion
functions of S and its base classes are considered. When
initializing a temporary to be bound to the first parameter of a
copy constructor (11.4.5.3 [class.copy.ctor])
that takes a reference to possibly cv-qualified T as its
first argument, called with a single argument in the context of
direct-initialization, explicit conversion functions are also
considered. Those that are not hidden within S and yield a
type whose cv-unqualified version is the same type as T or is
a derived class thereof are candidate functions. Conversion functions
that return “reference to X” return lvalues or
xvalues, depending on the type of reference, of type X and
are therefore considered to yield X for this process of
selecting candidate functions.
For example
struct C {
template <class T = int> C(C&, T = 0);
};
struct A {
explicit operator C&() const;
};
int main() {
A a;
C c (a); // should use template constructor
}
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
N3092 comment US 66Overload resolution should first look for a viable list constructor, then look for a non-list constructor if no list constructor is viable.
Proposed resolution (August, 2010) [SUPERSEDED]:
Change 9.5.5 [dcl.init.list] bullet 3.5 as follows:
Otherwise, if T is a class type,
constructors are considered. If T has an
initializer-list constructor, the argument list consists of
the initializer list as a single argument; otherwise, the
argument list consists of the elements of the initializer
list. The applicable constructors are enumerated
(12.2.2.8 [over.match.list]) and the best one
is chosen through overload resolution (12.2.2.8 [over.match.list], 12.2 [over.match]). If a
narrowing conversion (see below) is required to convert any
of the arguments, the program is ill-formed.
[Example:...
Change 12.2.2.8 [over.match.list] as follows:
When objects of non-aggregate class type T are list-initialized (9.5.5 [dcl.init.list]), overload resolution selects the constructor in two phases
as follows, where T is the cv-unqualified class type of the object being initialized:
If T has an initializer-list constructor (9.5.5 [dcl.init.list]),Initially, the candidate functions are the initializer-list constructors (9.5.5 [dcl.init.list]) of the class T and the argument list consists of the initializer list as a single argument.
; otherwise,If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.
For direct-list-initialization, the candidate functions are all the constructors of the class T.
ForIn copy-list-initialization,the candidate functions are all the constructors of T. However,if an explicit constructor is chosen, the initialization is ill-formed. [Note: This differs from other situations (12.2.2.4 [over.match.ctor], 12.2.2.5 [over.match.copy]), where only converting constructors are considered for copy-initialization. This restriction only applies if this initialization is part of the final result of overload resolution. —end note]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The changes for issue 990 did not address the description of overload resolution when an argument is an empty braced-init-list. For example:
struct A { A(); A(std::initializer_list<int>); A(std::initializer_list<double>); }; A a{}; // OK void f(A); void g() { f({}); // ambiguous }
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 67To determine whether there is an implicit conversion sequence that converts the argument to the corresponding parameter, 12.2.3 [over.match.viable] paragraph 3 uses 12.2.4.2 [over.best.ics] instead of just saying “there is an ICS if-and-only-if a copy initialization would be well-formed.” Apparently this is intended, but to a casual reader or an implementor reading these rules for the first time for a new implementation, it's not clear why that's desirable. A note should be added to explain the rationale.
Proposed resolution (August, 2010):
Change 12.2.4.2.5 [over.ics.ref] paragraph 3 as follows:
Except for an implicit object parameter, for which see 12.2.2 [over.match.funcs], a standard conversion sequence cannot be formed if it requires binding an lvalue referenceto non-constother than a reference to a non-volatile const type to an rvalue or binding an rvalue reference to an lvalue. [Note:...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The current wording makes some calls involving aggregate initialization ambiguous that should not be. For example, the calls below to f and g should each prefer the second overload:
struct A { int i; }; void f (const A &); void f (A &&); void g (A, double); void g (A, int); int main() { f ( { 1 } ); g ( { 1 }, 1 ); }
Proposed resolution (August, 2010) [SUPERSEDED]:
Change 12.2.4.3 [over.ics.rank] bullet 3.2 as follows:
User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 if they contain the same user-defined conversion function or constructor or aggregate initialization and if the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Currently overload resolution does not distinguish between binding an lvalue reference to a function lvalue and an rvalue reference to a function lvalue. The former should be preferred.
In a related point, the current wording of 12.2.4.2.5 [over.ics.ref] paragraph 3 forbids binding an rvalue reference to an lvalue; this should be changed to allow binding an rvalue reference to a function lvalue.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 68Overload resolution within the operand of a unary & operator is done by selecting the function “whose type matches the target type required in the context.” The criterion for determining whether the types match, however, is not defined. At least three possibilities suggest themselves:
The types are identical.
The source type can be implicitly converted to the target type.
The expression would be well-formed if the function under consideration were not overloaded.
This question arises for pointer-to-member types, where there is an implicit conversion from a pointer-to-base-member to a pointer-to-derived-member, as well as when the context is an explicit type conversion (which allows, for static_cast, a conversion from pointer-to-derived-member to a pointer-to-base-member and, in the reinterpret_cast interpretation of functional and old-style casts, essentially any conversion).
Notes from the August, 2010 meeting:
CWG observed that the only case in which the types might not match exactly was for pointers to member functions. In this case, the approach should be to ignore the class of which the functions are members and just match (exactly) on the function type.
Proposed resolution (September, 2010):
Change 12.3 [over.over] paragraph 1 as follows:
...The function selected is the one whose typematchesis identical to the function type of the target type required in the context. [Note: That is, the class of which the function is a member is ignored when matching a pointer-to-member-function type. —end note] The target can be...
Non-member functions and static member functions match targets of type “pointer-to-function” or “reference- to-function.” Nonstatic member functions match targets of type “pointer-to-member-function;.”the function type of the pointer to member is used to select the member function from the set of overloaded member functions.If a non-static member function is selected, the reference to the overloaded function name is required to have the form of a pointer to member as described in 7.6.2.2 [expr.unary.op].
[Voted into the WP at the November, 2010 meeting.]
According to Clause 13 [temp] paragraph 2,
In a function template declaration, the last component of the declarator-id shall be a template-name or operator-function-id (i.e., not a template-id).
This is too restrictive; it should also allow conversion-function-ids and literal-operator-ids.
Proposed resolution (September, 2010):
Change Clause 13 [temp] paragraph 2 as follows:
A template-declaration can appear only as a namespace scope or class scope declaration. In a function template declaration, the last component of the declarator-id shall not be a template-idtemplate-name or operator-function-id (i.e., not a template-id). [Note:inThat last component may be an identifier, an operator-function-id, a conversion-function-id, or a literal-operator-id. In a class template declaration, if the class name is a simple-template-id, the declaration declares a class template partial specialization (13.7.6 [temp.spec.partial]). —end note]
[Voted into the WP at the March, 2011 meeting.]
The removal of the export keyword inadvertently deleted the text (previously found in Clause 13 [temp] paragraph 8 of the 2003 Standard),
A non-exported template must be defined in every translation unit in which it is implicitly instantiated (13.9.2 [temp.inst]), unless the corresponding specialization is explicitly instantiated (13.9.3 [temp.explicit]) in some translation unit; no diagnostic is required.
This requirement must be reinstated.
Proposed resolution (January, 2011):
Add the following as a new paragraph following Clause 13 [temp] paragraph 5:
A function template, member function of a class template, or static data member of a class template shall be defined in every translation unit in which it is implicitly instantiated (13.9.2 [temp.inst]), unless the corresponding specialization is explicitly instantiated (13.9.3 [temp.explicit]) in some translation unit; no diagnostic is required.
[Voted into WP at August, 2010 meeting.]
13.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 (13.10.3 [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 (13.10.3 [temp.deduct])...
Proposed resolution (February, 2010):
Change 13.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 or class template partial specializations because template argumentsmightcan be deduced (13.10.3 [temp.deduct]). [Example:...
[Voted into the WP at the March, 2011 meeting as part of paper N3270.]
Consider an example like:
template <typename T, T Value> struct bar { }; template <typename... T, T ...Value> void foo(bar<T, Value>);
The current wording in 13.2 [temp.param] is unclear as to whether this is permitted or not. For comparison, 9.3.4.6 [dcl.fct] paragraph 13 says,
A declarator-id or abstract-declarator containing an ellipsis shall only be used in a parameter-declaration. Such a parameter-declaration is a parameter pack (13.7.4 [temp.variadic]). When it is part of a parameter-declaration-clause, the parameter pack is a function parameter pack (13.7.4 [temp.variadic]). [Note: Otherwise, the parameter-declaration is part of a template-parameter-list and the parameter pack is a template parameter pack; see 13.2 [temp.param]. —end note] A function parameter pack, if present, shall occur at the end of the parameter-declaration-list. The type T of the declarator-id of the function parameter pack shall contain a template parameter pack; each template parameter pack in T is expanded by the function parameter pack.
The requirement here that the type of a function parameter pack must contain a template parameter pack is not repeated for template non-type parameters in 13.2 [temp.param], nor is the statement that it expands the template parameter pack.
A related issue is that neither function nor template parameter packs are listed in 13.7.4 [temp.variadic] paragraph 4 among the contexts in which a pack expansion can appear.
Proposed resolution (November, 2010):
Change 7.6.2.5 [expr.sizeof] paragraph 5 as follows:
The identifier in a sizeof... expression shall name a parameter pack. The sizeof... operator yields the number of arguments provided for the parameter pack identifier.The parameter pack is expanded (13.7.4 [temp.variadic]) by the sizeof... operatorA sizeof... expression is a pack expansion (13.7.4 [temp.variadic]). [Example:...
Change 9.3.4.6 [dcl.fct] paragraph 13 as follows:
A declarator-id or abstract-declarator containing an ellipsis shall only be used in a parameter-declaration. Such a parameter-declaration is a parameter pack (13.7.4 [temp.variadic]). When it is part of a parameter-declaration-clause, the parameter pack is a function parameter pack (13.7.4 [temp.variadic]). [Note: Otherwise, the parameter-declaration is part of a template-parameter-list and the parameter pack is a template parameter pack; see 13.2 [temp.param]. —end note]The type T of the declarator-id of the function parameter pack shall contain a template parameter pack; each template parameter pack in T is expanded by the function parameter packA function parameter pack is a pack expansion (13.7.4 [temp.variadic]). [Example:...
Change 13.2 [temp.param] paragraph 15 as follows:
If a template-parameter is a type-parameter with an ellipsis prior to its optional identifier or is a parameter-declaration that declares a parameter pack (9.3.4.6 [dcl.fct]), then the template-parameter is a template parameter pack (13.7.4 [temp.variadic]). A template parameter pack that is a parameter-declaration whose type contains one or more unexpanded parameter packs is a pack expansion. Similarly, a template parameter pack that is a type-parameter with a template-parameter-list containing one or more unexpanded parameter packs is a pack expansion. [Example:
template <class... Types> class Tuple; // Types is a template type parameter pack and a pack expansion template <class T, int... Dims> struct multi_array; // Dims is a non-type template parameter pack but not a pack expansion template <class T, T... Values> struct static_array; // Values is a non-type template parameter pack and a pack expansion
Change 13.7.4 [temp.variadic] paragraphs 4-6 and add a new paragraph 7 as follows:
A pack expansion
is a sequence of tokens that names one or more parameter packs, followed by an ellipsis. The sequence of tokens is called the pattern of the expansion; its syntaxconsists of a pattern and an ellipsis, the instantiation of which produces zero or more instantiations of the pattern in a list (described below). The form of the pattern depends on the context in which the expansion occurs. Pack expansions can occur in the following contexts:
In a function parameter pack (9.3.4.6 [dcl.fct]); the pattern is the parameter-declaration without the ellipsis.
In a template parameter pack that is a pack expansion (13.2 [temp.param]):
if the template parameter pack is a parameter-declaration; the pattern is the parameter-declaration without the ellipsis,
if the template parameter pack is a type-parameter with a template-parameter-list; the pattern is the corresponding type-parameter without the ellipsis.
...
In a sizeof... expression (7.6.2.5 [expr.sizeof]), the pattern is an identifier.
[Example:...
A parameter pack whose name appears within the pattern of a pack expansion is expanded by that pack expansion. An appearance of the name of a parameter pack is only expanded by the innermost enclosing pack expansion. The pattern of a pack expansion shall name one or more parameter packs that are not expanded by a nested pack expansion; such parameter packs are called unexpanded parameter packs in the pattern. All of the parameter packs expanded...
... void g(Args ... args) { // OK: “Args” is expanded by the function parameter pack “args” ...The instantiation of
ana pack expansion that is not a sizeof... expression produces a list...The instantiation of a sizeof... expression (7.6.2.5 [expr.sizeof]) produces an integral constant containing the number of elements in the parameter pack it expands.
This resolution also resolves issues 1182 and 1183.
Additional note (February, 2011):
A problematic case is a function like
template<typename... T, T... t> void f(T...) { }
where each element of the nontype pack actually has a different type. This causes problems for template argument deduction, since T and t are supposed to be deduced independently, but they're linked through their sizes. There doesn't appear to be any use case for this kind of example, so it should be ill-formed.
The rule should probably be to consider a non-type template parameter pack that expands any template parameter packs from the same template-parameter-list as ill-formed.
[Voted into the WP at the November, 2010 meeting.]
std::nullptr_t is not currently allowed by 13.2 [temp.param] paragraph 4 to be used as the type of a non-type template parameter. However, this could arise for a template with a non-type template parameter with a dependent type in a template intended for use with pointers, e.g.,
template<typename T, T t> void f(); ... f<std::nullptr_t, nullptr>();
or in a case of delegation.
Proposed resolution (September, 2010):
Change 13.2 [temp.param] paragraph 4 as follows:
A non-type template-parameter shall have one of the following (optionally cv-qualified) types:
integral or enumeration type,
pointer to object or pointer to function,
lvalue reference to object or lvalue reference to function,
pointer to member
.,std::nullptr_t.
[Voted into the WP at the March, 2011 meeting.]
Since there appear to be no restrictions against it, it would appear that default arguments and template parameter packs can be used with template aliases just as with other templates. If that is the case, then, the current wording in 13.2 [temp.param] paragraph 11 requires adjustment:
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 class template is a template parameter pack, it shall be the last template-parameter.
Presumably these restrictions should also apply to template aliases, but as written, they only apply to class templates.
Proposed resolution (January, 2011):
Change 13.2 [temp.param] paragraph 11 as follows:
If a template-parameter of a class template or alias 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 or alias template is a template parameter pack, it shall be the last template-parameter. [Note:...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 13.2 [temp.param] paragraph 11,
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 or class template partial specializations because template arguments can be deduced (13.10.3 [temp.deduct])...
Should the Standard forbid non-final parameter packs in cases where the declaration does not allow the template arguments to be deduced? For example,
template<typename... T, typename... U> void f() { } template<typename... T, typename U> void g() { }
(See also issue 549.)
[Voted into WP at August, 2010 meeting.]
The following is the wording from 13.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 13.3 [temp.names] paragraph 5 as follows:
If aA name prefixed by the keyword templateis not the name of a template,shall be a template-id or the name shall refer to a class templatethe 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]
[Voted into WP at August, 2010 meeting.]
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 13.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 13.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 (March, 2010):
Change 13.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 object or pointer expression of the postfix-expression or the nested-name-specifier in the qualified-idexplicitlydepends on atemplate-parametertemplate parameter (13.8.3 [temp.dep]) but does not refer to a member of the current instantiation (13.8.3.2 [temp.dep.type]), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template. [Example:...
[Voted into WP at August, 2010 meeting.]
According to 13.4.3 [temp.arg.nontype] paragraph 1, bullet 3, one of the acceptable forms of a non-type, non-template template argument is:
the address of an object or function... expressed as & id-expression where the & is optional if the name refers to a function or array, or if the corresponding template-parameter is a reference
It is not clear from this whether a template argument like (&i) satisfies the requirement or not.
Notes from the March, 2009 meeting:
The consensus of the CWG was that the parentheses should be allowed.
Proposed resolution (February, 2010):
Change 13.4.3 [temp.arg.nontype] bullet 1.3 as follows:
the address of an object or function with external linkage,
including function templates and function template-ids but
excluding non-static class members, expressed (ignoring
parentheses) as & id-expression, except
that where the & is optional
may be omitted if the name refers to a function or
array, or and shall be omitted if the
corresponding template-parameter is a reference; or
[Drafting note: The change requiring the omission of the & in the reference case fixes an existing problem that is not related to this issue.]
[Voted into the WP at the November, 2010 meeting.]
The current wording of 13.4.3 [temp.arg.nontype] paragraph 1 does not prevent the use a reference as a non-type template argument. It simply requires
the address of an object or function with external linkage... expressed as & id-expression...
This would presumably (but unintentionally?) allow an example like the following:
struct S { }; template<S*> struct X { }; S s; S& ref = s; X<&ref> xr; // well-formed?
The expression &ref is not a constant expression, but the current wording of 13.4.3 [temp.arg.nontype] does not require a constant expression.
Proposed resolution (September, 2010):
Change 13.4.3 [temp.arg.nontype] bullet 1.3 as follows:
a constant expression (7.7 [expr.const]) that designates the address of an object or function with external linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference; or
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 69The standard permits the address of a thread_local object as a non-type template argument. The addresses of these objects are not constant, however. Such template arguments should require objects of static storage duration.
Proposed resolution (August, 2010):
This issue is resolved by the resolution of issue 1155.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment DE 12Now that local classes can be used as template arguments, it seems odd that there are “external linkage” restrictions on non-type template parameters. The addresses of objects and functions with internal linkage should be permitted as well.
Proposed resolution (August, 2010):
Change 13.4.3 [temp.arg.nontype] bullet 1.3 as follows:
the address of an object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates...
This resolution also resolves issue 1154.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The example in 13.6 [temp.type] paragraph 1 reads in significant part,
template<template<class> class TT> struct X { }; template<class> struct Y { }; template<class T> using Z = Y<T>; X<Y> y; X<Z> z;
and says that y and z have the same type.
This would only be true if alias template Z were considered to be equivalent to class template Y. However, 13.7.8 [temp.alias] describes equivalence only for specializations of alias templates, not for the alias templates themselves. Either such rules should be specified, which could be tricky, or the example should be deleted.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Presumably an out-of-class definition for an opaque enumeration member of a class template is intended to be allowed; however, the current wording of 13.7.2 [temp.class] provides only for out-of-class definitions of member functions, member classes, static data members, and member templates, not for opaque enumerations.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change Clause 13 [temp] paragraph 1 as follows:
...The declaration in a template-declaration shall
declare or define a function or class, or
define a member function, a member class, a member enumeration, or a static data member of a class template or of a class nested within a class template, or
...
Change 13.7.2 [temp.class] paragraph 3 as follows:
When a member function, a member class, a member enumeration, a static data member or a member template of a class template is defined outside of the class template definition...
Add a new section following 13.7.2.5 [temp.static]:
14.5.1.4 Enumeration members of class templates [temp.mem.enum]
An enumeration member of a class template may be defined outside the class template definition. [Example:
template<class T> struct A { enum E: T; }; A<int> a; template<class T> enum A<T>::E: T { e1, e2 }; A<int>::E e = A<int>::e1;—end example]
Change 13.9 [temp.spec] paragraph 2 as follows:
A function instantiated from a function template is called an instantiated function. A class instantiated from a class template is called an instantiated class. A member function, a member class, a member enumeration, or a static data member of a class template instantiated from the member definition of the class template is called, respectively, an instantiated member function, member class, member enumeration, or static data member. A member function...
Change 13.9.2 [temp.inst] paragraph 1 as follows:
...The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions or default arguments, of the class member functions, member classes, scoped member enumerations, static data members and member templates; and it causes the implicit instantiation of the definitions of unscoped member enumerations and member anonymous unions. Unless a member...
Change 13.9.4 [temp.expl.spec] paragraph 1 as follows:
An explicit specialization of any of the following:
function template
class template
member function of a class template
static data member of a class template
member class of a class template
member enumeration of a class template
member class template of a class or class template
member function template of a class or class template
can be declared by a declaration introduced by template<>...
Change 13.9.4 [temp.expl.spec] paragraph 4 as follows:
A member function, a member class, a member enumeration, or a static data member of a class template may be explicitly specialized for a class specialization that is implicitly instantiated...
Add the indicated text to the example in 13.9 [temp.spec] paragraph 6:
template<> void sort<>(Array(<char*>& v); // OK: sort<char*> not yet used template<class T> struct A { enum E: T; enum class S: T; }; template<> enum A<int>::E: int { eint }; // OK template<> enum class A<int>::S: int { sint }; // OK template<class T> enum A<T>::E: T { eT }; template<class T> enum class A<T>::S: T { sT }; template<> enum A<char>::E: int { echar }; // ill-formed, A<char>::E was instantiated when A<char> was instantiated template<> enum class A<char>::S: int { schar }; // OK
Change 13.9.4 [temp.expl.spec] paragraph 7 as follows:
The placement of explicit specialization declarations for function templates, class templates, member functions of class templates, static data members of class templates, member classes of class templates, member enumerations of class templates, member class templates of class templates, member function templates...
Type matching rules aren't well-specified in the current Standard, but it seems reasonable to say that if a declaration uses decltype, its definition must do so as well. For example, the following should be ill-formed:
template<class T, T* u> struct S { decltype(u) foo(T); }; template<class T, T *u> T* S<T, u>::foo(T) { return nullptr; }
Proposed resolution (March, 2011):
This issue is resolved by the resolution of issue 1057 in document N3262.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
It was intended for empty pack expansions to be useful in contexts like base-specifiers, e.g.,
template<class... T> struct A : T... {}; A<> x; // ok?
However, the current wording provides no description of how that might work. (More generally, the problem arises in any context where the pack expansion follows a token that should only be present when the pack expansion is non-empty: following another argument in a function call, etc.)
[Voted into the WP at the March, 2011 meeting as part of paper N3270.]
According to 13.7.4 [temp.variadic] paragraph 4,
A pack expansion is a sequence of tokens that names one or more parameter packs, followed by an ellipsis.
This is contradicted by 7.6.2.5 [expr.sizeof] paragraph 5, which describes sizeof...(Types) as an expansion, as well as the case where the expansion appears in a declarator like the example given in 9.3.4.6 [dcl.fct] paragraph 13:
template<typename... T> void f(T (* ...t)(int, int));
This is also described as a pack expansion, although it does not fit the syntactic summary.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 778.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Should the Standard allow declarations of variadic templates or member functions of class templates where only an empty expansion would be well-formed? For example,
template<typename ... T> struct A { void operator++(int, T... t); }; template<typename ... T> union X: T... { }; template<typename ... T> struct A: T..., T... { };
[Voted into WP at August, 2010 meeting.]
13.7.5 [temp.friend] bullet 1.3 says:
if the name of the friend is a qualified-id and a matching specialization of a function template is found in the specified class or namespace, the friend declaration refers to that function template specialization, otherwise,
I'm not sure this says what it's supposed to say. For example:
namespace N { template<class T> int f(T); } class A { friend int N::f(int); int m; A(); }; namespace N { template< class T > int f(T) { A a; // ok for T=int? return a.m; // ok for T=int? } } int m = N::f(42); // ok? char c = N::f('a'); // Clearly ill-formed.
The key is that the wording talks about a “matching specialization,” which to me means that N::f<int> is befriended only if that specialization existed in N before the friend declaration. So it's ill-formed as written, but if we move the call to N::f<int> up to a point before the definition of A, it's well-formed.
That seems surprising, especially given that the first bullet does not require a pre-existing specialization. So I suggest replacing bullet 3 with something like:
if the name of the friend is a qualified-id and a matching function template is found in the specified class or namespace, the friend declaration refers to the deduced specialization of that function template, otherwise,
Proposed resolution (June, 2010):
Change 13.7.5 [temp.friend] bullet 1.3 as follows:
...For a friend function declaration that is not a template declaration:
...
if the name of the friend is a qualified-id and a matching
specialization of afunction template is found in the specified class or namespace, the friend declaration refers tothat function template specializationthe deduced specialization of that function template (14.8.2.6 [temp.deduct.decl]), otherwise,...
(This resolution depends on that of issue 873; in particular, the cross-reference to 14.8.2.6 [temp.deduct.decl] refers to a new section added by the resolution of that issue.)
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Given an example like
template<typename T, typename U> struct Outer { template<typename X, typename Y> struct Inner; template<typename Y> struct Inner<T, Y> {}; template<typename Y> struct Inner<U, Y> {}; }; Outer<int, int> outer; // #1 Outer<int, int>::Inner<int, float> inner; // #2
Is #1 ill-formed because of the identical partial specializations? If not, presumably #2 is ill-formed because of the resulting ambiguity (13.7.6.2 [temp.spec.partial.match] paragraph 1).
Notes from the November, 2010 meeting:
The instantiation of Outer<int,int> results in duplicate declarations of the partial specialization, which are ill-formed by 11.4 [class.mem] paragraph 1. No normative change is required, but it might be helpful to add an example like this somewhere.
[Voted into WP at August, 2010 meeting.]
The Standard does not specify how member and nonmember function templates are to be ordered. This question arises with an example like the following:
struct A { template<class T> void operator<<(T&); }; template<class T> struct B { }; template<class T> void operator<<(A&, B<T>&); int main() { A a; B<A> b; a << b; }
The two candidates for “a << b” are:
How should we treat the implicit this parameter of #1 and the explicit first parameter of #2?
Option 0: Make them unordered.
Option 1: If either function is a non-static member function, ignore any this parameter and ignore the first parameter of any non-member function. This option will select #2, as “B<T>&” is more specialized than “T&”.
Option 2: Treat the this parameter as if it were of reference to object type, and then perform comparison to the first parameter of the other function. The other function's first parameter will either be another this parameter, or it will be a by-value or by-reference object parameter. In the example above, this option will also select #2.
The difference between option 1 and option 2 can be seen in the following example:
struct A { }; template<class T> struct B { template<typename R> int operator*(R&); // #1 }; template <typename T> int operator*(T&, A&); // #2 int main() { A a; B<A> b; b * a; }
Should this select #1, select #2, or be ambiguous? Option 1 will select #2, because “A&” is more specialized than “T&”. Option 2 will make this example ambiguous, because “B<A>&” is more specialized than “T&”.
If one were considering two non-member templates,
template <typename T> int operator*(T&, A&); // #2 template <typename T, typename R> int operator*(B<A>&, R&); // #3
the current rules would make these unordered. Option 2 thus seems more consistent with this existing behavior.
Notes from the April, 2006 meeting:
The group favored option 2.
Proposed resolution (February, 2010):
Change 13.7.7.3 [temp.func.order] paragraph 3 as follows:
...and substitute it for each occurrence of that parameter in the function type of the template. If only one of the function templates is a non-static member, that function template is considered to have a new first parameter inserted in its function parameter list. The new parameter is of type “reference to cv A,” where cv are the cv-qualifiers of the function template (if any) and A is the class of which the function template is a member. [Note: This allows a non-static member to be ordered with respect to a nonmember function and for the results to be equivalent to the ordering of two equivalent nonmembers. —end note] [Example:
struct A { }; template<class T> struct B { template<typename R> int operator*(R&); // #1 }; template<typename T, typename R> int operator*(T&, R&); // #2 // The declaration of B::operator* is transformed into the equivalent of // template<typename R> int operator*(B<A>&, R&); // #1a int main() { A a; B<A> b; b * a; // calls #1a }—end example]
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 7013.10.3.5 [temp.deduct.partial] paragraph 3 specifies that the deduction used in partial ordering in a non-call context is based on the complete function type of the function templates. The wording in 13.7.7.3 [temp.func.order] paragraph 2 (and echoed in paragraph 4) reflects an earlier specification, however, saying that the deduction uses only “the function parameter types, or in the case of a conversion function the return type.” This is a contradiction. The wording in 13.7.7.3 [temp.func.order] should be changed.
Proposed resolution (September, 2010):
Change 13.7.7.3 [temp.func.order] paragraph 2 as follows:
Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the functionparameter types, or in the case of a conversion function the returntype. The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process.
Change 13.7.7.3 [temp.func.order] paragraph 4 as follows:
Using the transformed function template's functionparameter list, or in the case of a conversion function its transformed returntype, perform type deduction against the functionparameter list (or return type)type of the other function template. The mechanism for performing these deductions is given in 13.10.3.5 [temp.deduct.partial].
The specification for how to handle default arguments and ellipsis in partial ordering of function templates is confusing. 13.7.7.3 [temp.func.order] paragraph 5 currently reads,
The presence of unused ellipsis and default arguments has no effect on the partial ordering of function templates.
It is not clear what “unused” means in this context. According to the original issue resolution that resulted in this wording (N1053, item 6.55), the intent was that “When partial ordering of function templates containing a different number of parameters is done, only the common parameters are considered.” Presumably this would include parameters with default arguments if each function had such parameters in corresponding positions.
The wording needs to be revised to make this intent clear.
Proposed resolution (March, 2011):
This issue is resolved by the resolution of issue 692 in document N3281.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 13.8.3.2 [temp.dep.type] paragraph 1, in a primary class template a name refers to the current instantiation if it is the injected-class-name or the name of the class template followed by the template argument list of the template. Although a template-id referring to a specialization of a template alias is described as “equivalent to” the associated type, a specialization of a template alias is neither of the things that qualifies as naming the current instantiation, so presumably the typename keyword in the following example is required:
template <class T> struct A; template <class T> using B = A<T>; template <class T> struct A { struct C {}; typename B<T>::C bc; // typename needed };
(However, the list in 13.8.3.2 [temp.dep.type] may not be exactly what we want; it doesn't allow use of a typedef denoting the type of the current instantiation, either, but that presumably should be accepted.)
For analogous reasons, it should not be permitted to use a template alias as a nested-name-specifier when defining the members of a class template:
template <class T> struct A { void g(); }; template <class T> using B = A<T>; template <class T> void B<T>::g() {} // error
Notes from the November, 2010 meeting:
The CWG disagreed with the suggested direction, feeling that aliases should work like typedefs and that the examples should be accepted.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 13.8.3.2 [temp.dep.type] paragraph 1 as follows:
In the definition of a class template, a nested class of a class template, a member of a class template, or a member of a nested class of a class template, a name refers to the current instantiation if it is
the injected-class-name (Clause 11 [class]) of the class template or nested class,
in the definition of a primary class template, the name of the class template followed by the template argument list of the primary template (as described below) enclosed in <> (or a template alias specialization equivalent to same),
in the definition of a nested class of a class template, the name of the nested class referenced as a member of the current instantiation, or
in the definition of a partial specialization, the name of the class template followed by the template argument list of the partial specialization enclosed in <> (or a template alias specialization equivalent to same). If the nth template parameter is a parameter pack, the nth template argument is a pack expansion (13.7.4 [temp.variadic]) whose pattern is the name of the parameter pack.
Change 13.8.3.2 [temp.dep.type] paragraph 3 as follows:
A template argument that is equivalent to a template parameter (i.e., has the same constant value or the same type as the template parameter) can be used in place of that template parameter in a reference to the current instantiation, except that a decltype-specifier that denotes a dependent type is always considered non-equivalent. In the case of a non-type template argument, the argument must have been given the value of the template parameter and not an expression in which the template parameter appears as a subexpression. [Example:...This resolution also resolves issue 1057.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 73The current wording of 9.2.4 [dcl.typedef] paragraph 2 requires that the identifier in an alias-declaration “...shall not appear in the type-id.” With template aliases, however, the name of the alias can be used indirectly:
template<typename T> struct A; template<typename T> using B=typename A<T>::U; template<typename T> struct A { typedef B<T> U; }; B<short> b;
Here the instantiation of B<short> causes the instantiation of A<short>, and the typedef in A<short> ends up attempting to use B<short>, which is still in the process of being instantiated.
There should be an explicit prohibition of such uses.
Proposed resolution (August, 2010):
Add the following as a new paragraph at the end of 13.7.8 [temp.alias]:
[Note: this wording assumes the change from “template alias” to “alias template” requested by comment FI 11 on FCD N3092.]The type-id in an alias template declaration shall not refer to the alias template being declared. The type produced by an alias template specialization shall not directly or indirectly make use of that specialization. [Example:
template <class T> struct A; template <class T> using B = typename A<T>::U; template <class T> struct A { typedef B<T> U; }; B<short> b; // Error: instantiation of B<short> uses own type via A<short>::U.—end example]
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 74An alias-declaration allows a class or enumeration type to be defined in its type-id (9.2.9 [dcl.type] paragraph 3). However, it's not clear that this is desirable when the alias-declaration is part of a template alias:
template<typename T> using A = struct { void f(T) { } };
Proposed resolution (August, 2010):
Change 9.2.9 [dcl.type] paragraph 3 as follows:
...A type-specifier-seq shall not define a class or enumeration unless it appears in the type-id of an alias-declaration (9.2.4 [dcl.typedef]) that is not the declaration of a template-declaration.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 44It is not clear from the current wording of 13.8 [temp.res] whether the following is well-formed or not:
template<typename T> struct id { typedef T type; }; template<typename T> void f() { int id<T>::type::*p = 0; } struct A { }; int main() { f<A>(); }
If typename is required before the use of id<T>::type, it is not permitted by the current syntax.
Proposed resolution (August, 2010):
Change 13.8 [temp.res] paragraph 5 as follows:
A qualified name used as the name in a mem-initializer-id, a base-specifier, or an elaborated-type-specifier is implicitly assumed to name a type, without the use of the typename keyword. In a nested-name-specifier that immediately contains a nested-name-specifier that depends on a template parameter, the identifier or simple-template-id is implicitly assumed to name a type, without the use of the typename keyword. [Note: the typename keyword is not permitted by the syntax of these constructs. —end note]
[Voted into WP at August, 2010 meeting.]
Is this program valid?
template <typename T> int g(int); class h{}; template <typename T> int l(){h j; return g<T>(j);} template <typename T> int g(const h&); class j{}; int jj(){return l<j>();}
The key issue is when "g" is looked up, i.e., whether both overloaded template "g" functions are available at the call site or only the first. Clearly, the entire postfix-expression "g<T>(j)" is dependent, but when is the set of available template functions determined?
For consistency with the rules about when the set of available overloads is determined when calling a function given by an unqualified-id, I would think that we should postpone determining the set of template functions if (and only if) any of the explicit template arguments are dependent.
John Spicer: I agree that there should be a core issue for this. The definition of "dependent name" (13.8.3 [temp.dep] paragraph 1) should probably be modified to cover this case. It currently only handles cases where the function name is a simple identifier.
Notes from the March 2004 meeting:
A related issue is a call with a qualified name and dependent arguments, e.g., x::y(depa, depb).
Proposed resolution (June, 2010):
Change 13.8.3 [temp.dep] paragraph 1 as follows:
...In an expression of the form:
postfix-expression ( expression-listopt )
where the postfix-expression is an
unqualified-idid-expression, theunqualified-idid-expression denotes a dependent name ifand only ifany of the expressions in the expression-list is a type-dependent expression (13.8.3.3 [temp.dep.expr]) or if the unqualified-id of the id-expression is a template-id in which any of the template arguments depends on a template parameter. If an operand of an operator is a type-dependent expression, the operator also denotes a dependent name. Such names are unbound and are looked up at the point of the template instantiation (13.8.4.1 [temp.point]) in both the context of the template definition and the context of the point of instantiation.
Change 13.8.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 or if the function is called using operator notation,the candidate functions are found using the usual lookup rules (6.5.3 [basic.lookup.unqual], 6.5.4 [basic.lookup.argdep], 6.5.5 [basic.lookup.qual]) except that:
For the part of the lookup using unqualified name lookup (6.5.3 [basic.lookup.unqual]) or qualified name lookup (6.5.5 [basic.lookup.qual]), only function declarations from the template definition context are found.
For the part of the lookup using associated namespaces (6.5.4 [basic.lookup.argdep]), only function declarations found in either the template definition context or the template instantiation context are found.
If the function name is an unqualified-id and the call would be ill-formed or would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced in those namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts, then the program has undefined behavior.
[Voted into WP at August, 2010 meeting.]
The list of cases in 13.8.2 [temp.local] about when a template parameter is hidden seems to be incomplete.
Consider
// example-1 struct S { int C; template<class> void f(); }; template<class C> void S::f() { C c; // #1 }
Someone asked whether line #1 is well-formed and I responded "no" based on my understanding of the rules in 14.6.1. After a second looking, I've realized that the above case is currently missing from the list.
The list in 14.6.1 covers cases like
// example-2 template<class T> struct S { int C; void f(); }; template<class C> void S<C>::f() { C c; // ERROR: 'C' is 'S::C' not the template parameter }or
// example-3 struct A { int C; } template<class C> struct S : A { C c; // ERROR: 'C' is 'A::C', not the template parameter };But the case of a 'member template' is missing. I believe it should follow the same rule as above. The reason is this.
In the case listed in 14.6.1 (having to do with members of classes), the "algorithm" seems to be this:
I believe that any rule, coherent with 14.6.1/5 and 14.6.1/7, for covering the cases of member templates (example-1) will be described by the above "algorithm".
Am I missing something?
[1] of course, the standard text does not formally speak of "template parameter scope", but we all know that the template parameters "live" somewhere. I'm using that terminology to designate the declarative region of the template parameters.
Mike Miller: I have a somewhat different perspective on this question. I think your example-1 is fundamentally different from your example-2 and example-3. Looking, for instance, at your example-2, I see four nested scopes:
namespace scope template scope (where the parameter is) class S scope S::f() block scope
Naturally, S::C hides the template parameter C. The same is true of your example-3, with three scopes:
namespace scope template scope class S scope (includes 10.2 base class lookup)
Again, it's clear that the C inherited from A hides the template parameter in the containing scope.
The scopes I see in your example-1, however, are different:
namespace scope struct S scope template scope (where the parameter is) S::f() block scope
Here it seems clear to me that the template parameter hides the class member.
It might help to look at the case where the function template is defined inline in the class:
struct S { int C; template<class C> int f() { C c; // #1 } };
It would be pretty strange, I think, if the #1 C were the member and not the template parameter. It would also be odd if the name lookup were different between an inline definition and an out-of-line definition.
See also issue 459.
Notes from the March 2004 meeting:
Basically, the standard is okay. We think Gaby's desired cases like #1 should be ill-formed.
There is a wording problem in 13.8.2 [temp.local] paragraph 7. It says:
In the definition of a member of a class template that appears outside of the class template definition, the name of a member of this template hides the name of a template-parameter.
It should say "hides the name of a template-parameter of the class template (but not a template-parameter of the member, if the member is itself a template)" or words to that effect.
Proposed resolution (February, 2010):
Change 13.8.2 [temp.local] paragraph 8 as follows:
In the definition of a member of a class template that appears outside of the class template definition, the name of a member of
thisthe class template hides the name of a template-parameter of any enclosing class templates (but not a template-parameter of the member, if the member is a class or function template). [Example:template<class T> struct A { struct B { /* ... */ }; typedef void C; void f(); template<class U> void g(U); }; template<class B> void A<B>::f() { B b; // A's B, not the template parameter } template<class B> template<class C> void A<B>::g(C) { B b; // A's B, not the template parameter C c; // the template parameter C, not A's C }
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Consider the following example:
template<class T> struct A { template<class U> friend struct A; // Which A? };
Presumably the lookup for A in the friend declaration finds the injected-class-name of the template. However, according to 13.8.2 [temp.local] paragraph 1,
The injected-class-name can be used with or without a template-argument-list. When it is used without a template-argument-list, it is equivalent to the injected-class-name followed by the template-parameters of the class template enclosed in <>. When it is used with a template-argument-list, it refers to the specified class template specialization, which could be the current specialization or another specialization.
If that rule applies, then this example is ill-formed (because you can't have a template-argument-list in a class template declaration that is not a partial specialization).
Mike Miller: The injected-class-name has a dual nature, as described in 13.8.2 [temp.local], acting as either a template name or a class name, depending on the context; a template argument list forces the name to be interpreted as a template. It seems reasonable that in this example the injected-class-name has to be understood as referring to the class template; a template header is at least as strong a contextual indicator as a template argument list. However, the current wording doesn't say that.
(See also issue 1004.)
Proposed resolution (November, 2010) [SUPERSEDED]:
This issue is resolved by the resolution of issue 1004.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The injected-class-name of a class template can be used either by itself, in which case it is a type denoting the current instantiation, or followed by a template argument list, in which case it is a template-name. It would be helpful to extend this treatment so that the injected-class-name could be used as an argument for a template template parameter:
template <class T> struct A { }; template <template <class> class TTP> struct B { }; struct C: A<int> { B<A> b; };
(This is accepted by g++.)
James Widman:
It would not be so helpful when used with overloaded function templates, for example:
template <template <class> class TTP> void f(); // #1 template < class T > void f(); // #2 template <class T> struct A { }; struct C: A<int> { void h( ) { f<A>(); // #1? #2? Substitution failure? } };
(See also issue 602.)
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 13.8.2 [temp.local] paragraphs 1-5 as follows:
Like normal (non-template) classes, class templates have an injected-class-name (Clause 11 [class]). The injected-class-name can be used
with or without a template-argument-listas a template-name or a type-name.When it is used without a template-argument-list, it is equivalent to the injected-class-name followed by the template-parameters of the class template enclosed in <>.When it is used with a template-argument-list, as a template-argument for a template template-parameter, or as the final identifier in the elaborated-type-specifier of a friend class template declaration it refers to thespecified class template specialization, which could be the current specialization or another specialization.class template itself. Otherwise, it is equivalent to the template-name followed by the template-parameters of the class template enclosed in <>.Within the scope of a class template specialization or partial specialization, when the injected-class-name is not
followed by a <used as a type-name, it is equivalent to theinjected-class-nametemplate-name followed by the template-arguments of the class template specialization or partial specialization enclosed in <>. [Example:template<template<class> class T> class A { }; template<class T> class Y; template<> class Y<int> { Y* p; // meaning Y<int> Y<char>* q; // meaning Y<char> A<Y>* a; // meaning A<::Y> class B { template<class> friend class Y; // meaning ::Y }; };—end example]
The injected-class-name of a class template or class template specialization can be used either
with or without a template-argument-listas a template-name or a type-name wherever it is in scope. [Example:template <class T> struct Base { Base* p; }; template <class T> struct Derived: public Base<T> { typename Derived::Base* p; // meaning Derived::Base<T> }; template<class T, template<class> class U = T::template Base> struct Third { }; Third<Base<int>> t; // OK, default argument uses injected-class-name as a template—end example]
A lookup that finds an injected-class-name (6.5.2 [class.member.lookup]) can result in an ambiguity in certain cases (for example, if it is found in more than one base class). If all of the injected-class-names that are found refer to specializations of the same class template, and if the name is
followed by a template-argument-listused as a template-name, the reference refers to the class template itself and not a specialization thereof, and is not ambiguous. [Example:template <class T> struct Base { }; template <class T> struct Derived: Base<int>, Base<char> { typename Derived::Base b; // error: ambiguous typename Derived::Base<double> d; // OK };—end example]
When the normal name of the template (i.e., the name from the enclosing scope, not the injected-class-name) is used
without a template-argument-list, it always refers to the class template itself and not a specialization of the template. [Example:...
This resolution also resolves issue 602.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
In an example like
void f(int, int, int); template<int ...N> void g() { f((N+N)...); } void h() { g<1, 2, 3>(); }
the call to f needs to be dependent; however, the arguments are not type-dependent, so the criteria of 13.8.3 [temp.dep] paragraph 1 are not met. Presumably the specification needs to be updated so that an argument list containing a type-level pack expansion is dependent.
[Voted into WP at August, 2010 meeting.]
The Standard is currently silent on the dependency status of enumerations and enumerators that are members of class templates. There are three questions that must be answered in this regard:
Are enumeration members of class templates dependent types?
It seems clear that nested enumerations must be dependent. For example:
void f(int); template<typename T> struct S { enum E { e0 }; void g() { f(e0); } }; void f(S<int>::E); void x() { S<int> si; si->g(); // Should call f(S<int>::E) }
Is sizeof applied to a nested enumeration a value-dependent expression (13.8.3.4 [temp.dep.constexpr])?
There are three distinct cases that might have different answers to this question:
template<typename T> struct S { enum E { e0 }; };
Here, the size of E is, in principle, known at the time the template is defined.
template<short I> struct S { enum E { e0 = I }; };
In this case, the minimum size required for E cannot be determined until instantiation, but it is clear that the underlying type need be no larger than short.
template<typename T> struct S { enum E { e0 = T::e0; }; }
Here, nothing can be known about the size of E at the time the template is defined.
13.8.3.4 [temp.dep.constexpr] paragraph 2 says that a sizeof expression is value-dependent if the type of the operand is type-dependent. Unless enumerations are given special treatment, all three of these examples will have value-dependent sizes. This could be surprising for the first case, at least, if not the second as well.
Are nested enumerators value-dependent expressions?
Again the question of dependent initializers comes into play. As an example, consider:
template<short I> struct S { enum E { e0, e1 = I, e2 }; };
There seem to be three possible approaches as to whether the enumerators of E are value-dependent:
The enumerators of a nested enumeration are all value-dependent, regardless of whether they have a value-dependent initializer or not. This is the current position of 13.8.3.4 [temp.dep.constexpr] paragraph 2, which says that an identifier is value-dependent if it is a name declared with a dependent type.
The enumerators of a nested enumeration are all value-dependent if any of the enumeration's enumerators has a value-dependent initializer. In this approach, e0 would be value-dependent, even though it is clear that it has the value 0.
An enumerator of a nested enumeration is value-dependent only if it has a value-dependent initializer (explict or implicit). This approach would make e1 and e2 value-dependent, but not e0.
An example that bears on the third approach is the following:
template<typename T> struct S { enum E { N = UINT_MAX, O = T::O }; int a[N + 2]; };
With the normal treatment of enumerations, the type of a might be either int[UINT_MAX+2] or int[1], depending on whether the value of T::O was such that the underlying type of E is unsigned int or long.
One possibility for addressing this problem under the third approach would be to treat a given enumerator as having the type of its initializer in such cases, rather than the enumeration type. This would be similar to the way enumerators are treated within the enumerator list, before the enumeration declaration is complete (9.8.1 [dcl.enum] paragraph 5). The argument against this is that it makes arithmetic using enumerators behave differently when the enumeration is a member of a class template and when it is not.
Notes from the April, 2005 meeting:
The CWG agreed on the following positions:
Nested enumerations are dependent types.
The result of the sizeof operator applied to a nested enumeration is value-dependent unless there are no dependent initializers in its definition; the first case above is not dependent, while the second and third are dependent.
The approach described in 3.C above is correct. This is similar to the treatment of static const integral data members, which are dependent only if their initializer is dependent.
Notes from the October, 2005 meeting:
There was no consensus among the CWG regarding question #3 (which enumerators should be considered value-dependent). The argument in favor of 3.C is principally that the values of enumerators with non-dependent initializers are known at definition time, so there is no need to treat them as dependent.
One objection to 3.C is that, according to the consensus of the CWG, the enumeration type is dependent and thus even the known values of the enumeration would have a dependent type, which could affect the results when such enumerations are used in expressions. A possible response to this concern would be to treat non-dependent initializers as having the type of the initializer rather than the enumeration type, similar to the treatment of enumerators within the enumerator-list (9.8.1 [dcl.enum] paragraph 5). However, this approach would be inconsistent with the treatment of other enumeration types. It would also interfere with overload resolution (e.g., the call in the example under question #1 above would resolve to f(int) with this approach rather than f(S<int>::E)).
Those in favor of option 3.A also suggested that it would be simpler and require less drafting: if all the enumerators have the (dependent) type of the enumeration, 13.8.3.4 [temp.dep.constexpr] paragraph 2 already says that a name with a dependent type is value-dependent, so nothing further would need to be said. Option 3.C would require additional caveats to exempt some enumerators.
The proponents of 3.A also pointed out that there are many other cases where a known value with a dependent type is treated as dependent:
static const T t = 0; ... A<t> ...
or
template <int I> void f() { g(I-I); }
With regard to current practice, g++ and MSVC++ implement 3.A, while EDG implements 3.C.
Notes from the July, 2009 meeting:
The consensus of the CWG was that all the types and values are dependent.
Proposed resolution (June, 2010):
Change 13.8.3.2 [temp.dep.type] paragraph 6 as follows:
A type is dependent if it is
...
a nested class or enumeration that is a member of the current instantiation,
...
In 13.8.3.2 [temp.dep.type] paragraph 5 we have:
A name is a member of an unknown specialization if the name is a qualified-id in which the nested-name-specifier names a dependent type that is not the current instantiation.
So given:
template<class T> struct A { struct B { struct C { A<T>::B::C f(); }; }; };
it appears that the name A<T>::B::C should be taken as a member of an unknown specialization, because the WP refers to “the” current instantiation, implying that there can be at most one at any given time. At the declaration of f(), the current instantiation is C, so A<T>::B is not the current instantiation.
Would it be better to refer to “a known instantiation” instead of “the current instantiation?”
Mike Miller:
I agree that there is a problem here, but I don't think the “current instantiation” terminology needs to be replaced. By way of background, paragraph 1 makes it clear that A<T>::B “refers to” the current instantiation:
In the definition of a class template, a nested class of a class template, a member of a class template, or a member of a nested class of a class template, a name refers to the current instantiation if it is
the injected-class-name (Clause 11 [class]) of the class template or nested class,
in the definition of a primary class template, the name of the class template followed by the template argument list of the primary template (as described below) enclosed in <>,
in the definition of a nested class of a class template, the name of the nested class referenced as a member of the current instantiation...
A<T>::B satisfies bullet 3. Paragraph 4 says,
A name is a member of the current instantiation if it is
An unqualified name that, when looked up, refers to a member of a class template. [Note: this can only occur when looking up a name in a scope enclosed by the definition of a class template. —end note]
A qualified-id in which the nested-name-specifier refers to the current instantiation.
So clearly by paragraphs 1 and 4, A<T>::B::C is a member of the current instantiation. The problem is in the phrasing of paragraph 5, which incorrectly requires that the nested-name-specifier “be” the current instantiation rather than simply “referring to” the current instantiation, which would be the correct complement to paragraph 4. Perhaps paragraph 5 could simply be rephrased as, “...a dependent type and it is not a member of the current instantiation.”
(Paragraph 1 may require a bit more wordsmithing to make it truly recursive across multiple levels of nested classes; as it stands, it's not clear whether the name of a nested class of a nested class of a class template is covered or not.)
Additional note (April, 2011):
It appears that these concerns are addressed by the resolution of issue 1043 in document N3283.
Proposed resolution (December, 2011):
This issue is resolved by the resolution of issue 1043.
[Voted into the WP at the March, 2011 meeting as paper N3283.]
13.8.3.2 [temp.dep.type] paragraph 4 treats unqualified-ids and qualified-ids in which the nested-name-specifier refers to the current instantiation as equivalent. However, the lookups associated with these two id-expressions are different in the presence of dependent base classes (13.8.3 [temp.dep] paragraph 3) : with an unqualified-id, a dependent base class scope is never examined, while with a qualified-id it is. The current wording does not specify how an example like the following is to be handled:
template<typename T> struct B {}; struct C { typedef int type; }; template<typename T> struct A : B<T>, C { template<typename U> type a(); // #1 template<typename U> typename A<T>::type a(); // #2: different from #1? }; template<typename T> template<typename U> typename A<T>::type A<T>::a() { ... } // defines #1 or #2?
There seem to be two possible strategies for the handling of typename A<T>::type:
It is handled like the unqualified-id case, looking only in non-dependent base classes and not being a dependent type.
Since the current instantiation has dependent base classes, it is handled as a dependent type.
EDG seems to be doing the former, g++ the latter.
Notes from the November, 2010 meeting:
The CWG agreed that if a name is found in a non-dependent base, the type should be treated as non-dependent also.
Additional note (November, 2010):
The overall treatment of dependent base classes in handling a qualified-id in which the nested-name-specifier names the current instantiation or in a member access expression where the object expression is *this is not very clear. It would be helpful if the resolution of this issue could clarify the overall treatment while dealing with the mixed dependent/non-dependent case given in the example.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 13.8.3.2 [temp.dep.type] paragraph 3,
A template argument that is equivalent to a template parameter (i.e., has the same constant value or the same type as the template parameter) can be used in place of that template parameter in a reference to the current instantiation.
This would presumably include something like
template<typename T> struct A { struct B { }; A<decltype(T())>::B b; // no typename };
However, this example is rejected by current implementations. Does this need to be clarified in the existing wording?
Notes from the November, 2010 meeting:
The example is not well-formed; if T is an rvalue reference type, for example, decltype(T()) is not equivalent to T.
Proposed resolution (November, 2010) [SUPERSEDED]:
This issue is resolved by the resolution of issue 1056.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 43The current rules for determining whether a name refers to the current instantiation, given in 13.8.3.2 [temp.dep.type] paragraph 1, do not cover the case when a template-id matching a primary template or partial specialization appears in the definition of a member of the template.
Proposed resolution (August, 2010):
Change 13.8.3.2 [temp.dep.type] paragraph 1 as follows:
In the definition of a class template, a nested class of a class template, a member of a class template, or a member of a nested class of a class template, aA name refers to the current instantiation if it is
in the definition of a class template, a nested class of a class template, a member of a class template, or a member of a nested class of a class template, the injected-class-name (Clause 11 [class]) of the class template or nested class,
in the definition of a primary class template or a member of a primary class template, the name of the class template followed by the template argument list of the primary template (as described below) enclosed in <>,
in the definition of a nested class of a class template, the name of the nested class referenced as a member of the current instantiation, or
in the definition of a partial specialization or a member of a partial specialization, the name of the class template followed by the template argument list of the partial specialization enclosed in <>. If the nth template parameter is a parameter pack, the nth template argument is a pack expansion (13.7.4 [temp.variadic]) whose pattern is the name of the parameter pack.
[Voted into the WP at the November, 2010 meeting.]
The Standard should, but does not currently, say that typeid is value-dependent if its expression or type is type-dependent.
Proposed resolution (September, 2010):
Change 13.8.3.4 [temp.dep.constexpr] paragraph 2 as follows:
...Expressions of the following form are value-dependent if the unary-expression or expression is type-dependent or the type-id is dependent:
sizeof unary-expression
sizeof ( type-id )
typeid ( expression )
typeid ( type-id )
alignof ( type-id )
noexcept ( expression )
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
It is not clear why a noexcept-expression is value-dependent if its operand is value-dependent. It would seem that the value of the expression depends only on the type of the operand, not its value.
Proposed resolution (November, 2010) [SUPERSEDED]:
Delete “noexcept( expression )” from the list in 13.8.3.4 [temp.dep.constexpr] paragraph 3.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Given
template <const char *N> struct A { static const char *p; }; template <class T> struct B { static const char c[1]; typedef A<B<T>::c> C; }; template <class T> struct D { static const char c[1]; typedef A<c> C; };
13.8.3.5 [temp.dep.temp] says that B<T>::c is dependent because it is used as a non-integral non-type template argument and it is a qualified-id with a nested-name-specifier that names a dependent type.
There doesn't seem to be anything to say that c in the definition of D<T>::C is dependent, which suggests that D<T>::C is the same type for all T, which is clearly false.
Instead of this special rule in 13.8.3.5 [temp.dep.temp], 13.8.3.4 [temp.dep.constexpr] should say that the address of a member of a dependent type is value-dependent, regardless of whether the address is written with a qualified-id.
Proposed resolution (November, 2010) [SUPERSEDED]:
Add a new paragraph at the end of 13.8.3.4 [temp.dep.constexpr]:
An id-expression is value-dependent if it names a member of an unknown specialization.
Change 13.8.3.5 [temp.dep.temp] paragraphs 2-3 as follows:
An integralA non-type template-argument is dependent if its type is dependent or the constant expression it specifies is value-dependent.
A non-integralFurthermore, a non-type template-argument is dependent ifits type is dependent or it has either of the following forms
qualified-id
& qualified-id
and contains a nested-name-specifier which specifies a class-name that names a dependent typethe corresponding non-type template-parameter is of reference or pointer type and the template-argument designates or points to a member of the current instantiation or a member of a dependent type.
[Voted into the WP at the March, 2011 meeting.]
The intent is that it is a permissible implementation technique to do template instantiation at the end of a translation unit rather than at an actual point of instantiation. This idea is not reflected in the current rules, however.
Proposed resolution (January, 2011):
Change 13.8.4.1 [temp.point] paragraph 7 as follows:
A specialization for a function template, a member function template, or of a member function or static data member of a class template may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. A specialization for a class template...
[Voted into WP at August, 2010 meeting.]
Issue 470 specified the explicit instantiation of members of explicitly-instantiated class templates. In restricting the affected members to those “whose definition is visible at the point of instantiation,” however, this resolution introduced an incompatibility between explicitly instantiating a member function or static data member and explicitly instantiating the class template of which it is a member (13.9.3 [temp.explicit] paragraph 3 requires only that the class template definition, not that of the member function or static data member, be visible at the point of the explicit instantiation). It would be better to treat the member instantiations the same, regardless of whether they are directly or indirectly explicitly instantiated.
Notes from the April, 2006 meeting:
In forwarding document J16/06-0057 = WG21 N1987 to be approved by the full Committee, the CWG reaffirmed its position that explicitly instantiating a class template only explicitly instantiates those of its members that have been defined before the point of the explicit instantiation. The effect of the position advocated above would be to require all non-exported member functions to be defined in the translation unit in which the class template is explicitly instantiated (cf paragraph 4), and we did not want to require that. We did agree that the “visible” terminology should be replaced by wording along the lines of “has been defined.”
Proposed resolution (February, 2010):
Change 13.9.3 [temp.explicit] paragraph 8 as follows:
An explicit instantiation definition that names a class template specialization explicitly instantiates the class template specialization and is only an explicit instantiation definition of memberswhose definition is visiblethat have been defined at the point of instantiation.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 13.9.3 [temp.explicit] paragraph 4,
For a given set of template parameters, if an explicit instantiation of a template appears after a declaration of an explicit specialization for that template, the explicit instantiation has no effect.
However, that does not appear to negate the requirements of paragraph 3 that a definition of the entity being instantiated must be in scope. Consequently, the following would appear to be ill-formed, even though there is no real need for the definition of C:
template<typename T> class C; template<> class C<int> { }; template class C<int>;
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 13.9.3 [temp.explicit] paragraphs 3-4 as follows:
A declaration of a function template shall be in scope at the point of the explicit instantiation of the function template. A definition of the class or class template containing a member function template shall be in scope at the point of the explicit instantiation of the member function template. A definition of a class template or class member template shall be in scope at the point of the explicit instantiation of the class template or class member template. A definition of a class template shall be in scope at the point of an explicit instantiation of a member function or a static data member of the class template. A definition of a member class of a class template shall be in scope at the point of an explicit instantiation of the member class.A declaration of a function template, a member function or static data member of a class template, or a member function template of a class or class template shall precede an explicit instantiation of that entity. A definition of a class template, a member class of a class template, or a member class template of a class or class template shall precede an explicit instantiation of that entity, unless the explicit instantiation is preceded by an explicit specialization of the entity with the same template arguments. If the declaration of the explicit instantiation names an implicitly-declared special member function (11.4.4 [special]), the program is ill-formed.For a given set of template
parametersarguments, if an explicit instantiation of a template appears after a declaration of an explicit specialization for that template, the explicit instantiation has no effect. Otherwise...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The Standard does not fully describe the syntax to be used when a member of an explicitly-specialized member class or member class template is defined in namespace scope. 13.9.4 [temp.expl.spec] paragraph 4 says that the “explicit specialization syntax” (presumably referring to “template<>”) is not used in defining a member of an explicit specialization when a class template is explicitly specialized as a class. However, nothing is said anywhere about how to define a member of a specialization when:
the entity being specialized is a class (member of a template class) rather than a class template.
the result of the specialization is a class template rather than a class (cf 13.9.4 [temp.expl.spec] paragraph 18, which describes this case as a “member template that... remain[s] unspecialized”).
(See paper J16/05-0148 = WG21 N1888 for further details, including a survey of existing implementation practice.)
Notes from the October, 2005 meeting:
The CWG felt that the best approach, balancing consistency with implementation issues and existing practice, would be to require that template<> be used when defining members of all explicit specializations, including those currently covered by 13.9.4 [temp.expl.spec] paragraph 4.
Proposed resolution (February, 2010) [SUPERSEDED]:
Change 13.9.4 [temp.expl.spec] paragraph 5 as follows:
...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. Definitions of members of an explicitly specialized class are defined
in the same manner as members of normal classes, and not using the syntax for explicit specializationusing the same template<> prefix(es) as the explicitly specialized class. [Example:template<class T> struct A { void f(T) { /* ... */ } struct B { /* ... */ }; template<class U> struct C { /* ... */ }; }; template<> struct A<int> { void f(int); struct B; template<class U> struct C; }; void h() { A<int> a; a.f(16); // A<int>::f must be defined somewhere }// explicit specialization syntax not used for a member of // explicitly specialized class template specialization// members of explicitly specialized classes are defined using //the same syntax as the explicitly specialized class: template<> void A<int>::f(int) { /* ... */ } template<> struct A<int>::B { /* ... */ }; template<> template<class T> struct A<int>::C { /* ... */ };—end example]
Note (June, 2010):
Because the survey of implementations on which the CWG relied in reaching this resolution is quite old, a new survey of current practice is needed.
[Voted into WP at August, 2010 meeting.]
Given
template <class T> static T f(T t) { ... } template <> int f(int t) { ... }
what is the linkage of f(int)?
Section Clause 13 [temp] paragraph 4 says,
Entities generated from a template with internal linkage are distinct from all entities generated in other translation units.
But is the explicit specialization “generated from” the primary template? Does it inherit the local linkage? If so, where do I find a reference saying so explicitly?
James Widman: Data points: EDG 3.8 inherits, GCC 4.0 does not.
Mike Miller: There's a pretty strong presumption that the linkage of an explicit specialization cannot be different from that of its primary template, given that storage class specifiers cannot appear in an explicit specialization (9.2.2 [dcl.stc] paragraph 1).
Notes from the April, 2007 meeting:
The CWG agreed that the linkage of an explicit specialization must be that of the template. Gabriel Dos Reis will investigate the reason for the different behavior of g++.
Proposed resolution (March, 2010):
Change Clause 13 [temp] paragraph 4 as follows:
...Entities generated fromSpecializations (explicit or implicit) of a templatewiththat has internal linkage are distinct from allentities generatedspecializations in other translation units...
[Voted into WP at August, 2010 meeting.]
It does not appear that the following example is well-formed, although most compilers accept it:
template <typename T> T foo(); template <> int foo();
The reason is that 13.9.4 [temp.expl.spec] paragraph 11 only allows trailing template-arguments to be omitted if they “can be deduced from the function argument type,” and there are no function arguments in this example.
13.9.4 [temp.expl.spec] should probably say “function type” instead of “function argument type.” Also, a subsection should probably be added to 13.10.3 [temp.deduct] to cover “Deducing template arguments from declarative contexts” or some such. It would be essentially the same as 13.10.3.3 [temp.deduct.funcaddr] except that the function type from the declaration would be used as the type of P.
Proposed resolution (March, 2008):
Insert the following as a new subsection after 13.10.3.6 [temp.deduct.type]:
14.9.2.6 Deducing template arguments in a declaration that names a specialization of a function template [temp.deduct.funcdecl] Template arguments can be deduced from the function type specified when declaring a specialization of a function template. [Note: this can occur in the context of an explicit specialization, an explicit instantiation, or a friend declaration. —end note] The function template's function type and the declared type are used as the types of P and A, and the deduction is done as described in 13.10.3.6 [temp.deduct.type].
Change 13.9.4 [temp.expl.spec] paragraph 11 as follows:
A trailing template-argument can be left unspecified in the template-id naming an explicit function template specialization provided it can be deduced from the functionargumenttype (14.9.2.6 [temp.deduct.funcdecl])...
Notes from the September, 2008 meeting:
The proposed resolution is probably more than is needed. Instead of a complete new section, the material could become a paragraph in 13.7.7 [temp.fct].
Proposed resolution (February, 2010):
Add the following paragraph at the end of 13.7.7 [temp.fct]:
In a declaration that names a specialization of a function template, template arguments can be deduced from the function type. [Note: this can occur in the context of an explicit specialization, an explicit instantiation, or a friend declaration. —end note] The function template's function type and the declared type are used as the types of P and A and the deduction is done as described in 13.10.3.6 [temp.deduct.type].
Proposed resolution (March, 2010):
This issue is resolved by the resolution of issue 873.
[Voted into WP at August, 2010 meeting.]
According to 13.9.4 [temp.expl.spec] paragraph 1, only non-deleted function templates may be explicitly specialized. There doesn't appear to be a compelling need for this restriction, however, and it could be useful to forbid use of implicitly-instantiated specializations while still allowing use of explicitly-specialized versions.
Proposed resolution (February, 2010):
Change 13.9.4 [temp.expl.spec] paragraph 1 as follows:
An explicit specialization of any of the following:
non-deletedfunction templateclass template
non-deletedmember function of a class templatestatic data member of a class template
member class of a class template
member class template of a class or class template
non-deletedmember function template of a class or class templatecan be declared...
[Voted into WP at August, 2010 meeting.]
The last two sentences of 13.10.3 [temp.deduct] paragraph 5 read:
When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters in non-deduced contexts are replaced with the corresponding deduced or default argument values. If the substitution results in an invalid type, as described above, type deduction fails.
Shouldn't the substitution occur for all uses of the parameters, so that any of them could result in deduction failure?
Proposed resolution (October, 2006):
Change 13.10.3 [temp.deduct] paragraph 5 as follows:
...When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters innon-deduced contextsthe function type are replaced with the corresponding deduced or default argument values. If the substitution results in an invalid type, as described above, type deduction fails.
Notes from the September, 2008 meeting:
This issue was returned to "drafting" status in order to coordinate the wording with the concepts proposal.
Proposed resolution (March, 2010):
Change 13.10.3 [temp.deduct] paragraph 5 as follows:
When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters innon-deduced contextsthe template parameter list of the template and the function type are replaced with the corresponding deduced or default argument values. If the substitution results in an invalid type, as described above, type deduction fails.
[Voted into WP at August, 2010 meeting.]
The current rules in 13.10.3 [temp.deduct] say that type deduction fails as a result of attempting to use a type that is not a class type in a qualified name. However, it is now possible to use enumeration names as nested-name-specifiers, so this rule needs to be updated accordingly.
Proposed resolution (February, 2010):
Change the third bullet of the note in 13.10.3 [temp.deduct] paragraph 8 as follows:
[Note: Type deduction may fail for the following reasons:
...
Attempting to use a type that is not a class or enumeration type in a qualified name. [Example:...
...
[Voted into the WP at the March, 2011 meeting.]
According to 13.10.3 [temp.deduct] paragraph 8,
Access checking is not done as part of the substitution process. Consequently, when deduction succeeds, an access error could still result when the function is instantiated.
This mimics the way access checking is done in overload resolution. However, experience has shown that this exemption of access errors from deduction failure significantly complicates the Standard library, so this rule should be changed.
Proposed resolution (January, 2011):
Change 13.10.3 [temp.deduct] paragraph 8 as follows:
If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. [Note: Access checking isnotdone as part of the substitution process. —end note]Consequently, when deduction succeeds, an access error could still result when the function is instantiated.Only invalid types...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 77The following example is ambiguous:
template<typename T> int f(T&); template<typename T> int f(T&&); int i; int j = f(i);
Because of the special deduction rule for lvalues passed to rvalue-reference parameters, deduction produces f(int&) for both templates, and they are indistinguishable.
Because f(T&) accepts a strict subset of the things that f(T&&) does, it should be considered more specialized by the partial ordering rules.
Proposed resolution (August, 2010):
Change 13.10.3.5 [temp.deduct.partial] paragraph 9 as follows:
If, for a given type, deduction succeeds in both directions (i.e., the types are identical after the transformations above) and both P and A were reference types (before being replaced with the type referred to above):
If the type from the argument template was an lvalue reference and the type from the parameter template was not, the argument type is considered to be more specialized than the other; otherwise,
andif the type from the argument template is more cv-qualified than the type from the parameter template (as described above), the argumentthattype is considered to be more specialized than the other.; otherwise
If neither type is more cv-qualified than the other thenneither type is more specialized than the other.
[Editing note: this change transforms the running text at the end of the paragraph into a bulleted list.]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
It is not clear whether the following example is well-formed or not:
template<class T> struct identity { typedef T type; }; template<class T, class C> void f(T C::*, typename identity<T>::type*){} struct X { void f() {}; }; int main() { f(&X::f, 0); }
The null pointer conversion required for the second parameter of f is not one of the ones permitted by 13.10.3.2 [temp.deduct.call] paragraph 4, but it's unclear whether that list should apply to parameters with nondeduced types or not. 13.10.2 [temp.arg.explicit] paragraph 6 is explicit that
Implicit conversions (7.3 [conv]) will be performed on a function argument to convert it to the type of the corresponding function parameter if the parameter type contains no template-parameters that participate in template argument deduction.
However, this statement appears in a section dealing with explicitly-specified template arguments, so its applicability to nondeduced contexts in general is not clear.
Implementations disagree on the handling of this example.
[Voted into the WP at the March, 2011 meeting as paper N3281.]
13.10.3.6 [temp.deduct.type] paragraph 22 describes how we cope with partial ordering between two function templates that differ because one has a function parameter pack while the other has a normal function parameter. However, this paragraph was meant to apply to template parameter packs as well, e.g., to help with partial ordering of class template partial specializations:
template <class T1, class ...Z> class S; // #1 template <class T1, class ...Z> class S<T1, const Z&...> {}; // #2 template <class T1, class T2> class S<T1, const T2&> {};; // #3 S<int, const int&> s; // both #2 and #3 match; #3 is more specialized
(See also issue 818.)
Proposed resolution (March, 2009):
Change 13.10.3.6 [temp.deduct.type] paragraphs 9-10 as follows (and add the example above to paragraph 9):
If P has a form that contains <T> or <i>, then each argument Pi of the respective template argument list of P is compared with the corresponding argument Ai of the corresponding template argument list of A. If the template argument list of P contains a pack expansion that is not the last template argument, the entire template argument list is a non-deduced context. If Pi is a pack expansion, then the pattern of Pi is compared with each remaining argument in the template argument list of A. Each comparison deduces template arguments for subsequent positions in the template parameter packs expanded by Pi. During partial ordering (13.10.3.5 [temp.deduct.partial]), if Ai was originally a pack expansion and Pi is not a pack expansion, or if P does not contain a template argument corresponding to Ai, argument deduction fails.
Similarly, if P has a form that contains (T), then each parameter type Pi of the respective parameter-type-list of P is compared with the corresponding parameter type Ai of the corresponding parameter-type-list of A. If the parameter-declaration corresponding to Pi is a function parameter pack, then the type of its declarator-id is compared with each remaining parameter type in the parameter-type-list of A. Each comparison deduces template arguments for subsequent positions in the template parameter packs expanded by the function parameter pack. During partial ordering (13.10.3.5 [temp.deduct.partial]), if Ai was originally a function parameter pack and Pi is not a function parameter pack, or if P does not contain a function parameter type corresponding to Ai, argument deduction fails. [Note: A function parameter pack can only occur at the end of a parameter-declaration-list (9.3.4.6 [dcl.fct]). —end note]
[Voted into WP at August, 2010 meeting.]
13.10.3.2 [temp.deduct.call] paragraph 3 gives the deduction of rvalue references special treatment in the context of a function call:
If P is of the form T&&, where T is a template parameter, and the argument is an lvalue, the type A& is used in place of A for type deduction.
A similar provision is needed, but is not present, in declarative contexts. For example:
template<typename T> void f(T&&); template<> void f(int&) { } // #1 template<> void f(int&&) { } // #2 void g(int i) { f(i); // calls f<int&>(int&), i.e., #1 f(0); // calls f<int>(int&&), i.e., #2 }
There need to be rules that deduce the template arguments for the specializations in the same way that the arguments are deduced in the calls.
Proposed resolution (February, 2010):
Change 13.10.3.6 [temp.deduct.type] paragraph 10 as follows:
Similarly, if P has a form that contains (T), then each parameter type Pi of the respective parameter-type-list of P is compared with the corresponding parameter type Ai of the corresponding parameter-type-list of A. If P and A are function types that originated from deduction when taking the address of a function template (13.10.3.3 [temp.deduct.funcaddr]) or when deducing template arguments from a function declaration ([temp.deduct.decl]) and Pi and Ai are parameters of the top-level parameter-type-list of P and A, respectively, Pi is adjusted if it is an rvalue reference to a cv-unqualified template parameter and Ai is an lvalue reference, in which case the type of Pi is changed to be the template parameter type (i.e., T&& is changed to simply T). [Note: As a result, when Pi is T&& and Ai is X&, the adjusted Pi will be T, causing T to be deduced as X&. —end note] [Example:
template<typename T> void f(T&&); template<> void f(int&) { } // #1 template<> void f(int&&) { } // #2 void g(int i) { f(i); // calls f<int&>(int&), i.e., #1 f(0); // calls f<int>(int&&), i.e., #2 }—end example]
If the parameter-declaration corresponding to Pi is a function parameter pack...
Add a new section under 13.10.3 [temp.deduct], either before or after 13.10.3.6 [temp.deduct.type], as follows:
14.8.2.x Deducing template arguments from a function declaration [temp.deduct.decl]
In a declaration whose declarator-id refers to a specialization of a function template, template argument deduction is performed to identify the specialization to which the declaration refers. Specifically, this is done for explicit instantiations (13.9.3 [temp.explicit]), explicit specializations (13.9.4 [temp.expl.spec]), and certain friend declarations (13.7.5 [temp.friend]). This is also done to determine whether a function template specialization matches a placement operator new (6.7.6.5.3 [basic.stc.dynamic.deallocation], 7.6.2.8 [expr.new]). In all these cases, P is the type of the function template being considered as a potential match and A is the function type from the declaration. The deduction is done as described in 13.10.3.6 [temp.deduct.type].
If, for the set of function templates so considered, there is either no match or more than one match after partial ordering has been considered (13.7.7.3 [temp.func.order]), deduction fails and the declaration is ill-formed.
(Note that the resolution of issue 674 depends on this resolution.)
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The new wording added by issue 873 says,
...This is also done to determine whether a function template specialization matches a placement operator new (6.7.6.5.3 [basic.stc.dynamic.deallocation], 7.6.2.8 [expr.new])... If, for the set of function templates so considered, there is either no match or more than one match after partial ordering has been considered (13.7.7.3 [temp.func.order]), deduction fails and the declaration is ill-formed.
The statement describing the consequence of deduction failure (“the declaration is ill-formed”) does not apply to the case when deduction is being performed for placement operator delete, as there is no declaration involved. It may not be necessary to describe what happens when deduction fails in that case, but at least the wording should be tweaked to limit the conclusion to declarative contexts.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 13.10.3.7 [temp.deduct.decl] paragraphs 1-2 as follows:
In a declaration whose declarator-id refers to a specialization of a function template, template argument deduction is performed to identify the specialization to which the declaration refers. Specifically, this is done for explicit instantiations (13.9.3 [temp.explicit]), explicit specializations (13.9.4 [temp.expl.spec]), and certain friend declarations (13.7.5 [temp.friend]). This is also done to determine whether a deallocation function template specialization matches a placement operator new (6.7.6.5.3 [basic.stc.dynamic.deallocation], 7.6.2.8 [expr.new]). In all these cases, P is the type of the function template being considered as a potential match and A is either the function type from the declaration or the type of the deallocation function that would match the placement operator new as described in 7.6.2.8 [expr.new]. The deduction is done as described in 13.10.3.6 [temp.deduct.type].
If, for the set of function templates so considered, there is either no match or more than one match after partial ordering has been considered (13.7.7.3 [temp.func.order]), deduction fails and, in the declaration cases, the
declarationprogram is ill-formed.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 78According to 14.3 [except.ctor] paragraph 2,
An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed base classes and non-variant members, that is, for subobjects for which the principal constructor (11.9.3 [class.base.init]) has completed execution and the destructor has not yet begun execution.
This wording leaves unclear whether the remaining elements of an array will be destroyed if the destructor for one of the elements exits via an exception: an array element is a subobject (6.7.2 [intro.object] paragraph 2), but it is not a base class or non-variant member.
Proposed resolution (September, 2010):
Change 14.3 [except.ctor] paragraph 2 as follows:
An objectthat is partially constructed or partially destroyedof any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructedbase classes and non-variant memberssubobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (11.9.3 [class.base.init]) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor...
The current wording of 14.4 [except.handle] paragraph 16 is:
The object declared in an exception-declaration or, if the exception-declaration does not specify a name, a temporary (6.7.7 [class.temporary]) is copy-initialized (9.5 [dcl.init]) from the exception object. 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.
There are two problems with this. First, it's not clear what it means for the handler's “parameter” to be a temporary. This possibility is briefly mentioned in 6.7.7 [class.temporary], but the lifetime of such a temporary is not defined there; the discussion of lifetime is restricted to those temporaries that arise during the evaluation of an expression, and this is not such a case.
Second, this wording assumes that there will be an object to be destroyed and thus ignores the possibility that the exception-declaration declares a reference.
Proposed resolution (March, 2011):
This issue is resolved by the resolution of issue 1166 in paper N3262.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
N3092 comment GB 45According to 14.4 [except.handle] paragraph 16,
The object declared in an exception-declaration or, if the exception-declaration does not specify a name, a temporary (6.7.7 [class.temporary]) is copy-initialized (9.5 [dcl.init]) from the exception object. 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.
This wording leaves unspecified how an exception-declaration that is a reference should be treated. For example, presumably a reference to an abstract class type should be permitted, but that is not specified. The treatment of ellipsis is also not clearly addressed.
[Voted into the WP at the March, 2011 meeting.]
N3092 comment CA 514.4 [except.handle] paragraph 8 defines the “currently handled exception” as
The exception with the most recently activated handler that is still active
This definition ignores the possibility that an exception might be thrown and caught in another thread during the execution of a handler. Since throw; rethrows the “currently handled exception,” one might conclude that it would be the other thread's exception that would be rethrown instead of the one that activated that handler.
Proposed resolution (January, 2011):
Change Clause 14 [except] paragraph 1 as follows:
Exception handling provides a way of transferring control and information from a point in the execution of aprogramthread to an exception handler associated with a point previously passed by the execution...
Change 14.2 [except.throw] paragraph 4 as follows:
...The implementation may then deallocate the memory for the exception object; any such deallocation is done in an unspecified way. [Note: An exception thrown by a throw-expression does not propagate to other threads unless caught, stored, and rethrown using appropriate library functions; see 17.9.7 [propagation] and 32.10 [futures]. —end note]
Change 14.4 [except.handle] paragraph 6 as follows:
[Voted into the WP at the March, 2011 meeting.]
It is not clear how to handle compatible dynamic-exception-specifications and noexcept-specifications. For example, given
void f() throw(); void f() noexcept { throw 1; }
should we call terminate() or unexpected()? And for
void g() throw (int); void g() noexcept (false) { throw 1.0; }
should this call unexpected or propagate the exception? Does the order of the declarations (and which is the definition) matter?
Alisdair Meredith:
And what about something like
struct A { ~A() throw() { } }; struct B { ~B() noexcept { } }; struct C: A, B { };
What is the exception specification for C's destructor?
Proposed resolution (November, 2010):
Change 14.5 [except.spec] paragraph 3 as follows:
Two exception-specifications are compatible if:
both are non-throwing (see below), regardless of their form,
both have the form noexcept(constant-expression) and the constant-expressions are equivalent, or
one exception-specification is a noexcept-specification allowing all exceptions and the other is of the form throw(type-id-list), orboth are dynamic-exception-specifications that have the same set of adjusted types.
Add the following note to the end of 14.5 [except.spec] paragraph 9:
Whenever an exception is thrown and the search...
—end example]
[Note: A function can have multiple declarations with different non-throwing exception-specifications; for this purpose, the one on the function definition is used. —end note]
[Voted into the WP at the March, 2011 meeting.]
N3092 comment GB 46It is not entirely clear that a function-try-block on a destructor will catch exceptions from a base or member destructor; whether such exceptions might be swallowed with a simple return statement rather than being rethrown; and whether such a clause might be entered multiple times if multiple bases/members throw, or if that is an automatic terminate call.
Proposed resolution (August, 2010):
Change Clause 14 [except] paragraph 4 as follows:
...An exception thrown during the execution of the initializer expressions in the ctor-initializer or during the execution of the compound-statement, or — in the case of a destructor — during the destruction of a subobject, transfers control to a handler in a function-try-block in the same way as an exception thrown during the execution of a try-block transfers control to other handlers. [Example:...
Additional note (October, 2010):
There is a related problem with this wording: it covers only “the execution of the initializer expressions in the ctor-initializer,” when it should also cover execution of base and member constructors, regardless of whether they have initializer expressions in the ctor-initializer or not.
The issue has been moved back to "review" status to allow consideration of amending the proposed resolution to something like
...during the execution of the compound-statement or, if the function is a constructor or destructor, during the initialization or destruction of the class's subobjects, transfers control...
Proposed resolution (November, 2010):
Change Clause 14 [except] paragraph 4 as follows:
A function-try-block associates a handler-seq with the ctor-initializer, if present, and the compound-statement. An exception thrown during the execution of theinitializer expressions in the ctor-initializer or during the execution of thecompound-statement or, for constructors and destructors, during the initialization or destruction, respectively, of the class's subobjects, transfers control to a handler in a function-try-block in the same way as an exception thrown during the execution of a try-block transfers control to other handlers.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 14.5 [except.spec] paragraph2 8 and 9,
A function is said to allow an exception of type E if its dynamic-exception-specification contains a type T for which a handler of type T would be a match (14.4 [except.handle]) for an exception of type E.
Whenever an exception is thrown and the search for a handler (14.4 [except.handle]) encounters the outermost block of a function with an exception-specification that does not allow the exception, then,
if the exception-specification is a dynamic-exception-specification, the function std::unexpected() is called (_N4606_.15.5.2 [except.unexpected]),
otherwise, the function std::terminate() is called (14.6.2 [except.terminate]).
This does not define what it means for a noexcept-specification to allow an exception.
Proposed resolution (November, 2010) [SUPERSEDED]:
Change 14.5 [except.spec] paragraph 8 as follows:
A function is said to allow an exception of type E if the constant-expression in its noexcept-specification evaluates to false or its dynamic-exception-specification contains a type T for which a handler of type T would be a match (14.4 [except.handle]) for an exception of type E.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 47The list of reasons for which std::terminate is called needs to be extended to cover several additional cases in C++0x:
when function std::nested_exception::rethrow_nested is called for an object that stores a null exception pointer.
when execution of a function registered with std::at_quick_exit exits using an exception.
when the destructor or a copy constructor of class std::thread is called for the object that is joinable.
Proposed resolution (August, 2010):
Change 14.6.2 [except.terminate] paragraph 1 as follows:
In
the followingsome situations exception handling must be abandoned for less subtle error handling techniques:. [Note: These situations are:
when the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught (14.2 [except.throw]), calls a function that exits via an
uncaughtexception,[Footnote: For example, if the object being thrown is of a class with a copy constructor, std::terminate() will be called if that copy constructor exits with an exception during a throw. —end footnote]orwhen the exception handling mechanism cannot find a handler for a thrown exception (14.4 [except.handle]), or
when the search for a handler (14.4 [except.handle]) encounters the outermost block of a function with a noexcept-specification that does not allow the exception (14.5 [except.spec]), or
when the destruction of an object during stack unwinding (15.2) terminates by throwing an exception, or
when initialization of a non-local variable with static or thread storage duration (6.9.3.2 [basic.start.static]
, 6.9.3.3 [basic.start.dynamic])terminates by throwingexits via an exception, orwhen destruction of an object with static or thread storage duration exits
usingvia an exception (6.9.3.3 [basic.start.dynamic]), orwhen execution of a function registered with std::atexit or std::at_quick_exit exits
usingvia an exception (17.5 [support.start.term]), orwhen a throw-expression with no operand attempts to rethrow an exception and no exception is being handled (14.2 [except.throw]), or
when std::unexpected throws an exception which is not allowed by the previously violated dynamic-exception-specification, and std::bad_exception is not included in that dynamic-exception-specification (_N4606_.15.5.2 [except.unexpected]), or
when the implementation's default unexpected exception handler is called (_N4606_.D.6.1 [unexpected.handler])
., orwhen the function std::nested_exception::rethrow_nested is called for an object that has captured no exception (17.9.8 [except.nested]), or
when execution of the initial function of a thread exits via an exception (32.4.3.3 [thread.thread.constr]), or
when the destructor or the copy assignment operator is invoked on a std::thread object that refers to a joinable thread (32.4.3.4 [thread.thread.destr], 32.4.3.5 [thread.thread.assign]).
—end note]
Insert the following as a new paragraph following 14.2 [except.throw] paragraph 6:
An exception is considered caught...
If the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught, calls a function that exits via an exception, std::terminate is called (14.6.2 [except.terminate]). [Example:
struct C { C() { } C(const C&) { throw 0; } }; int main() { try { throw C(); // calls std::terminate() } catch(C) { } }
—end example]
Change 14.3 [except.ctor] paragraph 3 as follows:
The process of calling destructors for automatic objects constructed on the path from a try block to a throw-expression is called “stack unwinding.”[Note:If a destructor called during stack unwinding exits with an exception, std::terminate is called (14.6.2 [except.terminate]). [Note: So destructors should generally catch exceptions and not let them propagate out of the destructor. —end note]
Change 6.9.3.2 [basic.start.static] paragraph 6 as follows:
[Note:If the initialization of a non-local variable with static or thread storage durationterminates by throwingexits via an exception, std::terminate is called (see14.6.2 [except.terminate]).—end note]
Change 6.9.3.3 [basic.start.dynamic] paragraph 1 as follows:
...[Note:If the destruction ofa non-localan object with static or thread storage durationterminates by throwingexits via an exception, std::terminate is called (see14.6.2 [except.terminate]).—end note]
Change 17.5 [support.start.term] bullet 8.1 as follows:
First, objects with thread storage duration...
If control leaves a registered function called by exit because the function does not provide a handler for a thrown exception, terminate() shall be called (14.6.2 [except.terminate]).
[Voted into the WP at the November, 2010 meeting.]
The current wording of 14.6.2 [except.terminate] paragraph 2 makes it sound as if stack unwinding in the case of a noexcept violation is an all-or-nothing proposition. It would be useful to be able to partially unwind the stack, in particular, not to call destructors for the function containing the noexcept-specification.
Proposed resolution (August, 2010):
Change 14.6.2 [except.terminate] paragraph 2 as follows:
...In the situation where the search for a handler (14.4 [except.handle]) encounters the outermost block of a function with a noexcept-specification that does not allow the exception (14.5 [except.spec]), it is implementation-defined whether the stack is unwound, unwound partially, or not unwound at all before std::terminate() is called. In all other situations, the stack shall not be unwound...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment DE 13The recommendations of document N2693 included a feature macro to enable a program to determine whether the implementation enforces strict pointer safety or not, but this macro is not specified in 15.12 [cpp.predefined].
Proposed resolution (August, 2010):
Add the following to the end of 15.12 [cpp.predefined] paragraph 2:
[Moved to DR at the September, 2013 meeting.]
According to _N4527_.12.9 [class.inhctor] paragraph 3,
For each non-template constructor in the candidate set of inherited constructors other than a constructor having no parameters or a copy/move constructor having a single parameter, a constructor is implicitly declared with the same constructor characteristics unless there is a user-declared constructor with the same signature in the class where the using-declaration appears.
It seems that this should be suppressing constructors that would be copy/move constructors in the derived class rather than copy/move constructors in the base class. For example:
struct B; struct A { A(const A&); A(const B&); A(int); }; struct B: A { using A::A; };
If B::B(const B&) is an inheriting constructor, other subobjects of B will not be copied. Also, if A::A(const A&) is not inherited, B objects cannot be constructed from an A object.
Proposed resolution (April, 2013):
Change _N4527_.12.9 [class.inhctor] paragraph 3 as follows:
For each non-template constructor in the candidate set of inherited constructors other than a constructor having no parameters or a copy/move constructor having a single parameter, a constructor is implicitly declared with the same constructor characteristics unless there is a user-declared constructor with the same signature in the class where the using-declaration appears or the constructor would be a default, copy, or move constructor for that class.
[Moved to DR at the February, 2014 meeting.]
It is not clear whether it is permitted to explicitly instantiate or explicitly specialize specializations of inheriting constructor templates. Since inheriting constructors are considered to be implicitly declared (_N4527_.12.9 [class.inhctor] paragraph 1), it might be inferred that 13.9.3 [temp.explicit] paragraph 4 forbids their explicit instantiation:
If the declaration of the explicit instantiation names an implicitly-declared special member function ( 11.4.4 [special]), the program is ill-formed.
Similarly, an explicit specialization provides a definition for the specialized member, and 11.4.4 [special] paragraph 1 forbids defining an implicitly-declared special member function.
These inferences do not seem conclusive, however, so an explicit statement in _N4527_.12.9 [class.inhctor] would be helpful.
(See also issue 1780.)
Proposed resolution (January, 2014):
Change _N4527_.12.9 [class.inhctor] paragraph 4 as follows:
A constructor so declared has the same access as the corresponding constructor in X. It is deleted if the corresponding constructor in X is deleted (9.6 [dcl.fct.def]). An inheriting constructor shall not be explicitly instantiated (13.9.3 [temp.explicit]) or explicitly specialized (13.9.4 [temp.expl.spec]).
[Moved to DR at the September, 2013 meeting.]
According to _N4885_6.7.5.5.4 [basic.stc.dynamic.safety] paragraph 4,
an implementation may have strict pointer safety, in which case a pointer value that is not a safely-derived pointer value is an invalid pointer value unless the referenced complete object is of dynamic storage duration and has previously been declared reachable (_N4885_.20.10.5 [util.dynamic.safety]).
“Safely-derived pointer” is defined only with respect to dynamically-allocated storage. Presumably pointers to objects with automatic and static storage duration should also be considered valid.
Proposed resolution (April, 2013):
Change _N4885_6.7.5.5.4 [basic.stc.dynamic.safety] paragraph 4 as follows:
Alternatively, an implementation may have strict pointer safety, in which case a pointer value referring to an object with dynamic storage duration that is not a safely-derived pointer value is an invalid pointer value unless the referenced complete objectis of dynamic storage duration andhas previously been declared reachable (_N4885_.20.10.5 [util.dynamic.safety]). [Note:...
[Moved to DR at the September, 2013 meeting.]
The term “non-template function” is used in various places but never defined.
Proposed resolution (June, 2013):
Change 7.6.1.3 [expr.call] paragraph 1 as follows:
There are two kinds of function call: ordinary function call and member function [Footnote: A static member function (11.4.9 [class.static]) is an ordinary function. —end foot note] (11.4.2 [class.mfct]) call.A function call is a postfix expression followed by parentheses containing a possibly empty, comma-separated list of initializer-clauses which constitute the arguments to the function. The postfix expression shall have function type or pointer to function type. Foran ordinary functiona call to a non-member function or to a static member function, the postfix expression shall be either... For amember functioncall to a non-static member function, the postfix expression shall be...
Add a new paragraph before 9.3.4.6 [dcl.fct] paragraph 13:
A non-template function is a function that is not a function template specialization. [Note: A function template is not a function. —end note]
A declarator-id or abstract-declarator containing an ellipsis shall only be used...
Change the footnote in 12.2.2 [over.match.funcs] paragraph 7 as follows:
The process of argument deduction fully determines the parameter types of the function template specializations, i.e., the parameters of function template specializations contain no template parameter types. Thereforethe, except where specified otherwise, function template specializationscan be treated as normal (non-template) functionsand non-template functions (9.3.4.6 [dcl.fct]) are treated equivalently for the remainder of overload resolution.
Change the final sub-bullet of 12.2.2.3 [over.match.oper] paragraph 3 as follows:
For a unary operator @...
...
For the operator ,, the unary operator &, or the operator ->, the built-in candidates set...
...
do not have the same parameter-type-list as any
non-templatenon-member candidate that is not a function template specialization.
Change the indicated bullet of the second bulleted list of 12.2.4 [over.match.best] paragraph 1 as follows:
...
F1 is not a non-template
function template specialization and F2 is a function
template specialization, or, if not that,
...
Change 12.3 [over.over] paragraph 4 as follows:
If more than one function is selected, any function template specializations in the set are eliminated if the set also contains anon-templatefunction that is not a function template specialization, and any given function template specialization F1 is eliminated
Change Clause 13 [temp] paragraph 5 as follows:
A class template shall not have the same name as any other template, class, function, variable, enumeration, enumerator, namespace, or type in the same scope (6.4 [basic.scope]), except as specified in (13.7.6 [temp.spec.partial]). Except that a function template can be overloaded either by(non-template)functions (9.3.4.6 [dcl.fct]) with the same name or by other function templates with the same name (13.10.4 [temp.over]), a template name declared in namespace scope or in class scope shall be unique in that scope.
Change 13.7.3 [temp.mem] paragraph 2 as follows:
A local class of non-closure type shall not have member templates. Access control rules (11.8 [class.access]) apply to member template names. A destructor shall not be a member template. A
normal (non-template)member function (9.3.4.6 [dcl.fct]) with a given name and type and a member function template of the same name, which could be used to generate a specialization of the same type, can both be declared in a class. When both exist, a use of that name and type refers to the non-template member unless an explicit template argument list is supplied. [Example:template <class T> struct A { void f(int); template <class T2> void f(T2); }; template <> void A<int>::f(int) { } // non-template member function template <> template <> void A<int>::f<>(int) { } //templatemember function template specialization int main() { A<char> ac; ac.f(1); // non-template ac.f('c'); // template ac.f<>(1); // template }—end example]
Change 13.7.5 [temp.friend] paragraph 1 as follows:
A friend of a class or class template can be a function template or class template, a specialization of a function template or class template, or
an ordinary (a non-template)function or class. For a friend function declaration that is not a template declaration:
if the name of the friend is a qualified or unqualified template-id, the friend declaration refers to a specialization of a function template, otherwise,
if the name of the friend is a qualified-id and a matching non-template function is found in the specified class or namespace, the friend declaration refers to that function, otherwise,
if the name of the friend is a qualified-id and a matching function template is found in the specified class or namespace, the friend declaration refers to the deduced specialization of that function template (13.10.3.7 [temp.deduct.decl]), otherwise,
the name shall be an unqualified-id that declares (or redeclares)
an ordinary (a non-template)function.
Change 13.7.5 [temp.friend] paragraph 4 as follows:
When a function is defined in a friend function declaration in a class template, the function is instantiated when the function is odr-used (6.3 [basic.def.odr]). The same restrictions...
Change 13.7.7 [temp.fct] paragraph 2 as follows:
A function template can be overloaded with other function templates and withnormal (non-template)functions (9.3.4.6 [dcl.fct]). Anormalnon-template function is not related to a function template (i.e., it is never considered to be a specialization), even if it has the same name and type as a potentially generated function template specialization.144
Change 13.10.2 [temp.arg.explicit] paragraph 4 as follows:
[Note: An empty template argument list can be used to indicate that a given use refers to a specialization of a function template even when anormal (i.e.,non-template)function (9.3.4.6 [dcl.fct]) is visible that would otherwise be used. For example:...
[Moved to DR at the February, 2014 meeting.]
According to phase 2 of 5.2 [lex.phases] paragraph 1,
Each instance of a backslash character (\) immediately followed by a new-line character is deleted, splicing physical source lines to form logical source lines. Only the last backslash on any physical source line shall be eligible for being part of such a splice. If, as a result, a character sequence that matches the syntax of a universal-character-name is produced, the behavior is undefined.
There does not appear to be a good reason for the behavior to be undefined when the line splice occurs within a raw string literal, since the splicing will be reverted (5.5 [lex.pptoken] paragraph 3).
Proposed resolution (September, 2013):
Change 5.2 [lex.phases] paragraph 1 phase 2 as follows:
Each instance of a backslash character (\) immediately followed
by a new-line character is deleted, splicing physical source lines to form
logical source lines. Only the last backslash on any physical source line
shall be eligible for being part of such a splice. If, as a
result, Except for splices reverted in a raw string literal, if
a splice results in a character sequence that matches the syntax of a
universal-character-name is produced, the behavior is
undefined. 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,
shall be processed as if an additional new-line character were appended to
the file.
[Applied to WP at the February, 2014 meeting.]
Table 6 of 5.13.2 [lex.icon] paragraph 2 covers only decimal, octal, and hexadecimal literals. Binary literals should be treated like the latter two. (It would also be more consistent to refer to these as “literals” instead of “constants.”)
Proposed resolution (September, 2013):
Change the caption and header row of Table 6 in 5.13.2 [lex.icon] paragraph 2 as follows:
Table 6 — Types of integer |
Suffix | Decimal |
Binary, |
---|
[Moved to DR at the February, 2014 meeting as part of document N3914.]
A UTF-8 string literal might result in a code unit with the value 0x80. However, plain char is not guaranteed to be able to represent 0x80.
[Moved to DR at the February, 2014 meeting.]
According to 7.3.2 [conv.lval] paragraph 2,
if the glvalue has a class type, the [lvalue-to-rvalue] conversion copy-initializes a temporary of type T from the glvalue and the result of the conversion is a prvalue for the temporary.
The implications of such a conversion for odr-use do not appear to have been factored into 6.3 [basic.def.odr] paragraph 3, which exempts constant objects that are immediately lvalue-to-rvalue converted. For example, given
struct S { int n; }; struct T { static constexpr S s = {}; }; void f(...); void g() { f(T::s); }
Does this odr-use T::s, requiring it to have a definition, because of binding it to the reference parameter of S's copy constructor? How about
struct S { int n; }; void f(...); void g() { constexpr S s = {}; [] { f(s); }; }
Does s need to be captured? There is implementation variance on both these examples.
Proposed resolution (September, 2013):
Change 6.3 [basic.def.odr] paragraph 3 as follows:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used unlessx satisfies the requirements for appearing in a constant expression (7.7 [expr.const])applying the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) to x yields a constant expression (7.7 [expr.const]) that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is applied to e, or e is a discarded-value expression ( Clause 7 [expr]). this is odr-used...
[Moved to DR at the February, 2014 meeting.]
According to 9.10 [namespace.udecl] paragraph 15,
When a using-declaration brings names from a base class into a derived class scope, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list (9.3.4.6 [dcl.fct]), cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting).
The algorithm for class-scope name lookup given in 6.5.2 [class.member.lookup], however, does not implement this requirement; there is nothing that removes a hidden base class member (replacing the using-declaration, per paragraph 3) from the result set.
Proposed resolution (September, 2013):
Change 6.5.2 [class.member.lookup] paragraph 3 as follows:
The lookup set for f in C, called S(f, C), consists of two component sets: the declaration set, a set of members named f; and the subobject set, a set of subobjects where declarations of these members (possibly including using-declarations) were found. In the declaration set, using-declarations are replaced by themembers they designateset of designated members that are not hidden or overridden by members of the derived class (9.10 [namespace.udecl]), and type declarations (including injected-class-names) are replaced by the types they designate...
[Moved to DR at the February, 2014 meeting.]
We can return a lambda or an object of a local class type from a lambda in c++11, and we can return them from normal functions in c++14. If those lambdas and normal functions are in a namespace, the returned lambdas/local-classes apparently aren't in that namespace, or even if they are, ADL won't find them. Is this intended?
(See also issue 1664.)
Proposed resolution (September, 2013):
This issue is resolved by the resolution of issue 1691.
[Moved to DR at the February, 2014 meeting.]
According to 6.5.4 [basic.lookup.argdep] paragraph 2,
If T is an enumeration type, its associated namespace is the namespace in which it is defined. If it is class member, its associated class is the member's class; else it has no associated class.
This does not take into account opaque enumerations, which can be defined in an enclosing namespace of the one of which is a member.
Proposed resolution (September, 2013):
Change 6.5.4 [basic.lookup.argdep] paragraph 2 as follows:
...The sets of namespaces and classes are determined in the following way:
...
If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the innermost enclosing namespaces of
whichits associated classesare members. Furthermore..If T is an enumeration type, its associated namespace is the innermost enclosing namespace
in which it is definedof its declaration. If it is a class member, its associated class is the member's class; else it has no associated class....
This resolution also resolves issues 1690 and 1692.
[Moved to DR at the February, 2014 meeting.]
According to 6.5.4 [basic.lookup.argdep] paragraph 2,
If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the namespaces of which its associated classes are members.
Consider an example like
struct A { struct B { struct C { }; }; };
A has one associated class, itself, and has the global namespace as its associated namespace. A::B has two associated classes, A and itself, and by virtue of its association with A, has the global namespace as its associated namespace. A::B::C has two associated classes, A::B and itself. However, because neither A::B nor A::B::C is a member of a namespace, A::B::C has no associated namespaces.
This seems like a defect.
Proposed resolution (September, 2013):
This issue is resolved by the resolution of issue 1691.
[Moved to DR at the February, 2014 meeting.]
According to 6.8 [basic.types] paragraph 9,
Arithmetic types (6.8.2 [basic.fundamental]), enumeration types, pointer types, pointer to member types (6.8.4 [basic.compound]), std::nullptr_t, and cv-qualified versions of these types (6.8.5 [basic.type.qualifier]) are collectively called scalar types... Scalar types, trivially copyable class types ( Clause 11 [class]), arrays of such types, and non-volatile const-qualified versions of these types (6.8.5 [basic.type.qualifier]) are collectively called trivially copyable types.
This is confusing, because “scalar types” include volatile-qualified types, but the intent of the definition of “trivially copyable type” appears to be to exclude volatile-qualified types. Perhaps the second quoted sentence should read something like,
A non-volatile type T or an array of such T is called a trivially copyable type if T is either a scalar type or a trivially copyable class type.
(Note that the following sentence, defining “trivial type,” has a similar formal issue, although it has no actual significance because all cv-qualifiers are permitted.)
(See also issue 496.)Proposed resolution (January, 2014):
Change 6.8 [basic.types] paragraph 10 as follows:
...ScalarCv-unqualified scalar types, trivially copyable class types ( Clause 11 [class]), arrays of such types, and non-volatile const-qualified versions of these types (6.8.5 [basic.type.qualifier]) are collectively called trivially copyable types...
[Moved to DR at the February, 2014 meeting as paper N3910.]
The wording of 6.9.1 [intro.execution] paragraph 6 is intended to describe the values of objects upon entry to and exit from the handler — i.e., that signal handler cannot rely on non-atomic objects being in a consistent state upon entry, nor can it reliably set the value of non-atomic objects and expect that they will continue to have those values after the handler exits. However, the wording could be read as saying even during the execution of the handler it cannot set and use non-atomic objects. The wording should be clarified.
[Moved to DR at the September, 2013 meeting.]
Currently, 6.9.1 [intro.execution] paragraph 3 says,
Certain other aspects and operations of the abstract machine are described in this International Standard as unspecified (for example, order of evaluation of arguments to a function).
However, the order of evaluation of function arguments is no longer “unspecified;” instead, their value computations are unsequenced. A better example of unspecified behavior is needed.
Proposed resolution (April, 2013):
Change 6.9.1 [intro.execution] paragraph 3 as follows:
Certain other aspects and operations of the abstract machine are described in this International Standard as unspecified (for example,order of evaluation of arguments to a functionevaluation of expressions in a new-initializer if the allocation function fails to allocate memory (7.6.2.8 [expr.new])). Where possible...
[Moved to DR at the February, 2014 meeting as part of document N3914.]
[The following is reproduced verbatim from WG14 DR406 as a C-liaison issue.]
It has been mathematically proved that a simplification can be made to the memory model as it is specified in the final draft of the C++11 standard. Essentially, the restriction defining visible sequence of side effects (vsse) is redundant and can be removed with no ill effects. The main motivation for doing this is that the current restriction is misleading. 5.1.2.4p22 defines vsse's:
The visible sequence of side effects on an atomic object M, with respect to a value computation B of M, is a maximal contiguous sub-sequence of side effects in the modification order of M, where the first side effect is visible with respect to B, and for every subsequent side effect, it is not the case that B happens before it. The value of an atomic object M, as determined by evaluation B, shall be the value stored by some operation in the visible sequence of M with respect to B.
The wording of this paragraph makes it seem as if the vsse identifies the writes that an atomic read is allowed to read from, but this is not the case. There can be writes in the vsse that cannot be read due to the coherence requirements (to be included in C, 1.10p15 through 1.10p18 in C++ N3291). Consequently this is even more confusing than it at first appears.
Also propose changing 5.1.2.4p22 to the following:
The value of an atomic object M, as determined by evaluation B, shall be the value stored by some side effect A that modifies M, where B does not happen before A.
With a note to remind the reader of the coherence requirements:
NOTE: The set of side effects that a given evaluation might take its value from is also restricted by the rest of the rules described here, and in particular, by the coherence requirements below
If the committee is concerned about allowing a differing text from C++11, then a note could be added to assure the reader:
NOTE: Although the rules for multi-threaded executions differ here from those of C++11, the executions they allow are precisely the same. Visible sequences of side effects are a redundant restriction.
Proposed resolution (January, 2014) [SSUPERSEDED]:
Change 6.9.2 [intro.multithread] paragraph 14 as follows:
The visible sequence of side effects on an atomic object M, with respect to a value computation B of M, is a maximal contiguous sub-sequence of side effects in the modification order of M, where the first side effect is visible with respect to B, and for every side effect, it is not the case that B happens before it.The value of an atomic object M, as determined by evaluation B, shall be the value stored by someoperation in the visible sequence of M with respect to Bside effect A that modifies M, where B does not happen before A. [Note:It can be shown that the visible sequence of side effects of a value computation is unique givenThe set of side effects that a given evaluation might take its value from is also restricted by the rest of the rules described here, and in particular, by the coherence requirements below. —end note]
Change 6.9.2 [intro.multithread] paragraph 20 as follows:
[Note: Thevisible sequence of side effectsvalue observed by a load of an atomic depends on the “happens before” relation, which depends on the values observed by loads of atomics, which we are restricting here. The intended reading is that there must exist an association of atomic loads with modifications they observe that, together with suitably chosen modification orders and the “happens before” relation derived as described above, satisfy the resulting constraints as imposed here. —end note]
Change 6.9.2 [intro.multithread] paragraph 22 as follows:
[Note: Compiler transformations that introduce assignments to a potentially shared memory location that would not be modified by the abstract machine are generally precluded by this standard, since such an assignment might overwrite another assignment by a different thread in cases in which an abstract machine execution would not have encountered a data race. This includes implementations of data member assignment that overwrite adjacent members in separate memory locations. Reordering of atomic loads in cases in which the atomics in question may alias is also generally precluded, since this may violate the“visible sequence”coherence rules. —end note]
Change 32.5.4 [atomics.order] paragraph 3 as follows:
There shall be a single total order S on all memory_order_seq_cst operations, consistent with the “happens before” order and modification orders for all affected locations, such that each memory_order_seq_cst operation B that loads a value from an atomic object M observes one of the following values:
the result of the last modification A of M that precedes B in S, if it exists, or
if A exists, the result of some modification of M
in the visible sequence of side effects with respect to Bthat is not memory_order_seq_cst and that does not happen before A, orif A does not exist, the result of some modification of M
in the visible sequence of side effects with respect to Bthat is not memory_order_seq_cst.[Note:...
[Applied to WP at the February, 2014 meeting.]
Should it be permitted for main to have a deduced return type?
(See also issue 1676.)
Proposed resolution (November, 2013):
Change 6.9.3.1 [basic.start.main] paragraph 2 as follows:
An implementation shall not predefine the main function. This function shall not be overloaded. It shall have a declared return type of type int, but otherwise its type is implementation-defined.
All implementationsAn implementation shall allow both
a function of () returning int and
a function of (int, pointer to pointer to char) returning int
as the type...
[Moved to DR at the February, 2014 meeting.]
According to 6.9.3.2 [basic.start.static] paragraph 2,
...Constant initialization is performed:
if each full-expression (including implicit conversions) that appears in the initializer of a reference with static or thread storage duration is a constant expression (7.7 [expr.const]) and the reference is bound to an lvalue designating an object with static storage duration or to a temporary (see 6.7.7 [class.temporary]);
...
This wording, presumably inadvertently, excludes a reference to a function from being constant initialization.
Proposed resolution (January, 2014):
Change 6.9.3.2 [basic.start.static] paragraph 2 as follows:
...Constant initialization is performed:
if each full-expression (including implicit conversions) that appears in the initializer of a reference with static or thread storage duration is a constant expression (7.7 [expr.const]) and the reference is bound to an lvalue designating an object with static storage duration,
orto a temporary (see 6.7.7 [class.temporary]), or to a function;...
[Moved to DR at the September, 2013 meeting.]
According to the current wording of Clause 7 [expr] paragraph 11, the lvalue-to-rvalue conversion applies only to volatile lvalues in the listed contexts. Presumably it should apply to volatile xvalues as well.
Proposed resolution (June, 2013):
Change Clause 7 [expr] paragraph 11 as follows:
...The lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is applied if and only if the expression isan lvaluea glvalue of volatile-qualified type and it is one of the following:...
[Moved to DR at the February, 2014 meeting.]
Consider an example like the following:
struct Base { virtual int call() = 0; }; Base *foo() { constexpr int x = 0; struct Local : Base { virtual int call() { return x; } }; static Local local; return &local; } int main() { return foo()->call(); }
While the likely intention is that the lvalue-to-rvalue conversion of the block-scope constant is implemented by using the value of the constant expression in place of reading from storage, it seems that the wording of 7.3.2 [conv.lval] paragraph 2 does not prevent this program from being subject to undefined behaviour caused by lifetime violation. In particular, it seems that a name expression that appears in a potentially-evaluated expression such that the object named is not odr-used (by that instance of the name) may still be evaluated, in theory, as an lvalue through which the object named or a subobject thereof is accessed.
Proposed resolution (September, 2013):
Change 7.3.2 [conv.lval] paragraph 2 as follows:
When an lvalue-to-rvalue conversion
occurs in an unevaluated operand or a subexpression thereof ( Clause 7 [expr])is applied to an expression e, and either
e is not potentially evaluated, or
the evaluation of e results in the evaluation of a member ex of the set of potential results of e, and ex names a variable x that is not odr-used by ex (6.3 [basic.def.odr]),
the value contained in the referenced object is not accessed. [Example:
struct S { int n; }; auto f() { S x { 1 }; constexpr S y { 2 }; return [&](bool b) { return (b ? y : x).n; }; } auto g = f(); int m = g(false); // undefined behavior due to access of x.n outside its lifetime int n = g(true); // OK, does not access y.n—end example] In all other cases, the result of the conversion...
[Moved to DR at the February, 2014 meeting as part of document N3914.]
The current wording of 7.3.2 [conv.lval] gives the result of fetching an uninitialized unsigned character an unspecified value, which is then stable: assigned to a different variable, it will be the same throughout the lifetime of that variable. It would be more helpful to optimizers for the unspecified value to be viral, so that fetching from the second variable would also yield an unspecified result, not necessarily the same each time.
[Moved to DR at the September, 2013 meeting.]
According to 7.3.7 [conv.prom] paragraph 4,
A prvalue of an unscoped enumeration type whose underlying type is fixed (9.8.1 [dcl.enum]) can be converted to a prvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, a prvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to a prvalue of the promoted underlying type.
Because both of these conversions have the same rank, a call like the following is ambiguous, even though conversion to the underlying type might seem better than conversion to int:
enum E : char { e };
void f(char);
void f(int);
void g() {
f(e); // ambiguous
}
On the other hand, character types often have non-numeric semantics in programs, and programmers might use a character type just to set the size of the enumeration's object representation, not to imply character semantics for the enumeration. It might be better to leave the ambiguity in place in order to require programmers to make their intent explicit.
Proposed resolution (June, 2013):
Change 12.2.4.3 [over.ics.rank] paragraph 4 as follows:
...Two conversion sequences with the same rank are indistinguishable unless one of the following rules applies:
A conversion that does not convert a pointer, a pointer to member, or std::nullptr_t to bool is better than one that does.
A conversion that promotes an enumeration whose underlying type is fixed to its underlying type is better than one that promotes to the promoted underlying type, if the two are different.
If class B is derived...
[Moved to DR at the February, 2014 meeting.]
Lambda expressions cannot appear in unevaluated operands nor in evaluated portions of constant expressions. However, the following example appears to circumvent those restrictions:
template <bool> struct BoolSink { typedef void type; }; template <typename T, typename U> struct AddRvalueReferenceImpl { typedef T type; }; template <typename T> struct AddRvalueReferenceImpl<T, typename BoolSink<false && [] { extern T &&tref; }>::type> { typedef T &&type; }; template <typename T> struct AddRvalueReference : AddRvalueReferenceImpl<T, void> { }; namespace ImplHelpers { template <typename T> typename AddRvalueReference<T>::type create(void) { } } template <typename T, typename U, typename ...Args> struct IsConstructibleImpl { enum { value = 0 }; }; template <typename T, typename ...Args> struct IsConstructibleImpl<T, typename BoolSink<false && [] { T t( ::ImplHelpers::create<Args>() ...); }>::type, Args ...> { enum { value = 1 }; }; template <typename T, typename ...Args> struct IsConstructible : IsConstructibleImpl<T, void, Args ...> { }; struct DestroyMe { ~DestroyMe() = delete; }; static_assert(+IsConstructible<int>::value, "error"); static_assert(!IsConstructible<void>::value, "error"); static_assert(+IsConstructible<int [1]>::value, "error"); static_assert(!IsConstructible<DestroyMe>::value, "error"); static_assert(!IsConstructible<int *, char *>::value, "error"); static_assert(+IsConstructible<int &&, int>::value, "error"); static_assert(!IsConstructible<int &&, int &>::value, "error"); static_assert(+IsConstructible<int &&, int &&>::value, "error");
Is this intended?
Additional notes, April, 2013:
Further discussion has arisen regarding lambda-expressions in function template signatures. Although the restriction that lambda-expressions cannot appear as unevaluated operands (7.5.6 [expr.prim.lambda] paragraph 2) was intended to avert the need to deal with them in function template signatures, the fact that 7.7 [expr.const] treats unevaluated subexpressions separately from unevaluated operands opens another avenue for lambda-expressions in template signatures, e.g.,
template<typename T> void f(int [(0 && [] { for (auto x : T()) {} }, 1)]);
Four possible approaches for dealing with this issue have been suggested:
Allow lambda-expressions in function template signatures. This would be costly in some implementations.
Give a function template internal linkage if its signature includes a lambda-expression. This would allow SFINAE and redeclaration to work without requiring that lambda-expressions be mangled.
Specify that a function signature containing a lambda-expression is not a redeclaration of any other function template, which would allow SFINAE to work but not require declaration matching and mangling.
Do not allow lambda-expressions in function template signatures.
If any of these approaches were adopted, the rationale for disallowing lambda-expressions in unevaluated operands would be removed, so it might make sense to remove the restriction at the same time.
Proposed resolution (September, 2013):
Change 7.5.6 [expr.prim.lambda] paragraph 2 as follows:
...A lambda-expression shall not appear in an unevaluated operand (Clause 7 [expr]), in a template-argument, in an alias-declaration, in a typedef declaration, or in the declaration of a function or function template outside its function body and default arguments. [Note: The intention is to prevent lambdas from appearing in a signature —end note]. [Note: A closure object behaves like a function object (22.10 [function.objects]). —end note]
[Moved to DR at the February, 2014 meeting.]
According to 7.5.6 [expr.prim.lambda] paragraph 3,
The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression. [Note: This determines the set of namespaces and classes associated with the closure type (6.5.4 [basic.lookup.argdep]). The parameter types of a lambda-declarator do not affect these associated namespaces and classes. —end note]
However, 13.9.2 [temp.inst] paragraph 13 says,
If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point.
A possibility, then, is that the closure type for a lambda expression in a default argument for a template function (or, presumably, a member function of a class template) is to be considered as having been declared in some block scope in the body of the fictional function template specialization.
Consider the following example:
namespace J { inline namespace K { template <typename T> int zap(const T &t) { foo(t); return 0; } template <typename T> void zip(int = zap([] { })) { } } template <typename T> void foo(const T &) { } } void bar() { J::K::zip<long>(); }
If zip were not a template, argument-dependent lookup successfully resolves the lookup for foo in all implementations tested; however, there is implementation variance in the handling of the example as written.
(See also issue 1690.)
Proposed resolution (September, 2013):
Change 13.9.2 [temp.inst] paragraph 13 as follows:
If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point, except that the scope in which a closure type is declared (7.5.6 [expr.prim.lambda]) — and therefore its associated namespaces — remain as determined from the context of the definition for the default argument. This analysis is called default argument instantiation. The instantiated default argument is then used as the argument of f.
[Moved to DR at the February, 2014 meeting.]
It is not clear whether __func__ in the body of a lambda refers to the operator() of the closure class or to the containing function (if any). Since lambdas can appear in non-function scope, it would be preferable for them to refer to the closure class's operator().
Proposed resolution (September, 2013):
Change 7.5.6 [expr.prim.lambda] paragraph 7 as follows:
The lambda-expression's compound-statement yields the function-body... —end example] Further, a variable __func__ is implicitly defined at the beginning of the compound-statement of the lambda-expression, with semantics as described in 9.6.1 [dcl.fct.def.general].
[Moved to DR at the February, 2014 meeting.]
The description of the characteristics of a closure class in 7.5.6 [expr.prim.lambda] leaves open the possibility that such a class might be a literal type, although it could also be a non-literal type. It would probably be good to specify that a closure class is not a literal type, in the interests of portability.
On a related note, it might be wise to remove the implementation freedom to make a closure class either a POD or not a POD; it seems only to be an invitation for portability problems with no real advantage.
Additional note, April, 2013:
Some have expressed the opinion that such portability issues are just “par for the course,” and that specifying these characteristics at this point might unnecessarily limit the ability to extend the language in desirable directions at some point in the future.
Proposed resolution (April, 2013) [superseded]:
Change 7.5.6 [expr.prim.lambda] paragraph 3 as follows:
...An implementation may define the closure type differently from what is described below provided this does not alter the observable behavior of the program other than by changing:
the size and/or alignment of the closure type,
whether the closure type is trivially copyable ( Clause 11 [class]),
- whether the closure type is a literal type (6.8 [basic.types]),
whether the closure type is a standard-layout class ( Clause 11 [class]), or
whether the closure type is a POD class ( Clause 11 [class]).
Notes from the September, 2013 meeting:
At the suggestion of EWG, it was decided that closure types should be specified as not literal, rather than being unspecified as in the April, 2013 proposed resolution. The issue has thus been returned to "drafting" status.
Proposed resolution (January, 2014):
Change 7.5.6 [expr.prim.lambda] paragraph 3 as follows:
The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type — called the closure type — whose properties are described below. This class type isnotneither an aggregate (9.5.2 [dcl.init.aggr]) nor a literal type (6.8 [basic.types]). The closure type is declared...
[Moved to DR at the February, 2014 meeting.]
It is not clear from the description of capturing in 7.5.6 [expr.prim.lambda] whether an implicit capture resulting from the odr-use of a member of an anonymous union captures that member or the anonymous union, and there is implementation divergence. For example,
int main() { static int result; struct A { int x; }; struct B { int y; }; union { A a; B b; }; a.x = 1; [=]() mutable { a.x = 2; result = b.y; }(); if (result == 1) return 0; throw 0; }
Notes from the April, 2013 meeting:
CWG decided that an attempt to capture a member of an anonymous union should be ill-formed.
Proposed resolution (September, 2013):
Change 7.5.6 [expr.prim.lambda] paragraph 15 as follows:
...An array of runtime bound (9.3.4.5 [dcl.array]) or a member of an anonymous union shall not be captured by copy.
Change 7.5.6 [expr.prim.lambda] paragraph 16 as follows:
...It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference. A member of an anonymous union shall not be captured by reference. [Note:...
[Moved to DR at the February, 2014 meeting.]
Since instances of a variable in constant expressions may be odr-uses, the ordering of:
constant expression evaluation,
the determination of implicit captures, and
the transformation to use closure members for odr-uses of captured variables
may affect the semantics of a program such as the one below.
The transformation under 7.5.6 [expr.prim.lambda] paragraph 17 introduces uses of the this pointer of the operator() in its function-body. These instances of this are invalid under issue 1369 if the transformation is applied before the evaluation of the constant expressions. Without the resolution of issue 1369, another situation occurs where instances of this in the compound-statement are transformed into class member access expressions (see the initializations of addrEqA and addrEqB below).
Also, for the initialization of nonZero below, the expression fails to be a constant expression if the transformation is applied before constant expression evaluation.
Finally, the answer to the static assertion changes depending on whether the constant expression evaluation is performed before the transformation as opposed to after and whether the proposed resolution issue 1472 is enabled.
There appears to be implementation divergence regarding
whether the lexical instances of this in the initialization of addrEqB is valid and
whether the static assertion should pass.
Using explicit value captures is not a panacea, since the paragraph 17 transformations only apply to odr-uses. As a result of the resolution of issue 1472, if the reference r below happened to have been initialized with a constant expression, the value of its (modifiable) target is not captured; if the same target were specified in the initialization of the reference with a non-constant expression, its value would be captured.
struct A { void foo(); }; struct LitType { int val; }; constexpr int ceFunc(const LitType &x) { return x.val; } void A::foo() { constexpr LitType y = { 0 }; static int z; int x, &r = z; [=] { constexpr bool addrEqA = &x == &x; // ill-formed under issue 1369 after transformation // under paragraph 17 constexpr bool addrEqB = &*this == &*this; // well-formed after transformation under N3290 // paragraph 17 constexpr bool nonZero = ceFunc(y); // lvalue-to-rvalue conversion occurs only after // function invocation substitution; the closure member, // being not a variable, cannot be constexpr static_assert(&r != &z, "reference which could be captured by value found to alias target"); // affected by issue 1472 }; }
Proposed resolution (September, 2013):
Add the following bullet to 7.7 [expr.const] paragraph 2:
A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following expressions:
...
in a lambda-expression, a reference to this or to a variable with automatic storage duration defined outside that lambda-expression, where the reference would be an odr-use (6.3 [basic.def.odr], 7.5.6 [expr.prim.lambda]);
a conversion from type cv void * to a pointer-to-object type;
...
[Moved to DR at the February, 2014 meeting.]
The example in 7.5.6 [expr.prim.lambda] paragraph 24 reads,
template<class... Args> void f(Args... args) { auto lm = [&, args...] { return g(args...); }; lm(); }
However, it's not clear how this example squares with the requirements in paragraph 10,
The identifier in a simple-capture is looked up using the usual rules for unqualified name lookup (6.5.3 [basic.lookup.unqual]); each such lookup shall find a variable with automatic storage duration declared in the reaching scope of the local lambda expression.
since a function parameter pack is not a variable. Although the requirement might be met in a given specialization of the function template, is there a rule that relaxes it in the context of the function template definition?
Proposed resolution (September, 2013):
Change 7.5.6 [expr.prim.lambda] paragraph 10 as follows:
The identifier in a simple-capture is looked up using the usual rules for unqualified name lookup (6.5.3 [basic.lookup.unqual]); each such lookup shall find an entity. An entity that is designated by a simple-capture is said to be explicitly captured, and shall be this or a variable with automatic storage duration declared in the reaching scope of the local lambda expression.An entity (i.e. a variable or this) is said to be explicitly captured if it is found by this process.
Change 13.7.4 [temp.variadic] paragraph 4 as follows:
...Pack expansions can occur in the following contexts:
...
For the purpose of determining whether a parameter pack satisfies a rule regarding entities other than parameter packs, the parameter pack is considered to be the entity that would result from an instantiation of the pattern in which it appears.
[Example:
...
Change 13.7.4 [temp.variadic] paragraph 6 as follows:
...Each Ei is generated by instantiating the pattern and replacing each pack expansion parameter with its ith element. Such an element, in the context of the instantiation, is interpreted as follows:
if the pack is a template parameter pack, the element is a template parameter (13.2 [temp.param]) of the corresponding kind (type or non-type) designating the type or value from the template argument; otherwise,
if the pack is a function parameter pack, the element is an id-expression designating the function parameter that resulted from the instantiation of the pattern where the pack is declared.
All of the Ei become elements in the enclosing list. [Note:...
[Applied to WP at the February, 2014 meeting.]
According to 7.5.6 [expr.prim.lambda] paragraph 10,
The identifier in a simple-capture is looked up using the usual rules for unqualified name lookup (6.5.3 [basic.lookup.unqual]); each such lookup shall find a variable with automatic storage duration declared in the reaching scope of the local lambda expression.
This does not permit a nested lambda to capture the non-static data member of the closure type introduced by an init-capture. A similar restriction applies to implicit capture in paragraph 12.
Presumably such captures should be allowed, capturing the non-static data member directly rather than the this pointer from the enclosing lambda's operator().
Proposed resolution (September, 2013):
This issue is resolved by the resolution of issue 1760.
[Applied to WP at the February, 2014 meeting.]
The access of the non-static data member corresponding to an init-capture is not specified. The question would be moot if the member were unnamed like the other non-static data members of the closure class.
Proposed resolution (September, 2013):
For every init-capture a non-static data member named by the identifier of the init-capture is declared in the closure type. This member is not a bit-field and not mutable. The type of that member corresponds to the type of a hypotheticalAn init-capture behaves as if it declares and explicitly captures a variabledeclarationof the form “auto init-capture ;” whose declarative region is the lambda-expression's compound-statement, except thatthe variable name (i.e., the identifier of the init-capture) is replaced by a unique identifier.:
if the capture is by copy (see below), the non-static data member declared for the capture and the variable are treated as two different ways of referring to the same object, which has the lifetime of the non-static data member, and no additional copy and destruction is performed, and
if the capture is by reference, the variable's lifetime ends when the closure object's lifetime ends.
[Note: This enables an init-capture like “x = std::move(x)”; the second “x” must bind to a declaration in the surrounding context. —end note]
No entity is captured by an init-capture. Within the lambda-expressions lambda-declarator and compound-statement, the identifier in the init-capture hides any declaration of the same name in scopes enclosing the lambda-expression.[Example:...
Change 7.5.6 [expr.prim.lambda] paragraph 15 as follows:
An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture thatdoes not include an &is not of the form & identifier or & identifier initializer. For each entity...
Change 7.5.6 [expr.prim.lambda] paragraph 18 as follows:
Every id-expression within the compound-statement of a lambda-expression that is an odr-use (6.3 [basic.def.odr]) of an entity captured by copy is transformed into an access to the corresponding unnamed data member of the closure type. [Note:...
This resolution also resolves issue 1681.
[Moved to DR at the February, 2014 meeting.]
In a declaration like
T&& r = static_cast<T&&>(T());
it is not clear what the lifetime of the T temporary should be. According to 7.6.1.9 [expr.static.cast] paragraph 4, the static_cast is equivalent to a declaration of an invented temporary variable t. The lifetime of the temporary is extended to that of t, but it is not clear what that lifetime should be, nor if the subsequent binding of t to r would affect the lifetime of the original temporary. (See also issue 1568.)
Notes from the February, 2012 meeting:
The reference is bound to the xvalue result of the static_cast, so the lifetime of the temporary is not extended and this example results in a dangling reference.
Proposed resolution (February, 2012) [SUPERSEDED]:
Change 7.6.1.9 [expr.static.cast] paragraph 4 as follows:
Otherwise, an expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration T t(e); is well-formed, for some inventedtemporaryvariable t (9.5 [dcl.init]). The effect...
Proposed resolution (September, 2013):
Change 7.6.1.9 [expr.static.cast] paragraph 3 as follows:
A glvalue, class prvalue, or array prvalue of type “cv1 T1” can be cast to type “rvalue reference to cv2 T2” if “cv2 T2” is reference-compatible with “cv1 T1” (9.5.4 [dcl.init.ref]). If theglvaluevalue is not a bit-field, the result refers to the object or the specified base class subobject thereof; otherwise, the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is applied to the bit-field and the resulting prvalue is used as the expression of the static_cast for the remainder of this section. If T2 is an inaccessible (11.8 [class.access]) or ambiguous (6.5.2 [class.member.lookup]) base class of T1, a program that necessitates such a cast is ill-formed.
[Moved to DR at the February, 2014 meeting.]
The specification of conversion from integral or enumeration type in 7.6.1.9 [expr.static.cast] paragraph 10 uses the phrase “explicitly converted,” while the description of conversion from floating point to enumeration omits the word “explicitly.” Presumably these should be harmonized.
Proposed resolution (January, 2014):
Change 7.6.1.9 [expr.static.cast] paragraph 10 as follows:
...A value of floating-point type can also be explicitly converted to an enumeration type...
[Applied to WP at the February, 2014 meeting as part of document N3914.]
The strategy of merging allocations could turn a small memory leak into a big one. For example,
class P { int x; }; class Q { public: Q(){ throw 42; } private: int x[LARGE_NUMBER]; }; { P* p1 = new P(); Q* q1 = new Q(); // bang :-( // don't get here delete q1; delete p1; }
Instead of just leaking the first allocation, this could result in leaking the combined allocation.
Notes from the September, 2013 meeting:
EWG was not so concerned with memory leaks, but there are problems with the current wording dealing with lifetime issues. In particular, the existing wording did not guarantee that all of the merged allocations would, in fact, be executed. The intended direction is to relax the strict lifetime containment requirement from the current wording but to ensure that all of the allocations and frees will be executed, which requires that the allocation and initialization of later objects be non-throwing.
[Moved to DR at the February, 2014 meeting.]
According to 7.6.10 [expr.eq] paragraph 2, pointers to data members compare equal
if and only if they would refer to the same member of the same most derived object (6.7.2 [intro.object]) or the same subobject if indirection with a hypothetical object of the associated class type were performed.
This specification is overly constrained. For data members, most implementations simply compare the offsets of the members involved, violating the “only if” part of the specification. For example:
struct A {}; struct B : A { int x; }; struct C : A { int x; }; int A::*bx = (int(A::*))&B::x; int A::*cx = (int(A::*))&C::x; bool b1 = bx == cx;
The existing wording requires b1 to have the value false, even though the offsets of the members are the same. It would be better if the result of the comparison were unspecified unless the class containing the original member for the LHS is a base or derived class of the class containing the original member for the RHS.
Proposed resolution (January, 2014):
Change 7.6.10 [expr.eq] paragraph 3 as follows:
...Comparing pointers to members is defined as follows:
...
If either is a pointer to a virtual member function, the result is unspecified.
If one refers to a member of class C1 and the other refers to a member of a different class C2, where neither is a base class of the other, the result is unspecified. [Example:
struct A {}; struct B : A { int x; }; struct C : A { int x; }; int A::*bx = (int(A::*))&B::x; int A::*cx = (int(A::*))&C::x; bool b1 = (bx == cx); // unspecified
—end example]
TwoOtherwise, two pointers to members compare equal if they would refer to the same member of the same most derived object (6.7.2 [intro.object]) or the same subobject if indirection with a hypothetical object of the associated class type were performed, otherwise they compare unequal. [Example:struct B { int f(); }; struct L : B { }; struct R : B { }; struct D : L, R { }; int (B::*pb)() = &B::f; int (L::*pl)() = pb; int (R::*pr)() = pb; int (D::*pdl)() = pl; int (D::*pdr)() = pr; bool x = (pdl == pdr); // false bool y = (pb == pl); // true—end example]
[Moved to DR at the February, 2014 meeting.]
The interaction of various issue resolutions has inadvertently resulted in the loss of the prohibition of defining types in conditions (8.5 [stmt.select] paragraph 1) and in a range-based for (8.6 [stmt.iter] paragraph 1). Issue 686 addressed a patchwork of restrictions by banning type definitions in type-specifier-seqs. Issue 948 then changed the definition of condition to use a decl-specifier-seq instead of a type-specifier-seq in order to permit the constexpr specifier. A similar change was made for range-based for statements by issue 1204.
The restrictions against type definitions in these contexts should be restored.
Proposed resolution (January, 2014):
Change 8.5 [stmt.select] paragraph 2 as follows:
The rules for conditions apply both to selection-statements and to the for and while statements (8.6 [stmt.iter]). The declarator shall not specify a function or an array. The decl-specifier-seq shall not define a class or enumeration. If the auto type-specifier appears in the decl-specifier-seq, the type of the identifier being declared is deduced from the initializer as described in 9.2.9.7 [dcl.spec.auto].
Change 8.6.5 [stmt.ranged] paragraph 2 as follows:
In the decl-specifier-seq of a for-range-declaration, each decl-specifier shall be either a type-specifier or constexpr. The decl-specifier-seq shall not define a class or enumeration.
[Moved to DR at the February, 2014 meeting.]
The description of the switch statement and case labels in 8.5.3 [stmt.switch] paragraph 2 apply integral promotions to the condition value and refer to the “promoted type” of the condition. However, the integral promotions (7.3.7 [conv.prom]) do not describe the result when they are applied to a scoped enumeration value.
Proposed resolution (September, 2013):
Change 8.5.3 [stmt.switch] paragraph 2 as follows:
The condition shall be of integral type, enumeration type, or class type. If of class type, the condition is contextually implicitly converted (7.3 [conv]) to an integral or enumeration type.
Integral promotions are performed.If the (possibly converted) type is subject to integral promotions (7.3.7 [conv.prom]), the condition is converted to the promoted type. Any statement within the switch statement can be labeled with one or more case labels as follows:case constant-expression :
where the constant-expression shall be a converted constant expression (7.7 [expr.const]) of the
promotedadjusted type of the switch condition. No two of the case constants in the same switch shall have the same value after conversionto the promoted type of the switch condition.
[Moved to DR at the September, 2013 meeting.]
According to 9.2.2 [dcl.stc] paragraph 4,
When thread_local is applied to a variable of block scope the storage-class-specifier static is implied if it does not appear explicitly.
This, presumably unintentionally, prohibits a declaration like
void f() { extern thread_local int n; }
Proposed resolution (April, 2013):
When thread_local is applied to a variable of block scope the storage-class-specifier static is implied ifit does not appear explicitlyno other storage-class-specifier appears in the decl-specifier-seq.
[Moved to DR at the September, 2013 meeting.]
After the resolution of issue 1359, one of the requirements for constexpr constructors is:
if the class is a non-empty union, or for each non-empty anonymous union member of a non-union class, exactly one non-static data member shall be initialized;
This wording does not appear to handle nested anonymous unions. For example:
struct S { union { union { int x = 1; float f; }; void *p = nullptr; }; };
Clearly here both S::x and S::p are initialized, but that does not appear to violate the new constraint.
Additional note (March, 2013):
It is not clear whether this example violates 11.5 [class.union] paragraph 5:
The member-specification of an anonymous union shall only define non-static data members. [Note: Nested types and functions cannot be declared within an anonymous union. —end note]
Is a nested anonymous union a “non-static data member” or a “nested type?”
See also issues 57, 1460, 1562, 1587, 1621, and 1623.
Proposed resolution (June, 2013):
Change 11.5 [class.union] paragraph 5 as follows:
...The member-specification of an anonymous union shall only define non-static data members. [Note: Nested types, anonymous unions, and functions cannot be declared within an anonymous union. —end note] The names of the members...
[Moved to DR at the September, 2013 meeting.]
According to 9.2.6 [dcl.constexpr] paragraph 5
For a constexpr constructor, if no argument values exist such that after function invocation substitution, every constructor call and full-expression in the mem-initializers would be a constant expression (including conversions), the program is ill-formed; no diagnostic required.
However, paragraph 4 also says,
every constructor involved in initializing non-static data members and base class sub-objects shall be a constexpr constructor;
violation of which requires a diagnostic. The question is whether a constructor call appearing in a mem-initializer expression is “involved in” the initialization of X::m. Given the “no diagnostic required” status of constructor calls in paragraph 5, the intent of the “involved in” phrasing would appear to be referring to constructors of members with class types and of base-class subobjects, but the wording should be clarified. For example, in a constructor definition like
constexpr X(): m(f(S())) { }
if S::S() is not constexpr, is a diagnostic required? For another example,
struct S { constexpr S() {} S(int); }; struct A { S s; }; struct C { A x; constexpr C(): x{ 1 } {} };
Is S::S(int) “involved?”
Proposed resolution (August, 2013):
Change 9.2.6 [dcl.constexpr] paragraph 4 as follows:
...In addition, either its function-body shall be = delete, or it shall satisfy the following constraints:
...
for a non-delegating constructor, every constructor
involved in initializingselected to initialize non-static data members and base class sub-objects shall be a constexpr constructor.;for a delegating constructor, the target constructor shall be a constexpr constructor.
[Applied to WP at the February, 2014 meeting.]
The current wording of 9.2.6 [dcl.constexpr] paragraph 8 is:
The constexpr specifier has no effect on the type of a constexpr function or a constexpr constructor. The class of which a constexpr function is a member shall be a literal type (6.8 [basic.types]).
The previous version of this wording made clear that the restriction on the class type applied only to non-static member functions; consequently, the new formulation has inadvertently banned static constexpr member functions of non-literal classes.
Proposed resolution (November, 2013):
Change 9.2.6 [dcl.constexpr] paragraph 8 as follows:
The constexpr specifier has no effect on the type of a constexpr function or a constexpr constructor.The class of which a constexpr function is a member shall be a literal type (6.8 [basic.types]).[Example:...
[Moved to DR at the February, 2014 meeting.]
The grammar for elaborated-type-specifier in 9.2.9.5 [dcl.type.elab] reads, in part,
This allows use of the template keyword without a nested-name-specifier, e.g., struct template S<int>. This is inconsistent with other uses of the template keyword. It might be better to split the production in two and only allow the keyword following a nested-name-specifier, i.e.,
Proposed resolution (January, 2014):
Change the grammar in 9.2.9.5 [dcl.type.elab] as follows:
[Applied to WP at the February, 2014 meeting.]
The following example appears in 9.2.9.7 [dcl.spec.auto] paragraph 12:
template <class T> auto f(T t) { return t; } // return type deduced at instantiation time typedef decltype(f(1)) fint_t; // instantiates f<int> to deduce return type template<class T> auto f(T* t) { return *t; } void g() { int (*p)(int*) = &f; } // instantiates both fs to determine return types, // chooses second
This is the desired behavior, but the current wording does not achieve that effect. One possible way this could work would be:
auto is treated as a non-deduced context
in 13.10.3.3 [temp.deduct.funcaddr], the deduced A is produced by substituting the deduced template arguments into the original function template, rather than substituting them into P, and the deduced A is the type of the produced function declaration (triggering instantiation of a definition as needed to complete the type)
Proposed resolution (November, 2013):
Add the following as a new paragraph at the end of 13.10.3.3 [temp.deduct.funcaddr]:
A placeholder type (9.2.9.7 [dcl.spec.auto]) in the return type of a function template is a non-deduced context. If template argument deduction succeeds for such a function, the return type is determined from instantiation of the function body.
[Moved to DR at the September, 2013 meeting.]
It is not sufficiently clear from the existing wording that pointers and references to function types containing cv-qualifiers or a ref-qualifier are not permitted and thus would result in a deduction failure if created during template argument substitution. Normative wording to that effect should be added to, e.g., 9.3.4.6 [dcl.fct].
Proposed resolution (October, 2012) [SUPERSEDED]:
Change 9.3.4.2 [dcl.ptr] paragraph 4 as follows:
[Note:There are no pointers to referencesForming a pointer to reference type is ill-formed; see 9.3.4.3 [dcl.ref]. Forming a pointer to a function type that has cv-qualifiers or a ref-qualifier is ill-formed; see 9.3.4.6 [dcl.fct]. Since the address of a bit-field (11.4.10 [class.bit]) cannot be taken, a pointer can never point to a bit-field. —end note]
Change 9.3.4.3 [dcl.ref] paragraph 6 as follows:
If atypedeftypedef-name (9.2.4 [dcl.typedef]), a type template-parameter (13.4.2 [temp.arg.type]), 13.2 [temp.param]) or a decltype-specifier (9.2.9.3 [dcl.type.simple]) denotes a type TR that is a reference to a type T, an attempt to create...
Add the following as a new paragraph at the end of 9.3.4.3 [dcl.ref]:
[Note: Forming a reference to a function type that has cv-qualifiers or a ref-qualifier is ill-formed; see 9.3.4.6 [dcl.fct]. —end note]
Change 9.3.4.6 [dcl.fct] paragraph 6 as follows:
A function type with a cv-qualifier-seq or a ref-qualifier (including by typedef-name (9.2.4 [dcl.typedef], 13.2 [temp.param]) or decltype-specifier (9.2.9.3 [dcl.type.simple])) shall only
be part ofappear as:
...
the type-id of a template-argument for a type-parameter (
13.3 [temp.names]13.4.2 [temp.arg.type]).[Example:
typedef int FIC(int) const; FIC f; // ill-formed: does not declare a member function struct S { FIC f; // OK }; FIC S::*pm = &S::f; // OK—end example]
The effect of a cv-qualifier-seq in a function declarator...
Delete the following text from 9.3.4.6 [dcl.fct] paragraph 10 (the example is moved to paragraph 6, as indicated in the preceding change):
A typedef of a function type whose declarator includes a cv-qualifier-seq shall be used only to declare the function type for a non-static member function, to declare the function type to which a pointer to member refers, or to declare the top-level function type of another function typedef declaration. [Example: ... —end example]
Additional note (March, 2013):
The issue is being returned to "review" status out of concern that 12.5 [over.built] paragraph 11 requires forming a reference to a function type that might have a cv-qualifier or ref-qualifier.
Proposed resolution (April, 2013):
Change 9.3.4.2 [dcl.ptr] paragraph 4 as follows:
[Note:There are no pointers to referencesForming a pointer to reference type is ill-formed; see 9.3.4.3 [dcl.ref]. Forming a pointer to function type is ill-formed if the function type has cv-qualifiers or a ref-qualifier; see 9.3.4.6 [dcl.fct]. Since the address of a bit-field (11.4.10 [class.bit]) cannot be taken, a pointer can never point to a bit-field. —end note]
Change 9.3.4.3 [dcl.ref] paragraph 6:
If atypedeftypedef-name (9.2.4 [dcl.typedef]), a type template-parameter (13.4.2 [temp.arg.type]), 13.2 [temp.param]) or a decltype-specifier (9.2.9.3 [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:...
Add the following as a new paragraph at the end of 9.3.4.3 [dcl.ref]:
[Note: Forming a reference to function type is ill-formed if the function type has cv-qualifiers or a ref-qualifier; see 9.3.4.6 [dcl.fct]. —end note]
Change 9.3.4.6 [dcl.fct] paragraph 6 as follows:
A function type with a cv-qualifier-seq or a ref-qualifier (including a type named by typedef-name (9.2.4 [dcl.typedef], 13.2 [temp.param])) shall appear only
be part ofas:
the function type for a non-static member function,
the function type to which a pointer to member refers,
the top-level function type of a function typedef declaration or alias-declaration,
the type-id in the default argument of a type-parameter (13.2 [temp.param]), or
the type-id of a template-argument for a type-parameter (
13.3 [temp.names]13.4.2 [temp.arg.type]).[Example:
typedef int FIC(int) const; FIC f; // ill-formed: does not declare a member function struct S { FIC f; // OK }; FIC S::*pm = &S::f; // OK—end example] The effect of a cv-qualifier-seq...
Change 9.3.4.6 [dcl.fct] paragraph 10 as follows, moving the example to paragraph 6 as shown above:
...—end example]A typedef of a function type whose declarator includes a cv-qualifier-seq shall be used only to declare the function type for a non-static member function, to declare the function type to which a pointer to member refers, or to declare the top-level function type of another function typedef declaration. [Example: ... —end example]
Change 12.5 [over.built] paragraph 11 as follows:
For every quintuple (C1, C2, T, CV1, CV2), where C2 is a class type, C1 is the same type as C2 or is a derived class of C2, T is an object type or a function type, and CV1 and CV2 are cv-qualifier-seqs, there exist candidate operator functions of the form
CV12 T& operator->*(CV1 C1*, CV2 T C2::*);
where CV12 is the union of CV1 and CV2. The return type is shown for exposition only; see 7.6.4 [expr.mptr.oper] for the determination of the operator's result type.
[Moved to DR at the February, 2014 meeting.]
According to 9.3.4.7 [dcl.fct.default] paragraph 9,
Default arguments are evaluated each time the function is called.
Obviously, what was intended by this is that the default argument is evaluated only if no corresponding actual argument is provided, but this could be read as indicating that the default argument is evaluated and discarded by every function call.
Proposed resolution (January, 2014):
Change 9.3.4.7 [dcl.fct.default] paragraph 9 as follows:
Default arguments areA default argument is evaluated each time the function is called with no argument for the corresponding parameter. The order of evaluation...
[Moved to DR at the September, 2013 meeting.]
In 9.5.5 [dcl.init.list] paragraph 5, both the cases in which a reference can be bound to the result of a conversion function use the phrase “can be implicitly converted to...” This is confusing, as it could be read as excluding explicit conversion functions. However, that appears not to be the intent, as 12.2.2.7 [over.match.ref], which is cited in these cases, allows explicit conversion functions for direct-initialization.
Proposed resolution (August, 2011) [SUPERSEDED]:
Change the two indicated (not contiguous) sub-bullets of 9.5.4 [dcl.init.ref] paragraph 5 as follows:
has a class type (i.e., T2 is a class type), where
T1 is not reference-related to T2, and can be
implicitly converted to an lvalue of type “cv3
T3,”...
has a class type (i.e., T2 is a class type), where
T1 is not reference-related to T2, and can be
implicitly converted to an xvalue, class prvalue, or
function lvalue of type “cv3 T3”, where
“cv1 T1” is reference-compatible with
“cv3 T3” (see 12.2.2.7 [over.match.ref]),
Additional note, January, 2012:
Questions have been raised regarding the consistency of the treatment of class prvalues in this resolution with other types . The issue is thus being returned to "review" status for additional discussion.
Proposed resolution (February, 2012) [SUPERSEDED]:
Change 9.5.4 [dcl.init.ref] paragraph 5 as follows:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
If the reference is an lvalue reference and the initializer expression
is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be
implicitlyconverted to an lvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3”106 (this conversion is selected by enumerating the applicable conversion functions (12.2.2.7 [over.match.ref]) and choosing the best one through overload resolution (12.2 [over.match])),then the reference is bound...
Otherwise, the reference shall be...
If the initializer expression
is an xvalue, class prvalue, array prvalue or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be
implicitlyconverted to an xvalue, class prvalue,or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (see 12.2.2.7 [over.match.ref]),then the reference is bound... [Example:
struct A { }; struct B : A { } b; extern B f(); const A& rca2 = f(); // bound to the A subobject of the B rvalue. A&& rra = f(); // same as above struct X { operator B(); operator int&(); } x;const A& r = x; // bound to the A subobject of the result of the conversionint i2 = 42; int&& rri = static_cast<int&&>(i2); // bound directly to i2B&& rrb = x; // bound directly to the result of operator Bint&& rri2 = X(); // error: lvalue-to-rvalue conversion applied to the // result of operator int&—end example]
Otherwise, a temporary... [Example:
const A& r = x; // r refers to a temporary B&& rrb = x; // rrb refers to a temporary const double& rcd2 = 2; // rcd2 refers to temporary with value 2.0 double&& rrd = 2; // rrd refers to temporary with value 2.0 ...
Change 12.2.2.7 [over.match.ref] paragraph 1 as follows:
Under the conditions specified in 9.5.4 [dcl.init.ref], a reference can be bound directly to a glvalue
or class prvaluethat is the result of applying a conversion function...
The conversion functions of S and its base classes are considered, except that for copy-initialization, only the non-explicit conversion functions are considered. Those that are not hidden within S and yield type “lvalue reference to cv2 T2” (when 9.5.4 [dcl.init.ref] requires an lvalue result) or
“cv2 T2” or“rvalue reference to cv2 T2” (when 9.5.4 [dcl.init.ref] requires an rvalue result), where “cv1 T” is reference-compatible (9.5.4 [dcl.init.ref]) with “cv2 T2”, are candidate functions.
Note from the April, 2013 meeting:
Because of concerns about slicing and performance in the February, 2012 proposed resolution, CWG decided to return to the August, 2011 proposed resolution and split off the discussion about class prvalues into issue 1650.
Proposed resolution (April, 2013):
Change the two indicated (not contiguous) sub-bullets of 9.5.4 [dcl.init.ref] paragraph 5 as follows:
has a class type (i.e., T2 is a class type), where
T1 is not reference-related to T2, and can be
implicitly converted to an lvalue of type “cv3
T3,”...
has a class type (i.e., T2 is a class type), where
T1 is not reference-related to T2, and can be
implicitly converted to an xvalue, class prvalue, or
function lvalue of type “cv3 T3”, where
“cv1 T1” is reference-compatible with
“cv3 T3” (see 12.2.2.7 [over.match.ref]),
[Moved to DR at the February, 2014 meeting.]
Bullet 2 sub-bullet 2 of 9.5.4 [dcl.init.ref] paragraph 5 says,
Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy-initialization (9.5 [dcl.init]). The reference is then bound to the temporary.
It is not clear what “using the rules for a non-reference copy-initialization” means. If it means that the temporary is initialized as if it were a standalone variable, the last bullet of 9.5 [dcl.init] paragraph 17 could apply:
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 12.2.2.5 [over.match.copy], and the best one is chosen through overload resolution (12.2 [over.match]). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type. The temporary is a prvalue. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.
That is, for an example like
struct X { X(int) {} X(X const &) = delete; }; void f() { X const &x = 0; }
the specification requires creation of a temporary X(0), copying that tempoary to another temporary (subject to copy elision), and binding the reference to that second temporary. In the example above, because the copy constructor is deleted, the example is ill-formed, although current implementations fail to diagnose it as an error.
Is this intended?
Proposed resolution (September, 2013):
Change the last bullet of 9.5.4 [dcl.init.ref] paragraph 5, breaking it into sub-bullets, and the subsequent example as follows:
Otherwise:
If T1 is a class type, user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1 T1” by user-defined conversion (9.5 [dcl.init], 12.2.2.5 [over.match.copy]); the program is ill-formed if the corresponding non-reference copy-initialization would be ill-formed. The result of the call to the conversion function, as described for the non-reference copy-initialization, is then used to direct-initialize the reference. The program is ill-formed if the direct-initialization does not result in a direct binding or if it involves a user-defined conversion.
If T1 is a non-class type, a temporary of type
“cv1 T1” is created
and copy-initialized (9.5 [dcl.init]) from the
initializer expression using the rules for a non-reference
copy-initialization (9.5 [dcl.init]). The reference is then
bound to the temporary.
If T1 is reference-related
to T2,:
cv1 shall be the same cv-qualification as, or greater
cv-qualification than, cv2.; and
If T1 is reference-related to T2 and
if the reference is an rvalue reference, the initializer
expression shall not be an lvalue.
[Example:
struct Banana { }; struct Enigma { operator const Banana(); }; void enigmatic() { typedef const Banana ConstBanana; Banana &&banana1 = ConstBanana(); // ill-formed Banana &&banana2 = Enigma(); // ill-formed } const double& rcd2 = 2; // rcd2 refers to temporary with value 2.0 ...
Change 12.2.2.5 [over.match.copy] paragraph 1 as follows:
Under the conditions specified in 9.5 [dcl.init], as part of a copy-initialization of an object of class type, a user-defined conversion can be invoked to convert an initializer expression to the type of the object being initialized. Overload resolution is used to select the user-defined conversion to be invoked. [Note: The conversion performed for indirect binding to a reference to a possibly cv-qualified class type is determined in terms of a corresponding non-reference copy-initialization. —end note] Assuming that
[Moved to DR at the September, 2013 meeting.]
Can a constructor template ever be an initializer-list constructor without std::initializer_list (or an alias template specialization for it) appearing in the template signature? E.g., is there any way that the constructor in:
struct S { template<typename T> S(T); };
can be an initializer-list constructor?
Proposed resolution (October, 2012) [superseded]:
Modify 9.5.5 [dcl.init.list] paragraph 2 as follows:
A constructor is an initializer-list constructor if its first parameter is of type std::initializer_list<E> or reference to possibly cv-qualified std::initializer_list<E> for some type E, and either there are no other parameters or else all other parameters have default arguments (9.3.4.7 [dcl.fct.default]). [Note: Initializer-list constructors are favored over other constructors in list-initialization (12.2.2.8 [over.match.list]). Given a class C, a constructor template such as template<class T> C(T) is never instantiated to produce an initializer-list constructor, because an initializer list argument causes the corresponding parameter to be a non-deduced context (13.10.3.2 [temp.deduct.call]). —end note] The template std::initializer_list is not predefined;...
Additional note (January, 2013):
The wording of the new note needs to be adjusted, because such a constructor template might have a default template argument that is a specialization of std::initializer_list. For example:
struct D { template<typename T = std::initializer_list<int>> D(T); }; D d{{1, 2, 3}};
Proposed resolution (June, 2013):
Change the note in 9.5.5 [dcl.init.list] paragraph 2 as follows:
[Note: Initializer-list constructors are favored over other constructors in list-initialization (12.2.2.8 [over.match.list]). Passing an initializer list as the argument to the constructor template template<class T> C(T) of a class C does not create an initializer-list constructor, because an initializer list argument causes the corresponding parameter to be a non-deduced context (13.10.3.2 [temp.deduct.call]). —end note]
[Moved to DR at the February, 2014 meeting.]
According to 9.6.2 [dcl.fct.def.default] paragraph 2,
An explicitly-defaulted function may be declared constexpr only if it would have been implicitly declared as constexpr, and may have an explicit exception-specification only if it is compatible (14.5 [except.spec]) with the exception-specification on the implicit declaration.
The requirement for exception-specifications has unfortunate consequences for the standard library component atomic, as described in LWG issue 2165: the component cannot be used with a T unless T is nothrow default constructible, even if the std::atomic<T> variable is never default initialized.
Proposed resolution (September, 2013):
Change 9.6.2 [dcl.fct.def.default] paragraphs 2 and 3 as follows:
An explicitly-defaulted function may be declared constexpr only if it would have been implicitly declared as constexpr
, and may have an explicit exception-specification only if it is compatible (14.5 [except.spec]) with the exception-specification on the implicit declaration. If a function is explicitly defaulted on its first declaration,
it is implicitly considered to be constexpr if the implicit declaration would be, and,
it is implicitly considered to have the same exception-specification as if it had been implicitly declared (14.5 [except.spec]).
If a function that is explicitly defaulted has an explicit exception-specification that is not compatible (14.5 [except.spec]) with the exception-specification on the implicit declaration, then
if the function is explicitly defaulted on its first declaration, it is defined as deleted;
otherwise, the program is ill-formed.
[Example:
struct S { constexpr S() = default; // ill-formed: implicit S() is not constexpr S(int a = 0) = default; // ill-formed: default argument void operator=(const S&) = default; // ill-formed: non-matching return type ~S() throw(int) = default; //ill-formeddeleted: exception specification does not match private: int i; S(S&); // OK: private copy constructor }; S::S(S&) = default; // OK: defines copy constructor—end example]
Additional note, January, 2014:
The proposed resolution appears to have the undesirable implication that a special member function could become deleted after the class is complete. For example, given
struct S { S() noexcept(false) = default; };
we need to check that the explicit exception specification is compatible with the one on the implicit declaration. After the resolution of issue 1330, the class is regarded as complete within exception-specifications, per 11.4 [class.mem] paragraph 2. This implies that the explicit exception-specification can only be checked once the class is complete.
The issue has been returned to "review" status to allow discussion of this concern.
[Moved to DR at the September, 2013 meeting.]
In an enumeration whose underlying type is not fixed, the type of the first enumerator is unspecified if it has no initializer, meaning that an implementation could choose either a signed or an unsigned type. As a result, the values of one and two in this example could be either -1 and 0 or very large unsigned numbers:
enum { zero, one = zero -1, two };
It would be better if 9.8.1 [dcl.enum] paragraph 5 specified the type of the first enumerator as a signed type.
Proposed resolution (August, 2013):
Change 9.8.1 [dcl.enum] paragraph 5 as follows:
...If the underlying type is not fixed, the type of each enumerator is the type of its initializing value:
If an initializer is specified for an enumerator...
If no initializer is specified for the first enumerator, the initializing value has an unspecified signed integral type.
Otherwise...
[Moved to DR at the February, 2014 meeting.]
Regarding the value of an enumerator whose enumeration's underlying type is not fixed, 9.8.1 [dcl.enum] paragraph 5 says,
the type of the initializing value is the same as the type of the initializing value of the preceding enumerator unless the incremented value is not representable in that type, in which case the type is an unspecified integral type sufficient to contain the incremented value.
It is not clear how this is to be applied when the preceding enumerator value is given by an enumerator whose value is the largest of its enumeration's values, and there is implementation variance on this point.
Proposed resolution (September, 2013):
Change 9.8.1 [dcl.enum] paragraph 5 as follows:
...If the underlying type is fixed, the type of each
enumeratorenumerator prior to the closing brace is the underlying type and the constant-expression in the enumerator-definition shall be a converted constant expression of the underlying type (7.7 [expr.const]); if the initializing value of an enumerator cannot be represented by the underlying type, the program is ill-formed. If the underlying type is not fixed, the type of each enumerator prior to the closing brace isthe type of its initializing valuedetermined as follows:
If an initializer is specified for an enumerator,
the initializing valuethe constant-expression shall be an integral constant expression (7.7 [expr.const]). If the expression has unscoped enumeration type, the enumerator has the underlying type of that enumeration type, otherwise it has the same type as the expressionand the constant-expression shall be an integral constant expression (7.7 [expr.const]).If no initializer is specified for the first enumerator,
the initializing value hasits type is an unspecified integral type.Otherwise the type of the
initializing valueenumerator is the same asthe typethat ofthe initializing value ofthe preceding enumerator unless the incremented value is not representable in that type, in which case the type is an unspecified integral type sufficient to contain the incremented value. If no such type exists, the program is ill-formed.
[Moved to DR at the September, 2013 meeting.]
There are at least a couple of problems with the current wording of 9.10 [namespace.udecl]. First is the use of the word “entity” to describe what the using-declaration represents. For example, in paragraph 1,
If a using-declaration names a constructor (6.5.5.2 [class.qual]), it implicitly declares a set of constructors in the class in which the using-declaration appears (_N4527_.12.9 [class.inhctor]); otherwise the name specified in a using-declaration is a synonym for the name of some entity declared elsewhere.
An overload set of functions and function templates is not an “entity,” according to 6.1 [basic.pre] paragraph 3. A better phrasing might be something like, “...a synonym for a set of declarations in a different declarative region.”
The other problem is in paragraph 11:
The entity declared by a using-declaration shall be known in the context using it according to its definition at the point of the using-declaration. Definitions added to the namespace after the using-declaration are not considered when a use of the name is made.
This has the same problem with use of the term “entity,” and it also refers to “definitions” when presumably it means “declarations.”
Proposed resolution (October, 2012) [superseded]:
Add the following as a new paragraph after 6.4.2 [basic.scope.pdecl] paragraph 3:
The point of declaration for a class or class template first declared by a class-specifier is immediately after the identifier or simple-template-id (if any) in its class-head ( Clause 11 [class]). The point of declaration for an enumeration is immediately after the identifier (if any) in either its enum-specifier (9.8.1 [dcl.enum]) or its first opaque-enum-declaration (9.8.1 [dcl.enum]), whichever comes first. The point of declaration of an alias or alias template immediately follows the type-id to which the alias refers.
The point of declaration of a using-declaration that does not name a constructor is immediately after the using-declaration (9.10 [namespace.udecl]).
Change 9.10 [namespace.udecl] paragraph 1 as follows:
...If a using-declaration names a constructor (6.5.5.2 [class.qual]), it implicitly declares a set of constructors in the class in which the using-declaration appears (_N4527_.12.9 [class.inhctor]); otherwise the name specified in a using-declaration is a synonym forthe name of some entity declared elsewherea set of declarations in another namespace or class.
Change 9.10 [namespace.udecl] paragraph 11 as follows:
The entity declared by a using-declaration shall be known in the context using it according to its definition at the point of the using-declaration. DefinitionsDeclarations added to the namespace after the using-declaration are not considered when a use of the name is made. [Note: Thus, additional overloads and default arguments (9.3.4.7 [dcl.fct.default]) added after the using-declaration are ignored, but template specializations (13.7.6 [temp.spec.partial], 13.9.4 [temp.expl.spec]) are considered. —end note] [Example:...
Additional note (October, 2012):
The note added by this resolution to 9.10 [namespace.udecl] paragraph 11 regarding the treatment of default arguments directly contradicts the normative statement of 9.3.4.7 [dcl.fct.default] paragraph 9:
When a declaration of a function is introduced by way of a using-declaration (9.10 [namespace.udecl]), any default argument information associated with the declaration is made known as well. If the function is redeclared thereafter in the namespace with additional default arguments, the additional arguments are also known at any point following the redeclaration where the using-declaration is in scope.
The issue has been returned to "review" status for reconciliation of these two passages.
Proposed resolution (June, 2013):
Insert a new paragraph following 6.4.2 [basic.scope.pdecl] paragraph 3, as follows
...whichever comes first. The point of declaration of an alias or alias template immediately follows the type-id to which the alias refers.
The point of declaration of a using-declaration that does not name a constructor is immediately after the using-declaration (9.10 [namespace.udecl]).
The point of declaration for an enumerator...
Change 9.10 [namespace.udecl] paragraph 1 as follows:
...If a using-declaration names a constructor (6.5.5.2 [class.qual]), it implicitly declares a set of constructors in the class in which the using-declaration appears (_N4527_.12.9 [class.inhctor]); otherwise the name specified in a using-declaration is a synonym forthe name of some entity declared elsewherea set of declarations in another namespace or class.
Change 9.10 [namespace.udecl] paragraph 11 as follows:
The entity declared by a using-declaration shall be known in the context using it according to its definition at the point of the using-declaration. DefinitionsMembers added to the namespace after the using-declaration are not considered when a use of the name is made. [Note: Thus, additional overloads added after the using-declaration are ignored, but default function arguments (9.3.4.7 [dcl.fct.default]), default template arguments (13.2 [temp.param]), and template specializations (13.7.6 [temp.spec.partial], 13.9.4 [temp.expl.spec]) are considered. —end note] [Example:
[Moved to DR at the February, 2014 meeting.]
Issue 1323 dealt with correcting the specification of the operand of alignas, which was originally given as the nonexistent term alignment-expression. It was corrected editorially to match the use of assignment-expression in 9.13.2 [dcl.align].
In 9.13.2 [dcl.align] paragraph 2, the expression is semantically constrained to be an integral constant expression. Since a constant-expression is syntactically a conditional-expression rather than an assignment-expression, it would probably make sense to change the syntactic nonterminal for the operand of alignas to be either a constant-expression or a conditional-expression.
Proposed resolution (November, 2013):
Change 9.13.1 [dcl.attr.grammar] paragraph 1 as follows:
Change 9.13.2 [dcl.align] paragraph 2 as follows:
When the alignment-specifier is of the form alignas(
assignment-expressionconstant-expression ):
the
assignment-expressionconstant-expression shall be an integral constant expression
[Moved to DR at the February, 2014 meeting.]
According to 11.4 [class.mem] paragraph 1,
Except when used to declare friends (11.8.4 [class.friend]) or to introduce the name of a member of a base class into a derived class (9.10 [namespace.udecl]), member-declarations declare members of the class, and each such member-declaration shall declare at least one member name of the class.
Unnamed bit-fields (11.4.10 [class.bit] paragraph 2) are described as not being members, and they obviously do not declare a member name; presumably they should therefore be included among the exceptions to this rule.
Additional note (October, 2013):
Curiously, the exemption for an unnamed bit-field not introducing names is in 9.1 [dcl.pre] paragraph 3, referring to a simple-declaration. However, a simple-declaration is not a member-declaration and thus does not apply.
Proposed resolution (November, 2013):
Change 9.1 [dcl.pre] paragraph 3 as follows:
...In such cases,and except for the declaration of an unnamed bit-field (11.4.10 [class.bit]),the decl-specifier-seq shall introduce one or more names into the program, or shall redeclare a name introduced by a previous declaration. [Example:...
Change 11.4 [class.mem] paragraph 1 as follows:
...Except when used to declare friends (11.8.4 [class.friend]), to declare an unamed bit-field (11.4.10 [class.bit]), or to introduce the name of a member of a base class into a derived class (9.10 [namespace.udecl]), member-declarations declare members of the class, and each such member-declaration shall declare at least one member name of the class. A member shall not...
[Moved to DR at the February, 2014 meeting.]
The grammar in 11.4 [class.mem] and 9.6.1 [dcl.fct.def.general] paragraph 1 are (in part):
This leads to the following curiosity:
struct A { void f1() = delete; // #1, ok void f2() = delete;; // #2, ok void f3() = delete;;; // #3, error, extraneous semicolon };
This could be addressed by moving the semicolon into the productions for function-body for the non-default/delete forms or by adding empty-declaration to the list of member-declaration productions, as is done with namespace-scope declarations.
Proposed resolution (November, 2013):
Change the grammar in 11.4 [class.mem] as follows:
Change 11.4 [class.mem] paragraph 1 as follows:
...Except when used to declare friends (11.8.4 [class.friend]) or to introduce the name of a member of a base class into a derived class (9.10 [namespace.udecl]), or when the declaration is an empty-declaration, member-declarations declare members of the class, and each such member-declaration shall declare at least one member name of the class.
[Moved to DR at the February, 2014 meeting.]
Bullet 6 of 11.4.5 [class.ctor] paragraph 5 gives an abstract class a deleted default constructor when the virtual base has no default constructor, even though the abstract class's default constructor can never construct the virtual base class subobject. This seems parallel to the case described in issue 257 for mem-initializers. Should a similar accommodation be made to avoid deleted default constructors in abstract classes?
Notes from the April, 2013 meeting:
CWG agreed that a virtual base class should not cause an abstract class's default constructor to be defined as deleted.
Proposed resolution (August, 2013) [superseded]:
Change 11.4.5 [class.ctor] paragraph 4 as follows:
...A defaulted default constructor for class X is defined as deleted if:
...
any
direct or virtual base class, ornon-static data member with no brace-or-equal-initializer, or any direct base class, or, if X is not abstract, any virtual base class, has class type M (or array thereof) and either M has no default constructor or overload resolution (12.2 [over.match]) as applied to M's default constructor results in an ambiguity or in a function that is deleted or inaccessible from the defaulted default constructor, or...
Proposed resolution (November, 2013):
This issue is resolved by the resolution of issue 1658.
[Moved to DR at the February, 2014 meeting.]
While reviewing the resolution of issue 1611, it was noticed that the final bullet of 11.4.5 [class.ctor] paragraph 4 has a similar issue:
...A defaulted default constructor for class X is defined as deleted if:
...
any direct or virtual base class or non-static data member has a type with a destructor that is deleted or inaccessible from the defaulted default constructor.
Presumably destructors for virtual bases of abstract classes should not be considered in making this determination.
A question was also raised regarding whether odr-use is correctly defined for destructors of virtual bases of abstract classes. 6.3 [basic.def.odr] paragraph 3 simply refers to 11.4.7 [class.dtor], where the relevant passage (paragraph 8) reads,
After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X's direct non-variant non-static data members, the destructors for X's direct base classes and, if X is the type of the most derived class (11.9.3 [class.base.init]), its destructor calls the destructors for X's virtual base classes.
It could be argued, particularly in light of the reference to 11.9.3 [class.base.init], that this is clear enough that the destructor for an abstract class does not invoke destructors for its virtual bases, but a note to that effect might be helpful.
Proposed resolution (November, 2013):
Add the following as a new paragraph at the end of 11.4.4 [special]:
For a class, its non-static data members, its non-virtual direct base classes, and, if the class is not abstract (11.7.4 [class.abstract]), its virtual base classes are called its potentially constructed subobjects.
Change 11.4.5 [class.ctor] paragraph 4 as follows:
...A defaulted default constructor for class X is defined as deleted if:
...
any
direct or virtual base class, or non-static data memberpotentially constructed subobject, except for a non-static data member withnoa brace-or-equal-initializer, has class type M (or array thereof) and either M has no default constructor or overload resolution (12.2 [over.match]) as applied to M's default constructor results in an ambiguity or in a function that is deleted or inaccessible from the defaulted default constructor, orany
direct or virtual base class or non-static data memberpotentially constructed subobject has a type with a destructor that is deleted or inaccessible from the defaulted default constructor.
Change 11.4.7 [class.dtor] paragraph 5 as follows:
A 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 memberspotentially constructed subobject has class type M (or array thereof) and M has a deleted destructor or a destructor that is inaccessible from the defaulted destructor,
any direct or virtual base class has a deleted destructor or a destructor that is inaccessible from the defaulted destructor,
Change 11.9.3 [class.base.init] paragraph 8 as follows:
In a non-delegating constructor, if a givennon-static data member or base classpotentially constructed subobject is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer)and the entity is not a virtual base class of an abstract class (11.7.4 [class.abstract]), then...
Change 11.9.3 [class.base.init] paragraph 10 as follows:
In a non-delegating constructor, the destructor for eachdirect or virtual base class and for each non-static data memberpotentially constructed subobject of class type is potentially invoked (11.4.7 [class.dtor]). [Note: This provision ensures that destructors can be called for fully-constructed sub-objects in case an exception is thrown (14.3 [except.ctor]). —end note]
Change 11.4.5.3 [class.copy.ctor] paragraph 8, replacing the bulleted list with a single sentence, as follows:
The implicitly-declared copy constructor for a class X will have the form
X::X(const X&)
if each potentially constructed subobject
each direct or virtual base class B of X has a copy constructor whose first parameter is of type const B& or const volatile B&, and
for all the non-static data members of X that areof a class type M (or array thereof), each such class typehas a copy constructor whose first parameter is of type const M& or const volatile M&.121Otherwise...
Change 11.4.5.3 [class.copy.ctor] paragraph 11 as follows:
An implicitly-declared copy/move constructor is an inline public member of its class. A defaulted copy/move constructor for a class X is defined as deleted (9.6.3 [dcl.fct.def.delete]) if X has:
a variant member with a non-trivial corresponding constructor and X is a union-like class,
a
non-static data memberpotentially constructed subobject of class type M (or array thereof) that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to M's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,
a direct or virtual base class B that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to B's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,any
direct or virtual base class or non-static data memberpotentially constructed subobject of a type with a destructor that is deleted or inaccessible from the defaulted constructor, or,...
Change 11.4.5.3 [class.copy.ctor] paragraph 14 as follows:
Before the defaulted copy/move constructor for a class is implicitly defined, all non-user-provided copy/move constructors for itsdirect and virtual base classes and its non-static data memberspotentially constructed subobjects shall have been implicitly defined. [Note: An implicitly-declared copy/move constructor has an exception-specification (14.5 [except.spec]). —end note]
Change 11.4.5.3 [class.copy.ctor] paragraph 23 as follows:
A defaulted copy/move assignment operator for class X is defined as deleted if X has:
...
a
non-static data memberpotentially constructed subobject of class type M (or array thereof) that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to M's corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator, or.
a direct or virtual base class B that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to B's corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator.
This resolution also resolves issue 1611.
[Moved to DR at the February, 2014 meeting.]
If default arguments added in the out-of-class definition of a constructor make it a special member function, this can affect the overload resolution and thus the implicit exception specification and the triviality of an implicitly-declared special member function in a derived class.
See also issue 1496, which should also be addressed by the resolution of this issue.
Notes from the September, 2013 meeting:
It was decided to resolve this issue separately from issue 1496, which is now decoupled from this issue.
Proposed resolution (September, 2013):
Change 9.3.4.7 [dcl.fct.default] paragraph 6 as follows:
Except for member functions of class templates, the default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition; the program is ill-formed if a default constructor (11.4.5 [class.ctor]), copy or move constructor, or copy or move assignment operator (11.4.5.3 [class.copy.ctor]) is so declared. Default arguments for a member function of a class template shall be specified on the initial declaration of the member function within the class template. [Example:...
Delete the following from 11.4.5.3 [class.copy.ctor] paragraph 7:
...The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.
Thus, for the class definitionstruct X { X(const X&, int); };
a copy constructor is implicitly-declared. If the user-declared constructor is later defined asX::X(const X& x, int i =0) { /* ... */ }
then any use of X's copy constructor is ill-formed because of the ambiguity; no diagnostic is required.
[Moved to DR at the February, 2014 meeting.]
The decision in 11.4.5.3 [class.copy.ctor] paragraph 32 on whether or not a copy must be replaced by a move is phrased largely in terms of when copy elision can be performed, as described in the preceding paragraph. In particular, the fourth bullet of paragraph 31 applies
when the exception-declaration of an exception handler ( Clause 14 [except]) declares an object of the same type (except for cv-qualification) as the exception object (14.2 [except.throw]), the copy/move operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration.
The determination of “when the meaning of the program will be unchanged” is, in the general case, not practical. Copy elision is optional, meaning that the compiler can simply choose not to perform it if the determination is too difficult. Choosing whether to do a copy or a move, however, is mandatory, even if the copy is elided, and such a broad criterion is not appropriate. Probably the best way to address this problem would be to eliminate exception-declarations as a possible target for move-construction.
(See also issue 1579.)
Proposed resolution (September, 2013):
Change 11.4.5.3 [class.copy.ctor] paragraphs 31 and 32 as follows:
...This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
...
when the exception-declaration of an exception handler ( Clause 14 [except]) declares an object of the same type (except for cv-qualification) as the exception object (14.2 [except.throw]), the copy
/moveoperation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration. [Note: there cannot be a move from the exception object because it is always an lvalue. —end note][Example:...
When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [Note:...
[Moved to DR at the February, 2014 meeting.]
Currently the conditions for moving from an object returned from a function are tied closely to the criteria for copy elision, which requires that the type of the object being returned be the same as the return type of the function. Another possibility that should be considered is to allow something like
optional<T> foo() { T t; ... return t; }
and allow optional<T>::optional(T&&) to be used for the initialization of the return type. Currently this can be achieved explicitly by use of std::move, but it would be nice not to have to remember to do so.
Similarly, the current rules apply only to complete objects; it could make sense to allow moving from subobjects of local objects.
(See also issue 1493 for other questions about the criteria for moving from a returned object.)
Rationale (April, 2013):
CWG felt that this suggestion should be considered in a broader context and was thus more appropriate for EWG.
Additional note (September, 2013):
Returned to "open" status in light of CD National Body comment.
Proposed resolution (September, 2013):
Change 11.4.5.3 [class.copy.ctor] paragraph 32 as follows:
When the criteria for elision of a copy operation are metor would be met save for the fact that the source object is a function parameter,and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails...
[Moved to DR at the September, 2013 meeting.]
Paragraphs 12 and 25 of 11.4.5.3 [class.copy.ctor] both say that the function
is trivial if it is not user-provided, its declared parameter type is the same as if it had been implicitly declared, and...
However, a non-user-provided function might have more than one parameter if default arguments are used. The phrasing would be better as something like “its parameter-type-list is equivalent to the parameter-type-list of an implicit declaration.” (For consistency, the same phrasing should be used in 11.4.5 [class.ctor] paragraph 5. )
Proposed resolution (June, 2013):
Change 11.4.5.3 [class.copy.ctor] paragraph 12 as follows:
A copy/move constructor for class X is trivial if it is not user-provided, itsdeclared parameter type is the same as if it had been implicitly declaredparameter-type-list is equivalent to the parameter-type-list of an implicit declaration, and if...
Change 11.4.5.3 [class.copy.ctor] paragraph 25 as follows:
A copy/move assignment operator for class X is trivial if it is not user-provided, itsdeclared parameter type is the same as if it had been implicitly declaredparameter-type-list is equivalent to the parameter-type-list of an implicit declaration, and if...
[Moved to DR at the September, 2013 meeting.]
Consider an example like:
struct S {
typedef int T;
enum E : T {};
enum E : T {}; // #1
};
The declaration at #1 is ambiguous: it could either be an invalid redefinition of enum E or a zero-length bit-field of type enum E (since T{} is zero-valued constant expression). The current Standard does not appear to have a rule to disambiguate the two.
Proposed resolution (October, 2012) [superseded]:
Change 9.8.1 [dcl.enum] paragraph 1 as follows:
...the attributes in that attribute-specifier-seq are thereafter considered attributes of the enumeration whenever it is named. In a member-specification, an ambiguity can arise from the similarity between the declaration of an enumeration with an enum-base and the declaration of a zero-length unnamed bit-field of enumeration type. The ambiguity appears as a choice between an enum-specifier and a member-declaration for a bit-field. The resolution is that a : following enum identifier is parsed as part of an enum-base. [Example:
struct S { enum E : int {}; enum E : int {}; // error: redeclaration of enumeration };
—end example]
Notes from the April, 2013 meeting:
The ambiguity does not occur only with an empty set of braces but also when there is a single identifier that could be taken as the name of a constant in a containing scope or the declaration of an enumerator.
The resolution above sounds as if it is to be applied only if an ambiguity occurs; it should instead be always applied.
Proposed resolution (June, 2013):
Change 9.8.1 [dcl.enum] paragraph 1 as follows:
...the attributes in that attribute-specifier-seq are thereafter considered attributes of the enumeration whenever it is named. A : following “enum identifier” is parsed as part of an enum-base. [Note: This resolves a potential ambiguity between the declaration of an enumeration with an enum-base and the declaration of an unnamed bit-field of enumeration type. [Example:
struct S { enum E : int {}; enum E : int {}; // error: redeclaration of enumeration };
—end example] —end note]
[Moved to DR at the September, 2013 meeting.]
When a similar question was raised in issue 413, the resolution was to remove the use of the term. The resolution of issue 1359 has now reintroduced the concept of an “empty” union, so there is once again the need to define it.
(See also issues 1562 and 1622.)
Proposed resolution (February, 2013) [superseded]:
Change 11.5 [class.union] paragraph 2 as follows:
...At most one non-static data member of a union may have a brace-or-equal-initializer. A union is an empty union if it has no non-static data members. [Note: If any...
Additional note (March, 2013):
The question was raised as to whether an example like
union A { union {}; union {}; constexpr A() {} }; A a = A();
is well-formed, which hinges on the question of whether A is an “empty union,” per 9.2.6 [dcl.constexpr] paragraph 4 bullet 5:
if the class is a non-empty union, or for each non-empty anonymous union member of a non-union class, exactly one non-static data member shall be initialized;
Must one of the empty anonymous union members be initialized for A's constructor to be constexpr?
The issue is being returned to "review" status for discussion of this point.
See also issues 1562, 1587, 1621, and 1623.
Proposed resolution (August, 2013):
Change 9.2.6 [dcl.constexpr] paragraph 4 as follows:
...In addition, either its function-body shall be = delete, or it shall satisfy the following constraints:
...
if the class is a non-empty union, or for each non-empty anonymous union member of a non-union class, exactly one non-static data member shall be initialized;if the class is a union having variant members (11.5 [class.union]), exactly one of them shall be initialized;
if the class is a union-like class, but is not a union, for each of its anonymous union members having variant members, exactly one of them shall be initialized;
every constructor involved...
Change 11.5 [class.union] paragraph 2 as follows:
A union can have member functions (including constructors and destructors), but not virtual (11.7.3 [class.virtual]) functions. A union shall not have base classes. A union shall not be used as a base class. If a union contains a non-static data member of reference type the program is ill-formed.At most one non-static data member of a union may have a brace-or-equal-initializer.[Note: If any non-static data member...
Change 11.5 [class.union] paragraph 8 as follows:
A union-like class is a union or a class that has an anonymous union as a direct member. A union-like class X has a set of variant members.
If X is a union its variant members are the non-static data members; otherwise, its variant members are the non-static data members of all anonymous unions that are members of X.If X is a union, a non-static data member of X that is not an anonymous union is a variant member of X. In addition, a non-static data member of an anonymous union that is a member of X is also a variant member of X. At most one variant member of a union may have a brace-or-equal-initializer. [Example:union U { int x = 0; union { }; union { int z; int y = 1; // error: initialization for second variant member of U }; };
—end example]
Change 11.9.3 [class.base.init] paragraph 8 as follows:
In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (11.7.4 [class.abstract]), then
if the entity is a non-static data member that has a brace-or-equal-initializer and either
the constructor's class is a union (11.5 [class.union]), and no other variant member of that union is designated by a mem-initializer-id or
the constructor's class is not a union, and, if the entity is a member of an anonymous union, no other member of that union is designated by a mem-initializer-id,
the entity is initialized as specified in 9.5 [dcl.init];
otherwise, if the entity...
(This resolution also resolves issue 1562.)
[Moved to DR at the September, 2013 meeting.]
The following example is ill-formed:
union U { int a = 0; float f; U() {} // ok, a initialized to 0 U(float f) : f(f) {} // error, constructor initializes both a and f };
because of 11.9.3 [class.base.init] paragraph 8:
An attempt to initialize more than one non-static data member of a union renders the program ill-formed.
In non-union classes, a mem-initializer takes precedence over a non-static data member initializer, per paragraph 9:
If a given non-static data member has both a brace-or-equal-initializer and a mem-initializer, the initialization specified by the mem-initializer is performed, and the non-static data member's brace-or-equal-initializer is ignored.
It would be reasonable to treat union mem-initializers in an analogous fashion.
(See also issue 1460.)
Proposed resolution (March, 2013) [superseded]:
Change 11.9.3 [class.base.init] paragraph 8 as follows:
[Note: this wording was reviewed during the 2013-03-25 teleconference.]In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (11.7.4 [class.abstract]), then
if the entity is a non-static data member that has a brace-or-equal-initializer and, if the entity is a member of a union, no other non-static data member of that union is designated by a mem-initializer-id, the entity is initialized as specified in 9.5 [dcl.init];
...
See also issues 1460, 1587, 1621, and 1623.
Proposed resolution (August, 2013):
This issue is resolved by the resolution of issue 1460.
[Moved to DR at the September, 2013 meeting.]
The grammar in 11.9.3 [class.base.init] paragraph 1 for mem-initializer-list is incorrect:
The ellipsis in the second production should be on mem-initializer, not on mem-initializer-list.
Proposed resolution (August, 2013):
Change the grammar in 11.9.3 [class.base.init] paragraph 1 as follows:
[Moved to DR at the September, 2013 meeting.]
There is an unfortunate disparity between the treatment of an example like
struct S { int operator[](int); auto f() -> decltype(this->operator[](0)); };
(which is permitted, cf _N4567_.5.1.1 [expr.prim.general] paragraph 3), and
struct S { int operator[](int); auto f() -> decltype((*this)[0]); };
which is not. The reason for rejecting the latter is 12.2.2.3 [over.match.oper] paragraph 3:
For a unary operator @ with an operand of a type whose cv-unqualified version is T1, and for a binary operator @ with a left operand of a type whose cv-unqualified version is T1 and a right operand of a type whose cv-unqualified version is T2, three sets of candidate functions, designated member candidates, non-member candidates and built-in candidates, are constructed as follows:
If T1 is a complete class type, the set of member candidates is the result of the qualified lookup of T1::operator@ (12.2.2.2.2 [over.call.func]); otherwise, the set of member candidates is empty.
...
It would be desirable to update the latter specification to allow lookup of preceding declarations in a class currently being defined, analogously with the lookup performed in the function-notation case.
Proposed resolution (April, 2013):
Change 12.2.2.3 [over.match.oper] bullet 3.1 as follows:
If T1 is a complete class type or a class currently being defined, the set of member candidates is the result of the qualified lookup of T1::operator@ (12.2.2.2.2 [over.call.func]); otherwise, the set of member candidates is empty.
[Moved to DR at the February, 2014 meeting.]
Consider an example like:
struct Y { operator int*(); }; extern int *p; int *a = p + 100.0; // #1 int *b = Y() + 100.0; // #2
#1 is ill-formed because it violates the requirement of 7.6.6 [expr.add] that the non-pointer operand have integral or enumeration type. It appears that #2 is well-formed, however, because 12.2.2.3 [over.match.oper] paragraph 7 says,
If a built-in candidate is selected by overload resolution, the operands are converted to the types of the corresponding parameters of the selected operation function. Then the operator is treated as the corresponding built-in operator and interpreted according to Clause 7 [expr].
In this case, the selected operation function is
int *operator+(int *, std::ptrdiff_t)
100.0 is thus converted to std::ptrdiff_t before reaching 7.6.6 [expr.add].
This problem could be addressed by restricting the conversion to the class or enumeration operand rather than both operands.
Proposed resolution (January, 2014):
Change 12.2.2.3 [over.match.oper] paragraph 7 as follows:
If a built-in candidate is selected by overload resolution, the operands of class type are converted to the types of the corresponding parameters of the selected operation function, except that the second standard conversion sequence of a user-defined conversion sequence (12.2.4.2.3 [over.ics.user]) is not applied. Then the operator is treated as the corresponding built-in operator and interpreted according to Clause 7 [expr]. [Example:
struct X { operator double(); }; struct Y { operator int*(); }; int *a = Y() + 100.0; // error: pointer arithmetic requires integral operand int *b = Y() + X(); // error: pointer arithmetic requires integral operand—end example]
[Moved to DR at the February, 2014 meeting as part of document N3914.]
Currently, 12.2.4.2 [over.best.ics] paragraph 4 reads,
However, when considering the argument of a constructor or user-defined conversion function that is a candidate by 12.2.2.4 [over.match.ctor] when invoked for the copying/moving of the temporary in the second step of a class copy-initialization, by 12.2.2.8 [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 12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv], or 12.2.2.7 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are considered.
This is cumbersome and hard to understand. A possible improvement might be:
However, only standard conversion sequences and ellipsis conversion sequences are considered if:
the parameter is the first parameter of a constructor of a class X, or
the parameter is the implicit object parameter of a user-defined conversion function, and
the constructor or user-defined conversion function is a candidate by:
12.2.2.4 [over.match.ctor] — when the argument is the temporary being copied/moved in the second step of a class copy-initialization.
12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv], or 12.2.2.7 [over.match.ref] — in all cases.
12.2.2.8 [over.match.list] — during phase two, when the argument was the only element in the initializer list, and the parameter is of type X or reference to (possibly cv-qualified) X.
(Note that this rewording removes the restriction that applies during phase one of 12.2.2.8 [over.match.list], as there is no longer any way to trigger it due to the fact that only initializer-list constructors are candidates. See this bug report for details.)
Proposed resolution (September, 2013) [SUPERSEDED]:
Change 12.2.4.2 [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 12.2.2.4 [over.match.ctor] when invoked for the copying/moving of the temporary in the second step of a class copy-initialization, by 12.2.2.8 [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 12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv], or 12.2.2.7 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are considered.if the target is
the first parameter of a constructor of a class X or
the implicit object parameter of a user-defined conversion function,
and the constructor or user-defined conversion function is a candidate by
12.2.2.4 [over.match.ctor], when the argument is the temporary acting as the source in the second step of a class copy-initialization,
12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv], or 12.2.2.7 [over.match.ref] (in all cases), or
the second phase of 12.2.2.8 [over.match.list] when the initializer list has exactly one element, and the conversion is to X or reference to (possibly cv-qualified) X,
user-defined conversion sequences are not considered. [Example:
struct X { X(); }; struct B { operator X&(); }; B b; X x({b}); // error: B::operator X&() is not a candidate—end example]
Additional note (October, 2013):
Questions have been raised about several of the bullets in the September, 2013 proposed resolution and whether a note would be preferable instead of or in addition to the example . The issue has been returned to "review" status to allow consideration of these questions.
Additional note (January, 2014):
It has also been observed that the proposed resolution would make the following example ill-formed by preventing the consideration of B's conversion function when initializing the first parameter of A's copy constructor:
struct A { A() {} A(const A &) {} }; struct B { operator A() { return A(); } } b; A a{b};
[Moved to DR at the September, 2013 meeting.]
In an example like
void f(int const(&)[2]); void f(int const(&)[3]); int main() { f({1, 2, 3}); }
the current overload resolution rules make no provision for different array sizes and thus treats the call as ambiguous, even though it seems obvious that the second f should be chosen in this case.
Rationale (August, 2011):
The implications of array temporaries for the language should be considered by the Evolution Working Group in a comprehensive fashion rather than on a case-by-case basis. See also issues 1300, 1326, and 1525.
Notes from the October, 2012 meeting:
CWG determined that this issue is unrelated to array temporaries and that a tiebreaker should be added for this case in overload resolution.
Proposed resolution, April, 2013:
Change 12.2.4.2.6 [over.ics.list] paragraph 2 as follows, adding a new paragraph (and moving the footnote to the new paragraph, as indicated):
[Drafting note: no other case in the remainder of the paragraph applies when the initializer list has more elements than the parameter array.]If the parameter type is std::initializer_list<X>
or “array of X” [Footnote: Since there are no parameters of array type, this will only occur as the underlying type of a reference parameter. —end footnote]and all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X. This conversion can be a user-defined conversion even in the context of a call to an initializer-list constructor. [Example: ... —end example]Otherwise, if the parameter type is “array of N X” [Footnote: Since there are no parameters of array type, this will only occur as the underlying type of a reference parameter. —end footnote], if the initializer list has exactly N elements or if it has fewer than N elements and X is default-constructible, and if all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X.
Change 12.2.4.3 [over.ics.rank] paragraph 3 as follows:
Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of the following rules applies:
...
List-initialization sequence L1 is a better conversion sequence than list-initialization sequence L2 if
L1 converts to std::initializer_list<X> for some X and L2 does not.
L1 converts to std::initializer_list<X> for some X and L2 does not, or, if not that,
L1 converts to type “array of N1 T,” L2 converts to type “array of N2 T”, and N1 is smaller than N2.
[Moved to DR at the February, 2014 meeting.]
The example in 12.6 [over.literal] paragraph 8 contains the line
float operator ""E(const char*); // OK
E does not begin with an underscore and thus is a reserved name.
Proposed resolution (September, 2013):
Change the example in 12.6 [over.literal] paragraph 8 as follows:
void operator "" _km(long double); // OK string operator "" _i18n(const char*, std::size_t); // OK template <char...> double operator "" _\u03C0(); // OK: UCN for lowercase pi float operator ""_E(const char*); // OK float operator ""E(const char*); // error: reserved identifier float operator " " B(const char*); // error: non-empty string-literal string operator "" 5X(const char*, std::size_t); // error: invalid literal suffix identifier double operator "" _miles(double); // error: invalid parameter-declaration-clause template <char...> int operator "" _j(const char*); // error: invalid parameter-declaration-clause
Rationale (February, 2014):
The specification was removed from the WP and moved into a Technical Specification.
[Moved to DR at the September, 2013 meeting.]
According to 13.4.3 [temp.arg.nontype] paragraph 1, the argument for a non-type template parameter of pointer or reference type must be
a constant expression (7.7 [expr.const]) that designates the address of an object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference
In C++03, the requirement for an id-expression eliminated the use of “addresses of array elements and names or addresses of non-static class members,” as noted in paragraph 3 of that section. With the advent of generalized constant expressions, however, it is possible to satisfy the requirements and still address these subobjects. For example:
extern constexpr int x[] = { 0, 1 }; constexpr const int *p1 = x + 1; const int &r = *p1; template <const int *> struct A; template <> struct A<&r> { };
If this is intentional, the note in 13.4.3 [temp.arg.nontype] paragraph 3 should be revised or removed; if not, the normative wording of paragraph 1 must be revised.
Notes from the April, 2013 meeting:
CWG did not favor extending the range of non-type template arguments to include subobjects, feeling that they should continue to be restricted to the address of a complete object.
Proposed resolution (June, 2013):
Change 13.4.3 [temp.arg.nontype] paragraph 1 as follows:
A template-argument for a non-type, non-template template-parameter shall be one of:
...
a constant expression (7.7 [expr.const]) that designates the address of
ana complete object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, where the id-expression is the name of an object or function, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference; or...
[Moved to DR at the February, 2014 meeting.]
The term “address constant expression” was removed by paper N3652, but it is still referenced in the list of permissible nontype template arguments in 13.4.3 [temp.arg.nontype] paragraph 1:
an address constant expression of type std::nullptr_t.
Proposed resolution (November, 2013):
Change 13.4.3 [temp.arg.nontype] paragraph 1 as follows:
A template-argument for a non-type, non-template template-parameter shall be one of:
...
an addressa constant expression of type std::nullptr_t.
[Moved to DR at the September, 2013 meeting.]
According to 13.4.4 [temp.arg.template] paragraph 3,
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 alias template (call it A) matches the corresponding template parameter in the template-parameter-list of P.
There does not appear to be a formal definition of the criteria for whether two template parameters “match,” however, and there is implementation variance in the treatment of an example like
struct A {
typedef int T1;
typedef int T2;
};
template<template<typename T, typename T::T1 N> class U>
struct B {
U<A, 0> u;
};
template<typename T, typename T::T2 N>
struct C {
};
B<C> b; // ok?
Proposed resolution (June, 2013):
Change 13.4.4 [temp.arg.template] paragraph 3 as follows:
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 alias template (call it A) matches the corresponding template parameter in the template-parameter-list of P. Two template parameters match if they are of the same kind (type, non-type, template), for non-type template-parameters, their types are equivalent (13.7.7.2 [temp.over.link]), and for template template-parameters, each of their corresponding template-parameters matches, recursively. When P's template-parameter-list contains a template parameter pack...
[Moved to DR at the February, 2014 meeting.]
Since we don't deduce the return type of a function temploid until it is instantiated, it seems that a call to such a function as a member of the current instantiation must have a dependent type, but 13.8.3.3 [temp.dep.expr] doesn't appear to say that. For example:
template<typename T> struct X { typedef int type; }; template<typename T> struct S { auto f() { return 0; } void g() { X<decltype(f())>::type x; // typename presumably needed here } }
Presumably there should be a bullet in 13.8.3.2 [temp.dep.type] paragraph 8 for this case.
Proposed resolution (November, 2013):
Change 13.8.3.3 [temp.dep.expr] paragraph 3 as follows:
An id-expression is type-dependent if it contains
an identifier associated by name lookup with one or more declarations declared with a dependent type,
an identifier associated by name lookup with one or more declarations of member functions of the current instantiation declared with a return type that contains a placeholder type (9.2.9.7 [dcl.spec.auto]),
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 dependent member...
[Moved to DR at the September, 2013 meeting.]
According to 13.10.3.6 [temp.deduct.type] paragraph 5, one of the non-deduced contexts is
A function parameter pack that does not occur at the end of the parameter-declaration-clause.
This would make the following example ill-formed:
template <typename R, typename ...P> void foo(R (&)(P ..., ...)) { } void bar(int, ...) { } void zip() { foo(bar); }
It is not clear whether this is intentional; if the wording referred to parameter-declaration-list instead of parameter-declaration-clause, the example would be accepted.
Proposed resolution (June, 2013):
Change 13.10.3.6 [temp.deduct.type] paragraph 5 as follows
The non-deduced contexts are:
...
A function parameter pack that does not occur at the end of the
parameter-declaration-clauseparameter-declaration-list.
[Moved to DR at the February, 2014 meeting.]
According to 13.10.3.6 [temp.deduct.type] paragraph 17,
If, in the declaration of a function template with a non-type template-parameter, the non-type template-parameter is used in an expression in the function parameter-list and, if the corresponding template-argument is deduced, the template-argument type shall match the type of the template-parameter exactly, except that a template-argument deduced from an array bound may be of any integral type.
This does not cover return types, leaving the outcome of an example like the following unclear:
template <int N> struct A; template <short N> A<N> *foo(); void bar() { A<1> *(*fp)(void) = &foo; }
Proposed resolution (September, 2013):
Change 13.10.3.6 [temp.deduct.type] paragraph 17 as follows:
If, in the declaration of a function template with a non-type template-parameter, the non-type template-parameter is used in an expression in the function parameter-list and, if the corresponding template-argument is deduced, the template-argument type shall match the type of the template-parameter exactly, except that a template-argument deduced from an array bound may be of any integral typeP has a form that contains <i>, and if the type of the corresponding value of A differs from the type of i, deduction fails. If P has a form that contains [i], and if the type of i is not an integral type, deduction fails.147 [Example:...
[Moved to DR at the September, 2013 meeting.]
The current specification does not appear to say whether an implementation is permitted/required/forbidden to complain when a sub-object's destructor is inaccessible. In particular, if there is no possibility for an exception to be thrown following a given sub-object's construction, should an implementation issue an error if that sub-object's destructor is inaccessible?
Proposed resolution (February, 2013):
Change 6.3 [basic.def.odr] paragraph 2 as follows:
...A destructor for a class is odr-usedas specified inif it is potentially invoked (11.4.7 [class.dtor]).
Change 7.6.2.8 [expr.new] paragraph 17 as follows:
If the new-expression creates an object or an array of objects of class type, access and ambiguity control are done for the allocation function, the deallocation function (11.4.11 [class.free]), and the constructor (11.4.5 [class.ctor]). If thenew expressionnew-expression creates an array of objects of class type,access and ambiguity control are done forthe destructor is potentially invoked (11.4.7 [class.dtor]).
Change 11.4.7 [class.dtor] paragraph 11 as follows:
Destructors areA destructor is invoked implicitly
for a constructed object
swith static storage duration (6.7.6.2 [basic.stc.static]) at program termination (6.9.3.3 [basic.start.dynamic]),for a constructed object
swith thread storage duration (6.7.6.3 [basic.stc.thread]) at thread exit,for a constructed object
swith automatic storage duration (6.7.6.4 [basic.stc.auto]) when the block in which an object is created exits (8.9 [stmt.dcl]),for a constructed temporary object
swhentheits lifetimeof a temporary objectends (6.7.7 [class.temporary]),.
for constructed objects allocated by a new-expression (7.6.2.8 [expr.new]), through use of a delete-expression (7.6.2.9 [expr.delete]),
in several situations due to the handling of exceptions (14.4 [except.handle]).In each case, the context of the invocation is the context of the construction of the object. A destructor is also invoked implicitly through use of a delete-expression (7.6.2.9 [expr.delete]) for a constructed object allocated by a new-expression (7.6.2.8 [expr.new]); the context of the invocation is the delete-expression. [Note: An array of class type contains several subobjects for each of which the destructor is invoked. —end note] A destructor can also be invoked explicitly. A destructor is potentially invoked if it is invoked or as specified in 7.6.2.8 [expr.new] and 11.9.3 [class.base.init]. A program is ill-formed if
an object of class type or array thereof is declared and the destructor for the class is not accessible at the point of the declarationa destructor that is potentially invoked is deleted or not accessible from the context of the invocation.Destructors can also be invoked explicitly.
Add the following as a new paragraph following 11.9.3 [class.base.init] paragraph 9:
If a given non-static data member has both...
In a non-delegating constructor, the destructor for each direct or virtual base class and for each non-static data member of class type is potentially invoked (11.4.7 [class.dtor]). [Note: This provision ensures that destructors can be called for fully-constructed sub-objects in case an exception is thrown (14.3 [except.ctor]). —end note]
In a non-delegating constructor, initialization proceeds...
[Moved to DR at the February, 2014 meeting.]
In saying that the catch-clause parameter is copy-initialized from the exception object, 14.4 [except.handle] paragraph 16 leaves open the possibility that a converting constructor might be used for the initialization when the type of the exception-declaration is a base of the type of the exception object. It should be specified that the parameter is copy-constructed from the corresponding base class subobject in such cases.
Proposed resolution (September, 2013) [superseded]:
Change 14.4 [except.handle] paragraph 16 as follows:
If the exception-declaration
specifiesintroduces a name, it declares a variablewhich is copy-initialized (9.5 [dcl.init]) from the exception object. If the exception-declaration denotes an object type but does not specify a name, a temporary (6.7.7 [class.temporary]) is copy-initialized (9.5 [dcl.init]) from the exception object.; otherwise, an unnamed variable is created. That variable, of type cv T or cv T&, is initialized from the exception object, of type E, as follows:
if T is a base class of E, the variable is copy-initialized from the corresponding base class subobject of the exception object;
otherwise, the variable is copy-initialized from the exception object.
The lifetime of the variable
or temporaryends when the handler exits, after the destruction of any automatic objects initialized within the handler.
Additional note (October, 2013):
Additional discussion has pointed out that, although the unnamed handler parameter is no longer called a “temporary” in the proposed resolution, 6.7.7 [class.temporary] paragraph 1 still lists “entering a handler (14.4 [except.handle])” as one of the contexts in which a temporary is created. A question was also raised as to whether it is necessary to deal with named and unnamed handler parameters separately, since both are now referred to as “variables” in the revised wording. This issue has therefore been returned to "review" status to allow consideration of these points.
Proposed resolution (November, 2013):
Change 6.1 [basic.pre] paragraph 6 as follows:
A variable is introduced by the declaration of a reference other than a non-static data member or of an object. The variable's name, if any, denotes the reference or object.
Change 6.7.7 [class.temporary] paragraph 1 as follows:
Temporaries of class type are created in various contexts: binding a reference to a prvalue (9.5.4 [dcl.init.ref]), returning a prvalue (8.7.4 [stmt.return]), a conversion that creates a prvalue (7.3.2 [conv.lval], 7.6.1.9 [expr.static.cast], 7.6.1.11 [expr.const.cast], 7.6.3 [expr.cast]), throwing an exception (14.2 [except.throw]),entering a handler (14.4 [except.handle]),and in some initializations (9.5 [dcl.init]). [Note:...
Change 14.2 [except.throw] paragraph 3 as follows:
Throwing an exception copy-initializes (9.5 [dcl.init], 11.4.5.3 [class.copy.ctor]) a temporary object, called the exception object. The temporary is an lvalue and is used to initialize the variablenameddeclared in the matching handler (14.4 [except.handle]). If the type...
Change 14.4 [except.handle] paragraph 16 as follows:
If the exception-declaration specifies a name, it declares a variable which is copy-initialized (9.5 [dcl.init]) from the exception object. If the exception-declaration denotes an object type but does not specify a name, a temporary (6.7.7 [class.temporary]) is copy-initialized (9.5 [dcl.init]) from the exception object.The variable declared by the exception-declaration, of type cv T or cv T&, is initialized from the exception object, of type E, as follows:
if T is a base class of E, the variable is copy-initialized (9.5 [dcl.init]) from the corresponding base class subobject of the exception object;
otherwise, the variable is copy-initialized (9.5 [dcl.init]) from the exception object.
The lifetime of the variable
or temporaryends when the handler exits, after the destruction of any automatic objects initialized within the handler.
[Moved to DR at the February, 2014 meeting.]
There is an ambiguity between a noexcept specifier's optional parenthesized constant-expression and an initializer:
void f() noexcept; void (*p)() noexcept (&f);
Here, we can just about make 9.3.3 [dcl.ambig.res] paragraph 1's rule fit, and say that the (&f) is part of the exception-specification rather than being an initializer. However, this case is much more problematic:
void (*fp2)() noexcept, (*fp)() noexcept (fp2 = 0);
The (fp = 0) here is unambiguously an initializer, because an assignment-expression cannot syntactically be a constant-expression, although current implementations treat it as an ill-formed part of the exception-specification.
Probably the best approach would be to change 14.5 [except.spec] to say that a ( following noexcept is always treated as being part of the noexcept-specification.
Proposed resolution (September, 2013):
Change 14.5 [except.spec] paragraph 1 as follows:
...In a noexcept-specification, the constant-expression, if supplied, shall be a constant expression (7.7 [expr.const]) that is contextually converted to bool (7.3 [conv]). A noexcept-specification noexcept is equivalent to noexcept(true). A ( token that follows noexcept is part of the noexcept-specification (and does not commence an initializer (9.5 [dcl.init]).
[Moved to DR at the November, 2016 meeting.]
The resolution of issue 1838 was intended to resolve issue 1021 but does not actually do so, as it applies only to declarations with a declarator-id, which is not true of classes and enumerations.
Proposed resolution (October, 2015):
Change _N4868_.9.8.2.3 [namespace.memdef] paragraph 1 as follows:
A declaration in a namespace N (excluding declarations in nested scopes) whose declarator-id is an unqualified-id (9.3.4 [dcl.meaning]), whose class-head-name (Clause 11 [class]) or enum-head-name (9.8.1 [dcl.enum]) is an identifier, or whose elaborated-type-specifier is of the form class-key attribute-specifier-seqopt identifier (9.2.9.5 [dcl.type.elab]), or that is an opaque-enum-declaration, declares (or redeclares) its unqualified-id or identifier as a member of N, and may be a definition. [Note:...
Change the grammar of 9.8.1 [dcl.enum] as follows:
[Adopted at the February/March, 2017 meeting.]
There is implementation divergence on the status of the following example:
namespace A { namespace B { int x; } } namespace C { namespace B = A::B; } using namespace A; using namespace C; int x = B::x;
This should presumably be valid: the lookup of B finds A::B and C::B, but it is not ambiguous because they denote the same entity. A similar example with a using-declaration or alias-declaration seems to be universally accepted. Perhaps the lookup rules need to be clarified regarding the status of this example.
Proposed resolution (November, 2016):
Change 6.5 [basic.lookup] paragraph 1 as follows:
The name lookup rules apply uniformly to all names (including typedef-names (9.2.4 [dcl.typedef]), namespace-names (9.9 [basic.namespace]), and class-names (11.3 [class.name])) wherever the grammar allows such names in the context discussed by a particular rule. Name lookup associates the use of a name with adeclarationset of declarations (6.2 [basic.def]) of that name.Name lookup shall find an unambiguous declaration for the name (see 6.5.2 [class.member.lookup]). Name lookup may associate more than one declaration with a name if it finds the name to be a function name;The declarations found by name lookup shall either all declare the same entity or shall all declare functions; in the latter case, the declarations are said to form a set of overloaded functions (_N4868_.12.2 [over.load]). Overload resolution...
[Adopted at the February/March, 2017 meeting.]
An example in 6.6 [basic.link] paragraph 6 creates two file-scope variables with the same name, one with internal linkage and one with external.
static void f(); static int i = 0; //1 void g() { extern void f(); // internal linkage int i; //2: i has no linkage { extern void f(); // internal linkage extern int i; //3: external linkage } }
Is this really what we want? C99 has 6.2.2.7/7, which gives undefined behavior for having an identifier appear with internal and external linkage in the same translation unit. C++ doesn't seem to have an equivalent.
Notes from October 2003 meeting:
We agree that this is an error. We propose to leave the example but change the comment to indicate that line //3 has undefined behavior, and elsewhere add a normative rule giving such a case undefined behavior.
Proposed resolution (October, 2005) [SUPERSEDED]:
Change 6.6 [basic.link] paragraph 6 as indicated:
...Otherwise, if no matching entity is found, the block scope entity receives external linkage. If, within a translation unit, the same entity is declared with both internal and external linkage, the behavior is undefined.
[Example:
static void f(); static int i = 0; // 1 void g () { extern void f (); // internal linkage int i; // 2: i has no linkage { extern void f (); // internal linkage extern int i; // 3: external linkage } }
There are three objects named i in this program. The object with internal linkage introduced by the declaration in global scope (line //1 ), the object with automatic storage duration and no linkage introduced by the declaration on line //2, and the object with static storage duration and external linkage introduced by the declaration on line //3.Without the declaration at line //2, the declaration at line //3 would link with the declaration at line //1. But because the declaration with internal linkage is hidden, //3 is given external linkage, resulting in a linkage conflict. —end example]
Notes from the April 2006 meeting:
According to 6.6 [basic.link] paragraph 9, the two variables with linkage in the proposed example are not “the same entity” because they do not have the same linkage. Some other formulation will be needed to describe the relationship between those two variables.
Notes from the October 2006 meeting:
The CWG decided that it would be better to make a program with this kind of linkage mismatch ill-formed instead of having undefined behavior.
Proposed resolution (November, 2016):
Change 6.6 [basic.link] paragraph 6 as follows:
...Otherwise, if no matching entity is found, the block scope entity receives external linkage. If, within a translation unit, the same entity is declared with both internal and external linkage, the program is ill-formed. [Example:
static void f(); static int i = 0; // #1 void g() { extern void f(); // internal linkage int i; // #2: i has no linkage { extern void f(); // internal linkage extern int i; // #3 external linkage, ill-formed } }
There are three objects named i in this program. The object with internal linkage introduced by the declaration in global scope (line #1 ), the object with automatic storage duration and no linkage introduced by the declaration on line #2, and the object with static storage duration and external linkage introduced by the declaration on line #3.Without the declaration at line #2, the declaration at line #3 would link with the declaration at line #1. Because the declaration with internal linkage is hidden, however, #3 is given external linkage, making the program ill-formed. —end example]
[Adopted at the February/March, 2017 meeting.]
According to the rules in 6.6 [basic.link] paragraph 4, the enumerators of an enumeration type with linkage also have linkage. Having same-named enumerators in different translation units would seem to be innocuous. Is there a rationale for this rule?
Proposed resolution (March, 2017):
Delete 6.6 [basic.link] bullet 4.5:
An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage. All other namespaces have external linkage. A name having namespace scope that has not been given internal linkage above has the same linkage as the enclosing namespace if it is the name of
...
an enumerator belonging to an enumeration with linkage; or...
Change 6.6 [basic.link] paragraph 9 as follows:
Two names that are the same (6.1 [basic.pre]) and that are declared in different scopes shall denote the same variable, function, type,enumerator,template or namespace if...
[Adopted at the February/March, 2017 meeting.]
According to 6.8.2 [basic.fundamental] paragraph 3,
The range of non-negative values of a signed integer type is a subrange of the corresponding unsigned integer type, and the value representation of each corresponding signed/unsigned type shall be the same.
The corresponding wording from C11 is,
The range of nonnegative values of a signed integer type is a subrange of the corresponding unsigned integer type, and the representation of the same value in each type is the same.
The C wording is arguably clearer, but it loses the implication of the C++ wording that the sign bit of a signed type is part of the value representation of the corresponding unsigned type.
Proposed resolution (January, 2017):
Change 6.8.2 [basic.fundamental] paragraph 3 as follows:
...The standard and extended unsigned integer types are collectively called unsigned integer types. The range of non-negative values of a signed integer type is a subrange of the corresponding unsigned integer type, the representation of the same value in each of the two types is the same, and the value representation of each corresponding signed/unsigned type shall be the same. The standard signed integer types...
[Adopted at the February/March, 2017 meeting.]
Issue 1059 changed 6.8.5 [basic.type.qualifier] paragraph 6 to read,
An array type whose elements are cv-qualified is also considered to have the same cv-qualifications as its elements.
However, that change overlooked the earlier statement in paragraph 2,
A compound type (6.8.4 [basic.compound]) is not cv-qualified by the cv-qualifiers (if any) of the types from which it is compounded. Any cv-qualifiers applied to an array type affect the array element type, not the array type (9.3.4.5 [dcl.array]).
Proposed resolution (January, 2017):
Change 6.8.5 [basic.type.qualifier] paragraph 2 as follows:
A compound type (6.8.4 [basic.compound]) is not cv-qualified by the cv-qualifiers (if any) of the types from which it is compounded. Any cv-qualifiers applied to an array type affect the array element type, not the array type(9.3.4.5 [dcl.array]).
[Moved to DR at the November, 2016 meeting as paper P0507R0.]
The current wording does not indicate that initialization of a non-class object is a full-expression, but presumably should do so.
Additional note, April, 2013:
There is implementation variance in the treatment of the following example:
struct A { A() { puts("ctor"); } A(const A&) { puts("copy"); } const A&get() const { return *this; } ~A() { puts("dtor"); } }; struct B { B(A, A) {} }; typedef A A2[2]; A2 a = { A().get(), A().get() }; B b = { A().get(), A().get() }; int c = (A2{ A().get(), A().get() }, 0); int d = (B{ A().get(), A().get() }, 0); int main() {}
Additional note (February, 2014):
Aggregate initialization could also involve more than one full-expression, so the limitation above to “initialization of a non-class object” is not correct.
Additional note (October, 2015):
For additional examples, consider:
int i = i++; int j = j;
The current sequencing rules do not cover these initializations.
Also, in
const int& f(const int& x) { return x; } int y = f(5);
it doesn't appear that 6.9.1 [intro.execution] paragraph 10 requires the temporary for 5 to persist until the initialization of y is complete.
Proposed resolution (June, 2016):
The resolution is given in paper P0507R0.
[Moved to DR at the November, 2016 meeting.]
According to 6.9.2 [intro.multithread] paragraph 23,
Two actions are potentially concurrent if
they are performed by different threads, or
they are unsequenced, and at least one is performed by a signal handler.
This definition should exclude the case when both actions are performed by a signal handler.
Notes from the October, 2015 meeting:
SG1 agrees that the existing wording should be amended to say something like “and they are not both performed by the same signal handler invocation.”
Proposed resolution (November, 2015):
Change 6.9.2 [intro.multithread] paragraph 23 as follows:
Two actions are potentially concurrent if
they are performed by different threads, or
they are unsequenced,
andat least one is performed by a signal handler, and they are not both performed by the same signal handler invocation.
[Adopted at the February/March, 2017 meeting as document P0250R3.]
Given the example:
auto id1 = std::this_thread::get_id(); thread_local auto id2 = std::this_thread::get_id(); int main() { auto id3 = std::this_thread::get_id(); auto id4 = std::this_thread::get_id(); std::cout << id1 << std::endl << id2 << std::endl << id3 << std::endl << id4 << std::endl; }
What is the thread of execution that initializes id1? Is it the same as, different from, or unspecified in relation to id3?
I believe this is unspecified by omission. (I can find no wording in the standard saying otherwise.) It looks like the standard attempts to require that these happen in the same thread, but fails to do so. We say that in a program that does not start a thread, all ordered dynamic initializations are sequenced, which implies they happen in the same thread — because “sequenced before” is an intra-thread notion — but we do not say that these initializations are sequenced before entering main (we just say they may be “done before” entering main). This seems like a defect to me.
What is the thread of execution that initializes id2? Is it the same as, different from, or unspecified in relation to id1 and id3?
This also seems to be unspecified; this seems like a defect. We say in 6.9.3.2 [basic.start.static] paragraph 5:
It is implementation-defined whether the dynamic initialization of a non-local variable with static or thread storage duration is done before the first statement of the initial function of the thread. If the initialization is deferred to some point in time after the first statement of the initial function of the thread, it shall occur before the first odr-use (6.3 [basic.def.odr]) of any variable with thread storage duration defined in the same translation unit as the variable to be initialized.
but “done before” and “occurs before” are meaningless noise here. I think what is intended is that these are a “sequenced before” relation for initialization of thread storage duration entities (implying initialization in the same thread) and “happens before” for initialization of static storage duration entities (implying initialization in some thread but visible to this thread).
Am i guaranteed to have only one copy of id2, or could there be more?
You are guaranteed to have one per thread, per 6.7.6.3 [basic.stc.thread] paragraph 1.
I cannot find any explicit guarantee that the implementation will not create additional threads behind your back, so there appears to be no guarantee that you have only one id2 variable. That may be a defect.
I'm also concerned by 6.9.3.2 [basic.start.static] paragraph 2:
...Variables with ordered initialization defined within a single translation unit shall be initialized in the order of their definitions in the translation unit...
This is obviously wrong if a translation unit contains both static and thread storage duration variables.
Notes from the October, 2015 meeting:
A paper is required to address these issues fully. A lot of the existing text talks about sequencing when it should refer to “the transitive subset of happens before,” which could be, but is not currently, defined. “Happens before” would do if we didn't have memory_order_consume. The SG1 consensus on what should be said includes the following:
main should have a proper thread id, which should not be the default-constructed thread id.
atexit should use flows of control with valid thread ids.
For non-thread-local objects, we should guarantee only happens-before relationships for ordered initializations, slightly strengthened if we have memory_order_consume.
Thread-locals should be initialized by the thread that they correspond to (which simplifies initializations that rely on each other).
A C++ program should be allowed to start a std::thread even if the user never starts one.
The thread-id for global static initialization is unspecified, but it is not the default-constructed thread.
Notes from the February, 2016 meeting:
SG1 is included to introduce the “strongly happens before” relation from P0250, although possibly in a differnt way. It seems to be needed to fix all sorts of wording that's currently imperfect because it doesn't work in the presence of memory_order_consume.
[Adopted at the February/March, 2017 meeting.]
The resolution of issue 1489 added wording regarding value initialization to 6.9.3.2 [basic.start.static] paragraph 2 in an attempt to clarify the status of an example like
int a[1000]{};
However, this example is aggregate initialization, not value initialization. Also, now that we allow brace-or-equal-initializers in aggregates, this wording also needs to be updated to allow an aggregate with constant non-static data member initializers to qualify for constant initialization.
Proposed resolution (November, 2016):
Change 6.9.3.2 [basic.start.static] paragraph 2 as follows, converting the bulleted list into running text as indicated:
A constant initializer for
ana variable or temporary object o is anexpression thatinitializer whose full-expression is a constant expression, except thatitif o is an object, such an initializer may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types. [Note: Such a class may have a non-trivial destructor —end note] Constant initialization is performed:
if each full-expression (including implicit conversions) that appears in the initializer of a reference with static or thread storage duration is a constant expression (7.7 [expr.const]) and the reference is bound to a glvalue designating an object with static storage duration, to a temporary object (see 6.7.7 [class.temporary]) or subobject thereof, or to a function;if
ana variable or temporary object with static or thread storage duration is initialized by aconstructor call, and if the initialization full-expression is aconstant initializer for theobject;entity
if an object with static or thread storage duration is not initialized by a constructor call and if either the object is value-initialized or every full-expression that appears in its initializer is a constant expression.If constant initialization is not performed...
Change 7.7 [expr.const] paragraph 2 as follows:
A conditional-expressionAn expression e is a core constant expression unless...
[Adopted at the February/March, 2017 meeting.]
Consider an example like
void *p; void (*pf)(); auto x = true ? p : pf;
The rules in Clause 7 [expr] paragraph 13 say that the composite type between a void* and a function pointer type is void*. This is surprising, since a function pointer type cannot be implicitly converted to void*.
Proposed resolution (January, 2017):
Change Clause 7 [expr] bullet 14.5 as follows:
The cv-combined type of two types T1 and T2 is a type T3 similar to T1 whose cv-qualification signature (7.3.6 [conv.qual]) is:
...
if T1 or T2 is “pointer to cv1 void” and the other type is “pointer to cv2 T”, where T is an object type or void, “pointer to cv12 void”, where cv12 is the union of cv1 and cv2;
...
[Adopted at the February/March, 2017 meeting as document P0613R0.]
The Standard refers to capturing “entities,” and a reference is an entity. However, it is not clear what capturing a reference by reference would mean. In particular, 7.5.6 [expr.prim.lambda] paragraph 16 says,
It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference.
If a reference captured by reference is not represented by a member, it is hard to see how something like the following example could work:
#include <functional> #include <iostream> std::function<void()> make_function(int& x) { return [&]{ std::cout << x << std::endl; }; } int main() { int i = 3; auto f = make_function(i); i = 5; f(); }
Should this be undefined behavior or should it print 5?
Proposed resolution (November, 2014) [SUPERSEDED]:
Change 7.5.6 [expr.prim.lambda] paragraph 18 as follows:
Every id-expression within the compound-statement of a lambda-expression that is an odr-use (6.3 [basic.def.odr]) of an entity captured by copy is transformed into an access to the corresponding unnamed data member of the closure type. [Note: An id-expression that is not an odr-use refers to the original entity, never to a member of the closure type. Furthermore, such an id-expression does not cause the implicit capture of the entity. —end note] If this is captured, each odr-use of this is transformed into an access to the corresponding unnamed data member of the closure type, cast (7.6.3 [expr.cast]) to the type of this. [Note: The cast ensures that the transformed expression is a prvalue. —end note] An id-expression within the compound-statement of a lambda-expression that is an odr-use of a reference captured by reference refers to the entity to which the captured reference is bound and not to the captured reference. [Note: Such odr-uses are not invalidated by the end of the captured reference's lifetime. —end note] [Example:
void f(const int*); void g() { const int N = 10; [=] { int arr[N]; // OK: not an odr-use, refers to automatic variable f(&N); // OK: causes N to be captured; &N points to the // corresponding member of the closure type }; } auto h(int &r) { return [&]() { ++r; // Valid after h returns if the lifetime of the // object to which r is bound has not ended }; }—end example]
Change 7.5.6 [expr.prim.lambda] paragraph 23 as follows:
[Note: Ifana non-reference entity is implicitly or explicitly captured by reference, invoking the function call operator of the corresponding lambda-expression after the lifetime of the entity has ended is likely to result in undefined behavior. —end note]
Proposed resolution (February, 2017):
Change 7.5.6 [expr.prim.lambda] paragraph 17 as follows:
Every id-expression within the compound-statement of a lambda-expression that is an odr-use (6.3 [basic.def.odr]) of an entity captured by copy is transformed into an access to the corresponding unnamed data member of the closure type. [Note: An id-expression that is not an odr-use refers to the original entity, never to a member of the closure type. Furthermore, such an id-expression does not cause the implicit capture of the entity. —end note] If *this is captured by copy, each odr-use of this is transformed into a pointer to the corresponding unnamed data member of the closure type, cast (7.6.3 [expr.cast]) to the type of this. [Note: The cast ensures that the transformed expression is a prvalue. —end note] An id-expression within the compound-statement of a lambda-expression that is an odr-use of a reference captured by reference refers to the entity to which the captured reference is bound and not to the captured reference. [Note: The validity of such captures is determined by the lifetime of the object to which the reference refers, not by the lifetime of the reference itself. —end note] [Example:
void f(const int*); void g() { const int N = 10; [=] { int arr[N]; // OK: not an odr-use, refers to automatic variable f(&N); // OK: causes N to be captured; &N points to the // corresponding member of the closure type }; } auto h(int &r) { return [&] { ++r; // Valid after h returns if the lifetime of the // object to which r is bound has not ended }; }—end example]
Change 7.5.6 [expr.prim.lambda] paragraph 25 as follows:
[Note: Ifana non-reference entity is implicitly or explicitly captured by reference, invoking the function call operator of the corresponding lambda-expression after the lifetime of the entity has ended is likely to result in undefined behavior. —end note]
[Adopted at the February/March, 2017 meeting.]
Consider:
#include <iostream> int main() { [x=2](int x) { std::cout << x << std::endl; }(3); }
What is the code supposed to print? There is implementation divergence.
Proposed resolution (February, 2017):
Add the following as a new paragraph after 7.5.6 [expr.prim.lambda] paragraph 11:
The identifier in a simple-capture is looked up using the usual rules for unqualified name lookup (6.5.3 [basic.lookup.unqual]); each such lookup shall find an entity. An entity that is designated by a simple-capture is said to be explicitly captured, and shall be *this (when the simple-capture is “this ” or “* this ”) or a variable with automatic storage duration declared in the reaching scope of the local lambda expression.
If an identifier in a simple-capture appears as the declarator-id of a parameter of the lambda-declarator's parameter-declaration-clause, the program is ill-formed. [Example:
void f() { int x = 0; auto g = [x](int x) { return 0; } // error: parameter and simple-capture have the same name }
—end example]
Change the example of 7.5.6 [expr.prim.lambda] paragraph 12 as follows:
int x = 4; auto y = [&r = x, x = x+1]()->int { r += 2; return x+2; }(); // Updates ::x to 6, and initializes y to 7. auto z = [a = 42](int a) { return 1; } // error: parameter and local variable have the same name
[Adopted at the February/March, 2017 meeting.]
Consider:
#include <cstdarg> #include <iostream> auto f(int i, ...) { return [&]() { va_list l; va_start(l, i); std::cout << "i = " << i << ", " << va_arg(l, int) << std::endl; va_end(l); }; } int main() { auto l = f(100, 42); l(); }
Is this defined behavior, accessing the variadic arguments passed to f?
Notes from the December, 2016 teleconference:
Such examples should have undefined behavior; va_start should only be permitted to access the arguments for the current function.
Proposed resolution (February, 2017):
Change 17.14.2 [cstdarg.syn] paragraph 1 as follows:
The contents of the header <cstdarg> are the same as the C standard library header <stdarg.h>, with the following changes: The restrictions that ISO C places on the second parameter to the va_start() macro in header <stdarg.h> are different in this International Standard. The parameter parmN isthe identifier ofthe rightmost parameter in the variable parameter list of the function definition (the one just before the ... ).223 If the parameter parmN is a pack expansion (13.7.4 [temp.variadic]) or an entity resulting from a lambda capture (7.5.6 [expr.prim.lambda]), the program is ill-formed, no diagnostic required. If the parameter parmN is of a reference type, or of a type that is not compatible with the type that results when passing an argument for which there is no parameter, the behavior is undefined.
Calling a function through an expression whose language linkage is different from that of the called function is explicitly called out as undefined behavior, even though there is a general provision that makes any difference in the types of the expression and function undefined. The special treatment for language linkage should be removed.
Notes from the November, 2016 meeting:
This issue will be resolved editorially and is being placed in "review" status until the corresponding change appears in a working draft.
Additional note, February, 2021:
This issue was resolved editorially.
[Adopted at the February/March, 2017 meeting.]
The current wording of 7.6.1.9 [expr.static.cast] paragraph 2 appears to permit the following example:
struct B {
int i;
};
struct D : B {
int j;
B b;
};
int main() {
D d;
B &br = d.b;
D &dr = static_cast<D&>(br); // Okay?
}
Presumably such casts should only be supported if the operand object is a base class subobject, not a member subobject.
Proposed resolution (January, 2017):
Change 7.6.1.9 [expr.static.cast] paragraph 2 as follows:
...If the object of type “cv1 B” is actually a base class subobject of an object of type D, the result refers to the enclosing object of type D. Otherwise, the behavior is undefined. [Example:...
[Adopted at the February/March, 2017 meeting.]
Consider:
T *p = new (::operator new(sizeof(T) + 100)) T; delete p;
It is infeasible for the implementation to infer the size of the block of storage, yet the standard does not permit undefined behavior for this case.
Proposed resolution (December, 2016):
Change 7.6.2.9 [expr.delete] paragraph 11 as follows:
When a delete-expression is executed, the selected deallocation function shall be called with the address of theblock of storage to be reclaimedmost-derived object in the delete object case, or the address of the object suitably adjusted for the array allocation overhead (7.6.2.8 [expr.new]) in the delete array case, as its first argument. If a deallocation function with a parameter of type std::align_val_t is used, the alignment of the type of the object to be deleted is passed as the corresponding argument. If a deallocation function with a parameter of type std::size_t is used, the size of theblockmost-derived type, or of the array plus allocation overhead, respectively, is passed as the corresponding argument. [Note: If this results in a call to a usual deallocation function, and either the first argument was not the result of a prior call to a usual allocation function or the second argument was not the corresponding argument in said call, the behavior is undefined (17.6.3.2 [new.delete.single], 17.6.3.3 [new.delete.array]). —end note]
[Adopted at the February/March, 2017 meeting.]
Given an example like
void f() {
int arr[] = { 1, 2, 3 };
for (int val : arr) {
int val; // Redeclares index variable
}
}
one might expect that the redeclaration of the index variable would be an error, as it is in the corresponding classic for statement. However, the restriction that makes the latter an error is phrased in terms of the condition nonterminal in 8.5 [stmt.select] paragraph 3, and the range-based for does not refer to condition. Should there be an explicit prohibition of such a redeclaration?
Proposed resolution (January, 2017):
Add the following as a new paragraph after 8.6 [stmt.iter] paragraph 3:
Thus after the while statement, i is no longer in scope. —end example]
If a name introduced in an init-statement or for-range-declaration is redeclared in the outermost block of the substatement, the program is ill-formed. [Example:
void f() { for (int i = 0; i < 10; ++i) int i = 0; // error: redeclaration for (int i : { 1, 2, 3 }) int i = 1; // error: redeclaration }—end example]
[Adopted at the February/March, 2017 meeting as document P0250R3.]
Regarding initialization of a block-scope static variable, 8.9 [stmt.dcl] paragraph 4 says,
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
This specification does not use the terminology of 6.9.2 [intro.multithread], so the meaning of “wait” is not clear. For example, will a concurrent thread that “waited” see (in the sense of happens-before) the result of the initialization (including side effects caused during the initialization)?
Perhaps the “synchronizes-with” terminology could be used here.
Notes from the February, 2016 meeting:
SG1 concluded that wording similar to the following should be added:
For any action A such that the declaration is sequenced before A, initialization shall happen before A. The concurrent execution shall block for completion of the initialization.
[Adopted at the February/March, 2017 meeting.]
Issue 2004 concerns this example:
union U { int a; mutable int b; }; constexpr U u1 = {1}; int k = (u1.b = 2); constexpr U u2 = u1;
Clearly this must be ill-formed. But issue 2004 goes too far by making the copy and move operations of U non-constexpr. This breaks reasonable code such as:
constexpr int f() { U u = {1}; U v = u; return v.a; }
Proposed resolution (February, 2017):
Add the following as a new bullet following 7.7 [expr.const] bullet 2.8
A conditional-expression e is a core constant expression unless the evaluation of e , following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following expressions:
...
an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) that is applied to a glvalue that refers to a non-active member of a union or a subobject thereof;
an invocation of an implicitly-defined copy/move constructor or copy/move assignment operator for a union whose active member (if any) is mutable, unless the lifetime of the union object began within the evaluation of e;
...
Delete bullet 3.2 in 9.2.6 [dcl.constexpr]:
for a defaulted copy/move assignment, the class of which it
is a member shall not have a mutable subobject that is a variant
member;
Delete bullet 4.2 in 9.2.6 [dcl.constexpr]:
for a defaulted copy/move constructor, the class shall not
have a mutable subobject that is a variant member;
[Adopted at the February/March, 2017 meeting.]
According to 9.3.3 [dcl.ambig.res] paragraph 3,
Another ambiguity arises in a parameter-declaration-clause of a function declaration, or in a type-id that is the operand of a sizeof or typeid operator, when a type-name is nested in parentheses.
There are two problems here: first, a parameter-declaration-clause appears in a lambda-expression, not just in a function declaration. Second, the ambiguity can arise in a type-id appearing in any context, not just in a sizeof or typeid expression.
Proposed resolution (January, 2017):
Change 9.3.3 [dcl.ambig.res] paragraph 3 as follows:
Another ambiguity arises in a parameter-declaration-clauseof a function declaration, or in a type-id that is the operand of a sizeof or typeid operator, when a type-name is nested in parentheses. In this case, the choice is between the declaration of a parameter of type pointer to function and the declaration of a parameter with redundant parentheses around the declarator-id. The resolution is to consider the type-name as a simple-type-specifier rather than a declarator-id. [Example:...
[Adopted at the November, 2016 meeting as part of paper P0490R0.]
Paragraph 9 of 9.5 [dcl.init] says:
If no initializer is specified for an object, and the object is of (possibly cv-qualified) non-POD class type (or array thereof), the object shall be default-initialized; if the object is of const-qualified type, the underlying class type shall have a user-declared default constructor. Otherwise, if no initializer is specified for an object, the object and its subobjects, if any, have an indeterminate initial value; if the object or any of its subobjects are of const-qualified type, the program is ill-formed.
What if a const POD object has no non-static data members? This wording requires an empty initializer for such cases:
struct Z { // no data members operator int() const { return 0; } }; void f() { const Z z1; // ill-formed: no initializer const Z z2 = { }; // well-formed }
Similar comments apply to a non-POD const object, all of whose non-static data members and base class subobjects have default constructors. Why should the class of such an object be required to have a user-declared default constructor?
(See also issue 78.)
Additional note (February, 2011):
This issue should be brought up again in light of constexpr constructors and non-static data member initializers.
Notes from the August, 2011 meeting:
If the implicit default constructor initializes all subobjects, no initializer should be required.
[Adopted at the February/March, 2017 meeting.]
Consider:
struct V { int n; }; struct A : virtual V {}; struct B : A { B() : V{123}, A() {} } b;
Initialization of b first performs aggregate initialization of the V virtual base subobject. Then it performs value initialization of the A base subobject. Per 9.5.1 [dcl.init.general] bullet 9.1.2, the A base subobject is zero-initialized. Per 9.5.1 [dcl.init.general] bullet 6.2, this zero-initializes the V virtual base subobject.
Result: b.n is required to be 0, not 123.
Proposed resolution (November, 2016):
Change 9.5 [dcl.init] bullet 6.2 as follows:
To zero-initialize an object or reference of type T means:
if T is a scalar type (6.8 [basic.types]), the object is initialized to the value obtained by converting the integer literal 0 (zero) to T;103
if T is a (possibly cv-qualified) non-union class type, each non-static data member, each non-virtual base class subobject, and, if the object is not a base class subobject, each virtual base
-class subobject is zero-initialized and padding is initialized to zero bits;...
[Adopted at the February/March, 2017 meeting.]
According to 9.5.2 [dcl.init.aggr] paragraph 15,
When a union is initialized with a brace-enclosed initializer, the braces shall only contain an initializer-clause for the first non-static data member of the union.
This would appear to preclude using {} as the initializer for a union, which would otherwise have reasonable semantics. Is there a reason for this restriction?
Also, paragraph 7 reads,
If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized from an empty initializer list (9.5.5 [dcl.init.list]).
There should presumably be special treatment for unions, so that only a single member is initialized in such cases.
(See also issue 1460.)
Proposed resolution (November, 2016) [SUPERSEDED]:
Change 9.5.2 [dcl.init.aggr] paragraph 8 as follows:
If there are fewer initializer-clauses in the list than there are elements in the aggregate, then eachEach non-variant element of the aggregate that is not explicitly initializedshall beis initialized from its default member initializer (11.4 [class.mem]) or, if there is no default member initializer, copy-initialized from an empty initializer list (9.5.5 [dcl.init.list]). If the aggregate is a union and the initializer list is empty, then
if any union member has a default member initilizer, that member is initialized from its default member initializer;
otherwise, the first member of the union (if any) is copy-initialized from an empty initializer list.
[Example:...
Proposed resolution (February, 2017):
The resolution of issue 2272 also resolves this issue.
[Adopted at the February/March, 2017 meeting.]
The Standard does not specify whether the initialization from {} that is done for omitted initializers in aggregate initialization is direct or copy initialization. There is divergence among implementations.
Proposed resolution (May, 2015) [SUPERSEDED]:
This issue is resolved by the resolution of issue 1630.
Notes from the October, 2015 meeting:
CWG agreed that copy initialization should be used; paragraph 7 should have wording similar to paragraph 2. See also issue 1518.
Additional notes (July, 2022):
The resolution of issue 2272 also resolves this issue.
[Adopted at the February/March, 2017 meeting.]
Consider:
struct S { const int &i; } s{};
This example ought to be ill-formed, but 9.5.2 [dcl.init.aggr] paragraph 8 states that i is instead initialized from an empty initializer list, which causes i to bind to a value-initialized temporary of type int.
Proposed resolution (February, 2017):
Change 9.5.2 [dcl.init.aggr] paragraph 8 as follows:
If there are fewer initializer-clauses in the list than there are elements in
thea non-union aggregate, then each element not explicitly initializedshall be initialized from its default member initializer (11.4 [class.mem]) or, if there is no default member initializer, from an empty initializer list (9.5.5 [dcl.init.list]).is initialized as follows:
If the element has a default member initializer (11.4 [class.mem]), the element is initialized from that initializer.
Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list (9.5.5 [dcl.init.list]).
Otherwise, the program is ill-formed.
If the aggregate is a union and the initializer list is empty, then
if any variant member has a default member initializer, that member is initialized from its default member initializer;
otherwise, the first member of the union (if any) is copy-initialized from an empty initializer list.
[Example:...
Delete 9.5.2 [dcl.init.aggr] paragraph 11:
If an incomplete or empty initializer-list leaves a member of reference type uninitialized, the program is ill-formed.
This resolution also resolves issues 1622 and 2116.
[Adopted at the February/March, 2017 meeting.]
P0138R2 adds a new bullet for enum initialization after bullet 8 of 9.5.5 [dcl.init.list] paragraph 3. However, paragraph 7 already dealt with all the cases where the initializer list contains a single element and the target type is a non-reference type, so the new paragraph 9 rule is unreachable.
Proposed resolution (December, 2016):
Reorder the bullets in 9.5.5 [dcl.init.list] paragraph 3 as follows:
List-initialization of an object or reference of type T is defined as follows:
...
Otherwise, if T is a class type, constructors are considered...
Otherwise, if T is an enumeration with a fixed underlying type (9.8.1 [dcl.enum]), the initializer-list has a single element v, and the initialization is direct-list-initialization, the object is initialized with the value T(v) (7.6.1.4 [expr.type.conv]); if a narrowing conversion is required to convert v to the underlying type of T, the program is ill-formed. [Example:...
Otherwise, if the initializer list has a single element of type E...
Otherwise, if T is a reference type...
Otherwise, if T is an enumeration with a fixed underlying type...
[Adopted at the February/March, 2017 meeting.]
There does not seem to be a good reason not to permit attributes on an asm declaration. This would be handy for things like:
[[vendor::asm_syntax("intel")]] asm(...);
Notes from the December, 2016 teleconference:
The omission seems to have been an oversight that should be corrected.
Proposed resolution (January, 2017):
Change 9.11 [dcl.asm] paragraph 1 as follows:
An asm declaration has the form
asm-definition:
attribute-specifier-seqopt asm ( string-literal ) ;
The asm declaration is conditionally-supported; its meaning is implementation-defined. The optional attribute-specifier-seq in an asm-definition appertains to the asm declaration. [Note: Typically it is used to pass information through the implementation to an assembler. —end note]
[Adopted at the February/March, 2017 meeting.]
According to 9.13.1 [dcl.attr.grammar] paragraph 5, a program is ill-formed if an attribute appertains to an entity or statement to which it is not allowed to apply. Presumably an alignment-specifier should have the same restriction.
Proposed resolution (November, 2016):
Change 9.13.1 [dcl.attr.grammar] paragraph 5 as follows:
Each attribute-specifier-seq is said to appertain to some entity or statement, identified by the syntactic context where it appears (Clause 8 [stmt], 9.1 [dcl.pre], 9.3 [dcl.decl]). If an attribute-specifier-seq that appertains to some entity or statement contains an attribute or alignment-specifier that is not allowed to apply to that entity or statement, the program is ill-formed. If an attribute-specifier-seq appertains to a friend declaration (11.8.4 [class.friend]), that declaration shall be a definition. No attribute-specifier-seq shall appertain to an explicit instantiation (13.9.3 [temp.explicit]).
[Moved to DR at the November, 2016 meeting.]
The restrictions against aliasing this inside a constructor should apply to all objects, not just to const objects.
Proposed resolution (June, 2016):
Change 11.4.5 [class.ctor] paragraph 12 as follows:
During the construction of
a constan object, if the value of the object or any of its subobjects is accessed through a glvalue that is not obtained, directly or indirectly, from the constructor's this pointer, the value of the object or subobject thus obtained is unspecified. [Example:struct C; void no_opt(C*); struct C { int c; C() : c(0) { no_opt(this); } }; const C cobj; void no_opt(C* cptr) { int i = cobj.c * 100; // value of cobj.c is unspecified cptr->c = 1; cout << cobj.c * 100 // value of cobj.c is unspecified << '\n'; } extern struct D d; struct D { D(int a) : a(a), b(d.a) {} int a, b; }; D d = D(1); // value of d.b is unspecified—end example]
[Moved to DR at the November, 2016 meeting.]
The resolution of issue 496 included the addition of 11.4.5.3 [class.copy.ctor] paragraph 25. 2, making a class's copy/move constructor non-trivial if it has a non-static data member of volatile-qualified type. This change breaks the IA-64 ABI, so it has been requested that CWG reconsider this aspect of the resolution.
On a related note, the resolution of issue 496 also changed 6.8 [basic.types] paragraph 9, which makes volatile-qualified scalar types “trivial” but not “trivially copyable.” It is not clear why there is a distinction made here; the only actual use of “trivial type” in the Standard appears to be in the description of qsort, which should probably use “trivially copyable.” (See also issue 1746.)
Notes from the February, 2016 meeting:
CWG agreed with the suggested direction for the changes in 11.4.5.3 [class.copy.ctor]; the use of “trivial” will be dealt with separately and not as part of the resolution of this issue.
Proposed resolution (June, 2016):
Change 6.8 [basic.types] paragraph 9 as follows:
...called POD types. Cv-unqualified scalar types, trivially copyable class types (Clause 11 [class]), arrays of such types, andnon-volatile const-qualifiedcv-qualified versions of these types (6.8.5 [basic.type.qualifier]) are collectively called trivially copyable types. Scalar types...
Delete bullet 12.2 of 11.4.5.3 [class.copy.ctor]:
A copy/move constructor for class X is trivial if it is not user-provided, its parameter-type-list is equivalent to the parameter-type-list of an implicit declaration, and if
...
class X has no non-static data members of volatile-qualified type, and...
Delete bullet 25.2 of 11.4.5.3 [class.copy.ctor]:
A copy/move assignment operator for class X is trivial if it is not user-provided, its parameter-type-list is equivalent to the parameter-type-list of an implicit declaration, and if
...
class X has no non-static data members of volatile-qualified type, and
The resolution to issue 535 replaced references to "copy constructor" with "constructor selected by overload resolution". 11.4.5.3 [class.copy.ctor] paragraph 10 was missed.
Notes from the November, 2016 meeting:
This issue is to be handled editorially and is in "review" status to check that the change has been applied.
Additional notes (February, 2022):
The change has been applied editorially with commit 6953b2.
[Adopted at the February/March, 2017 meeting.]
The term “direct member” is used in 11.5 [class.union] paragraph 8 but is not defined. It might be better to refer to the class's member-specification instead.
Additional note, October, 2015:
This issue is expected to be addressed by the wording of N4532 or a successor thereof (“Default Comparisons”).
Proposed resolution (November, 2016):
Change 9.5.2 [dcl.init.aggr] paragraph 2 as follows:
The elements of an aggregate are:
for an array, the array elements in increasing subscript order, or
for a class, the direct base classes in declaration order followed by the direct non-static data members that are not members of an anonymous union, in declaration order.
Change 11.4 [class.mem] paragraph 1 as follows:
The member-specification in a class definition declares the full set of members of the class; no member can be added elsewhere. A direct member of a class X is a member of X that was first declared within the member-specification of X, including anonymous union objects and direct members thereof. Members of a class are...
Change 11.5.2 [class.union.anon] paragraph 1 as follows:
A union of the form
union { member-specification } ;
is called an anonymous union; it defines an unnamed type and an unnamed object of
...unnamedthat type called an anonymous union object. Each member-declaration in the member-specification of an anonymous union
[Adopted at the February/March, 2017 meeting.]
A class-or-decltype is used as a base-specifier and as a mem-initializer-id that names a base class. It is specified in 11.7 [class.derived] paragraph 1 as:
Consequently, a declaration like
template<typename T> struct D : T::template B<int>::template C<int> {};
is ill-formed, although most implementations accept it; some actually require the use of the template keyword, although the relevant wording in 13.3 [temp.names] paragraph 4 only requires it in a qualified-id, not in a class-or-decltype. It would probably be good to add a production like
to the definition of class-or-decltype and explicitly mention those contexts in 13.3 [temp.names] as not requiring use of the template keyword.
Additional note (January, 2014):
This is effectively issues 314 and 343.
See also issue 1812.
Proposed resolution (February, 2014) [SUPERSEDED]:
Change Clause 11 [class] paragraph 3 as follows:
If a class is marked with the class-virt-specifier final and it appears as abase-type-specifierclass-or-decltype in a base-clause (11.7 [class.derived]), the program is ill-formed. Whenever a class-key is followed...
Change the grammar in 11.7 [class.derived] paragraph 1 as follows:
Delete paragraph 4 and change paragraph 5 of 13.3 [temp.names] as follows, splitting paragraph 5 into two paragraphs and moving the example from paragraph 4 into paragraph 5:
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 object expression of the postfix-expression is type-dependent or the nested-name-specifier in the qualified-id refers to a dependent type, but the name is not a member of the current instantiation (13.8.3.2 [temp.dep.type]), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template. [Example: ... —end example]A name prefixed by the keyword template shall be a template-id or the name shall refer to a class template or alias template. [Note: The keyword template may not be applied to non-template members of class templates. —end note] The nested-name-specifier (_N4567_.5.1.1 [expr.prim.general]) of
a class-head-name (Clause 11 [class]) or enum-head (9.8.1 [dcl.enum]) (if any) or
a qualified-id in a declarator-id (9.3 [dcl.decl]),
or a nested-name-specifier directly contained in such a nested-name-specifier (recursively), shall not be of the form
nested-name-specifier template simple-template-id ::
[Note: That is, a simple-template-id shall not be prefixed by the keyword template in these cases. —end note]
The keyword template is optional in a typename-specifier (13.8 [temp.res]), elaborated-type-specifier (9.2.9.5 [dcl.type.elab]), using-declaration (9.10 [namespace.udecl]), or class-or-decltype (11.7 [class.derived]), and in recursively directly-contained nested-name-specifiers thereof. In these contexts, a < token is always assumed to introduce a template-argument-list. [Note: Thus, if the preceding name is not a template-name, the program is ill-formed. —end note] In other contexts, when the name of a member template specialization appears after a nested-name-specifier that denotes a dependent type, but the name is not a member of the current instantiation, the member template name shall be prefixed by the keyword template. Similarly, when the name of a member template specialization appears after . or -> in a postfix-expression (7.6.1 [expr.post]) and the object expression of the postfix-expression is type-dependent, but the name is not a member of the current instantiation (13.8.3.2 [temp.dep.type]), the member template name shall be prefixed by the keyword template. Otherwise, the name is assumed to name a non-template. [Example:
<From original paragraph 4>—end example] [Note: As is the case with the typename prefix...
This resolution also resolves issues 314, 343, 1794, and 1812.
Additional note, November, 2014:
Concerns have been expressed over the clarity and organization of the proposed resolution, so the issue has been moved back to "review" status to allow CWG to address these concerns.
Proposed resolution, March, 2017:
Change Clause 11 [class] paragraph 3 as follows:
If a class is marked with the class-virt-specifier final and it appears as abase-type-specifierclass-or-decltype in a base-clause ( 11.7 [class.derived]), the program is ill-formed. Whenever a class-key is followed by a class-head-name...
Change the grammar in 11.7 [class.derived] paragraph 1 as follows:
Change 11.7 [class.derived] paragraph 2 as followx:
The type denoted by a base-type-specifierA class-or-decltype shallbedenote a class type that is not an incompletely defined class (Clause 11 [class]); this. The class denoted by the class-or-decltype of a base-specifier is called a direct base class for the class being defined. During the lookup for a base class name...
Change 13.3 [temp.names] paragraphs 4 and 5 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 object expression of the postfix-expression is type-dependent or the nested-name-specifier in the qualified-id refers to a dependent type, but the name is not a member of the current instantiationThe keyword template is said to appear at the top level in a qualified-id if it appears outside of a template-argument-list or decltype-specifier. In a qualified-id of a declarator-id or in a qualified-id formed by a class-head-name ( Clause 11 [class]) or enum-head-name (9.8.1 [dcl.enum]), the keyword template shall not appear at the top level. In a qualified-id used as the name in a typename-specifier (13.8 [temp.res]), elaborated-type-specifier (9.2.9.5 [dcl.type.elab]), using-declaration (9.10 [namespace.udecl]), or class-or-decltype ( 11.7 [class.derived]), an optional keyword template appearing at the top level is ignored. In these contexts, a < token is always assumed to introduce a template-argument-list. In all other contexts, when naming a template specialization of a member of an unknown specialization (13.8.3.2 [temp.dep.type]), the member template namemustshall be prefixed by the keyword template.Otherwise the name is assumed to name a non-template.[Example:...A name prefixed by the keyword template shall be a template-id or the name shall refer to a class template or an alias template. [Note: The keyword template may not be applied to non-template members of class templates. —end note]...
Change 13.8 [temp.res] paragraph 5 as follows:
A qualified name used as the name in amem-initializer-id, a base-specifier,class-or-decltype ( 11.7 [class.derived]) or an elaborated-type-specifier is implicitly assumed to name a type, without the use of the typename keyword. In a nested-name-specifier that immediately contains a nested-name-specifier that depends on a template parameter, the identifier or simple-template-id is implicitly assumed to name a type, without the use of the typename keyword. [Note: The typename keyword is not permitted by the syntax of these constructs. —end note]
[Adopted at the February/March, 2017 meeting.]
The EDG front-end accepts:
template <typename T> struct A { template <typename U> struct B {}; }; template <typename T> struct C : public A<T>::template B<T> { };
It rejects this code if the base-specifier is spelled A<T>::B<T>.
However, the grammar for a base-specifier does not allow the template keyword.
Suggested resolution:
It seems to me that a consistent approach to the solution that looks like it will be adopted for issue 180 (which deals with the typename keyword in similar contexts) would be to assume that B is a template if it is followed by a "<". After all, an expression cannot appear in this context.Notes from the 4/02 meeting:
We agreed that template must be allowed in this context. The syntax needs to be changed. We also opened the related issue 343.
Additional note (August, 2010):
The same considerations apply to mem-initializer-ids, as noted in issue 1019.
Additional note (January, 2014):
See also issue 1710.
Proposed resolution (March, 2017):
This issue is resolved by the resolution of issue 1710.
[Adopted at the February/March, 2017 meeting.]
By analogy with typename, the keyword template used to indicate that a dependent name will be a template name should be optional in contexts where a type is required, e.g., base class lists. We could also consider member and parameter declarations.
This was suggested by issue 314.
Additional note (January, 2014):
See also issue 1710.
Proposed resolution (March, 2017):
This issue is resolved by the resolution of issue 1710.
[Adopted at the February/March, 2017 meeting.]
The current wording of 13.3 [temp.names] paragraph 5 is:
A name prefixed by the keyword template shall be a template-id or the name shall refer to a class template.
Presumably this should also allow template before alias templates. For example,
template<template<typename> class Template> struct Internal { template<typename Arg> using Bind = Template<Arg>; }; template<template<typename> class Template, typename Arg> using Instantiate = Template<Arg>; template<template<typename> class Template, typename Argument> using Bind = Instantiate<Internal<Template>::template Bind, Argument>;
Proposed resolution (March, 2017):
This issue is resolved by the resolution of issue 1710.
[Adopted at the February/March, 2017 meeting.]
According to 13.3 [temp.names] paragraph 4,
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 object expression of the postfix-expression is type-dependent or the nested-name-specifier in the qualified-id refers to a dependent type, but the name is not a member of the current instantiation (13.8.3.2 [temp.dep.type]), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.
This does not seem necessary in a typename-specifier; a < following a qualified-id in a typename-specifier could safely be assumed to begin a template argument list, so the template keyword should be optional in this case. Some implementations already do not enforce this requirement.
See also issue 1710.
Proposed resolution (March, 2017):
This issue is resolved by the resolution of issue 1710.
[Moved to DR at the November, 2016 meeting as paper P0522R0.]
[Picked up by evolution group at October 2002 meeting.]
How are default template arguments handled with respect to template template parameters? Two separate questions have been raised:
template <class T, class U = int> class ARG { }; template <class X, template <class Y> class PARM> void f(PARM<X>) { } // specialization permitted? void g() { ARG<int> x; // actually ARG<int, int> f(x); // does ARG (2 parms, 1 with default) // match PARM (1 parm)?Template template parameters are deducible (13.10.3.6 [temp.deduct.type] paragraph 9) , but 13.4.4 [temp.arg.template] does not specify how matching is done.
Jack Rouse: I implemented template template parameters assuming template signature matching is analogous to function type matching. This seems like the minimum reasonable implementation. The code in the example would not be accepted by this compiler. However, template default arguments are compile time entities so it seems reasonable to relax the matching rules to allow cases like the one in the example. But I would consider this to be an extension to the language.
Herb Sutter: An open issue in the LWG is that the standard doesn't explicitly permit or forbid implementations' adding additional template-parameters to those specified by the standard, and the LWG may be leaning toward explicitly permitting this. [Under this interpretation,] if the standard is ever modified to allow additional template-parameters, then writing "a template that takes a standard library template as a template template parameter" won't be just ugly because you have to mention the defaulted parameters; it would not be (portably) possible at all except possibly by defining entire families of overloaded templates to account for all the possible numbers of parameters vector<> (or anything else) might actually have. That seems unfortunate.
template <template <class T, class U = int> class PARM> class C { PARM<int> pi; };
Jack Rouse: I decided they could not in the compiler I support. This continues the analogy with function type matching. Also, I did not see a strong need to allow default arguments in this context.
A class template used as a template template argument can have default template arguments from its declarations. How are the two sources of default arguments to be reconciled? The default arguments from the template template formal could override. But it could be cofusing if a template-id using the argument template, ARG<int>, behaves differently from a template-id using the template formal name, FORMAL<int>.
Rationale (10/99): Template template parameters are intended to be handled analogously to function function parameters. Thus the number of parameters in a template template argument must match the number of parameters in a template template parameter, regardless of whether any of those paramaters have default arguments or not. Default arguments are allowed for the parameters of a template template parameter, and those default arguments alone will be considered in a specialization of the template template parameter within a template definition; any default arguments for the parameters of a template template argument are ignored.
Note (Mark Mitchell, February, 2006):
Perhaps it is already obvious to all, but it seems worth noting that this extension would change the meaning of conforming programs:
struct Dense { static const unsigned int dim = 1; }; template <template <typename> class View, typename Block> void operator+(float, View<Block> const&); template <typename Block, unsigned int Dim = Block::dim> struct Lvalue_proxy { operator float() const; }; void test_1d (void) { Lvalue_proxy<Dense> p; float b; b + p; }
If Lvalue_proxy is allowed to bind to View, then the template operator+ will be used to perform addition; otherwise, Lvalue_proxy's implicit conversion to float, followed by the built-in addition on floats will be used.
Note (March, 2008):
The Evolution Working Group has accepted the intent of this issue and referred it to CWG for action (not for C++0x). See paper J16/07-0033 = WG21 N2173.
Notes from the June, 2008 meeting:
The CWG decided to take no action on this issue until an interested party produces a paper with analysis and a proposal.
[Accepted at the November, 2016 meeting as part of paper P0195R2.]
It would be handy if a pack expansion could appear in a using-declaration:
template <class... Mixins> class X : Mixins... { using Mixins::foo ...; // Currently not supported };
Rationale (February, 2012):
This is not a defect but a request for an extension and thus more appropriately addressed by EWG.
[Adopted at the February/March, 2017 meeting.]
According to 13.7.5 [temp.friend] paragraph 4,
When a function is defined in a friend function declaration in a class template, the function is instantiated when the function is odr-used (6.3 [basic.def.odr]). The same restrictions on multiple declarations and definitions that apply to non-template function declarations and definitions also apply to these implicit definitions.
This seems to imply that:
Instantiating a class template that contains a friend function definition instantiates the declaration, but not the definition, of that friend function, as usual (but see below).
If the function is odr-used, a definition is instantiated for each such class template specialization whose template had a definition.
If that results in multiple definitions, the program is ill-formed as usual.
The intent appears to be that the instantiated friend function declarations should be treated as if they were definitions, but that's not clear from the wording. This wording is also missing similar provisions for friend function template definitions; there is implementation divergence on the treatment of such cases.
There also does not appear to be wording that says that instantiating a class template specialization results in the instantiation of friend functions declared/defined therein (the relevant wording was removed from this section by issue 329). Presumably this should be covered in 13.9.2 [temp.inst] paragraph 1, which also includes the following wording that could be reused for the friend case:
However, for the purpose of determining whether an instantiated redeclaration of a member is valid according to 11.4 [class.mem], a declaration that corresponds to a definition in the template is considered to be a definition.
Also, the reliance on odr-use to trigger friend instantiation is out of date, as there are other contexts that can require an instantiation when there is no odr-use (a constexpr function invoked within an unevaluated operand).
Proposed resolution (October, 2015) [SUPERSEDED]:
Delete 13.7.5 [temp.friend] paragraph 4:
When a function is defined in a friend function declaration in a class template, the function is instantiated when the function is odr-used (6.3 [basic.def.odr]). The same restrictions on multiple declarations and definitions that apply to non-template function declarations and definitions also apply to these implicit definitions.
Change 13.9.2 [temp.inst] paragraph 1 as follows:
...The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or exception-specifications of the class member functions, member classes, scoped member enumerations, static data members,andmember templates, and friends; and it causes the implicit instantiation of the definitions of unscoped member enumerations and member anonymous unions. However, for the purpose of determining whether an instantiated redeclarationof a memberis valid according to 6.3 [basic.def.odr] and 11.4 [class.mem], a declaration that corresponds to a definition in the template is considered to be a definition. [Example:...
Change 13.9.2 [temp.inst] paragraph 3 as follows:
Unless a function template specialization has been explicitly instantiated or explicitly specialized, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist. A function whose declaration was instantiated from a friend function definition is implicitly instantiated when it is referenced in a context that requires a function definition to exist. Unless a call is to a function template explicit specialization or to a member function of an explicitly specialized class template, a default argument for a function template or a member function of a class template is implicitly instantiated when the function is called in a context that requires the value of the default argument.
Proposed resolution (November, 2016):
Delete 13.7.5 [temp.friend] paragraph 4:
When a function is defined in a friend function declaration in a class template, the function is instantiated when the function is odr-used (6.3 [basic.def.odr]). The same restrictions on multiple declarations and definitions that apply to non-template function declarations and definitions also apply to these implicit definitions.
Change 13.9.2 [temp.inst] paragraph 1 as follows, splitting it into two paragraphs as indicated:
... [Note: Within a template declaration, a local class (11.6 [class.local]) or enumeration and the members of a local class are never considered to be entities that can be separately instantiated (this includes their default arguments, exception-specifications, and non-static data member initializers, if any). As a result, the dependent names are looked up, the semantic constraints are checked, and any templates used are instantiated as part of the instantiation of the entity within which the local class or enumeration is declared. —end note]
The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or exception-specifications of the class member functions, member classes, scoped member enumerations, static data members,
andmember templates, and friends; and it causes the implicit instantiation of the definitions of unscoped member enumerations and member anonymous unions. However, for the purpose of determining whether an instantiated redeclarationof a memberis valid according to 6.3 [basic.def.odr] and 11.4 [class.mem], a declaration that corresponds to a definition in the template is considered to be a definition. [Example:template<class T, class U> struct Outer { template<class X, class Y> struct Inner; template<class Y> struct Inner<T, Y>; // #1a template<class Y> struct Inner<T, Y> { }; // #1b; OK: valid redeclaration of #1a template<class Y> struct Inner<U, Y> { }; // #2 }; Outer<int, int> outer; // error at #2Outer<int, int>::Inner<int, Y> is redeclared at #1b. (It is not defined but noted as being associated with a definition in Outer<T, U>.) #2 is also a redeclaration of #1a. It is noted as associated with a definition, so it is an invalid redeclaration of the same partial specialization.
template<typename T> struct Friendly { template<typename U> friend int f(U) { return sizeof(T); } }; Friendly<char> fc; Friendly<float> ff; // ill-formed: produces second definition of f(U)
—end example]
Change 13.9.2 [temp.inst] paragraph 3 as follows:
Unless a function template specialization has been explicitly instantiated or explicitly specialized, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist. A function whose declaration was instantiated from a friend function definition is implicitly instantiated when it is referenced in a context that requires a function definition to exist. Unless a call is to a function template explicit specialization or to a member function of an explicitly specialized class template, a default argument for a function template or a member function of a class template is implicitly instantiated when the function is called in a context that requires the value of the default argument.
[Moved to DR at the November, 2016 meeting.]
There does not appear to be a rule that causes p or this to be dependent in the following example:
template <typename T> struct A { void foo() { A* p = 0; bar(p); // will be found by ADL at the point of instantiation bar(this); // same here } }; void bar(...); int main() { A<int> a; a.foo(); }
Proposed resolution (February, 2016) [SUPERSEDED]:
Change 13.8.3.2 [temp.dep.type] bullet 9.7 as follows:
A type is dependent if it is
...
an injected-type-name ( Clause 11 [class]) of a class template or a simple-template-id in which either the template name is a template parameter or any of the template arguments is a dependent type or an expression that is type-dependent or value-dependent or is a pack expansion, or
denoted by...
Proposed resolution (March, 2016):
Change 13.8.3.2 [temp.dep.type] bullet 9.7 as follows:
A type is dependent if it is
...
a simple-template-id in which either the template name is a template parameter or any of the template arguments is a dependent type or an expression that is type-dependent or value-dependent or is a pack expansion [Note: This includes an injected-class-name (Clause 11 [class]) of a class template used without a template-argument-list. —end note], or
denoted by...
[Moved to DR at the November, 2016 meeting.]
According to bullet 2.4 of 13.8.3.4 [temp.dep.constexpr], an id-expression is value-dependent if
it names a static data member that is a dependent member of the current instantiation and is not initialized in a member-declarator,
This implies that the address of an initialized static data member is not value-dependent, which is incorrect.
Proposed resolution (June, 2016):
Change 13.8.3.4 [temp.dep.constexpr] paragraph 5 as follows:
An expression of the form &qualified-id where the qualified-id names a dependent member of the current instantiation is value-dependent. An expression of the form
&cast-expression
is also value-dependent if evaluating cast-expression as a core constant expression (7.7 [expr.const]) succeeds and the result of the evaluation refers to a templated entity that is an object with static or thread storage duration or a member function.
[Adopted at the February/March, 2017 meeting.]
Consider:
template <bool B> struct A { void static foo() noexcept(B);// only dependent on B void bar(struct X* x) { buz<noexcept(foo)>(x); // dependent call through explicit template arguments? } };
A value-dependent exception specification ought to make the type of the corresponding function type-dependent.
Proposed resolution (February, 2017):
Add the following as a new bullet following 13.8.3.2 [temp.dep.type] bullet 9.6:
...
an array type whose element type is dependent or whose bound (if any) is value-dependent,
a function type whose exception specification is value-dependent,
...
[Resolved by P0391R0 (Introducing the term "templated entity"), adopted at the June, 2016 meeting.]
Many statements in the Standard apply only to templates, for example, 13.8 [temp.res] paragraph 8:
If no valid specialization can be generated for a template definition, and that template is not instantiated, the template definition is ill-formed, no diagnostic required.
This clearly should apply to non-template member functions of class templates, not just to templates per se. Terminology should be established to refer to these generic entities that are not actually templates.
Additional notes (August, 2012):
Among the generic entities that should be covered by such a term are default function arguments, as they can be instantiated independently. If issue 1330 is resolved as expected, exception-specifications should also be covered by the same term.
See also issues 1484 and 1626.
CWG 2022-11-11
With the introduction of the family of terms "templated entity", this has mostly been addressed. The remaining open issue is the timing details of the parsing of the complete-class contexts.
CWG 2024-06-26
Parsing of the complete-class contexts is not the topic of this issue.
[Adopted as a DR at the February/March, 2017 meeting.]
13.9.4 [temp.expl.spec] paragraph 2 requires that explicit specializations of member templates be declared in namespace scope, not in the class definition. This restriction does not apply to partial specializations of member templates; that is,
struct A { template<class T> struct B; template <class T> struct B<T*> { }; // well-formed template <> struct B<int*> { }; // ill-formed };
There does not seem to be a good reason for this inconsistency.
Additional note (October, 2013):
EWG has requested CWG to consider resolving this issue. See EWG issue 41.
Additional note, November, 2014:
See also paper N4090.
Proposed resolution (March, 2017):
Change 13.7.6.1 [temp.spec.partial.general] paragraph 6 as follows:
A class template partial specialization may be declared
or redeclaredin anynamespacescope in which the corresponding primary template may be defined (_N4868_.9.8.2.3 [namespace.memdef]and, 11.4 [class.mem], 13.7.3 [temp.mem]). [Example:template<class T> struct A { struct C { template<class T2> struct B { }; template<class T2> struct B<T2**> { }; // partial specialization #1 }; }; // partial specialization of A<T>::C::B<T2> template<class T> template<class T2> struct A<T>::C::B<T2*> { }; // #2 A<short>::C::B<int*> absip; // uses partial specialization #2—end example]
Change 13.9.4 [temp.expl.spec] paragraph 2 as follows:
An explicit specializationshall be declared in a namespace enclosing the specialized template. An explicit specialization whose declarator-id or class-head-name is not qualified shall be declared in the nearest enclosing namespace of the template, or, if the namespace is inline (9.9.2 [namespace.def]), any namespace from its enclosing namespace set. Such a declaration may also be a definitionmay be declared in any scope in which the corresponding primary template may be defined (_N4868_.9.8.2.3 [namespace.memdef], 11.4 [class.mem], 13.7.3 [temp.mem]).If the declaration is not a definition, the specialization may be defined later (_N4868_.9.8.2.3 [namespace.memdef]).
[Moved to DR at the November, 2016 meeting.]
Given the following example,
template <class ...T> int f(T*...) { return 1; } template <class T> int f(const T&) { return 2; } void g() { f((int*)0); }
the current specification makes the call ambiguous because deduction fails in both directions: with A being T and P being T* in one direction and A being T* and P being T, because 13.10.3.5 [temp.deduct.partial] paragraph 8 says,
If A was transformed from a function parameter pack and P is not a parameter pack, type deduction fails.
It is not clear whether this is the best outcome, however; it might be better to consider the first template more specialized, with the variadic/non-variadic test being a tie-breaker if there is no other reason to prefer one over the other based on the parameter types.
Notes from the February, 2014 meeting:
CWG felt that the best approach would be, when comparing P and A, if A is a pack and P is not, A should be repeated for each remaining instance of P and then use the variadic/nonvariadic criterion as a late tiebreaker if the result is still ambiguous. This would apply in the general case (including 13.10.3.5 [temp.deduct.partial]), not just in function calls.
Proposed resolution (June, 2016):
This issue is resolved by the resolution of issue 1395.
[Moved to DR at the November, 2016 meeting.]
The resolution of issue 692 (found in document N3281) made the following example ambiguous and thus ill-formed:
template<class T> void print(ostream &os, const T &t) { os << t; } template <class T, class... Args> void print(ostream &os, const T &t, const Args&... rest) { os << t << ", "; print(os, rest...); } int main() { print(cout, 42); print(cout, 42, 1.23); }
This pattern seems fairly intuitive; is it reason to reconsider or modify the outcome of issue 692?
(See also issue 1432.)
Notes from the October, 2012 meeting:
CWG agreed that the example should be accepted, handling this case as a late tiebreaker, preferring an omitted parameter over a parameter pack.
Additional note (March, 2013):
For another example:
template<typename ...T> int f(T*...) { return 1; } template<typename T> int f(const T&) { return 2; } int main() { if (f((int*)0) != 1) { return 1; } return 0; }
This worked as expected prior to the resolution of issue 692.
Proposed resolution (June, 2016):
Change 13.10.3.5 [temp.deduct.partial] paragraph 8 as follows:
If A was transformed from a function parameter pack and P is not a parameter pack, type deduction fails. Otherwise, usingUsing the resulting types P and A, the deduction is then done as described in 13.10.3.6 [temp.deduct.type]. If P is a function parameter pack, the type A of each remaining parameter type of the argument template is compared with the type P of the declarator-id of the function parameter pack. Each comparison deduces template arguments for subsequent positions in the template parameter packs expanded by the function parameter pack. Similarly, if A was transformed from a function parameter pack, it is compared with each remaining parameter type of the parameter template. If deduction succeeds for a given type, the type from the argument template is considered to be at least as specialized as the type from the parameter template. [Example:...
Add the following as a new paragraph following 13.10.3.5 [temp.deduct.partial] paragraph 10:
Function template F is at least as specialized as function template G if, for each pair of types used to determine the ordering, the type from F is at least as specialized as the type from G. F is more specialized than G if F is at least as specialized as G and G is not at least as specialized as F.
If, after considering the above, function template F is at least as specialized as function template G and vice-versa, and if G has a trailing paramter pack for which F does not have a corresponding parameter, and if F does not have a trailing parameter pack, then F is more specialized than G.
This resolution also resolves issue 1825.
[Adopted at the November, 2016 meeting as part of paper P0003R5.]
Consider the following example:
struct B { virtual void f() { } }; struct D : B { } d; bool b = noexcept(typeid(d));
According to 7.6.2.7 [expr.unary.noexcept] paragraph 3, the value of b should be false, because 14.5 [except.spec] bullet 14.6 says,
If e is a typeid expression applied to a glvalue expression whose type is a polymorphic class type (7.6.1.8 [expr.typeid]), S consists of the type std::bad_typeid.
and d is such an expression. This is clearly bogus, as the expression cannot possibly throw; according to 7.6.1.8 [expr.typeid] paragraph 2, the condition under which the exception might be thrown is:
If the glvalue expression is obtained by applying the unary * operator to a pointer69 and the pointer is a null pointer value (7.3.12 [conv.ptr]), the typeid expression throws an exception (14.2 [except.throw]) of a type that would match a handler of type std::bad_typeid exception (17.7.5 [bad.typeid]).
Proposed resolution (November, 2016):
Change 14.5 [except.spec] bullet 13.6 as follows:
The set of potential exceptions of an expression e is empty if e is a core constant expression (7.7 [expr.const]). Otherwise, it is the union of the sets of potential exceptions of the immediate subexpressions of e, including default argument expressions used in a function call, combined with a set S defined by the form of e, as follows:
...
If e is a typeid expression applied to a
glvalue expression whose type is(possibly parenthesized) built-in unary * operator applied to a pointer to a polymorphic class type (7.6.1.8 [expr.typeid]), S consists of the type std::bad_typeid. [Example: ...
[Adopted as a DR at the November, 2019 meeting.]
The list of entities in 6.3 [basic.def.odr] paragraph 12 that can have multiple definitions across translation units does not, but should, include variable templates.
Proposed resolution (October, 2019):
[Drafting note: “with external linkage” is not needed for the inline entities because the other cases - entities attached to a named module and multiple definitions in the same translation unit - are ruled out later in that paragraph.]There can be more than one definition of a
class type (Clause 11 [class]),
enumeration type (9.8.1 [dcl.enum]),
inline function or variable
with external linkage(9.2.8 [dcl.inline]),
inline variable with external linkage (9.2.8 [dcl.inline]),
class template (Clause 13 [temp]),
non-static function template (13.7.7 [temp.fct]),
concept (13.7.9 [temp.concept]),
static data member of a class template (13.7.2.5 [temp.static]),
member function of a class template (13.7.2.2 [temp.mem.func]),
template specialization for which some template parameters are not specified (13.9 [temp.spec], 13.7.6 [temp.spec.partial]),templated entity (13.1 [temp.pre]),
default argument for a parameter (for a function in a given scope), or
default template argument
in a program provided that...
[Adopted at the October, 2017 meeting as paper P0767R1.]
P0488R0 comment US 101The term POD no longer serves a purpose in the standard, it is merely defined, and restrictions apply for when a few other types preserve this vestigial property. The is_pod trait should be deprecated, moving the definition of a POD type alongside the trait in Annex Clause Annex D [depr], and any remaining wording referring to POD should be struck, or revised to clearly state intent (usually triviality) without mentioning PODs.
[Adopted as a DR at the November, 2019 meeting.]
According to 6.9 [basic.exec] paragraph 5,
A full-expression is
...
an invocation of a destructor generated at the end of the lifetime of an object other than a temporary object (6.7.7 [class.temporary]), or
...
This definition excludes the destruction of temporaries that are bound to references from being treated as full-expressions. It is not clear whether this omission has observable effects or not. See editorial issue 2664.
Proposed resolution (October, 2019):
Change 6.9 [basic.exec] bullet 5.5 as follows:
A full-expression is
...
an invocation of a destructor generated at the end of the lifetime of an object other than a temporary object (6.7.7 [class.temporary]) whose lifetime has not been extended, or
...
[Accepted (as paper P2095R0) at the February, 2020 (Prague) meeting.]
The grammar for init-capture is:
As a consequence a reference init-capture pack is written as
[...&x = init]
instead of the way packs of references are commonly written:
[&...x = init]
Notes from the November, 2018 meeting:
CWG agreed with the suggested direction.
[Adopted as a DR at the November, 2019 meeting.]
According to 7.6.1.3 [expr.call] paragraph 9,
If the argument has integral or enumeration type that is subject to the integral promotions (7.3.7 [conv.prom]), or a floating-point type that is subject to the floating-point promotion (7.3.8 [conv.fpprom]), the value of the argument is converted to the promoted type before the call. These promotions are referred to as the default argument promotions.
A scoped enumeration with an underlying type that is shorter than int will not be widened when passed to an ellipsis. Should it be?
Notes from the June, 2018 meeting:
The consensus of CWG was that the value passed ougnt to be widened to match the promoted type of the underlying type.
Proposed resolution (May, 2019): [SUPERSEDED]
Change 7.6.1.3 [expr.call] paragraph 12 as follows:
...If the argument has an integral or enumeration type that is subject to the integral promotions (7.3.7 [conv.prom]), a scoped enumeration type whose underlying type is subject to the integral promotions, or a floating-point type that is subject to the floating-point promotion (7.3.8 [conv.fpprom]), the value of the argument is converted to the promoted type before the call. These promotions are referred to as the default argument promotions.
Notes from the September, 2019 teleconference:
The consensus was that passing scoped enumerations to ellipsis should be conditionally-supported behavior, similar to the treatment of class types with nontrivial copy semantics.
Proposed resolution (October, 2019):
Change 7.6.1.3 [expr.call] paragraph 12 as follows:
...Passing a potentially-evaluated argument of a scoped enumeration type or of a class type ( Clause 11 [class]) having an eligible non-trivial copy constructor, an eligible non-trivial move constructor, or a non-trivial destructor (11.4.4 [special]), with no corresponding parameter, is conditionally-supported with implementation-defined semantics. If the argument...
[Accepted as a DR at the November, 2019 meeting.]
The restrictions in 7.6.2.8 [expr.new] against a placement deallocation function signature matching a usual deallocation function signature consider only std::size_t parameters, omitting std::align_val_t parameters.
Proposed resolution (February, 2017):
Change 7.6.2.8 [expr.new] paragraph 27 as follows:
A declaration of a placement deallocation function matches the declaration of a placement allocation function if it has the same number of parameters and, after parameter transformations (9.3.4.6 [dcl.fct]), all parameter types except the first are identical. If the lookup finds a single matching deallocation function, that function will be called; otherwise, no deallocation function will be called. If the lookup finds a usual deallocation function>with a parameter of type std::size_t (6.7.6.5.3 [basic.stc.dynamic.deallocation])and that function, considered as a placement deallocation function, would have been selected as a match for the allocation function, the program is ill-formed. For a non-placement allocation function, the normal deallocation function lookup is used to find the matching deallocation function (7.6.2.9 [expr.delete]). [Example:...
[Adopted as a DR at the November, 2019 meeting.]
The fallback treatment for alignment and non-alignment allocation and deallocation functions is asymmetric. While a deletion of a non-overaligned class object will match a class-specific alignment deallocation function if no class-specific non-alignment deallocation function is provided, the same is not true for allocation: a new-expression for a non-overaligned class type will fail if an alignment allocation function is provided with no non-alignment allocation function. The allocation behavior should be changed to match the deallocation behavior.
Proposed resolution (September, 2019):
Change 7.6.2.8 [expr.new] paragraph 18 as follows, splitting the running text into bullets:
Overload resolution is performed on a function call created by assembling an argument list. The first argument is the amount of space requested, and has type std::size_t. If the type of the allocated object has new-extended alignment, the next argument is the type's alignment, and has type std::align_val_t. If the new-placement syntax is used, the initializer-clauses in its expression-list are the succeeding arguments. If no matching function is found then
andif the allocated object type has new-extended alignment, the alignment argument is removed from the argument list,;otherwise, an argument list that is the type's alignment and has type std::align_val_t is added into the argument list immediately after the first argument;
and then overload resolution is performed again.
[Adopted as a DR at the November, 2019 meeting.]
CCCC,Before the resolution of issue 1596, 7.6.6 [expr.add] specified that:
For the purposes of these operators, a pointer to a nonarray object behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.
where “these operators” refers to the additive operators. This provision thus applied to any pointer, regardless of its provenance. In its place, the normative provision for this treatment was restricted to the & operator only, in 7.6.2.2 [expr.unary.op] paragraph 3:
For purposes of pointer arithmetic (7.6.6 [expr.add]) and comparison (7.6.9 [expr.rel], 7.6.10 [expr.eq]), an object that is not an array element whose address is taken in this way is considered to belong to an array with one element of type T.
Thus, for example:
int *p1 = new int; int *p2 = &*p1; bool b1 = p1 < p1+1; // undefined behavior bool b2 = p2 < p2+1; // well-defined
This restriction does not seem desirable.
Proposed resolution (October, 2019):
Change 7.6.2.2 [expr.unary.op] bullet 3.2 as follows:
The result of the unary & operator is a pointer to its operand.
...
Otherwise, if the operand is an lvalue of type T, the resulting expression is a prvalue of type “pointer to T” whose result is a pointer to the designated object (6.7.1 [intro.memory]) or function. [Note: In particular, taking the address of a variable of type “cv T” yields a pointer of type “pointer to cv T”. —end note]
For purposes of pointer arithmetic (7.6.6 [expr.add]) and comparison (7.6.9 [expr.rel], 7.6.10 [expr.eq]), an object that is not an array element whose address is taken in this way is considered to belong to an array with one element of type T....
Change 6.8.4 [basic.compound] paragraph 3 as follows:
...A value of a pointer type that is a pointer to or past the end of an object represents the address of the first byte in memory (6.7.1 [intro.memory]) occupied by the object43 or the first byte in memory after the end of the storage occupied by the object, respectively. [Note: A pointer past the end of an object (7.6.6 [expr.add]) is not considered to point to an unrelated object of the object's type that might be located at that address. A pointer value becomes invalid when the storage it denotes reaches the end of its storage duration; see 6.7.6 [basic.stc]. —end note] For purposes of pointer arithmetic (7.6.6 [expr.add]) and comparison (7.6.9 [expr.rel], 7.6.10 [expr.eq]), a pointer past the end of the last element of an array x of n elements is considered to be equivalent to a pointer to a hypothetical element x[n] and an object of type T that is not an array element is considered to belong to an array with one element of type T. The value representation of pointer types...
Change the footnote in 7.6.6 [expr.add] bullet 4.2 as follows:
When an expression J that has integral type is added to or subtracted from an expression P of pointer type, the result has the type of P.
...
Otherwise, if P points to an array element i of an array object x with n elements (9.3.4.5 [dcl.array]), [Footnote:
AnAs specified in 6.8.4 [basic.compound]. an object that is not an array element is considered to belong to a single-element array for this purpose; see 7.6.2.2 [expr.unary.op]. Aand a pointer past the last element of an array x of n elements is considered to be equivalent to a pointer to a hypothetical array element n for this purpose; see 6.8.4 [basic.compound]. —end footnote] the expressions...
Change the footnote in 7.6.9 [expr.rel] paragraph 4 aa follows:
The result of comparing unequal pointers to objects [Footnote:AnAs specified in 6.8.4 [basic.compound], an object that is not an array element is considered to belong to a single-element array for this purpose; see 7.6.2.2 [expr.unary.op]. Aand a pointer past the last element of an array x of n elements is considered to be equivalent to a pointer to a hypothetical array elementx [ n ]n for this purpose; see 6.8.4 [basic.compound]. —end footnote] is defined in terms of a partial order consistent with the following rules:
Change 26.10.16 [numeric.ops.midpoint] paragraph 5 as follows:
Expects: a and b point to, respectively, elements x[i] and x[j] of the same array object x. [Note:AnAs specified in 6.8.4 [basic.compound], an object that is not an array element is considered to belong to a single-element array for this purpose; see 7.6.2.2 [expr.unary.op]. Aand a pointer past the last element of an array x of n elements is considered to be equivalent to a pointer to a hypothetical array elementx[n]n for this purpose; see 6.8.4 [basic.compound]. —end note]
[Adopted as a DR at the November, 2019 meeting.]
According to 7.6.19 [expr.assign] paragraph 7,
A simple assignment whose left operand is of a volatile -qualified non-class type is deprecated (D.4 [depr.volatile.type]) unless the assignment is either a discarded-value expression or appears in an unevaluated context.
The deprecations of increment, decrement, and compound assignment operators do not, but presumably should, mention unevaluated contexts.
Notes from the August, 2019 teleconference:
The omission of those operators was intentional; the deprecation is intended only to affect cases where using the result of the operation would result in a subsequent fetch of the value. However, some shortcomings of the existing wording were noted and will be addressed in the resolution.
Proposed resolution (October, 2019):
Change 7.6.19 [expr.assign] paragraph 5 as follows:
A simple assignment whose left operand is of a volatile-qualified type is deprecated (D.4 [depr.volatile.type]) unless the (possibly parenthesized) assignment iseithera discarded-value expression orappears inan unevaluatedcontextoperand.
[Adopted as a DR at the November, 2019 meeting.]
Consider an example like the following:
typedef const int CI[3]; constexpr CI &ci = CI{11, 22, 33}; static_assert(ci[1] == 22, "");
This is ill-formed because the lifetime of the array temporary did not start within the current evaluation. Perhaps we should treat all lifetime-extended temporaries of const-qualified literal type that are initialized by constant expressions as if they are constexpr objects?
Proposed resolution (October, 2019):
Change 7.7 [expr.const] paragraph 3 as follows:
A variable is usable in constant expressions after its initializing declaration is encountered if it is a constexpr variable, or it is a constant-initialized variable of reference type or of const-qualified integral or enumeration type. An object or reference is usable in constant expressions if it is
a variable that is usable in constant expressions, or
a template parameter object (13.2 [temp.param]), or
a string literal object (5.13.5 [lex.string]), or
a temporary object of non-volatile const-qualified literal type whose lifetime is extended (6.7.7 [class.temporary]) to that of a variable that is usable in constant expressions, or
a non-mutable subobject or reference member of any of the above
, or.
a complete temporary object of non-volatile const-qualified integral or enumeration type that is initialized with a constant expression.
This resolution also resolves issue 2439.
[ Resolved by P0595R2, adopted in November, 2018. ]
Similar to the concern of issue 2166, the requirement of 7.7 [expr.const] bullet 2.7.1 for
a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or
does not specify the point at which the determination of “preceding initialization” is made: is it at the point at which the reference to the variable appears lexically, or is it the point at which the outermost constant evaluation occurs? There is implementation divergence on this point.
Additional notes (July, 2024)
The words in question were removed by P0595R2 (std::is_constant_evaluated()), adopted in November, 2018.
[Adopted as a DR at the November, 2019 meeting.]
According to 7.7 [expr.const] paragraph 3,
An object or reference is usable in constant expressions if it is
...
a complete temporary object of non-volatile const-qualified integral or enumeration type that is initialized with a constant expression.
The phrase “initialized with a constant expression” is not defined (which causes problems with std::is_constant_evaluated). It would be better to use “is constant-initialized” instead.
Proposed resolution (October, 2019):
This issue is resolved by the resolution of issue 2126.
[Adopted as a DR at the November, 2019 meeting.]
According to 6.7.6.3 [basic.stc.thread] paragraph 2,
A variable with thread storage duration shall be initialized before its first odr-use (6.3 [basic.def.odr]) and, if constructed, shall be destroyed on thread exit.
According to 8.9 [stmt.dcl] paragraph 4, for block-scope variables this initialization is peformed when the declaration statement is executed:
Dynamic initialization of a block-scope variable with static storage duration (6.7.6.2 [basic.stc.static]) or thread storage duration (6.7.6.3 [basic.stc.thread]) is performed the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization.
However, there are cases in which the flow of control does not pass through a variable's declaration, leaving it uninitialized in spite of it being odr-used. For example:
#include <thread> #include <iostream> struct Object { int i; Object() : i(3) {} }; int main(void) { static thread_local Object o; std::cout << "[main] o.i = " << o.i << std::endl; std::thread([] { std::cout << "[new thread] o.i = " << o.i << std::endl; }).join(); }
o is a block-scope variable with thread storage and a dynamic initializer. The lambda passed into std::thread's constructor refers to o but does not capture it, which should be fine since o is not a variable with automatic storage. However, when control passes through the lambda, it will do so on a new thread. When that happens, it will refer to o for the first time. Because o is a thread-local variable, it should be initialized, but because it is declared in block scope, it will only be initialized when control passes through its declaration, which will never happen on the new thread.
This example is straightforward, but others are more ambiguous:
#include <thread> #include <iostream> struct Object { int i; Object(int v) : i(3 + v) {} }; int main(void) { int w = 4; static thread_local Object o(w); std::cout << "[main] o.i = " << o.i << std::endl; std::thread([] { std::cout << "[new thread] o.i = " << o.i << std::endl; }).join(); }
Here the initialization of o uses the value of w, which is not captured. Perhaps it should be ill-formed for a lambda to refer to a block-scope thread-local variable.
Proposed resolution (October, 2019):
Change 6.7.6.3 [basic.stc.thread] paragraphs 1 and 2 as follows:
All variables declared with the thread_local keyword have thread storage duration. The storage for these entities
shall lastlasts for the duration of the thread in which they are created. There is a distinct object or reference per thread, and use of the declared name refers to the entity associated with the current thread.[Note: A variable with thread storage duration
shall beis initializedbefore its first odr-use (6.3 [basic.def.odr])as specified in 6.9.3.2 [basic.start.static], 6.9.3.3 [basic.start.dynamic], and 8.9 [stmt.dcl] and, if constructed,shall beis destroyed on thread exit (6.9.3.4 [basic.start.term]). —end note]
[Adopted as a DR at the November, 2019 meeting.]
Paper P1331R2 removed the requirement that a constexpr constructor initialize every non-variant non-static data member, but it left untouched the corresponding requirements for variant members. That is, the modified text in 9.2.6 [dcl.constexpr] paragraph 4 still contains:
The definition of a constexpr constructor whose function-body is not = delete shall additionally satisfy the following requirements:
if the class is a union having variant members (11.5 [class.union]), exactly one of them shall be initialized;
if the class is a union-like class, but is not a union, for each of its anonymous union members having variant members, exactly one of them shall be initialized;
Presumably this was an oversight and these two bullets should be changed from “exactly” to “at most” or something similar.
Proposed resolution (September, 2019):
Delete the indcated text from 9.2.6 [dcl.constexpr] paragraph 4:
The definition of a constexpr constructor whose function-body is not = delete shall additionally satisfy the following requirements:
if the class is a union having variant members (11.5 [class.union]), exactly one of them shall be initialized;
if the class is a union-like class, but is not a union, for each of its anonymous union members having variant members, exactly one of them shall be initialized;for a non-delegating constructor, every constructor selected to initialize non-static data members and base class subobjects shall be a constexpr constructor;
for a delegating constructor, the target constructor shall be a constexpr constructor.
[Accepted as a DR at the November, 2019 meeting.]
According to 9.2.8 [dcl.inline] paragraph 5,
The inline specifier shall not appear on a block scope declaration.
Function parameters, however, are not block scope declarations, but they should also not be declared inline.
Proposed resolution (November, 2019):
Change 9.2.8 [dcl.inline] paragraph 5 as follows:
The inline specifier shall not appear on a block scope declaration.87 or on the declaration of a function parameter.
[Accepted as a DR at the February, 2020 (Prague) meeting.]
According to 9.2.9.7 [dcl.spec.auto] paragraph 3,
If the auto type-specifier appears as one of the decl-specifiers in the decl-specifier-seq of a parameter-declaration of a lambda-expression, the lambda is a generic lambda (7.5.6 [expr.prim.lambda]).
and 7.5.6 [expr.prim.lambda] paragraph 5 says,
For a generic lambda, the closure type has a public inline function call operator member template (13.7.3 [temp.mem]) whose template-parameter-list consists of one invented type template-parameter for each occurrence of auto in the lambda's parameter-declaration-clause, in order of appearance.
However, an auto that signals a trailing-return-type should be excluded from these descriptions.
Proposed resolution (February, 2020):
This issue is resolved by the resolution of issue 2447.
[Accepted at the February, 2020 (Prague) meeting.]
The current wording for abbreviated function templates could lead to the incorrect conclusion that f and g in the example below are well-formed abbreviated function templates. The g case is the abbreviated function template analog of issue 2053 regarding generic lambdas.
9.2.9.7 [dcl.spec.auto] paragraph 4 clearly disallows the ap case. The inconsistency between the paragraph 2 wording for abbreviated function templates and the paragraph 4 wording is unintentional.
template <typename> struct A; void f(A<auto> x); void g(auto f() -> int); A<auto> *ap = static_cast<A<int> *>(0);
Proposed resolution (February, 2020):
Change 9.2.9.7 [dcl.spec.auto] paragraph 2 as follows:
A placeholder-type-specifier of the form type-constraintopt auto can be usedinas a decl-specifier of the decl-specifier-seq of a parameter-declaration of a function declaration or lambda-expression and, if it is not the auto type-specifier introducing a trailing-return-type (see below), is a generic parameter type placeholder of the function declaration or lambda-expression. [Note: Having a generic parameter type placeholder signifies that the function is an abbreviated function template (9.3.4.6 [dcl.fct]) or the lambda is a generic lambda (7.5.6 [expr.prim.lambda]). —end note]
Change 9.3.4.6 [dcl.fct] paragraph 18 as follows:
An abbreviated function template is a function declarationwhose parameter-type-list includesthat has one or more generic parameter type placeholders (9.2.9.7 [dcl.spec.auto]). An abbreviated function template is equivalent to a function template (13.7.7.2 [temp.over.link]) whose template-parameter-list includes one invented type template-parameter for eachoccurrence of ageneric parameter type placeholdertype in the decl-specifier-seq of a parameter-declaration in the function's parameter-type-listof the function declaration, in order of appearance. For a placeholder-type-specifier of the form...
Change 7.5.6 [expr.prim.lambda] paragraph 5 as follows:
A lambda is a generic lambda ifthere is a decl-specifier that is a placeholder-type-specifier in the decl-specifier-seq of a parameter-declaration ofthe lambda-expression has any generic parameter type placeholders (9.2.9.7 [dcl.spec.auto]), or if the lambda has a template-parameter-list. [Example:...
This resolution also resolves issue 2053.
[Adopted as a DR at the November, 2019 meeting.]
According to 9.5.5 [dcl.init.list] bullet 3.8,
Otherwise, if T is an enumeration with a fixed underlying type (9.8.1 [dcl.enum]), the initializer-list has a single element v, and the initialization is direct-list-initialization, the object is initialized with the value T(v) (7.6.1.4 [expr.type.conv]); if a narrowing conversion is required to convert v to the underlying type of T, the program is ill-formed.
The conversion T(v) is too broad, allowing, e.g., conversion from a different scoped enumeration type. The intent presumably was only to allow v to be a value of T's underlying type.
Notes from the October, 2018 teleconference:
CWG agreed with the suggested direction, along the lines of “...can be implicitly converted to the underlying type of T...”
Proposed resolution (May, 2019): [SUPERSEDED]
Change bullet 3.8 of 9.5.5 [dcl.init.list] as follows:
Otherwise, if T is an enumeration with a fixed underlying type (9.8.1 [dcl.enum]), the initializer-list has a single element v, v can be implicitly converted to the underlying type of T, and the initialization is direct-list-initialization, the object is initialized with the value T(v) (7.6.1.4 [expr.type.conv]); if a narrowing conversion is required to convert v to the underlying type of T, the program is ill-formed. [Example:...
Proposed resolution (October, 2019):
Change bullet 3.8 of 9.5.5 [dcl.init.list] as follows:
Otherwise, if T is an enumeration with a fixed
underlying type
(9.8.1 [dcl.enum]) U,
the initializer-list has a single
element v, v can be implicitly
converted to U, and the initialization is
direct-list-initialization, the object is initialized with
the value T(v) (7.6.1.4 [expr.type.conv]); if a
narrowing conversion is required to convert v to
the underlying type of T
U, the program is
ill-formed. [Example:...
[Accepted (as paper P2107R0) at the February, 2020 (Prague) meeting.]
(This was previously issue 33 in the coroutine issue list.)
According to 9.6.4 [dcl.fct.def.coroutine] paragraph 13,
When a coroutine is invoked, a copy is created for each coroutine parameter. Each such copy is an object with automatic storage duration that is direct-initialized from an lvalue referring to the corresponding parameter if the parameter is an lvalue reference, and from an xvalue referring to it otherwise.
This means that parameters to a coroutine that are const-qualified will be copy-constructed rather than move-constructed. For example, changing the signature of a coroutine from task<void> f(std::string) to task<void>(const std::string) can introduce an extra string copy and potential heap allocation that may not be obvious to the author.
It also means that it is not possible to write a coroutine with a const-qualified move-only parameter type like const std::unique_ptr<T>.
The original parameter to the function is generally not observable to the coroutine body, so there seems to be little benefit to preserving the constness of the original parameter when copying the parameter into the coroutine frame.
Suggested resolution:
When a coroutine is invoked, a copy is created for each coroutine parameter. Each such copy is an object or reference with automatic storage durationthatand has the same type as the corresponding parameter. Each copy is direct-initialized (9.5 [dcl.init]) froman lvalue referring tothe corresponding parameterif the parameter is an lvalue reference, and from an xvalue referring to it otherwise. If the type of the copy is an rvalue reference type, then for the purpose of this initialization the value category of the corresponding parameter is an rvalue. Areference touse of a parameter in thefunction-bodyfunction-body of the coroutine and in the call to the coroutine promise constructor is replaced bya reference toits copy. The initialization and destruction of each parameter copy...
[Adopted as a DR at the November, 2019 meeting.]
According to 11.4 [class.mem] paragraph 7,
A class is considered a completely-defined object type (6.8 [basic.types]) (or complete type) at the closing } of the class-specifier. The class is regarded as complete within its complete-class contexts; otherwise it is regarded as incomplete within its own class member-specification.
The complete-class contexts (paragraph 6) include the body of a member function but not its return and parameter types. Thus it appears that an example like the following is ill-formed:
struct S { S f(S s) { return s; } };
because of 9.6.1 [dcl.fct.def.general] paragraph 2
The type of a parameter or the return type for a function definition shall not be an incomplete or abstract (possibly cv-qualified) class type in the context of the function definition unless the function is deleted (9.6.3 [dcl.fct.def.delete]).
The words “in the context of the function definition” were added by the resolution of issue 1824 to address this problem, but “context” is most naturally read as referring to the lexical context where the definition appears rather than within its body.
Proposed resolution (October, 2019):
Change 9.6.1 [dcl.fct.def.general] paragraph 2 as follows:
...The type of a parameter or the return type for a function definition shall not bean incomplete or abstract (possibly cv-qualified) class type in the context of the function definitiona (possibly cv-qualified) class type that is incomplete or abstract within the function body unless the function is deleted (9.6.3 [dcl.fct.def.delete]).
[Adopted as a DR at the November, 2019 meeting.]
The effect of a non-static data member initializer in an anonymous union is not clearly described in the current wording. Consider the following example:
struct A {
struct B {
union {
int x = 37;
};
union {
int y = x + 47; // Well-formed?
};
} a;
};
Does an anonymous union have a constructor that applies a non-static data member initializer? Or is the initialization performed by the constructor of the class in which the anonymous union appears? In particular, is the reference to x in the initializer for y well-formed or not? If the initialization of y is performed by B's constructor, there is no problem because B::x is a member of the object being initialized. If an anonymous union has its own constructor, B::x is just a member of the containing class and is a reference to a non-static data member without an object, which is ill-formed. Implementations currently appear to take the latter interpretation and report an error for that initializer.
As a further example, consider:
union { // #1 union { // #2 union { // #3 int y = 32; }; }; } a { } ;
One interpretation might be that union #3 has a non-trivial default constructor because of the initializer of y, which would give union #2 a deleted default constructor, which would make the example ill-formed.
As yet another example, consider:
union { union { int x; }; union { int y = 3; }; union { int z; }; } a { };
Assuming the current proposed resolution of issue 1502, what is the correct interpretation of this code? Is it well-formed, and if so, what initialization is performed?
Finally, consider
struct S { union { int x = 1; }; union { int y = 2; }; } s{};
Does this violate the prohibition of aggregates containing member initializers in 9.5.2 [dcl.init.aggr] paragraph 1?
See also issues 1460, 1562, 1587, and 1623.
Proposed resolution (October, 2019):
Change 11.4.3 [class.mfct.non.static] paragraph 1 as follows:
...A non-static member function may also be called directly using the function call syntax (7.6.1.3 [expr.call], 12.2.2.2 [over.match.call]) from withinthe body of a member function ofits class orofa class derived from its class, or a member thereof, as described below. .
Change 11.4.3 [class.mfct.non.static] paragraph 3 as follows:
When an id-expression (7.5.5 [expr.prim.id]) that is not part of a class member access syntax (7.6.1.5 [expr.ref]) and not used to form a pointer to member (7.6.2.2 [expr.unary.op]) is used in a member of class X in a context where this can be used (7.5.3 [expr.prim.this]), if name lookup (6.5 [basic.lookup]) resolves the name in the id-expression to a non-static non-type member of some class C, and if either the id-expression is potentially evaluated or C is X or a base class of X, the id-expression is transformed into a class member access expression (7.6.1.5 [expr.ref]) using (*this) (_N4868_.11.4.3.2 [class.this]) as the postfix-expression to the left of the . operator. [Note: If C is not X or a base class of X, the class member access expression is ill-formed. —end note]Similarly during name lookup, when an unqualified-id (7.5.5.2 [expr.prim.id.unqual]) used in the definition of a member function for class X resolves to a static member, an enumerator or a nested type of class X or of a base class of X, the unqualified-id is transformed into a qualified-id (7.5.5.3 [expr.prim.id.qual]) in which the nested-name-specifier names the class of the member function. These transformations doThis transformation does not apply in the template definition context (13.8.3.2 [temp.dep.type]). [Example:...
Delete 11.4.9 [class.static] paragraph 3:
If an unqualified-id (7.5.5.2 [expr.prim.id.unqual]) is used in the definition of a static member following the member's declarator-id, and name lookup (6.5.3 [basic.lookup.unqual]) finds that the unqualified-id refers to a static member, enumerator, or nested type of the member's class (or of a base class of the member's class), the unqualified-id is transformed into a qualified-id expression in which the nested-name-specifier names the class scope from which the member is referenced. [Note: See 7.5.5 [expr.prim.id] for restrictions on the use of non-static data members and non-static member functions. —end note]
Change 11.5.2 [class.union.anon] paragraph 1 as follows:
A union of the form
union { member-specification } ;
is called an anonymous union; it defines an unnamed type and an unnamed object of that type called an anonymous union object. Each member-declaration in the member-specification of an anonymous union shall either define a non-static data member or be a static_assert-declaration.
[Note:Nested types, anonymous unions, and functionscannotshall not be declared within an anonymous union.—end note]The names of the members...
[Resolved by paper P2002R1, adopted at the February, 2020 meeting.]
According to 11.10.1 [class.compare.default] paragraph 2,
If the class definition does not explicitly declare an == operator function, but declares a defaulted three-way comparison operator function, an == operator function is declared implicitly with the same access as the three-way comparison operator function. The implicitly-declared == operator for a class X is an inline member and is defined as defaulted in the definition of X. If the three-way comparison operator function is declared as a non-static const member, the implicitly-declared == operator function is a member of the form
bool X::operator==(const X&) const;Otherwise, the implicitly-declared == operator function is of the form
friend bool operator==(const X&, const X&);
Paragraph 1 of the section does not preclude declaring both a member and a friend operator<=>, and it is not clear how the operator== should be declared in that case.
[Adopted as a DR at the November, 2019 meeting.]
It is unclear what the constraints are on the type R. We define the "synthesized three-way comparison for comparison category type R", but it's defined in such a way that it works for an arbitrary type R, and the uses of it do not impose a constraint that R is a comparison category type. Should it be permissible to default an operator<=> with some other return type, so long as the construction described in 11.10.3 [class.spaceship] works (specifically, so long as all subobjects have operator<=>s that can be converted to the specified return type)?
Proposed resolution (September, 2019)
Change 11.10.3 [class.spaceship] paragraphs 1-3, changing the running text of paragraph 2 to into a bulleted list, as follows:
The synthesized three-way comparison
for comparison categoryof type R (17.12.2 [cmp.categories]) of glvalues a and b...Let R be the declared return type of a defaulted three-way comparison operator function. Given an expanded list of subobjects for an object x of type C, let Ri be the type of the expression xi <=> xi
is denoted by Ri. If, or void if overload resolutionasapplied toxi <=> xithat expression does not find a usable function, then Ri is void.
If
the declared return type of a defaulted three-way comparison operator functionR is auto, then the return type is deduced as the common comparison type (see below) of R0, R1, ..., Rn-1. If the return type is deduced as void, the operator function is defined as deleted.
If the declared return type of a defaulted three-way comparison operator function is R andOtherwise, if the synthesized three-way comparisonfor comparison categoryof type R between any objects xi and xi is not defined or would be ill-formed, the operator function is defined as deleted....until the first index i where the synthesized three-way comparison
for comparison categoryof type R between xi and yi yields...
[Adopted as a DR at the November, 2019 meeting.]
According to 11.10.3 [class.spaceship] paragraph 3,
The return value V of type R of the defaulted three-way comparison operator function with parameters x and y of the same type is determined by comparing corresponding elements xi and yi in the expanded lists of subobjects for x and y (in increasing index order) until the first index i where the synthesized three-way comparison for comparison category type R between xi and yi yields a result value vi where vi != 0, contextually converted to bool, yields true; V is vi converted to R. If no such index exists, V is std::strong_ordering::equal converted to R.
This is meaningless, however, because the kind of conversion is not specified. According to bullet 1.1,
If overload resolution for a <=> b finds a usable function (12.2 [over.match]), static_cast<R>(a <=> b).
so consistency would suggest that static_cast<R>(std::strong_ordering::equal) is probably the right answer.
Proposed resolution (October, 2019):
Change 11.10.3 [class.spaceship] paragraph 3 as follows:
...If no such index exists, V is static_cast<R>(std::strong_ordering::equal)converted to R.
[Adopted as a DR at the November, 2019 meeting.]
(From editorial issue 3235.)
According to 12.2.3 [over.match.viable] bullet 2.3 says,
First, to be a viable function, a candidate function shall have enough parameters to agree in number with the arguments in the list.
...
A candidate function having more than m parameters is viable only if the (m+1)st parameter has a default argument (9.3.4.7 [dcl.fct.default]). [Footnote: According to 9.3.4.7 [dcl.fct.default], parameters following the (m+1)st parameter must also have default arguments. —end footnote] For the purposes of overload resolution, the parameter list is truncated on the right, so that there are exactly m parameters.
However, this is incorrect; 9.3.4.7 [dcl.fct.default] paragraph 4 permits parameter packs to follow parameters with default arguments.
Proposed resolution (October, 2019):
Change 12.2.3 [over.match.viable] bullet 2.3 as follows:
A candidate function having more than m parameters is viable only if
the (m+1)st parameter has a default argument (9.3.4.7 [dcl.fct.default]). [Footnote: According to 9.3.4.7 [dcl.fct.default], parameters following the (m+1)st parameter must also have default arguments. —end footnote]all parameters following the mth have default arguments (9.3.4.7 [dcl.fct.default]). For the purposes of overload resolution, the parameter list is truncated on the right, so that there are exactly m parameters.
[Adopted at the November, 2019 meeting as part of paper P1907R1.]
According to 13.2 [temp.param] bullet 4.2, non-type template parameters of pointer type must be either
pointer to object or pointer to function
This excludes void*, which is an object pointer but not a pointer to object. However, most or all current implementations accept void* as a non-type template parameter.
Notes from the April, 2018 teleconference:
Not all implementations accept a void* template parameter, so this should not be a DR if it is eventually adopted. Furthermore, there is some implementation divergence over the kinds of template arguments that can be passed to a void* template parameter. CWG felt that EWG should weigh in on the desirability and content of this change.
Additional note, February, 2021:
Although this issue was ultimately resolved for C++20 by the adoption of paper P1907R1, it was previously addressed in a different manner by paper P0732R2, adopted at the June, 2018 meeting.
[Adopted at the November, 2019 meeting as part of paper P1907R1.]
The status quo appears to be that pointers-to-members as members of a non-type template parameter of class type are permitted, and are compared with == (which is unsound), whereas pointers-to-members as non-type template parameter are permitted with different semantics (equality only if they denote the same member).
[Adopted as a DR at the November, 2019 meeting.]
According to 13.7.2.3 [temp.deduct.guide] paragraph 1, the syntax of a deduction-guide is:
Instead of explicit, this production should use the explicit-specifier nonterminal. (The wording of 12.2.2.9 [over.match.class.deduct] has references to the explicit-specifier of a deduction guide, for example.)
Proposed resolution (September, 2019):
Change the grammar in 13.7.2.3 [temp.deduct.guide] paragraph 1 as follows:
[Accepted at the February, 2020 (Prague) meeting.]
The tiebreaker based on partial ordering of function templates should presumably operate upon rewritten candidates based on their parameter lists for the purpose of overload resolution (12.2.2 [over.match.funcs]) without substitution of template arguments instead of the function parameter lists of the templates themselves.
It is observed, however, that neither GCC nor Clang performs partial ordering in the manner described above. In the following case, considering the templates with the reordering should yield 1a as being more specialized than 2; however, the wording is not especially clear about this and both GCC and Clang seems to fall past the partial ordering tiebreaker to pick 2 for the case as-is. If 1b is introduced, then it is more specialized than 2 in either ordering, and it is chosen by both GCC and Clang.
template <typename> constexpr bool F = false; template <typename T> struct A { }; template <typename T, typename U> // bool operator==(A<T>, A<U *>); // 1b bool operator==(T, A<U *>); // 1a template <typename T, typename U> bool operator!=(A<T>, U) { // 2 static_assert(F<T>, "Isn't this less specialized?"); return false; } bool f(A<int> ax, A<int *> ay) { return ay != ax; }
Proposed resolution (February, 2020):
Change 13.7.7.3 [temp.func.order] paragraph 3 as follows:
To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs (13.7.4 [temp.variadic]) thereof) synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template. [Note: The type replacing the placeholder in the type of the value synthesized for a non-type template parameter is also a unique synthesized type. —end note]
If only one of theEach functiontemplatestemplate M that is a member function, and that function is a non-static member of some class A, Mis considered to have a new first parameter of type X(M), described below, inserted in its function parameter list.Given cv as theIf exactly one of the function templates was considered by overload resolution via a rewritten candidate (12.2.2.3 [over.match.oper]) with a reversed order of parameters, then the order of the function parameters in its transformed template is reversed. For a function template M with cv-qualifiersof M (if any), the new parametercv that is a member of a class A:
The type X(M) is
of type“rvalue reference to cv A” if the optional ref-qualifier of M is && or if M has no ref-qualifier and thefirstpositionally corresponding parameter of the other transformed template has rvalue reference type; if this determination depends recursively upon whether X(M) is an rvalue reference type, it is not considered to have rvalue reference type.Otherwise,
the new parameterX(M) isof type“lvalue reference to cv A”.[Note: This allows...
[Accepted at the February, 2020 (Prague) meeting.]
The rules for type-dependency do not appear to take the bool type associated with concept-ids into account. For example:
template <typename T> concept C = true; template <typename T> struct A; template <> struct A<bool> { using type = bool; }; template <typename T> void f(A<decltype(C<T>)>::type); // error: needs typename to avoid vexing parse
Proposed resolution (February, 2020):
Change 13.8.3.3 [temp.dep.expr] paragraph 3 as follows:
An id-expression is type-dependent if it is not a concept-id and it contains
...
Change 13.8.3.4 [temp.dep.constexpr] paragraph 3 as follows:
An id-expression is value-dependent if:
it is a concept-id and any of its arguments are dependent,
it is type-dependent,
...
[Accepted as a DR at the November, 2019 meeting.]
According to 13.9.4 [temp.expl.spec] paragraph 13,
An explicit specialization of a function or variable template is inline only if it is declared with the inline specifier or defined as deleted, and independently of whether its function or variable template is inline.
The current wording does not specify the status of explicit specializations of constexpr and consteval function templates and members of class templates. Should a similar rule apply in those cases?
Proposed resolution (November, 2019):
Change 13.9.4 [temp.expl.spec] paragraph 14 as follows:
An explicit specialization of a function or variable template is inline only if it is declared with the inline specifier or defined as deleted, and independently of whether its function or variable template is inline.The inline, constexpr, and consteval properties of an explicit specialization of a function or variable template are determined by the explicit specialization and are independent of those properties of the template. [Example:...
[Adopted as a DR at the November, 2019 meeting.]
Consider the following example:
template<typename T> struct A { private: ~A() {} }; A<char> g(); A<char> f() { return g(); }
According to 14.3 [except.ctor] paragraph 2,
If an exception is thrown during the destruction of temporaries or local variables for a return statement (8.7.4 [stmt.return]), the destructor for the returned object (if any) is also invoked.
In f() there is no possibility of an exception occuring during the processing of the return statement, so there appears to be no reason for a reference to the private destructor of A<char>. Current implementations, however, issue an access error for this example. Is wording needed to make that destructor potentially invoked in such cases?
Proposed resolution (September, 2019):
Change 8.7.4 [stmt.return] paragraph 2 as follows:
...[Note: A return statement can involve an invocation of a constructor to perform a copy or move of the operand if it is not a prvalue or if its type differs from the return type of the function. A copy operation associated with a return statement may be elided or converted to a move operation if an automatic storage duration variable is returned (11.9.6 [class.copy.elision]). —end note] [Example:
std::pair<std::string,int> f(const char* p, int x) { return {p,x}; }—end example] The destructor for the returned object is potentially invoked (11.4.7 [class.dtor], 14.3 [except.ctor]). [Example:class A { ~A() {} }; A f() { return A(); } // error: destructor of A is private (even though it is never invoked)—end example] Flowing off the end...
Change 11.4.7 [class.dtor] paragraph 14 as follows:
A destructor can also be invoked explicitly. A destructor is potentially invoked if it is invoked or as specified in 7.6.2.8 [expr.new], 8.7.4 [stmt.return], 9.5.2 [dcl.init.aggr], 11.9.3 [class.base.init], and 14.2 [except.throw]. A program is ill-formed if a destructor that is potentially invoked is deleted or not accessible from the context of the invocation.
According to Clause Annex B [implimits] paragraph 1,
Because computers are finite, C++ implementations are inevitably limited in the size of the programs they can successfully process. Every implementation shall document those limitations where known.
Because Annex Clause Annex B [implimits] is informative, not normative, it should not use “shall.”
Additional notes (May, 2024)
The Annex was editorially switched from "informative" to "normative" in September 2020 with this change description:
[intro.compliance.general, implimits] Cite Annex B normatively.
This change also promotes Annex B [implimits] to a "normative" annex. The existing wording in the annex is already normative in character.
Discussion about the intended normative contents of Annex B is pursued in issue 2891.
[Accepted as a DR at the February, 2023 meeting.]
According to 3.53 [defns.signature.templ], the signature of a function template includes:
name, parameter-type-list, enclosing namespace, return type, template-head, and trailing requires-clause (if any)
The mention of the template-head is a change from previous versions of the Standard, which referred to the “template parameter list”, in order to include the requires-clause in the signature. However, template-head is a syntactic nonterminal and thus includes everything in that production, including default template arguments. It seems undesirable to make the default arguments part of the template signature.
CWG 2022-11-11
CWG agrees with the suggested direction.
Proposed resolution (approved by CWG 2023-02-09):
Change in 3.52 [defns.signature.friend] as follows:
signature
<function template>
name, parameter-type-list, enclosing namespace, return type, signature of the template-head, and trailing requires-clause (if any)
Change in 3.53 [defns.signature.templ] as follows:
signature
<friend function template with constraint involving enclosing template parameters>
name, parameter-type-list, return type, enclosing class, signature of the template-head, and trailing requires-clause (if any)
Change in 3.56 [defns.signature.member] as follows:
signature
<class member function template>
name, parameter-type-list, class of which the function is a member, cv-qualifiers (if any), ref-qualifier (if any), return type (if any), signature of the template-head, and trailing requires-clause (if any)
Add a new section in Clause 3 [intro.defs] as follows:
signature
<template-head>
template parameter list, excluding template parameter names and default arguments, and requires-clause (if any)
[Accepted as a DR at the February, 2023 meeting.]
According to 4.1.1 [intro.compliance.general] bullet 2.2,
a conforming implementation shall issue at least one diagnostic message
for an ill-formed program that is not “no diagnostic required.” The relationship between this diagnostic message and the ones required by the #error and #warning preprocessor directives is not clear. For example, is an implementation required to emit multiple diagnostics if multiple #error directives are encountered, even though conformance requires only one? Could an implementation count the diagnostic emitted by #warning as satisfying the requirement for an ill-formed program?
See also issue 745.
Suggested resolution [SUPERSEDED]:
Add a new paragraph after 4.1.1 [intro.compliance.general] paragraph 2 as follows:
Although this document states only requirements on C++ implementations, those requirements are often easier to understand if they are phrased as requirements on programs, parts of programs, or execution of programs. Such requirements have the following meaning:
- If a program contains no violations of the rules in Clause Clause 5 [lex] through Clause Clause 32 [thread] and Annex D, a conforming implementation shall, within its resource limits as described in Annex B, accept and correctly execute [ Footnote: ... ] that program.
- If a program contains a violation of any diagnosable rule or an occurrence of a construct described in this document as “conditionally-supported” when the implementation does not support that construct, a conforming implementation shall issue at least one diagnostic message.
- If a program contains a violation of a rule for which no diagnostic is required, this document places no requirement on implementations with respect to that program.
[Note: ... — end note]
Furthermore, a conforming implementation
- shall not accept a translation unit containing a #error preprocessing directive (15.9 [cpp.error]) unless it is part of a group skipped by conditional inclusion (15.2 [cpp.cond]) and
- shall issue at least one diagnostic message for each #warning or #error preprocessing directive not following a #error preprocessing directive, ignoring #warning and #error preprocessing directives appearing in groups skipped by conditional inclusion.
For classes and class templates, ...
Notes from the November, 2022 meeting
EWG review solicited via cplusplus/papers#1366.
EWG 2023-02-06
EWG agreed with the suggested resolution, but resolved to amend it to treat static_assert similarly.
Proposed resolution (February, 2023) [SUPERSEDED]:
Change and add a new paragraph after 4.1.1 [intro.compliance.general] paragraph 2 as follows:
Although this document states only requirements on C++ implementations, those requirements are often easier to understand if they are phrased as requirements on programs, parts of programs, or execution of programs. Such requirements have the following meaning:
- If a program contains no violations of the rules in Clause Clause 5 [lex] through Clause Clause 32 [thread] and Annex D, a conforming implementation shall, within its resource limits as described in Annex B, accept and correctly execute [ Footnote: ... ] that program.
- If a program contains a violation of a rule for which no diagnostic is required, this document places no requirement on implementations with respect to that program.
- Otherwise, if
Ifa program contains a violation of any diagnosable rule or an occurrence of a construct described in this document as “conditionally-supported” when the implementation does not support that construct, a conforming implementation shall issue at least one diagnostic message.If a program contains a violation of a rule for which no diagnostic is required, this document places no requirement on implementations with respect to that program.[Note: During template argument deduction and substitution, certain constructs that in other contexts require a diagnostic are treated differently; see [temp.deduct] — end note]Furthermore, a conforming implementation
- shall not accept a preprocessing translation unit containing a #error preprocessing directive (15.9 [cpp.error]),
- shall issue at least one diagnostic message for each #warning or #error preprocessing directive not following a #error preprocessing directive in a preprocessing translation unit, and
- shall not accept a translation unit with a failed static_assert-declaration (9.1 [dcl.pre]) outside an uninstantiated template.
For classes and class templates, ...
Change in 5.1 [lex.separate] paragraph 1 as follows:
The text of the program is kept in units called source files in this document. A source file together with all the headers (16.4.2.3 [headers]) and source files included (15.3 [cpp.include]) via the preprocessing directive #include, less any source lines skipped by any of the conditional inclusion (15.2 [cpp.cond]) preprocessing directives, is called a preprocessing translation unit.
Change in 5.2 [lex.phases] paragraph 7 as follows:
... The resulting tokens constitute a translation unit and are syntactically and semantically analyzed and translatedas a translation unit.
Change in 9.1 [dcl.pre] paragraph 10 as follows:
In a static_assert-declaration, the constant-expression is contextually converted to bool and the converted expression shall be a constant expression (7.7 [expr.const]). If the value of the expression when so converted is true, the declaration has no effect. Otherwise, the static_assert-declaration has failed, the program is ill-formed, and the resulting diagnostic message (4.1 [intro.compliance]) should include the text of the string-literal, if one is supplied.
CWG 2023-02-10
CWG decided to fold P2593R1 (static_assert(false)) into the proposed resolution.
Proposed resolution (approved by CWG 2023-02-10):
Change and add a new paragraph after 4.1.1 [intro.compliance.general] paragraph 2 as follows:
Although this document states only requirements on C++ implementations, those requirements are often easier to understand if they are phrased as requirements on programs, parts of programs, or execution of programs. Such requirements have the following meaning:
- If a program contains no violations of the rules in Clause Clause 5 [lex] through Clause Clause 32 [thread] and Annex D, a conforming implementation shall, within its resource limits as described in Annex B, accept and correctly execute [ Footnote: ... ] that program.
- If a program contains a violation of a rule for which no diagnostic is required, this document places no requirement on implementations with respect to that program.
- Otherwise, if
Ifa program contains a violation of any diagnosable rule or an occurrence of a construct described in this document as “conditionally-supported” when the implementation does not support that construct, a conforming implementation shall issue at least one diagnostic message.If a program contains a violation of a rule for which no diagnostic is required, this document places no requirement on implementations with respect to that program.[Note: During template argument deduction and substitution, certain constructs that in other contexts require a diagnostic are treated differently; see [temp.deduct] — end note]Furthermore, a conforming implementation
- shall not accept a preprocessing translation unit containing a #error preprocessing directive (15.9 [cpp.error]),
- shall issue at least one diagnostic message for each #warning or #error preprocessing directive not following a #error preprocessing directive in a preprocessing translation unit, and
- shall not accept a translation unit with a static_assert-declaration that fails (9.1 [dcl.pre]).
For classes and class templates, ...
Change in 5.1 [lex.separate] paragraph 1 as follows:
The text of the program is kept in units called source files in this document. A source file together with all the headers (16.4.2.3 [headers]) and source files included (15.3 [cpp.include]) via the preprocessing directive #include, less any source lines skipped by any of the conditional inclusion (15.2 [cpp.cond]) preprocessing directives, is called a preprocessing translation unit.
Change in 5.2 [lex.phases] paragraph 7 as follows:
... The resulting tokens constitute a translation unit and are syntactically and semantically analyzed and translatedas a translation unit.
Change in 9.1 [dcl.pre] paragraph 10 as follows:
In a static_assert-declaration, the constant-expression is contextually converted to bool and the converted expression shall be a constant expression (7.7 [expr.const]). If the value of the expression when so converted is true or the expression is evaluated in the context of a template definition, the declaration has no effect. Otherwise, the static_assert-declaration fails, the program is ill-formed, and the resulting diagnostic message (4.1 [intro.compliance]) should include the text of the string-literal, if one is supplied. [ Example:static_assert(sizeof(int) == sizeof(void*), "wrong pointer size"); static_assert(sizeof(int[2])); // OK, narrowing allowed-- end example ]template <class T> void f(T t) { if constexpr (sizeof(T) == sizeof(int)) { use(t); } else { static_assert(false, "must be int-sized"); } } void g(char c) { f(0); // OK f(c); // error: must be int-sized }
Change in 13.8 [temp.res] paragraph 6 as follows:
... The program is ill-formed, no diagnostic required, if:
- no valid specialization, ignoring static_assert-declarations that fail, can be generated for a template or a substatement of a constexpr if statement (8.5.2 [stmt.if]) within a template and the template is not instantiated, or
- ...
[Accepted at the November, 2022 meeting.]
Translation phases 2 and 3 assume that lines are terminated by "new-line characters". However, the current specification of phase 1 does not guarantee that to be true. In particular, for a UTF-8 file the verbatim sequence of source file characters forms the input for phase 2, even on systems where the line terminator is a carriage return. The non-UTF-8 specification is also defective in that it speaks of "introducing" new-line characters, even for encodings like Latin-1 where new-lines might already be present and no "introduction" is needed or appropriate.
Proposed resolution [SUPERSEDED]:
Change in 5.2 [lex.phases] paragraph 1.1 as follows:
... If an input file is determined to be a UTF-8 file, then it shall be a well-formed UTF-8 code unit sequence and it is decoded to produce a sequence of UCS scalar values that constitutes the sequence of elements of the translation character set, representing each line-termination character or character sequence as a new-line character.
For any other kind of input file supported by the implementation, characters are mapped, in an implementation-defined manner, to a sequence of translation character set elements (5.3.1 [lex.charset]) (
introducing new-line characters forrepresenting end-of-line indicators as new-line characters).
Proposed resolution (approved by CWG 2022-11-08):
Change in 5.2 [lex.phases] paragraph 1.1 as follows:
... If an input file is determined to be a UTF-8 file, then it shall be a well-formed UTF-8 code unit sequence and it is decoded to produce a sequence of UCS scalar values that constitutes the sequence of elements of the translation character set. In the resulting sequence, each pair of characters in the input sequence consisting of U+000D CARRIAGE RETURN followed by U+000A LINE FEED, as well as each U+000D CARRIAGE RETURN not immediately followed by a U+000A LINE FEED, is replaced by a single new-line character.
For any other kind of input file supported by the implementation, characters are mapped, in an implementation-defined manner, to a sequence of translation character set elements (5.3.1 [lex.charset])
(introducing new-line characters for, representing end-of-line indicators as new-line characters).
[Accepted at the November, 2022 meeting.]
The n-char grammar term is defined to match only the Latin uppercase, Latin digit, hyphen and space characters. This results in \N{ABC} matching named-universal-character while \N{abc} does not. This leads to programs like the following being unexpectedly well-formed because the \N{abc} sequence is lexed as the preprocessing token sequence , N, {, abc, }. The expansion of macro a then leads to the token sequence being passed as an argument to macro z where it is discarded.
#define z(x) 0 #define a z( int x = a\N{abc});
Changes to make the above program ill-formed would provide two benefits:
Proposed resolution (approved by CWG 2022-11-07):
Change the grammar in 5.3.1 [lex.charset] paragraph 3 as follows:
n-char:A B C D E F G H I J K L M N O P Q R S T U V W X Y Z0 1 2 3 4 5 6 7 8 9U+002d hyphen-minusU+0020 spaceany member of the translation character set except the U+007D RIGHT CURLY BRACKET or new-line character
[Accepted as a DR at the November, 2022 meeting.]
Subclause 7.5.2 [expr.prim.literal] paragraph 1 specifies:
... A string-literal is an lvalue designating a corresponding string literal object (5.13.5 [lex.string]), a user-defined-literal has the same value category as the corresponding operator call expression described in 5.13.9 [lex.ext], and any other literal is a prvalue.
Yet, there is redundant specification in 5.13.2 [lex.icon] paragraph 3:
The type of an integer-literal is the first type in the list in Table 9 corresponding to its optional integer-suffix in which its value can be represented. An integer-literal is a prvalue.
And in 5.13.7 [lex.bool] paragraph 1:
The Boolean literals are the keywords false and true. Such literals are prvalues and have type bool.
And in 5.13.8 [lex.nullptr] paragraph 1:
The pointer literal is the keyword nullptr. It is a prvalue of type std::nullptr_t.
Proposed resolution (approved by CWG 2022-11-10):
Change in 5.13.1 [lex.literal.kinds] paragraph 1 as follows:
There are several kinds of literals. [ Footnote: ... ]literal: integer-literal character-literal floating-point-literal string-literal boolean-literal pointer-literal user-defined-literal[ Note: When appearing as an expression, a literal has a type and a value category (7.5.2 [expr.prim.literal]). -- end note ]
Change in 5.13.2 [lex.icon] paragraph 3 as follows:
The type of an integer-literal is the first type in the list in Table 9 corresponding to its optional integer-suffix in which its value can be represented.An integer-literal is a prvalue.
Change in 5.13.7 [lex.bool] paragraph 1 as follows:
The Boolean literals are the keywords false and true. Such literalsare prvalues andhave type bool.
Change in 5.13.8 [lex.nullptr] paragraph 1 as follows:
The pointer literal is the keyword nullptr. Itis a prvalue ofhas type std::nullptr_t.
[Accepted at the February, 2023 meeting.]
The lexer grammar production hexadecimal-escape-sequence matches text of the form \x{20}ab due to its recursive definition.
Proposed resolution (approved by CWG 2023-01-27):
Change in 5.13.3 [lex.ccon] as follows:
hexadecimal-escape-sequence : \xhexadecimal-digitsimple-hexadecimal-digit-sequencehexadecimal-escape-sequence hexadecimal-digit\x{ simple-hexadecimal-digit-sequence }
[Accepted as a DR at the November, 2022 meeting.]
Consider the following example:
// tu1.cpp extern const int a = 1; inline auto f() { static const int b = a; struct A { auto operator()() { return &b; } } a; return a; } // tu2.cpp extern const int a; inline auto f() { static const int b = a; struct A { auto operator()() { return &b; } } a; return a; } int main() { return *decltype(f())()(); }
Here, b may or may not have constant initialization. This example should be an ODR violation.
(Split off from issue 2123.)
Proposed resolution (approved by CWG 2022-11-11):
Insert after 6.3 [basic.def.odr] bullet 14.7 as follows:
[Accepted as a DR at the February, 2023 meeting.]
Issue 2494 specified a list of definable items and required that no translation unit contain more than one definition of any of those items. However, the list omits enumeration constants, implicitly allowing an example like:
enum E { e, e };
According to 6.1 [basic.pre] paragraph 3, an enumerator is an entity. According to 6.2 [basic.def] paragraph 2,
Each entity declared by a declaration is also defined by that declaration unless: ...
and enumerators are not on the list of excluded cases, so an enumerator-definition is a definition. Furthermore, 6.6 [basic.link] paragraph 8 says,
Two declarations of entities declare the same entity if, considering declarations of unnamed types to introduce their names for linkage purposes, if any (9.2.4 [dcl.typedef], 9.8.1 [dcl.enum]), they correspond (6.4.1 [basic.scope.scope]), have the same target scope that is not a function or template parameter scope, and either
they appear in the same translation unit, or
...
In the example above, both enumerators thus define the same entity, so the one-definition rule is responsible for excluding the duplicate definitions but does not do so.
Suggested resolution [SUPERSEDED]:
Change 6.3 [basic.def.odr] paragraph 1 as follows:Each of the following is termed a definable item:
Proposed resolution (approved by CWG 2022-12-02):
Change in 9.8.1 [dcl.enum] paragraph 2 as follows:
... The identifiers in an enumerator-list are declared as constants, and can appear wherever constants are required. The same identifier shall not appear as the name of multiple enumerators in an enumerator-list. An enumerator-definition with = gives the associated enumerator the value indicated by the constant-expression. If the first enumerator has no initializer, the value of the corresponding constant is zero. An enumerator-definition without an initializer gives the enumerator the value obtained by increasing the value of the previous enumerator by one.
[Accepted as a DR at the February, 2023 meeting.]
Consider:
#include <source_location> inline char *f() { static char array[std::source_location::current().line()]; return array; }
The sequence of tokens comprising the definition of f can appear in multiple translation units, on different lines. The one-definition rule is not violated. Thus, there is a single function f in the program with a unique static local object array, but that object would have a different type in each translation unit. It is unclear how to implement this, absent the conservative approach of always returning a value-initialized object from std::source_location::current, which would defeat its purpose.
Possible approaches to resolve this issue might include:
2023-01-08
Forwarded to LWG / LEWG via cplusplus/papers#1416, by decision of the CWG chair.
EWG 2023-02-07
EWG approves of the approach for CWG2678 of changing the ODR to make use of source_location in a way that causes an inline function/function template/etc to 'be different' be an ODR violation.
Proposed resolution (approved by CWG 2023-02-08):
Add a new bullet after 6.3 [basic.def.odr] bullet 14.8 as follows:
- In each such definition, const objects with static or thread storage duration shall be constant-initialized if the object is constant-initialized in any such definition.
- In each such definition, corresponding manifestly constant-evaluated expressions that are not value-dependent shall have the same value (7.7 [expr.const], 13.8.3.4 [temp.dep.constexpr]).
- In each such definition, the overloaded operators referred to, the implicit calls to conversion functions, constructors, operator new functions and operator delete functions, shall refer to the same function.
[Accepted as a DR at the February, 2023 meeting.]
According to 6.4.2 [basic.scope.pdecl] paragraph 3,
The locus of an enum-specifier or opaque-enum-declaration is immediately after the identifier (if any) in it (9.8.1 [dcl.enum]).
Equivalent wording has been present for a very long time; see, for instance, issue 1482. However, most or all implementations reject the example from that issue:
template<typename T> struct S { typedef char I; }; enum E: S<E>::I { e }; // Implementations say E is undeclared in S<E>
In addition to recognizing current implementation practice, it would be practically useful if the locus were specified instead as after the enum-head or complete opaque-enum-declaration, as it would allow use of SFINAE in std::is_scoped_enum to distinguish between scoped and unscoped enumerations rather than requiring special compiler support.
CWG 2022-11-11
Move the locus to immediately after the enum-head.
Proposed resolution (approved by CWG 2023-02-06):
The locus of an enum-specifieror opaque-enum-declarationis immediately afterthe identifier (if any) in itits enum-head; the locus of an opaque-enum-declaration is immediately after it (9.8.1 [dcl.enum]).
[Accepted as a DR at the February, 2023 meeting.]
P2720R0 comment US 7-035T and C are used inconsistently throughout these paragraphs.
Proposed resolution [SUPERSEDED]:
Change in 6.5.2 [class.member.lookup] paragraph 1 as follows:
A search in a scope X for a name N from a program point P is a single search in X for N from P unless X is the scope of a class or class templateTC, in which case the following steps define the result of the search.
Change in 6.5.2 [class.member.lookup] paragraph 4 as follows:
[Note 2: IfTC is incomplete, only base classes whose base-specifier appears before P are considered. IfTC is an instantiated class, its base classes are not dependent. —end note]
Change in 6.5.2 [class.member.lookup] paragraph 6 as follows:
The result of the search is the declaration set of S(N,TC). If it is an invalid set, the program is ill-formed. If it differs from the result of a search inTC for N in a complete-class context (11.4 [class.mem]) ofTC, the program is ill-formed, no diagnostic required.
Change in 6.5.2 [class.member.lookup] paragraph 7 as follows:
If N is a non-dependent conversion-function-id, conversion function templates that are members ofTC are considered. For each such template F, the lookup set S(t,TC) is constructed, considering a function template declaration to have the name t only if it corresponds to a declaration of F (6.4.1 [basic.scope.scope]).
Change in 6.5.2 [class.member.lookup] paragraph 8 as follows:
[Note 4: A static member, a nested type or an enumerator defined in a base classTB can unambiguously be found even if an object has more than one base class subobject of typeTB. Two base class subobjects share the non-static member subobjects of their common virtual base classes. —end note]
CWG 2022-12-02
The resolution proposed above is incorrect: T is the parameter for the overall search and C is the parameter for the S(N,C) construction. Highlight that fact in paragraph 2.
Proposed resolution (approved by CWG 2023-01-06):
Change in 6.5.2 [class.member.lookup] paragraph 1 as follows:
A search in a scope X for a nameNM from a program point P is a single search in X forNM from P unless X is the scope of a class or class template T, in which case the following steps define the result of the search. [Note 1: The result differs only ifNM is a conversion-function-id or if the single search would find nothing. —end note]
Change in 6.5.2 [class.member.lookup] paragraph 2 as follows:
The lookup set for a name N in a class or class template C, called S(N, C), consists of two component sets: the declaration set, a set of members named N ; and the subobject set, a set of subobjects where declarations of these members were found (possibly via using-declarations). In the declaration set, type declarations (including injected-class-names) are replaced by the types they designate. S(N, C) is calculated as follows:
Change in 6.5.2 [class.member.lookup] paragraph 4 as follows:
... [Note 2: IfTC is incomplete, only base classes whose base-specifier appears before P are considered. IfTC is an instantiated class, its base classes are not dependent. —end note]
Change in 6.5.2 [class.member.lookup] paragraph 6 as follows:
The result of the search is the declaration set of S(NM, T). If it is an invalid set, the program is ill-formed. If it differs from the result of a search in T forNM in a complete-class context (11.4 [class.mem]) of T , the program is ill-formed, no diagnostic required.
Change in 6.5.2 [class.member.lookup] paragraph 7 as follows:
IfNM is a non-dependent conversion-function-id, conversion function templates that are members of T are considered. For each such template F, the lookup set S(t, T) is constructed, considering a function template declaration to have the name t only if it corresponds to a declaration of F (6.4.1 [basic.scope.scope]). The members of the declaration set of each such lookup set, which shall not be an invalid set, are included in the result.
[Accepted as a DR at the February, 2023 meeting.]
According to 6.7.2 [intro.object] paragraph 3,
If a complete object is created (7.6.2.8 [expr.new]) in storage associated with another object e of type “array of N unsigned char” or of type “array of N std::byte” (17.2.1 [cstddef.syn]), that array provides storage for the created object if...
However, note 4 in paragraph 13 indicates that a char array can also provide storage:
An operation that begins the lifetime of an array of char, unsigned char, or std::byte implicitly creates objects within the region of storage occupied by the array.
[Note 4: The array object provides storage for these objects. —end note]
The normative text and the note should be reconciled.
Proposed resolution (approved by CWG 2023-02-09):
Change in 6.7.2 [intro.object] paragraph 13 as follows:
An operation that begins the lifetime of an array ofchar,unsigned char,or std::byte implicitly creates objects within the region of storage occupied by the array.
[Accepted as a DR at the November, 2022 meeting.]
Consider:
struct S {}; int main() { S* p = new S; p->~S(); delete p; }
This code appears to be allowed per 6.7.4 [basic.life] bullet 6.1:
The program has undefined behavior if:
- the object will be or was of a class type with a non-trivial destructor and the pointer is used as the operand of a delete-expression,
- ...
However, this calls the (trivial) destructor on *p twice. Invoking a non-static member function of an out-of-lifetime object is generally undefined behavior per 6.7.4 [basic.life] bullet 6.2 and 6.7.4 [basic.life] bullet 7.2. The rules ought to be consistent.
Proposed resolution (approved by CWG 2022-10-07):
Change in 6.7.4 [basic.life] bullet 6.1 as follows:
The program has undefined behavior if:
the object will be or was of a class type with a non-trivial destructor andthe pointer is used as the operand of a delete-expression,- ...
[Accepted at the November, 2022 meeting as part of paper P2718R0.]
Temporaries created in the expression of the range-based for statement are not given special treatment, so they only persist to the end of the expression. This can lead to undefined behavior as __range and the iterators are used in the expansion of the statement. Such temporaries should have their lifetimes extended until the end of the statement.
Rationale (October, 2009):
In the expansion, expression is used to initialize a reference. If expression is a temporary, its lifetime is thus extended to that of the reference, which is the entire for statement.
Additional notes, February, 2017:
Posting from Daniel Frey to the std-discussion group:
Some people have tried
namespace detail { template< class C > struct reverse_range { explicit reverse_range (C& _c) : c(_c) {} auto begin () { using std::rbegin; return rbegin(c); } auto end () { using std::rend; return rend(c); } private: C& c; }; } template< class C > auto reverse (C& c) { return detail::reverse_range<C>{c}; }
In an attempt to allow:
// some function std::vector<int> foo(); // correct usage auto v = foo(); for( auto i : reverse(v) ) { std::cout << i << std::endl; } // problematic usage for( auto i : reverse(foo()) ) { std::cout << i << std::endl; }
The problem is that the temporary returned by foo() is destructed before the loop starts executing. [This issue] was supposed to be about that, but considers only the top-level temporary.
It might be reasonable to make the range-based for treat the for-range-initializer like a function argument: all temporaries of the expression should be destructed after the execution of the loop. This also removes the only place where binding a reference to a temporary extends its lifetime implicitly, unseen by the user.
Notes from the February, 2017 meeting:
CWG was inclined to accept the suggested change but felt that EWG involvement was necessary prior to such a decision.
[Accepted as a DR at the November, 2022 meeting.]
According to 6.8.1 [basic.types.general] paragraph 10, a type is a literal type only if it satisfies the following:
A type is a literal type if it is:[Note 4: A literal type is one for which it might be possible to create an object within a constant expression. ... —end note]
- ...
- a possibly cv-qualified class type (Clause 11 [class]) that has all of the following properties:
- it has a constexpr destructor (9.2.6 [dcl.constexpr]),
- it is either a closure type (7.5.6.2 [expr.prim.lambda.closure]), an aggregate type (9.5.2 [dcl.init.aggr]), or has at least one constexpr constructor or constructor template (possibly inherited (9.10 [namespace.udecl]) from a base class) that is not a copy or move constructor,
- if it is a union, at least one of its non-static data members is of non-volatile literal type, and
- if it is not a union, all of its non-static data members and base classes are of non-volatile literal types.
However, the normative rule disagrees with the note. Consider:
struct A { A(); }; union U { A a; constexpr U() {} constexpr ~U() {} };
It is certainly possible to create an object of type U in a constant expression, even though U is not a literal type.
In the suggested resolution, the aggregate type rule is intended to capture the fact that it is not possible for aggregate initialization of a non-empty union to leave no active member (and similarly for each anonymous union member in a non-union union-like class).
Suggested resolution [SUPERSEDED]:
Change in 6.8.1 [basic.types.general] paragraph 10 as follows:
A type is a literal type if it is:
- ...
- a possibly cv-qualified class type (Clause 11 [class]) that has all of the following properties:
- it has a constexpr destructor (9.2.6 [dcl.constexpr]),
- it
iseither
- is a closure type (7.5.6.2 [expr.prim.lambda.closure]),
- is an aggregate type (9.5.2 [dcl.init.aggr]) for which that type (if it is a union) or each of its anonymous union members (otherwise) either has at least one variant member of non-volatile literal type or has no variant members, or
- has at least one constexpr constructor or constructor template (possibly inherited (9.10 [namespace.udecl]) from a base class) that is not a copy or move constructor, and
if it is a union, at least one of its non-static data members is of non-volatile literal type, andif it is not a union,all of its non-static non-variant data members and base classes are of non-volatile literal types.
Proposed resolution (CWG telecon 2022-08-12) [SUPERSEDED]:
Change in 6.8.1 [basic.types.general] paragraph 10 as follows:
A type is a literal type if it is:
- ...
- a possibly cv-qualified class type (Clause 11 [class]) that has all of the following properties:
- it has a constexpr destructor (9.2.6 [dcl.constexpr]),
- all of its non-static non-variant data members and base classes are of non-volatile literal types, and
- it
iseither
- is a closure type (7.5.6.2 [expr.prim.lambda.closure]),
- is an aggregate type (9.5.2 [dcl.init.aggr]) for which that type (if it is a union) or each of its anonymous union members (otherwise) either has at least one variant member of non-volatile literal type or has no variant members, or
- has at least one constexpr constructor or constructor template (possibly inherited (9.10 [namespace.udecl]) from a base class) that is not a copy or move constructor
,.if it is a union, at least one of its non-static data members is of non-volatile literal type, andif it is not a union, all of its non-static data members and base classes are of non-volatile literal types.
Proposed resolution (approved by CWG 2022-11-09):
Change 6.8.1 [basic.types.general] paragraph 10 as follows:
A type is a literal type if it is:
...
a possibly cv-qualified class type (Clause 11 [class]) that has all of the following properties:
it has a constexpr destructor (9.2.6 [dcl.constexpr]),
all of its non-static non-variant data members and base classes are of non-volatile literal types, and
it
is either
is a closure type (7.5.6.2 [expr.prim.lambda.closure]),
an aggregate type (9.5.2 [dcl.init.aggr]),is an aggregate union type that has either no variant members or at least one variant member of non-volatile literal type,
is a non-union aggregate type for which each of its anonymous union members satisfies the above requirements for an aggregate union type, or
has at least one constexpr constructor or constructor template (possibly inherited (9.10 [namespace.udecl]) from a base class) that is not a copy or move constructor
,.
if it is a union, at least one of its non-static data members is of non-volatile literal type, and
if it is not a union, all of its non-static data members and base classes are of non-volatile literal types.
[Accepted as a DR at the November, 2022 meeting.]
Subclause 6.8.1 [basic.types.general] paragraph 6 specifies:
... The type of a pointer to array of unknown bound, or of a type defined by a typedef declaration to be an array of unknown bound, cannot be completed.
This is misleading; such a type is already complete.
Proposed resolution:
Change in 6.8.1 [basic.types.general] paragraph 6 as follows:
... [ Note: The type of a pointer or reference to array of unknown bound permanently points to or refers to an incomplete type. An array of unknown bound, or of a type definednamed by a typedef declarationto be an array of unknown bound,permanently refers to an incomplete type. In either case, the array type cannot be completed. -- end note ]
[Accepted as a DR at the February, 2023 meeting.]
(From editorial issue 3953.)
Although an object cannot be defined with a type of cv void, there is nothing preventing a non-defining declaration of an object with that type. Should it be disallowed?
Notes from the December, 2020 teleconference:
Such declarations are permitted in C, so this question was referred to the C liaison for investigation.
CWG 2022-11-11
CWG resolved to making such declarations ill-formed.
Proposed resolution (approved by CWG 2022-12-02; amended 2023-02-06):
Change in 9.1 [dcl.pre] paragraph 7 as follows:
If the decl-specifier-seq contains the typedef specifier, the declaration iscalleda typedef declaration and each declarator-id is declared to be a typedef-name, synonymous with its associated type (9.2.4 [dcl.typedef]). [ Note 4: Such a declarator-id is an identifier (11.4.8.3 [class.conv.fct]). —end note]If the decl-specifier-seq contains no typedef specifier,Otherwise, if the type associated with a declarator-id is a function type (9.3.4.6 [dcl.fct]), the declaration iscalleda function declarationif the type associated with a declarator-id is a function type (9.3.4.6 [dcl.fct]) and. Otherwise, if the type associated with a declarator-id is an object or reference type, the declaration is an object declarationotherwise. Otherwise, the program is ill-formed.[ Example:
int f(), x; // OK, function declaration for f and object declaration for x extern void g(), // OK, function declaration for g y; // error: void is not an object type-- end example ]
[Accepted as a DR at the February, 2023 meeting.]
Consider an example like:
void f(unsigned char i, unsigned ui) { i <=> ui; }
According to 7.6.8 [expr.spaceship] paragraph 4, the usual arithmetic conversions are applied to the operands. According to 7.4 [expr.arith.conv] bullet 1.5, the integral promotions are performed on both operands, resulting in i being converted from unsigned char to int. The operands are then of types int and unsigned int, so bullet 1.5.5 applies, further converting i to type unsigned int.
Unfortunately, that latter conversion, from int to unsigned int, is a narrowing conversion, which runs afoul of 7.6.8 [expr.spaceship] bullet 4.1, which prohibits narrowing conversions other than integral to floating in three-way comparisons.
Suggested resolution [SUPERSEDED]:
Change 7.4 [expr.arith.conv] bullet 1.5 as follows:
Otherwise,
the integral promotions (7.3.7 [conv.prom]) shall be performed on both operandseach operand shall be converted to a common type C. The integral promotion rules (7.3.7 [conv.prom] shall be used to determine a type T1 and type T2 for each operand.50 Then the following rules shall be applied tothe promoted operandsdetermine C:
If
both operands haveT1 and T2 are the same type,no further conversion is neededC shall be that type.Otherwise, if
both operands haveT1 and T2 are both signed integer types or bothhaveare unsigned integer types,the operand with the type of lesser integer conversion rank shall be converted to the type of the operandC shall be the type with greater rank.Otherwise, if the
operand that hasthe type U that is an unsigned integer type has rank greater than or equal to the rank of the other typeof the other operand, the operand with signed integer type shall be converted to the type of the operand with unsigned integer type, C shall be U.Otherwise, if the type
of the operand withS that is a signed integer type can represent all of the values of the other typeof the operand with unsigned integer type, the operand with unsigned integer type shall be converted to the type of the operand with signed integer type, C shall be S.Otherwise,
both operands shall be converted toC shall be the unsigned integer type corresponding to thetype of the operand withsigned integer type.
Proposed resolution (approved by CWG 2023-02-09):
Change in 7.4 [expr.arith.conv] bullet 1.3 as follows, adding sub-bullets:
Otherwise,the integral promotions (7.3.7 [conv.prom]) are performed on both operandseach operand is converted to a common type C. The integral promotion rules (7.3.7 [conv.prom]) are used to determine a type T1 and type T2 for each operand. [ Footnote: ... ] Then the following rules are applied tothe promoted operandsdetermine C:
- If
both operands haveT1 and T2 are the same type,no further conversion is neededC is that type.- Otherwise, if T1 and T2 are both
operands havesigned integer types or are bothhaveunsigned integer types,the operand with the type of lesser integer conversion rank is converted to the type of the operandC is the type with greater rank.- Otherwise,
if the operand that haslet U be the unsigned integer type and S be the signed integer type.
- If U has rank greater than or equal to the rank of
the type of the other operand, the operand with signed integer type is converted to the type of the operand with unsigned integer typeS, C is U.- Otherwise, if
the type of the operand with signed integer typeS can represent all of the values ofthe type of the operand with unsigned integer type, the operand with unsigned integer type is converted to the type of the operand with signed integer typeU, C is S.- Otherwise,
both operands are converted toC is the unsigned integer type corresponding tothe type of the operand with signed integer typeS.
[Accepted as a DR at the November, 2022 meeting.]
The comment in the example in 7.5.6.3 [expr.prim.lambda.capture] paragraph 6 refers to "local variable", but should refer to init-capture instead.
Possible resolution:
Change in 7.5.6.3 [expr.prim.lambda.capture] paragraph 6 as follows:
auto z = [a = 42](int a) { return 1; }; // error: parameter and conceptual local variable have the same name
[Accepted as a DR at the February, 2023 meeting.]
According to 7.5.8.5 [expr.prim.req.nested] paragraph 2,
A local parameter shall only appear as an unevaluated operand (7.2.3 [expr.context]) within the constraint-expression. [Example 2:
template<typename T> concept C = requires (T a) { requires sizeof(a) == 4; // OK requires a == 0; // error: evaluation of a constraint variable };—end example]
However, a can't be used in a constant expression in any event, so the restriction is meaningless, except for ruling out an expression like true ? true : a, but there seems no reason to have a special rule for such a case.
Proposed resolution (approved by CWG 2023-01-06):
Remove 7.5.8.5 [expr.prim.req.nested] paragraph 2, including its example:
A local parameter shall only appear as an unevaluated operand (7.2.3 [expr.context]) within the constraint-expression. [Example 2:template<typename T> concept C = requires (T a) { requires sizeof(a) == 4; // OK requires a == 0; // error: evaluation of a constraint variable };
Additional notes (February, 2023)
After adopting paper P2280, it is no longer accurate that any use of requirement parameters makes an expression non-constant. However, the resolution as adopted makes the treatment of examples like the following uniform in requirements and other constant expression contexts:
template<typename ArrayType> concept LargeArray = requires (ArrayType my_array) { requires my_array.size() > 5; }
[Accepted as a DR at the November, 2022 meeting.]
Subclause 7.6.1.3 [expr.call] paragraph 8 specifies:
The postfix-expression is sequenced before each expression in the expression-list and any default argument. The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter. [Note 8: All side effects of argument evaluations are sequenced before the function is entered (see 6.9.1 [intro.execution]). —end note]
Consider:
f(std::unique_ptr<int>(new int),std::unique_ptr<int>(new int));
It is not clear from the phrasing whether the evaluation of each new int is part of the "initialization of [its] parameter" or whether only the initialization of f's parameters from the completed std::unique_ptr<int> objects is included. The note does not help, since it can be read as distinguishing argument evaluations from initialization.
Suggested resolution [SUPERSEDED]:
Insert before 9.5.1 [dcl.init.general] paragraph 18 as follows:
An initializer-clause followed by an ellipsis is a pack expansion (13.7.4 [temp.variadic]).
Initialization includes the evaluation of all subexpressions of each initializer-clause of the initializer (possibly nested within braced-init-lists).
If the initializer is a parenthesized expression-list, the expressions are evaluated in the order specified for function calls (7.6.1.3 [expr.call]).
Proposed resolution (approved by CWG 2022-08-26):
Insert before 9.5.1 [dcl.init.general] paragraph 18 as follows:
An initializer-clause followed by an ellipsis is a pack expansion (13.7.4 [temp.variadic]).
Initialization includes the evaluation of all subexpressions of each initializer-clause of the initializer (possibly nested within braced-init-lists) and the creation of any temporary objects for function arguments or return values (6.7.7 [class.temporary]).
If the initializer is a parenthesized expression-list, the expressions are evaluated in the order specified for function calls (7.6.1.3 [expr.call]).
[Accepted as a DR at the November, 2022 meeting.]
The phrase "default argument promotions" is apparently unused.
Possible resolution [SUPERSEDED]:
Change in 7.6.1.3 [expr.call] paragraph 12 as follows:
...If the argument has integral or enumeration type that is subject to the integral promotions (7.3.7 [conv.prom]), or a floating-point type that is subject to the floating-point promotion (7.3.8 [conv.fpprom]), the value of the argument is converted to the promoted type before the call.These promotions are referred to as the default argument promotions.
Change in 17.14.2 [cstdarg.syn] as follows:
See also: ISO C 7.16.1.1 7.16.1
CWG 2022-11-10
The phrase is useful and needed to override C's deviating definition, as applicable to va_arg.
Proposed resolution (approved by CWG and LWG 2022-11-11):
Change in 17.14.2 [cstdarg.syn] as follows:
The contents of the header <cstdarg> are the same as the C standard library header <stdarg.h>, with the following changes: In lieu of the default argument promotions specified in ISO C 6.5.2.2, the definition in 7.6.1.3 [expr.call] applies. The restrictions that ISO C places on the second parameter to the va_start macro in header <stdarg.h> are different in this document. ...
[Accepted as a DR at the November, 2022 meeting.]
Subclause 7.6.1.5 [expr.ref] paragraph 6 specifies:
If E2 is declared to have type “reference to T”, then E1.E2 is an lvalue; the type of E1.E2 is T. Otherwise, ...
This does not specifiy which object or functiom the resulting lvalue designates. A similar problem exists with member enumerators:
If E2 is a member enumerator and the type of E2 is T, the expression E1.E2 is a prvalue. The type of E1.E2 is T.
Proposed resolution (approved by CWG 2022-09-23):
Split and change in 7.6.1.5 [expr.ref] paragraph 6 as follows:
If E2 is declared to have type “reference to T”, then E1.E2 is an lvalue
; theof typeof E1.E2 isT. If E2 is a static data member, E1.E2 designates the object or function to which the reference is bound, otherwise E1.E2 designates the object or function to which the corresponding reference member of E1 is bound.Otherwise, ...
Change in 7.6.1.5 [expr.ref] bullet 6.5 as follows:
If E2 is a member enumerator and the type of E2 is T, the expression E1.E2 is a prvalue. Theof typeof E1.E2 isT whose value is the value of the enumerator.
[Accepted as a DR at the November, 2022 meeting.]
Subclause 7.6.2.2 [expr.unary.op] paragraph 10 specifies:
The operand of ~ shall have integral or unscoped enumeration type; the result is the ones' complement of its operand. Integral promotions are performed. The type of the result is the type of the promoted operand. There is an ambiguity in the grammar...
This should be phrased in terms of the base-2 representation similar to bitwise-AND, instead of alluding to some bit representation by using the term "ones' complement".
Proposed resolution (approved by CWG 2022-10-07):
Subclause 7.6.2.2 [expr.unary.op] paragraph 10 specifies:
The operand of ~ shall have integral or unscoped enumeration type; the result is the ones' complement of its operand. Integral promotions are performed. The type of the result is the type of the promoted operand. Given the coefficients xi of the base-2 representation (6.8.2 [basic.fundamental]) of the promoted operand x, the coefficient ri of the base-2 representation of the result r is 1 if xi is 0, and 0 otherwise. There is an ambiguity in the grammar...
[Accepted as a DR at the November, 2022 meeting.]
Consider:
char *p = static_cast<char*>(operator new[](2)); p = new (p) char[2]; // #1 delete[] p; // #2
Subclause 7.6.2.8 [expr.new] paragraph 16 specifies:
... When a new-expression calls an allocation function and that allocation has not been extended, the new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array and the allocation function is not a non-allocating form (17.6.3.4 [new.delete.placement]). ...
Subclause 7.6.2.9 [expr.delete] paragraph 2 specifies:
... In an array delete expression, the value of the operand of delete may be a null pointer value or a pointer value that resulted from a previous array new-expression. [ Footnote: ... ] If not, the behavior is undefined.
The non-allocating form of the new-expression at #1 is constrained not to place an array cookie at the start of the array. Yet, the array delete appears to be expected to divine that fact.
Proposed resolution (approved by CWG 2022-10-07):
Change in 7.6.2.9 [expr.delete] paragraph 2 as follows:
... In an array delete expression, the value of the operand of delete may be a null pointer value or a pointer value that resulted from a previous array new-expression whose allocation function was not a non-allocating form (17.6.3.4 [new.delete.placement]). [ Footnote: ... ] If not, the behavior is undefined.
[Accepted as a DR at the February, 2023 meeting.]
Prior to the adoption of paper N3624 (resolving issue 1512), comparison of void* pointers was explicitly unspecified. The current wording of 7.6.9 [expr.rel], however, describes only comparison of “pointers to objects” (paragraphs 4 and 5), but a pointer to void is not a pointer to an object, only an object pointer type (as opposed to a function pointer type). Formally, that means that comparing void* pointers is undefined behavior, which seems undesirable.
As a related note, there is implementation divergence over whether relational comparisons of void* pointers are accepted in constant expressions (when the void* values are converted from pointers that would otherwise be comparable in constant expressions).
CWG 2022-11-11
Paper N3624 erroneously removed support for void* and function pointer relational comparisons. That ought to be restored.
Proposed resolution (approved by CWG 2023-02-09):
Change in 7.6.9 [expr.rel] paragraph 5 as follows:
... Otherwise, the result of each of the operators is unspecified. [ Note: A relational operator applied to unequal function pointers or to unequal pointers to void yields an unspecified result. -- end note ]
[Accepted as a DR at the November, 2022 meeting.]
Many functions of volatile were deprecated in C++20. In C++23, volatile compound operations were de-deprecated by P2327. The rationale for this de-deprecation is lacking.
Deprecation is not removal. P2327 in C++23 is stopping the change that we began with C++20, mere moments ago if counting by adoption time. It primarily argued that it's an inconvenient change, and that some of the audience would just not cooperate with WG21's indicated direction, so WG21 should compromise the technical consistency of the Standard so they could continue to not cooperate.
The paper did not bring new information on the technical merits of the case, and net the result of applying it was a technically dissonant Standard specification --- a strictly worse specification than C++20. Bitwise compound operations are not special for any technical reason, they are only special because making them special shields some from deprecation diagnostics.
EWG 2022-11-07
Contrary to the direction desired in the NB comment, EWG resolved to un-deprecate all volatile compound assignments.
Proposed resolution (approved by CWG 2022-11-08):
Change in 7.6.19 [expr.assign] paragraph 6 as follows:
The behavior of an expression of the form E1 op= E2 is equivalent to E1 = E1 op E2 except that E1 is evaluated only once. [ Note: The object designated by E1 is accessed twice. -- end note ]Such expressions are deprecated if E1 has volatile-qualified type and op is not one of the bitwise operators |, &, ^; see D.5.
Change in D.4 [depr.volatile.type] paragraph 2 as follows:
brachiosaur += neck; //deprecatedOKbrachiosaur = brachiosaur + neck; // OKbrachiosaur |= neck; // OK, bitwise compound expression
[Accepted as a DR at the November, 2022 meeting.]
According to 7.6.2.8 [expr.new] paragraph 8, if the expression in a noptr-new-declarator is a core constant expression, the program is ill-formed if the expression is erroneous, e.g., negative. However, consider the following example:
template<class T = void> constexpr int f() { T t; return 1; } using _ = decltype(new int[f()]);
f() is a core constant expression, so it must be evaluated to determine its value. However, because the expression appears in an unevaluated operand, it is not “potentially constant evaluated” and thus f is not “needed for constant evaluation”, so the template is not instantiated (13.9.2 [temp.inst] paragraph 7). There is implementation divergence on the handling of this example.
CWG telecon 2022-09-09:
The example should be well-formed, because f is not instantiated.
A similar situation arises for narrowing conversions, except that in the latter case, determining the value at compile-time empowers to allow additional cases, whereas the new-expression case uses a compile-time value to prohibit additional cases.
Proposed resolution (approved by CWG 2022-09-23):
Change in 7.6.2.8 [expr.new] paragraph 8 as follows:
If the expression is erroneous after converting to std::size_t:
- if the expression is a potentially-evaluated core constant expression, the program is ill-formed;
- otherwise, an allocation function is not called; instead...
[Accepted as a DR at the November, 2022 meeting.]
7.7 [expr.const] paragraph 5 attempts to describe allowable allocation/deallocation calls in terms of what could be called “core constant subexpressions,” but the actual definition of a core constant expression in paragraph 4 is in terms of evaluation.
Suggested resolution [SUPERSEDED]:
Replace the entirety of 7.7 [expr.const] paragraph 6 with the following:
For the purposes of determining whether an expressionEis a core constant expression, the evaluation ofa call tothe body of a member function of std::allocator<T> as defined in 20.2.10.2 [allocator.members], where T is a literal type,does not disqualify E from being a core constant expression, even if the actual evaluation of such a call would otherwise fail the requirements for a core constant expressionis ignored. Similarly, the evaluation ofa call tothe body of std::destroy_at, std::ranges::destroy_at, std::construct_at, or std::ranges::construct_at(26.11.8 [specialized.construct]) does not disqualify E from being a core constant expression unless the first argument, of type T*, does not point to storage allocated with std::allocator<T> or to an object whose lifetime began within the evaluation of E, or the evaluation ofis considered to include only the underlying constructorcall disqualifies E from being a core constant expression(for the functions construct_at) or destructor (for the functions destroy_at) call if the first argument (of type T*) points to storage allocated with std::allocator<T>.
CWG telecon 2022-10-21:
The references to destroy_at were removed in an unrelated update to the Working Draft. Also, restore the reference to local objects whose lifetime began within E.
Proposed resolution (approved by CWG 2022-11-10):
Change in 7.7 [expr.const] paragraph 6 as follows:
For the purposes of determining whether an expression E is a core constant expression, the evaluation ofa call tothe body of a member function of std::allocator<T> as defined in 20.2.10.2 [allocator.members], where T is a literal type,does not disqualify E from being a core constant expression, even if the actual evaluation of such a call would otherwise fail the requirements for a core constant expressionis ignored. Similarly, the evaluation ofa call tothe body of std::construct_at or std::ranges::construct_at(26.11.8 [specialized.construct]) does not disqualify E from being a core constant expression unless the first argument, of type T*, does not point to storage allocated with std::allocator<T> or to an object whose lifetime began within the evaluation of E, or the evaluation ofis considered to include only the underlying constructorcall disqualifies E from being a core constant expressioncall if the first argument (of type T*) points to storage allocated with std::allocator<T> or to an object whose lifetime began within the evaluation of E.
[Accepted as a DR at the February, 2023 meeting.]
According to 7.7 [expr.const] bullet 5.8, one criterion that disqualifies an expression from being a core constant expression is:
an operation that would have undefined behavior as specified in Clause 4 [intro] through Clause 15 [cpp]
One potential source of undefined behavior is the omission of a call to a destructor for a constructed object, as described in 6.7.4 [basic.life] paragraph 5:
A program may end the lifetime of an object of class type without invoking the destructor, by reusing or releasing the storage as described above. [Note 3: A delete-expression (7.6.2.9 [expr.delete]) invokes the destructor prior to releasing the storage. —end note] In this case, the destructor is not implicitly invoked and any program that depends on the side effects produced by the destructor has undefined behavior.
For example:
#include <memory>
constexpr int test_basic_life_p5() {
class guard_t {
int &ref_;
public:
explicit constexpr guard_t(int &i) : ref_{i} {}
constexpr ~guard_t() { ref_ = 42; }
};
int result = 0;
auto alloc = std::allocator<guard_t>{};
auto pguard = alloc.allocate(1);
std::construct_at(pguard, result);
// std::destroy_at(pguard);
alloc.deallocate(pguard, 1);
return result; // value depends on destructor execution
}
int main() {
constexpr auto v = test_basic_life_p5();
return v;
}
It is not clear that it is reasonable to require implementations to diagnose this form of undefined behavior in constant expressions.
A somewhat related question is raised by the restrictions on the use of longjmp in 17.14.3 [csetjmp.syn] paragraph 2:
A setjmp/longjmp call pair has undefined behavior if replacing the setjmp and longjmp by catch and throw would invoke any non-trivial destructors for any objects with automatic storage duration.
Here the undefined behavior occurs for any non-trivial destructor that is skipped, not just one for which the program depends on its side effects, as in 6.7.4 [basic.life] paragraph 5. Perhaps these two specifications should be harmonized.
Additional notes (April, 2022):
The phrase "[a] program that depends on the side effects" may have these meanings:
The second option would need a fork in the evaluation of constant expressions to determine whether undefined behavior occurs.
Proposed resolution (approved by CWG 2022-11-11):
Change in 6.7.4 [basic.life] paragraph 5 as follows:
A program may end the lifetime of an object of class type without invoking the destructor, by reusing or releasing the storage as described above. [Note 3: A delete-expression (7.6.2.9 [expr.delete]) invokes the destructor prior to releasing the storage. —end note] In this case, the destructor is not implicitly invokedand any program that depends on the side effects produced by the destructor has undefined behavior. [Note: The correct behavior of a program often depends on the destructor being invoked for each object of class type. -- end note]
[Accepted as a DR at the February, 2023 meeting.]
According to 9.2.6 [dcl.constexpr] paragraph 10, a constexpr variable must have constant destruction. However, 7.7 [expr.const] paragraph 7 only defines constant destruction for objects, not for references. Presumably constexpr references should also be able to have constant destruction, and any temporary object to which such a reference is bound should also be required to have constant destruction.
Proposed resolution (approved by CWG 2022-12-02):
Change in 9.2.6 [dcl.constexpr] paragraph 7 as follows:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression (7.7 [expr.const]). A constexpr variable that is an object, as well as any temporary to which a constexpr reference is bound, shall have constant destruction. [ Example: ... ]
[Accepted as a DR at the February, 2023 meeting.]
Consider:
struct A {
int n;
consteval A() {}
};
constexpr A a; // implementations reject
Paper P1331R2 (Permitting trivial default initialization in constexpr contexts) dropped the restriction that immediate invocations cannot yield results with some subobjects left uninitialized. It is unclear whether that change was intentional or accidental.
Furthermore, indeterminate values of pointer type are currently not permitted as the result of a constant expression per 7.7 [expr.const] bullet 12.2; indeterminate values of scalar types are permitted only due to the absence of a restriction.
This issue is closely related to issue 2536.
2022-12-03
Forwarded to EWG with cplusplus/papers#1380.
EWG 2023-01-19
Uninitialized non-variant direct subobjects should not be allowed to appear in the result of a constant expression. There was no consensus in support of the statements "Union types shall be initialized such that they have an active member in the result of a constant expression" and "EWG confirms that padding bits and data belonging to non-active variant members are permitted to have indeterminate values in the static initialization of objects".
Proposed resolution (approved by CWG 2023-01-27):
Change in 7.7 [expr.const] paragraph 12 as follows:
A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints:
- if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a constant expression,
- if the value is an object of scalar type, it does not have indeterminate value (6.7.5 [basic.indet]),
- ...
- if the value is an object of class or array type, each subobject satisfies these constraints for the value.
[Accepted as a DR at the November, 2022 meeting.]
Consider:
consteval int const_div(int a, int b) { return a / b; } int func(int x = const_div(10, 0));
According to 7.7 [expr.const] paragraph 14:
An expression or conversion is an immediate invocation if it is a potentially-evaluated explicit or implicit invocation of an immediate function and is not in an immediate function context. An immediate invocation shall be a constant expression.
Subclause 9.3.4.7 [dcl.fct.default] paragraph 5 specifies:
The default argument has the same semantic constraints as the initializer in a declaration of a variable of the parameter type, using the copy-initialization semantics (9.5 [dcl.init]). The names in the default argument are looked up, and the semantic constraints are checked, at the point where the default argument appears. Name lookup and checking of semantic constraints for default arguments of templated functions are performed as described in 13.9.2 [temp.inst].
Checking the semantic constraints of the default argument appears to include a check whether the immediate invocation of const_div is actually a constant expression, even though the default argument's value might never actually be used for any function call in the program.
However, instantiation of a consteval function template to be able to perform the constant evaluation is not permitted per 13.9.2 [temp.inst] paragraph 11:
... The use of a template specialization in a default argument shall not cause the template to be implicitly instantiated except that a class template may be instantiated where its complete type is needed to determine the correctness of the default argument. The use of a default argument in a function call causes specializations in the default argument to be implicitly instantiated.
Example 2:
constexpr int g();
consteval int f() {
return g();
}
int k(int x = f()) { // error: constexpr evaluation of undefined function g
return x;
}
constexpr int g() {
return 42;
}
int main() {
return k();
}
Example 3:
#include <source_location> #include <iostream> consteval int const_div(int a, int b) { return a / b; } #line 5 void foo(int x = const_div(1000, std::source_location::current().line() - 15)) { std::cout << x << "\n"; } // Should the definition of `bar` produce errors? (division by zero during constant // evaluation for constraint checking) #line 10 void bar(int x = const_div(1000, std::source_location::current().line() - 10)) { std::cout << x << "\n"; } int main() { // Should this call produce errors? (division by zero during constant // evaluation of the default argument) #line 15 foo(); #line 20 bar(); }
Note that source_location::current() is specified to take its value from the location where it is evaluated, if it appears in a default argument (17.8.2.2 [support.srcloc.cons] paragraph 2):
Remarks: Any call to current that appears as a default member initializer (11.4 [class.mem]), or as a subexpression thereof, should correspond to the location of the constructor definition or aggregate initialization that uses the default member initializer. Any call to current that appears as a default argument (9.3.4.7 [dcl.fct.default]), or as a subexpression thereof, should correspond to the location of the invocation of the function that uses the default argument (7.6.1.3 [expr.call]).
Proposed resolution (September, 2022) [SUPERSEDED]:
Split 9.3.4.7 [dcl.fct.default] paragraph 5 and change as follows:
The default argument has the same semantic constraints as the initializer in a declaration of a variable of the parameter type, using the copy-initialization semantics (9.5 [dcl.init]).
A default argument context is
The names in the default argument are looked up, and the semantic constraints are checked, at the point where the default argument appears, except that an immediate invocation (7.7 [expr.const]) in a default argument context is neither evaluated nor checked for whether it is a constant expression at that point. Name lookup and checking of semantic constraints for default arguments of templated functions are performed as described in 13.9.2 [temp.inst].
- the initializer-clause in a parameter-declaration, including any conversions to the parameter type,
- a subexpression of one of the above that is not a subexpression of a nested unevaluated operand.
Change in 13.9.2 [temp.inst] paragraph 11 as follows:
... The use of a template specialization in a default argument shall not cause the template to be implicitly instantiated except that a class template or a function template with a deduced return type may be instantiated where its complete type is needed to determine the correctness of the default argument. The use of a default argument in a function call causes specializations in the default argument to be implicitly instantiated.
Approved by EWG telecon 2022-09-29.
Amendment to also cover default member initializers (October, 2022) [SUPERSEDED]:
Split 9.3.4.7 [dcl.fct.default] paragraph 5 and change as follows:
The default argument has the same semantic constraints as the initializer in a declaration of a variable of the parameter type, using the copy-initialization semantics (9.5 [dcl.init]).
A default argument context of an initializer is
The names in the default argument are looked up, and the semantic constraints are checked, at the point where the default argument appears, except that an immediate invocation (7.7 [expr.const]) in a default argument context of the initializer-clause in a parameter-declaration is neither evaluated nor checked for whether it is a constant expression at that point. Name lookup and checking of semantic constraints for default arguments of templated functions are performed as described in 13.9.2 [temp.inst].
- the initializer, including any conversions to the target type, or
- a subexpression thereof that is not a subexpression of a nested unevaluated operand.
Change in 11.4.1 [class.mem.general] paragraph 11 as follows:
A brace-or-equal-initializer for a non-static data member specifies a default member initializer for the member, and shall not directly or indirectly cause the implicit definition of a defaulted default constructor for the enclosing class or the exception specification of that constructor. An immediate invocation (7.7 [expr.const]) in a default argument context (9.3.4.7 [dcl.fct.default]) of a default member initializer is neither evaluated nor checked for whether it is a constant expression at the point where it appears.
Change in 13.9.2 [temp.inst] paragraph 11 as follows:
... The use of a template specialization in a default argument shall not cause the template to be implicitly instantiated except that a class template or a function template with a deduced return type may be instantiated where its complete type is needed to determine the correctness of the default argument. The use of a default argument in a function call causes specializations in the default argument to be implicitly instantiated.
CWG telecon 2022-10-07:
The new term should be defined in 6.9.1 [intro.execution] without reference to immediacy.
Possible resolution [SUPERSEDED]:
Change in 6.9.1 [intro.execution] paragraph 4 as follows:
A subexpression of an expression E is an immediate subexpression of E or a subexpression of an immediate subexpression of E. [ Note: ... -- end note ]
The potentially-evaluated subexpressions of an expression, conversion, or initializer E are
- the constituent expressions of E and
- the subexpressions thereof that are not subexpressions of a nested unevaluated operand (7.2.3 [expr.context]).
Change in 7.7 [expr.const] paragraph 16 as follows:
An expression or conversion is potentially constant evaluated if it is:
- ...
- a potentially-evaluated subexpression (6.9.1 [intro.execution]) of one of the above
that is not a subexpression of a nested unevaluated operand (7.2.3 [expr.context]).
Change in 9.3.4.7 [dcl.fct.default] paragraph 5 as follows:
The default argument has the same semantic constraints as the initializer in a declaration of a variable of the parameter type, using the copy-initialization semantics (9.5 [dcl.init]). The names in the default argument are looked up, and the semantic constraints are checked, at the point where the default argument appears, except that an immediate invocation (7.7 [expr.const]) that is a potentially-evaluated subexpression (6.9.1 [intro.execution]) of the initializer-clause in a parameter-declaration is neither evaluated nor checked for whether it is a constant expression at that point. Name lookup and checking of semantic constraints for default arguments of templated functions are performed as described in 13.9.2 [temp.inst].
Add a paragraph before 9.5.1 [dcl.init.general] paragraph 17 as follows:
An immediate invocation (7.7 [expr.const]) that is not evaluated where it appears (9.3.4.7 [dcl.fct.default], 11.4.1 [class.mem.general]) is evaluated and checked for whether it is a constant expression at the point where the enclosing initializer is used in a function call, a constructor definition, or an aggregate initialization.
An initializer-clause followed by an ellipsis is a pack expansion (13.7.4 [temp.variadic]).
Change in 11.4.1 [class.mem.general] paragraph 11 as follows:
A brace-or-equal-initializer for a non-static data member specifies a default member initializer for the member, and shall not directly or indirectly cause the implicit definition of a defaulted default constructor for the enclosing class or the exception specification of that constructor. An immediate invocation (7.7 [expr.const]) that is a potentially-evaluated subexpression (6.9.1 [intro.execution]) of a default member initializer is neither evaluated nor checked for whether it is a constant expression at the point where the subexpression appears.
Change in 13.9.2 [temp.inst] paragraph 11 as follows:
... The use of a template specialization in a default argument or default member initializer shall not cause the template to be implicitly instantiated except that a templated classtemplate, a templated function with a deduced return type, or a variable template with a deduced type may be instantiated where its complete type is needed to determine the correctness of the default argument or default member initializer. The use of a default argument in a function call causes specializations in the default argument to be implicitly instantiated. Similarly, the use of a default member initializer in a constructor definition or an aggregate initialization causes specializations in the default member initializer to be instantiated.
CWG telecon 2022-10-21:
In the above wording, "constituent expression of a conversion" is not a defined term.
Possible resolution [SUPERSEDED]:
Change in 6.9.1 [intro.execution] paragraph 2 as follows:
A constituent expression is defined as follows:
- The constituent expression of an expression is that expression.
- The constituent expression of a conversion is the corresponding implicit function call, if any, or the converted expression otherwise.
- The constituent expressions of a braced-init-list or of a (possibly parenthesized) expression-list are the constituent expressions of the elements of the respective list.
- The constituent expressions of a brace-or-equal-initializer of the form = initializer-clause are the constituent expressions of the initializer-clause.
Change in 6.9.1 [intro.execution] paragraph 4 as follows:
A subexpression of an expression E is an immediate subexpression of E or a subexpression of an immediate subexpression of E. [ Note: ... -- end note ]
The potentially-evaluated subexpressions of an expression, conversion, or initializer E are
- the constituent expressions of E and
- the subexpressions thereof that are not subexpressions of a nested unevaluated operand (7.2.3 [expr.context]).
Change in 7.7 [expr.const] paragraph 16 as follows:
An expression or conversion is potentially constant evaluated if it is:
- ...
- a potentially-evaluated subexpression (6.9.1 [intro.execution]) of one of the above
that is not a subexpression of a nested unevaluated operand (7.2.3 [expr.context]).
Change in 9.3.4.7 [dcl.fct.default] paragraph 5 as follows:
The default argument has the same semantic constraints as the initializer in a declaration of a variable of the parameter type, using the copy-initialization semantics (9.5 [dcl.init]). The names in the default argument are looked up, and the semantic constraints are checked, at the point where the default argument appears, except that an immediate invocation (7.7 [expr.const]) that is a potentially-evaluated subexpression (6.9.1 [intro.execution]) of the initializer-clause in a parameter-declaration is neither evaluated nor checked for whether it is a constant expression at that point. Name lookup and checking of semantic constraints for default arguments of templated functions are performed as described in 13.9.2 [temp.inst].
Add a paragraph before 9.5.1 [dcl.init.general] paragraph 17 as follows:
An immediate invocation (7.7 [expr.const]) that is not evaluated where it appears (9.3.4.7 [dcl.fct.default], 11.4.1 [class.mem.general]) is evaluated and checked for whether it is a constant expression at the point where the enclosing initializer is used in a function call, a constructor definition, or an aggregate initialization.
An initializer-clause followed by an ellipsis is a pack expansion (13.7.4 [temp.variadic]).
Change in 11.4.1 [class.mem.general] paragraph 11 as follows:
A brace-or-equal-initializer for a non-static data member specifies a default member initializer for the member, and shall not directly or indirectly cause the implicit definition of a defaulted default constructor for the enclosing class or the exception specification of that constructor. An immediate invocation (7.7 [expr.const]) that is a potentially-evaluated subexpression (6.9.1 [intro.execution]) of a default member initializer is neither evaluated nor checked for whether it is a constant expression at the point where the subexpression appears.
Change in 13.9.2 [temp.inst] paragraph 11 as follows:
... The use of a template specialization in a default argument or default member initializer shall not cause the template to be implicitly instantiated except that a templated classtemplate, a templated function with a deduced return type, or a variable template with a deduced type may be instantiated where its complete type is needed to determine the correctness of the default argument or default member initializer. The use of a default argument in a function call causes specializations in the default argument to be implicitly instantiated. Similarly, the use of a default member initializer in a constructor definition or an aggregate initialization causes specializations in the default member initializer to be instantiated.
CWG 2022-11-07
Expand requirement to avoid template instantiations in default arguments by not giving a specific, closed list.
Proposed resolution (approved by CWG 2022-11-08):
Change in 6.9.1 [intro.execution] paragraph 2 as follows:
A constituent expression is defined as follows:
- The constituent expression of an expression is that expression.
- The constituent expression of a conversion is the corresponding implicit function call, if any, or the converted expression otherwise.
- The constituent expressions of a braced-init-list or of a (possibly parenthesized) expression-list are the constituent expressions of the elements of the respective list.
- The constituent expressions of a brace-or-equal-initializer of the form = initializer-clause are the constituent expressions of the initializer-clause.
Change in 6.9.1 [intro.execution] paragraph 4 as follows:
A subexpression of an expression E is an immediate subexpression of E or a subexpression of an immediate subexpression of E. [ Note: ... -- end note ]
The potentially-evaluated subexpressions of an expression, conversion, or initializer E are
- the constituent expressions of E and
- the subexpressions thereof that are not subexpressions of a nested unevaluated operand (7.2.3 [expr.context]).
Change in 7.7 [expr.const] paragraph 16 as follows:
An expression or conversion is potentially constant evaluated if it is:
- ...
- a potentially-evaluated subexpression (6.9.1 [intro.execution]) of one of the above
that is not a subexpression of a nested unevaluated operand (7.2.3 [expr.context]).
Change in 9.3.4.7 [dcl.fct.default] paragraph 5 as follows:
The default argument has the same semantic constraints as the initializer in a declaration of a variable of the parameter type, using the copy-initialization semantics (9.5 [dcl.init]). The names in the default argument are looked up, and the semantic constraints are checked, at the point where the default argument appears, except that an immediate invocation (7.7 [expr.const]) that is a potentially-evaluated subexpression (6.9.1 [intro.execution]) of the initializer-clause in a parameter-declaration is neither evaluated nor checked for whether it is a constant expression at that point. Name lookup and checking of semantic constraints for default arguments of templated functions are performed as described in 13.9.2 [temp.inst].
Add a paragraph before 9.5.1 [dcl.init.general] paragraph 17 as follows:
An immediate invocation (7.7 [expr.const]) that is not evaluated where it appears (9.3.4.7 [dcl.fct.default], 11.4.1 [class.mem.general]) is evaluated and checked for whether it is a constant expression at the point where the enclosing initializer is used in a function call, a constructor definition, or an aggregate initialization.
An initializer-clause followed by an ellipsis is a pack expansion (13.7.4 [temp.variadic]).
Change in 11.4.1 [class.mem.general] paragraph 11 as follows:
A brace-or-equal-initializer for a non-static data member specifies a default member initializer for the member, and shall not directly or indirectly cause the implicit definition of a defaulted default constructor for the enclosing class or the exception specification of that constructor. An immediate invocation (7.7 [expr.const]) that is a potentially-evaluated subexpression (6.9.1 [intro.execution]) of a default member initializer is neither evaluated nor checked for whether it is a constant expression at the point where the subexpression appears.
Change in 13.9.2 [temp.inst] paragraph 11 as follows:
... The use of a template specialization in a default argument or default member initializer shall not cause the template to be implicitly instantiated exceptthat a class template may be instantiatedwhereits complete type isneeded to determine the correctness of the default argument or default member initializer. The use of a default argument in a function call causes specializations in the default argument to be implicitly instantiated. Similarly, the use of a default member initializer in a constructor definition or an aggregate initialization causes specializations in the default member initializer to be instantiated.
[Accepted as a DR at the November, 2022 meeting.]
The criteria for a variable to be needed for constant evaluation are inconsistent with those for it to be usable in constant expressions (/4).
Proposed resolution (approved by CWG 2022-11-08):
Change in 7.7 [expr.const] paragraph 3 as follows:
A variable is potentially-constant if it is constexpr or it has reference or non-volatile const-qualified integral or enumeration type.
Change in 7.7 [expr.const] paragraph 16.7 as follows:
A function or variable is needed for constant evaluation if it is:
- a constexpr function that is named by an expression (6.3) that is potentially constant evaluated, or
- a potentially-constant variable named by a potentially constant evaluated expression
that is either a constexpr variable or is of non-volatile const-qualified integral type or of reference type.
[Accepted as a DR at the February, 2023 meeting.]
Consider:
struct A { char c; int x; };
union U { A a; };
constexpr int f() {
U u;
u.a.c = 1;
u.a.x = 2;
U v = u; // indeterminate padding bytes read!
return u.a.x;
}
extern constexpr int x = f();
Subclause 7.7 [expr.const] bullet 5.11 added by P1331R2 prohibits lvalue-to-rvalue conversion of objects having indeterminate value during evaluation of a core constant expression. Trivial copy constructors of unions copy the object representation (not just the active member). The new prohibition causes cases where bytes not involved in the value presentation of the active member and having indeterminate values would prevent a union from being copied by a trivial copy constructor (for example, the padding bytes in the above case).
Note: The source of a union copy is never a prvalue within the evaluation of a trivial copy constructor because the reference parameter is bound to a glvalue.
Proposed resolution (January, 2023):
Add a new paragraph after 7.7 [expr.const] paragraph 6 as follows:
... to an object whose lifetime began within the evaluation of E.
For the purposes of determining whether E is a core constant expression, lvalue-to-rvalue conversion of an object of indeterminate value during the evaluation of a call to a trivial copy/move constructor or copy/move assignment operator of a union does not disqualify E from being a core constant expression unless the active member of the source union object contains a subobject of indeterminate value.
Proposed resolution (approved by CWG 2023-02-07):
Add a new paragraph after 7.7 [expr.const] paragraph 6 as follows:
... to an object whose lifetime began within the evaluation of E.
For the purposes of determining whether E is a core constant expression, the evaluation of a call to a trivial copy/move constructor or copy/move assignment operator of a union is considered to copy/move the active member of the union, if any. [ Note: The copy/move of the active member is trivial. -- end note ]
[Accepted as a DR at the November, 2022 meeting.]
Consider:
for (int i = 0; i< 10; ++i){
auto f = [](){
break; // #1
};
}
Subclause 8.7.2 [stmt.break] paragraph 1 specifies:
The break statement shall occur only in an iteration-statement or a switch statement and causes termination of the smallest enclosing iteration-statement or switch statement; control passes to the statement following the terminated statement, if any.
Does the break at #1 "occur" in the for loop?
Proposed resolution (approved by CWG 2022-08-26):
Append to 8.1 [stmt.pre] paragraph 3 as follows:
... A statement S1 is enclosed by a statement S2 if S2 encloses S1.
The rules for conditions apply both...
Change in 8.2 [stmt.label] paragraph 2 as follows:
Case labels and default labels shall occur only in switch statementsA labeled-statement whose label is a case or default label shall be enclosed by (8.1 [stmt.pre]) a switch statement (8.5.3 [stmt.switch])..
Change in 8.7.2 [stmt.break] paragraph 1 as follows:
TheA break statement shalloccur only inbe enclosed by (8.1 [stmt.pre]) an iteration-statement (8.6 [stmt.iter]) or a switch statementand(8.5.3 [stmt.switch]). The break statement causes termination of the smallest such enclosingiteration-statement or switchstatement; control passes to the statement following the terminated statement, if any.
Change in 8.7.3 [stmt.cont] paragraph 1 as follows:
TheA continue statement shalloccur only inbe enclosed by (8.1 [stmt.pre]) an iteration-statement (8.6 [stmt.iter])and. The continue statement causes control to pass to the loop-continuation portion of the smallest such enclosingiteration-statementstatement, that is, to the end of the loop. ...
[Accepted as a DR at the November, 2022 meeting.]
Consider:
switch(float v = 0) { case 0: ; }
Subclause 8.1 [stmt.pre] paragraph 5 specifies:
... The value of a condition that is an initialized declaration in a switch statement is the value of the declared variable if it has integral or enumeration type, or of that variable implicitly converted to integral or enumeration type otherwise. ...
That appears to permit variables of floating-point type, whose value can be converted to integral type. In contrast, expressions of floating-point type are prohibited by 8.5.3 [stmt.switch] paragraph 2:
The condition shall be of integral type, enumeration type, or class type. ...
Possible resolution [SUPERSEDED]:
Change in 8.1 [stmt.pre] paragraph 5 as follows:
... The value of a condition that is an initialized declaration in a switch statement is the value of the declared variable if it has integral or enumeration type, or of that variable implicitly converted to integral or enumeration type if it has class type; otherwise the program is ill-formed. ...
CWG telecon 2022-10-21:
Rewording is needed.
Proposed resolution (approved by CWG 2022-11-09):
Change in 8.1 [stmt.pre] paragraph 5 as follows:
...The value of a condition that is an initialized declaration in a switch statement is the value of the declared variable if it has integral or enumeration type, or of that variable implicitly converted to integral or enumeration type otherwise....
Change in 8.5.3 [stmt.switch] paragraph 2 as follows:
The value of a condition that is an initialized declaration is the value of the declared variable, or the value of the expression otherwise. The value of the condition shall be of integral type, enumeration type, or class type. If of class type, the condition is contextually implicitly converted (7.3 [conv]) to an integral or enumeration type. If the (possibly converted) type is subject to integral promotions (7.3.7 [conv.prom]), the condition is converted to the promoted type. ...
[Accepted as a DR at the November, 2022 meeting.]
Consider:
template<class T> concept C = true;
C auto [x, y] = std::pair{1, 2}; // ok?
Subclause 9.1 [dcl.pre] paragraph 6 specifies:
A simple-declaration with an identifier-list is called a structured binding declaration (9.7 [dcl.struct.bind]). If the decl-specifier-seq contains any decl-specifier other than static, thread_local, auto (9.2.9.7 [dcl.spec.auto]), or cv-qualifier s, the program is ill-formed.
Use of the word "contains" leads to an interpretation that any placeholder-type-specifier (9.2.9.7.1 [dcl.spec.auto.general]), possibly including a type-constraint, is valid here, since a placeholder-type-specifier is a decl-specifier and "contains" auto.
However, paper P1141R2 (Yet another approach for constrained declarations), applied in November 2018, expressly excludes structured bindings from constrained auto:
Structured bindings do deduce auto in some cases; however, the auto is deduced from the whole (and not from the individual components). It is somewhat doubtful that applying the constraint to the whole, as opposed to (for example) applying separately to each component, is the correct semantic. Therefore, we propose to defer enabling the application of constraints to structured bindings to separate papers.
Notwithstanding, clang, gcc, and MSVC accept the example.
Proposed resolution (approved by CWG 2022-10-21):
Change in 9.1 [dcl.pre] paragraph 6 specifies:
A simple-declaration with an identifier-list is called a structured binding declaration (9.7 [dcl.struct.bind]).If the decl-specifier-seq contains any decl-specifier other than static, thread_local, auto (9.2.9.7 [dcl.spec.auto]), or cv-qualifiers, the program is ill-formed.Each decl-specifier in the decl-specifier-seq shall be static, thread_local, auto (9.2.9.7 [dcl.spec.auto]), or a cv-qualifier. [ Example:template<class T> concept C = true; C auto [x, y] = std::pair{1, 2}; // error: constrained placeholder-type-specifier not permitted for structured bindings
-- end example ]
[Accepted as a DR at the November, 2022 meeting.]
The intent for immediate functions is that they can only be called at compile time. That rule is enforced by the wording of 7.5.5 [expr.prim.id] paragraph 3:
An id-expression that denotes an immediate function (9.2.6 [dcl.constexpr]) shall appear as a subexpression of an immediate invocation or in an immediate function context (7.7 [expr.const]).
However, this restriction does not apply to implicit function calls such as constructor and operator invocations. Presumably some additional wording is needed for such cases.
Additional note, July, 2019:
This issue would appear to be NAD because of the following wording from 7.7 [expr.const] paragraph 10:
An expression or conversion is an immediate invocation if it is an explicit or implicit invocation of an immediate function and is not in an immediate function context. An immediate invocation shall be a constant expression.
Proposed resolution (approved by CWG 2022-10-21):
Change in 7.7 [expr.const] paragraph 10 as follows:
Anexpression or conversioninvocation is an immediate invocation if it is an explicit or implicit invocation of an immediate function and is not in an immediate function context. An immediate invocation shall be a constant expression.
[Accepted as a DR at the February, 2023 meeting.]
It is not clear whether a defaulted consteval function is still an immediate function even if it is not a valid constexpr function. For example:
template <typename Ty> struct A { Ty n; consteval A() {} }; template <typename Ty> struct B { Ty n; consteval B() = default; }; A<int> a; B<int> b;
The declarations of a and b should both fail due to an uninitialized member n in each of A and B. The = default; should not make a difference. However, there is implementation divergence. We should be able to lean on 7.7 [expr.const] bullet 5.5 to handle this when the immediate invocation is required.
Possible resolution:
Change in 9.2.6 [dcl.constexpr] paragraph 7 as follows:
If the instantiated template specialization of a constexpr templated functiontemplate or member function of a class templatewould fail to satisfy the requirements for a constexpr function, that specialization is still a constexpr function, even though a call to such a function cannot appear in a constant expression. Similarly, if the instantiated template specialization of a consteval templated function would fail to satisfy the requirements for a consteval function, that specialization is still an immediate function, even though an immediate invocation would be ill-formed. If no specialization of the template would satisfy the requirements for a constexpr or consteval function when considered as a non-template function, the template is ill-formed, no diagnostic required.
Proposed resolution (August, 2022) [SUPERSEDED]:
Change in 9.2.6 [dcl.constexpr] paragraph 4 as follows:
If the instantiated template specialization of a constexpr templated functiontemplate or member function of a class templatewould fail to satisfy the requirements for a constexpr function, that specialization is still a constexpr function, even though a call to such a function cannot appear in a constant expression. Similarly, if the instantiated template specialization of a consteval templated function would fail to satisfy the requirements for a consteval function, that specialization is still an immediate function, even though an immediate invocation would be ill-formed.
Additional notes (November, 2022)
The proposed wording is possibly not addressing the point of the issue; the issue has been retracted from the WG21 plenary straw polls for further consideration in CWG.
Proposed resolution (approved by CWG 2023-01-27):
Change in 9.2.6 [dcl.constexpr] paragraph 3 as follows:
The definition of a constexpr function shall satisfy the following requirements:A function is constexpr-suitable if:Except for instantiated constexpr functions, non-templated constexpr functions shall be constexpr-suitable.
- it
shallis notbea coroutine (9.6.4 [dcl.fct.def.coroutine]);, and- if the function is a constructor or destructor, its class
shalldoes not have any virtual base classes.
Delete 9.2.6 [dcl.constexpr] paragraph 4:
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, that specialization is still a constexpr function, even though a call to such a function cannot appear in a constant expression.
Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 5 as follows:
... The function call operator or any given operator template specialization is a constexpr function if either the corresponding lambda-expression's parameter-declaration-clause is followed by constexpr or consteval, or itsatisfies the requirements for a constexpr functionis constexpr-suitable (9.2.6 [dcl.constexpr]). ...
Change in 7.7 [expr.const] bullet 5.5 as follows:
- an invocation of an instantiated constexpr function that
fails to satisfy the requirements for a constexpr functionis not constexpr-suitable;
Change in 9.6.2 [dcl.fct.def.default] paragraph 3 as follows:
A function explicitly defaulted on its first declaration is implicitly inline (9.2.8 [dcl.inline]), and is implicitly constexpr (9.2.6 [dcl.constexpr]) if itsatisfies the requirements for a constexpr functionis constexpr-suitable.
Change in 11.4.7 [class.dtor] paragraph 9 as follows:
A defaulted destructor is a constexpr destructor if itsatisfies the requirements for a constexpr functionis constexpr-suitable (9.2.6 [dcl.constexpr]).
[Accepted as a DR at the February, 2023 meeting.]
Subclause 9.2.7 [dcl.constinit] paragraph 2 states:
If a variable declared with the constinit specifier has dynamic initialization (6.9.3.3 [basic.start.dynamic]), the program is ill-formed. [ Note: The constinit specifier ensures that the variable is initialized during static initialization (6.9.3.2 [basic.start.static]). —end note]
Subclause 6.9.3.2 [basic.start.static] paragraph 3 gives permission for an implementation to perform static initialization in lieu of dynamic initialization:
An implementation is permitted to perform the initialization of a variable with static or thread storage duration as a static initialization even if such initialization is not required to be done statically, provided that ...
constinit will assuredly not give a diagnostic for variables that are constant initialized (7.7 [expr.const] paragraph 2), because those are required to be statically initialized and thus will never be dynamically initialized. Conversely, constinit is guaranteed to give a diagnostic for variables that cannot be statically initialized, for example those with an initializer whose value depends on runtime conditions.
Between those boundaries, it is unclear whether constinit ought to give a diagnostic for variables whose initializer does not satisfy the constraints of constant-initialized, yet the implementation takes advantage of the permission to turn dynamic initialization into static initialization. For example,
float f;
constinit int * pi = (int*) &f; // reinterpret_cast, not constant-initialized
The current wording seems to imply that constinit accurately reflects whether dynamic initialization was turned into static initialization by the implementation. However, that is impossible to implement, because such decisions are often made by the optimizer, which runs later than the compiler front-end interpreting the program text containing constinit.
There is value in permitting constinit not to diagnose some of the dynamic initializations that are turned into static initializations.
There is also value in having portable semantics of constinit.
See also issue 2536.
Notes from the November, 2022 meeting
CWG seeks the advice of EWG how to proceed with this issue, via cplusplus/papers#1379.
EWG 2023-01-19
EWG resolved to desire portable, guaranteed semantics for constinit. That means, constinit should produce a diagnostic if de-jure dynamic initialization is turned into de-facto static initialization as permitted by 6.9.3.2 [basic.start.static] paragraph 3.
Proposed resolution (approved by CWG 2023-02-06):
Change in 9.2.7 [dcl.constinit] paragraph 2 as follows:
If a variable declared with the constinit specifier has dynamic initialization (6.9.3.3 [basic.start.dynamic]), the program is ill-formed, even if the implementation would perform that initialization as a static initialization (6.9.3.2 [basic.start.static]). [Note 1: The constinit specifier ensures that the variable is initialized during static initialization(6.9.3.2 [basic.start.static]). —end note]
[Accepted as a DR at the November, 2022 meeting.]
Subclause 9.3.3 [dcl.ambig.res] paragraph 1 specifies:
The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in 8.10 [stmt.ambig] can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. Just as for the ambiguities mentioned in 8.10 [stmt.ambig], the resolution is to consider any construct that could possibly be a declaration a declaration.
The specification correctly describes an ambiguity between a function declaration and an object declaration, but resolves the ambiguity to a "declaration", which does not offer any insight.
Proposed resolution (2022-09-09) [SUPERSEDED]:
Change in 9.3.3 [dcl.ambig.res] paragraph 1 as follows:
...Just as for the ambiguities mentioned in 8.10 [stmt.ambig], theThe resolution is to consider any construct that could possibly be a function declaration a function declaration.
Proposed resolution (approved by CWG 2022-09-23):
Change in 9.3.3 [dcl.ambig.res] paragraph 1 as follows:
The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in 8.10 [stmt.ambig] can also occur in the context of a declaration. In that context, the choice is betweena function declaration with a redundant set of parentheses around a parameter name andan object declaration with a function-style cast as the initializer and a declaration involving a function declarator with a redundant set of parentheses around a parameter name. Just as for the ambiguities mentioned in 8.10 [stmt.ambig], the resolution is to consider any construct, such as the potential parameter declaration, that could possibly be a declaration to be a declaration.
[Accepted at the November, 2022 meeting.]
The syntax and semantics appear to allow:
struct S { void f(this const S& = S{}); };This is probably no more than an oddity, but perhaps it should be prevented.
Proposed resolution (approved by CWG 2022-11-08):
Change in 9.3.4.6 [dcl.fct] paragraph 3 as follows:
parameter-declaration: attribute-specifier-seqopt thisopt decl-specifier-seq declarator attribute-specifier-seqoptthisoptdecl-specifier-seq declarator = initializer-clause attribute-specifier-seqopt thisopt decl-specifier-seq abstract-declaratoropt attribute-specifier-seqoptthisoptdecl-specifier-seq abstract-declaratoropt = initializer-clause
[Accepted as a DR at the November, 2022 meeting.]
Subclause 9.5.1 [dcl.init.general] bullet 16.6.1 says:
[Example 2: T x = T(T(T())); calls the T default constructor to initialize x. —end example]
This is incorrect; in some situations, the default constructor is not invoked (see 9.5.1 [dcl.init.general] paragraph 9).
Proposed resolution (approved by CWG 2022-08-26):
Change in 9.5.1 [dcl.init.general] bullet 16.6.1 as follows:
[Example 2: T x = T(T(T()));calls the T default constructor to initializevalue-initializes x. —end example]
[Accepted as a DR at the November, 2022 meeting.]
The wording appears to prohibit aggregates from having indirect private base classes. That does not match existing practice and is an unnecessary restriction. For example:
#include <type_traits> struct B1 {}; struct B2 : private B1 {}; struct S : public B2 {}; void f() { static_assert(std::is_aggregate<S>::value); }
Proposed resolution (approved by CWG 2022-08-26):
Change in 9.5.2 [dcl.init.aggr] paragraph 1 as follows:
An aggregate is an array or a class (Clause 11 [class]) with
- no user-declared or inherited constructors (11.4.5 [class.ctor]),
- no private or protected direct non-static data members (11.8 [class.access]),
- no private or protected direct base classes (11.8.3 [class.access.base]), and
- no virtual functions (11.7.3 [class.virtual]) or virtual base classes (11.7.2 [class.mi]).
, andno virtual, private, or protected base classes (11.7.2 [class.mi]).
[Accepted as a DR at the November, 2022 meeting.]
Consider:
struct S { explicit S(int){} }; struct A { S s; }; struct B { union { S s; }; }; int main() { A a1 = {.s{0}}; // #1 A a2{.s{0}}; // #2 B b1 = {.s{0}}; // #3 B b2{.s{0}}; // #4 }
Subclause 9.5.2 [dcl.init.aggr] bullet 4.2 specifies:
Otherwise, the element is copy-initialized from the corresponding initializer-clause or is initialized with the brace-or-equal-initializer of the corresponding designated-initializer-clause.
It is unclear what kind of initialization is performed for "is initialized". For example, one could imagine that the top-level kind of initialization is inherited. On the other hand, 9.5.1 [dcl.init.general] paragraph 14 specifies:
The initialization that occurs in the = form of a brace-or-equal-initializer or condition (8.5 [stmt.select]), as well as in argument passing, function return, throwing an exception (14.2 [except.throw]), handling an exception (14.4 [except.handle]), and aggregate member initialization (9.5.2 [dcl.init.aggr]), is called copy-initialization.
There is implementation divergence: gcc and icc reject the example; clang and MSVC accept.
Suggested resolution [SUPERSEDED]:
Change in 9.5.2 [dcl.init.aggr] bullet 4.1 as follows:
If the element is an anonymous union member and the initializer list is a brace-enclosed designated-initializer-list, the element is initialized by thedesignated-initializer-listbraced-init-list { D }, where D is the designated-initializer-clause naming a member of the anonymous union member.
Change in 9.5.2 [dcl.init.aggr] bullet 4.2 as follows:
Otherwise, the element is copy-initialized from the corresponding initializer-clause or isinitializedcopy-initialized or direct-initialized with the brace-or-equal-initializer of the corresponding designated-initializer-clause, according to the form of the brace-or-equal-initializer (9.5.1 [dcl.init.general]). ...
CWG telecon 2022-09-09:
The examples #1 to #4 should all be valid, direct-initializing the s member.
Proposed resolution (approved by CWG 2022-09-23):
Change in 9.5.1 [dcl.init.general] paragraph 14 as follows:
The initialization that occurs in the = form of a brace-or-equal-initializer or condition (8.5 [stmt.select]), as well as in argument passing, function return, throwing an exception (14.2 [except.throw]), handling an exception (14.4 [except.handle]), and aggregate member initialization other than by a designated-initializer-clause (9.5.2 [dcl.init.aggr]), is called copy-initialization.
Change in 9.5.2 [dcl.init.aggr] bullet 4.1 as follows:
If the element is an anonymous union member and the initializer list is a brace-enclosed designated-initializer-list, the element is initialized by thedesignated-initializer-listbraced-init-list { D }, where D is the designated-initializer-clause naming a member of the anonymous union member.
Change in 9.5.2 [dcl.init.aggr] bullet 4.2 as follows:
Otherwise, the element is copy-initialized from the corresponding initializer-clause or is initialized with the brace-or-equal-initializer of the corresponding designated-initializer-clause. If that initializer is of the form assignment-expression or = assignment-expression and a narrowing conversion (9.5.5 [dcl.init.list]) is required to convert the expression, the program is ill-formed. [ Note: If the initialization is by designated-initializer-clause, its form determines whether copy-initialization or direct-initialization is performed.-- end note]
[Accepted as a DR at the November, 2022 meeting.]
Consider:
struct C {
long long i : 8;
};
void f() {
C x{1}, y{2};
x.i <=> y.i; // error: narrowing conversion required (7.6.8 [expr.spaceship] bullet 4.1)
}
The rules for narrowing conversions in 9.5.5 [dcl.init.list] paragraph 7 consider only the source type, even though integral promotions can change the type of a bit-field to a smaller integer type without loss of value range according to 7.3.7 [conv.prom] paragraph 5:
A prvalue for an integral bit-field (11.4.10 [class.bit]) can be converted to a prvalue of type int if int can represent all the values of the bit-field; otherwise, it can be converted to unsigned int if unsigned int can represent all the values of the bit-field. If the bit-field is larger yet, no integral promotion applies to it. If the bit-field has enumeration type, it is treated as any other value of that type for promotion purposes.
There is implementation divergence in the handling of this example.
Proposed resolution (approved by CWG 2022-10-07):
Change in 9.5.5 [dcl.init.list] bullet 7.4 as follows:
A narrowing conversion is an implicit conversion
- ...
- from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where
- the source is a bit-field whose width w is less than that of its type (or, for an enumeration type, its underlying type) and the target type can represent all the values of a hypothetical extended integer type with width w and with the same signedness as the original type or
- the source is a constant expression whose value after integral promotions will fit into the target type, or
- ...
[Accepted as a DR at the November, 2022 meeting.]
The exposition in terms of F1 and F2 as well as T1 and T2 is confusing.
Possible resolution:
Change in 9.6.2 [dcl.fct.def.default] paragraph 2 as follows:
An explicitly defaulted special member function F1with type T1is allowed to differ from the corresponding special member function F2with type T2that would have been implicitly declared, as follows:If
T1F1 andT2F2 may have differing ref-qualifier s;- if F2 has an implicit object parameter of type “reference to C”, F1 may be an explicit object member function whose explicit object parameter is of type “reference to C”, in which case
T1the type of F1 would differ fromT2the type of F2 in thatT1the type of F1 has an additional parameter;T1F1 andT2F2 may have differing exception specifications; and- if F2 has a non-object parameter of type const C&, the corresponding non-object parameter of F1 may be of type C&.
T1the type of F1 differs fromT2the type of F2 in a way other than as allowed by the preceding rules, then:
- if F1 is an assignment operator, and the return type of
T1F1 differs from the return type ofT2F2 or F1 's non-object parameter type is not a reference, the program is ill-formed;- otherwise, if F1 is explicitly defaulted on its first declaration, it is defined as deleted;
- otherwise, the program is ill-formed.
[Accepted as a DR at the November, 2022 meeting.]
According to 9.6.4 [dcl.fct.def.coroutine] paragraph 14,
If the evaluation of the expression promise.unhandled_exception() exits via an exception, the coroutine is considered suspended at the final suspend point.
However, the “final suspend point” is defined as being “the await-expression containing the call to final_suspend” (bullet 5.2), and it is not desired to evaluate the final_suspend expression in this case.
Suggested resolution [SUPERSEDED]:
Change 9.6.4 [dcl.fct.def.coroutine] paragraph 5 as follows:
...where
the await-expression containing the call to initial_suspend is the initial
suspend pointawait expression, andthe await-expression containing the call to final_suspend is the final
suspend pointawait expression, andinitial-await-resume-called is initially false and is set to true immediately before the evaluation of the await-resume expression (7.6.2.4 [expr.await]) of the initial
suspend pointawait expression, and...
promise-constructor-arguments is determined as follows: overload resolution is performed on a promise constructor call created by assembling an argument list with lvalues p1 ... pn. If a viable constructor is found (12.2.3 [over.match.viable]), then promise-constructor-arguments is (p1, ..., pn), otherwise promise-constructor-arguments is empty
., andA coroutine is suspended at a final suspend point if it is suspended
at a final await expression or
due to an exception exiting from unhandled_exception()
Change bullet 3.2 of 7.6.2.4 [expr.await] as follows:
Evaluation of an await-expression involves the following auxiliary types, expressions, and objects:
...
a is the cast-expression if the await-expression was implicitly produced by a yield-expression (7.6.17 [expr.yield]), an initial
suspend pointawait expression, or a finalsuspend pointawait expression (9.6.4 [dcl.fct.def.coroutine]). Otherwise...
If needed, change 9.6.4 [dcl.fct.def.coroutine] paragraph 14 as follows:
If the evaluation of the expression promise.unhandled_exception() exits via an exception, the coroutine is considered suspended at the final suspend point and the exception propagates to the caller or resumer.
Notes from the August, 2020 teleconference [SUPERSEDED]:
CWG expressed some concern about the lack of a precise definition of “suspend point”. Gor Nishanov suggests the following change, in 7.6.2.4 [expr.await] bullet 5.1:
If the evaluation of await-suspend exits via an exception, the exception is caught, the coroutine is resumed, and the exception is immediately re-thrown (14.2 [except.throw]). Otherwise, control flow returns to the current coroutine caller or resumer (9.6.4 [dcl.fct.def.coroutine]) without exiting any scopes (8.7 [stmt.jump]). The point in the coroutine immediately prior to control returning to its caller or resumer is a coroutine suspend point.
Proposed resolution (approved by CWG 2022-11-10):
Change in 7.6.2.4 [expr.await] bullet 3.2 as follows:
Evaluation of an await-expression involves the following auxiliary types, expressions, and objects:
...
a is the cast-expression if the await-expression was implicitly produced by a yield-expression (7.6.17 [expr.yield]), an initial
suspend pointawait expression, or a finalsuspend pointawait expression (9.6.4 [dcl.fct.def.coroutine]). Otherwise...
Change in 7.6.2.4 [expr.await] bullet 5.1 as follows:
If the evaluation of await-suspend exits via an exception, the exception is caught, the coroutine is resumed, and the exception is immediately re-thrown (14.2 [except.throw]). Otherwise, control flow returns to the current coroutine caller or resumer (9.6.4 [dcl.fct.def.coroutine]) without exiting any scopes (8.7 [stmt.jump]). The point in the coroutine immediately prior to control returning to its caller or resumer is a coroutine suspend point.
Change in 9.6.4 [dcl.fct.def.coroutine] paragraph 5 as follows:
...where
the await-expression containing the call to initial_suspend is the initial
suspend pointawait expression, andthe await-expression containing the call to final_suspend is the final
suspend pointawait expression, andinitial-await-resume-called is initially false and is set to true immediately before the evaluation of the await-resume expression (7.6.2.4 [expr.await]) of the initial
suspend pointawait expression, and...
promise-constructor-arguments is determined as follows: overload resolution is performed on a promise constructor call created by assembling an argument list with lvalues p1 ... pn. If a viable constructor is found (12.2.3 [over.match.viable]), then promise-constructor-arguments is (p1, ..., pn), otherwise promise-constructor-arguments is empty
., and- a coroutine is suspended at the initial suspend point if it is suspended at the initial await expression, and
a coroutine is suspended at a final suspend point if it is suspended
at a final await expression or
due to an exception exiting from unhandled_exception()
Change 9.6.4 [dcl.fct.def.coroutine] paragraph 14 as follows:
If the evaluation of the expression promise.unhandled_exception() exits via an exception, the coroutine is considered suspended at the final suspend point and the exception propagates to the caller or resumer.
[Accepted as a DR at the November, 2022 meeting.]
Subclause 9.6.4 [dcl.fct.def.coroutine] paragraph 8 specifies:
A suspended coroutine can be resumed to continue execution by invoking a resumption member function (17.13.4.6 [coroutine.handle.resumption]) of a coroutine handle (17.13.4 [coroutine.handle]) that refers to the coroutine. The function that invoked a resumption member function is called the resumer.
However, non-functions can also resume a coroutine, for example:
Task task() {
std::cout << "in task\n";
int r = co_await Line();
std::cout << "resumed\n";
co_return r;
}
auto r = task();
auto c = (r.coro_.resume(), 0); // #1
Proposed resolution (approved by CWG 2022-08-26):
Change in 9.6.4 [dcl.fct.def.coroutine] paragraph 8 as follows:
A suspended coroutine can be resumed to continue execution by invoking a resumption member function (17.13.4.6 [coroutine.handle.resumption]) of a coroutine handle (17.13.4 [coroutine.handle]) that refers to the coroutine. Thefunctionevaluation that invoked a resumption member function is called the resumer.
[Accepted as a DR at the November, 2022 meeting.]
Subclause 9.8.1 [dcl.enum] specifies how the underlying type of an enumeration is determined, and, for enumerations whose underlying type is fixed, specifies that the enumeration has the same set of values as the underlying type. However, the specification does not relate the size and alignment requirements of the enumeration to those of the underlying type. Those ought to be the same.
Suggested resolution [SUPERSEDED]:
Add a new paragraph after 9.8.1 [dcl.enum] paragraph 8:
For an enumeration whose underlying type is fixed, ...
An enumeration has the same size, value representation, and alignment requirements (6.7.3 [basic.align]) as its underlying type. Furthermore, each value of an enumeration has the same representation as the same value of the underlying type.
Two enumeration types are layout-compatible enumerations if ...
Proposed resolution (approved by CWG 2022-08-26):
Add a new paragraph after 9.8.1 [dcl.enum] paragraph 8:
For an enumeration whose underlying type is fixed, ...
An enumeration has the same size, value representation, and alignment requirements (6.7.3 [basic.align]) as its underlying type. Furthermore, each value of an enumeration has the same representation as the corresponding value of the underlying type.
Two enumeration types are layout-compatible enumerations if ...
[Accepted as a DR at the November, 2022 meeting.]
Consider:
enum class E {
a, b, c
};
using MyE = E;
int main() {
using enum MyE; // #1
}
Does the lookup for the elaborated-enum-specifier at #1 use type-only lookup per 6.5.6 [basic.lookup.elab]? There is implementation divergence; EDG, gcc, and MSVC accept, clang rejects.
Suggested resolution [SUPERSEDED]:
Change in 9.8.2 [enum.udecl] paragraph 1 as follows:
Lookup for the elaborated-enum-specifier is as specified in 6.5.6 [basic.lookup.elab]. The elaborated-enum-specifier shall not name a dependent type and the type shall have a reachable enum-specifier.
CWG telecon 2022-09-09:
The example at #1 is intended to be valid; even though the grammar suggests that an elaborated-type-specifier is used here, an ordinary (not a type-only) lookup is performed.
Proposed resolution (approved by CWG 2022-09-23) [SUPERSEDED]:
Change in 9.8.2 [enum.udecl] paragraph 1 as follows:
The terminal name of the elaborated-enum-specifier undergoes ordinary lookup (6.5.3 [basic.lookup.unqual], 6.5.5 [basic.lookup.qual]). The elaborated-enum-specifier shall not name a dependent type and the type shall have a reachable enum-specifier.
CWG 2022-11-07:
Use the wording approach presented in CA-054, with necessary adjunct fixes.
Proposed resolution (approved by CWG 2022-11-10):
Change the grammar before 9.2.9.5 [dcl.type.elab] paragraph 1 as follows, merging the grammar productions:
elaborated-type-specifier : class-key attribute-specifier-seqopt nested-name-specifieropt identifier class-key simple-template-id class-key nested-name-specifier templateopt simple-template-idelaborated-enum-specifier elaborated-enum-specifier :enum nested-name-specifieropt identifier
Change the grammar before 9.8.2 [enum.udecl] paragraph 1 as follows:
using-enum-declaration: usingelaborated-enum-specifierenum using-enum-declarator ; using-enum-declarator: nested-name-specifieropt identifier nested-name-specifieropt simple-template-id
Change in 9.8.2 [enum.udecl] paragraph 1 as follows:
A using-enum-declarator names the set of declarations found by lookup (6.5.3 [basic.lookup.unqual], 6.5.5 [basic.lookup.qual]) for the using-enum-declarator. Theelaborated-enum-specifierusing-enum-declarator shallnot name a dependentdesignate a non-dependent typeand the type shall havewith a reachable enum-specifier.
[Accepted as a DR at the February, 2023 meeting.]
According to 9.12 [dcl.link] paragraph 5,
A C language linkage is ignored in determining the language linkage of class members, friend functions with a trailing requires-clause, and the function type of class member functions.
It doesn't make sense that static member functions should behave like non-static member functions in this regard:
extern "C" {
struct A {
static void f();
constexpr static void (*p)()=f; // error: must point to a function whose type has C language linkage
};
}
Suggested resolution:
Change 9.12 [dcl.link] paragraph 5 as follows:
A C language linkage is ignored in determining the language linkage of class members, friend functions with a trailing requires-clause, and the function type of non-static class member functions.
Notes from the August, 2021 teleconference:
There was some question as to whether a linkage specification should affect the language linkage of any function declarators within class scope. The question was also raised as to whether some non-typedef syntax should be available for affecting language linkage, which would be a question for EWG.
Proposed resolution (approved by CWG 2022-11-10):
Change 9.12 [dcl.link] paragraph 5 as follows:
A C language linkage is ignored in determining the language linkage of class members, friend functions with a trailing requires-clause, and the function type of non-static class member functions.
[Accepted as a DR at the November, 2022 meeting.]
Subclause 9.13.1 [dcl.attr.grammar] paragraph 6 specifies that an unrecognized attribute-token is ignored:
For an attribute-token (including an attribute-scoped-token) not specified in this document, the behavior is implementation-defined. Any attribute-token that is not recognized by the implementation is ignored.
The intent is that only non-standard unrecognized attribute-tokens can be ignored; in particular, an implementation is required to syntax-check all standard attributes, even if the implementation then chooses not to effect any semantics for that attribute.
The paper introducing attributes was N2761; the phrasing in question was introduced by P0283R2 attempting to implement the design change presented in P0283R1.
See also paper P2552 (On the ignorability of standard attributes).
Suggested resolution:
Change in 9.13.1 [dcl.attr.grammar] paragraph 6 as follows:
For an attribute-token (including an attribute-scoped-token) not specified in this document, the behavior is implementation-defined. Any; any such attribute-token that is not recognized by the implementation is ignored.
EWG telecon 2022-05-26
See paper issue 1252.
There was consensus for the statement "It is EWG's intent that [dcl.attr]/6 ONLY permits an implementation to ignore a standard attribute's effect, but not appertainment and argument parsing." To be confirmed by electronic polling.
Proposed resolution (approved by CWG 2022-07-01) [SUPERSEDED]:
Change in 9.13.1 [dcl.attr.grammar] paragraph 6 as follows:
For an attribute-token (including an attribute-scoped-token) not specified in this document, the behavior is implementation-defined. Any; any such attribute-token that is not recognized by the implementation is ignored. [ Note: A program is ill-formed if it contains an attribute specified in 9.13 [dcl.attr] that violates the rules to which entity or statement the attribute may apply or the syntax rules for the attribute's attribute-argument-clause, if any. -- end note ]
EWG 2022-06 electronic polling
No consensus. See vote.
EWG 2022-11-08
Approved the direction of the 2022-07-01 proposed resolution.
Proposed resolution (approved by CWG 2022-11-08):
Change in 9.13.1 [dcl.attr.grammar] paragraph 6 as follows:
For an attribute-token (including an attribute-scoped-token) not specified in this document, the behavior is implementation-defined. Any; any such attribute-token that is not recognized by the implementation is ignored. [ Note: A program is ill-formed if it contains an attribute specified in 9.13 [dcl.attr] that violates the rules specifying to which entity or statement the attribute may apply or the syntax rules for the attribute's attribute-argument-clause, if any. -- end note ]
[Accepted as a DR at the February, 2023 meeting.]
EWG resolved to reflect the understanding of semantic ignorability of attributes in a note.
Proposed resolution (approved by CWG 2023-02-09):
Add to 9.13.1 [dcl.attr.grammar] paragraph 6 as follows:
[Note 4: A program is ill-formed if it contains an attribute specified in 9.13 [dcl.attr] that violates the rules specifying to which entity or statement the attribute can apply or the syntax rules for the attribute's attribute-argument-clause, if any. —end note] [Note: The attributes specified in 9.13 [dcl.attr] have optional semantics: given a well-formed program, removing all instances of any one of those attributes results in a program whose set of possible executions (4.1.2 [intro.abstract]) for a given input is a subset of those of the original program for the same input, absent implementation-defined guarantees with respect to that attribute. -- end note ]
[Accepted as a DR at the November, 2022 meeting, as part of paper P2615R1 (Meaningful exports).]
According to 10.2 [module.interface] paragraph 1, export does not interfere with other definitions; paragraph 3 merely requires that it appear in a declaration that declares at least one name. 13.1 [temp.pre] paragraph 4 prevents using an export-declaration as the declaration of a template-declaration.
With some interpretation, these rules appear to allow various useless constructs like:
template export void f(); export template void f(); export template<> void g(int); template<> export void g(int); export template<class T> struct trait<T*>;
Simply forbidding them in 10.2 [module.interface] paragraph 3 would also prohibit their appearance in export blocks:
export { template<class> struct A; template<class T> struct A<T*>; }
It is already the case that the closely-related example
export { template<class T> struct A {A(non_deducible<T>);}; template<class U> A(U) -> A<find_param<U>>; }
is disallowed, although a fix is pending in EWG.
Suggested resolution: Forbid the direct use of the export keyword in these contexts but continue to allow them (and perhaps more) in export { }.
Notes from the February, 2021 teleconference:
CWG agreed with the suggested direction.
Notes from the 2022-05-20 CWG telecon:
CWG agreed with the wording suggested by Herring; forwarding to EWG for approval.
[Accepted as a DR at the November, 2022 meeting.]
Subclause 11.2 [class.prop] paragraph 9 specifies:
A class S is an implicit-lifetime class if
- it is an aggregate or
- it has at least one trivial eligible constructor and a trivial, non-deleted destructor.
However, an aggregate may have a non-deleted non-trivial destructor:
struct X { Y i; ~X(); };
This class is an aggregate, but destroying X itself (ignoring the subobjects) does not satisfy "destroying an instance of the type runs no code"; see P0593R6 "Implicit creation of objects for low-level object manipulation" section 3.1.
Additional notes (September, 2022):
From a thread starting here: What if X had a deleted destructor (either explicitly or implicitly)?
CWG 2022-11-09:
A deleted destructor does not prevent an aggregate from being an implicit-lifetime class.
Proposed resolution (approved by CWG 2022-11-09):
Change in 11.2 [class.prop] paragraph 9 as follows:
A class S is an implicit-lifetime class if
- it is an aggregate whose destructor is not user-provided or
- it has at least one trivial eligible constructor and a trivial, non-deleted destructor.
[Accepted as a DR at the November, 2022 meeting.]
Consider:
struct A { int i; char c; }; struct B { int i; alignas(8) char c; }; union U { A a; B b; };
On a lot of platforms, A and B do not have the same layout, yet 11.4.1 [class.mem.general] paragraph 23 does not consider differences in alignment in the rules for "common initial sequence":
The common initial sequence of two standard-layout struct (11.2 [class.prop]) types is the longest sequence of non-static data members and bit-fields in declaration order, starting with the first such entity in each of the structs, such that corresponding entities have layout-compatible types (6.8 [basic.types]), either both entities are declared with the no_unique_address attribute (9.13.11 [dcl.attr.nouniqueaddr]) or neither is, and either both entities are bit-fields with the same width or neither is a bit-field.
In the following example,
struct S0 { alignas(16) char x[128]; int i; }; struct alignas(16) S1 { char x[128]; int i; };
S0 and S1 have the same alignment, yet per the suggested rules below, they will not be layout-compatible.
Suggested resolution [SUPERSEDED]:
Change in 11.4.1 [class.mem.general] paragraphs 23-25 as follows (also add bullets):
The common initial sequence of two standard-layout struct (11.2 [class.prop]) types is the longest sequence of non-static data members and bit-fields in declaration order, starting with the first such entity in each of the structs, such that
- corresponding entities have layout-compatible types (6.8 [basic.types]),
- either both entities have alignment-specifiers that specify equivalent alignment or neither entity has an alignment-specifier (9.13.2 [dcl.align]),
- either both entities are declared with the no_unique_address attribute (9.13.11 [dcl.attr.nouniqueaddr]) or neither is, and
- either both entities are bit-fields with the same width or neither is a bit-field.
[...]
Two standard-layout struct (11.2 [class.prop]) types are layout-compatible classes if their common initial sequence comprises all members and bit-fields of both classes (6.8 [basic.types]) and either both types are declared with alignment-specifiers that specify equivalent alignment or neither type has an alignment-specifier.
Two standard-layout unions are layout-compatible if they have the same number of non-static data members and corresponding non-static data members (in any order)
- have layout-compatible types (6.8.1 [basic.types.general]) and
- either both have alignment-specifiers that specify equivalent alignment or neither has an alignment-specifier.
Proposed resolution (approved by CWG telecon 2022-08-26):
Change in 11.4.1 [class.mem.general] paragraphs 23-25 as follows (also add bullets):
The common initial sequence of two standard-layout struct (11.2 [class.prop]) types is the longest sequence of non-static data members and bit-fields in declaration order, starting with the first such entity in each of the structs, such that
- corresponding entities have layout-compatible types (6.8 [basic.types]),
- corresponding entities have the same alignment requirements (6.7.3 [basic.align]),
- either both entities are declared with the no_unique_address attribute (9.13.11 [dcl.attr.nouniqueaddr]) or neither is, and
- either both entities are bit-fields with the same width or neither is a bit-field.
[...]
[Accepted as a DR at the November, 2022 meeting.]
Consider:
// translation unit 1 export module A; export class X {}; // translation unit 2 import A; X x; // is X complete at this point?
Subclause 11.4.1 [class.mem.general] paragraph 8 specifies:
A class is considered a completely-defined object type (6.8.1 [basic.types.general]) (or complete type) at the closing } of the class-specifier. ...
The syntactic (even lexical) reference to the closing } does not address the question when a different translation unit regards a class as complete. However, it seems this provision is entirely redundant given 6.3 [basic.def.odr] paragraph 13:
A definition of a class shall be reachable in every context in which the class is used in a way that requires the class type to be complete.
The standard never asks the question: "Is class X complete?"; it always specifies "X shall be complete" (otherwise the program is ill-formed).
Possible resolution [SUPERSEDED]:
Change in 11.4.1 [class.mem.general] paragraph 8 as follows:
A class is considered a completely-defined object type (6.8.1 [basic.types.general]) (or complete type) at the closing } of the class-specifier. TheA class is regarded as complete within its complete-class contexts; otherwise it is regarded as incomplete within its own class member-specification.
CWG telecon 2022-10-21:
Explicitly refer to reachable definitions.
Proposed resolution (approved by CWG 2022-11-09):
Change in 11.4.1 [class.mem.general] paragraph 8 as follows:
A class is considered a completely-defined object type (6.8.1 [basic.types.general]) (or complete type) at the closing } of the class-specifier. TheA class is regarded as complete where its definition is reachable and within its complete-class contexts; otherwise it is regarded as incomplete within its own class member-specification.
[Accepted at the February, 2023 meeting.]
Constructors are not intended to have explicit object parameters, but the standard is missing a corresponding prohibition.
Proposed resolution (approved by CWG 2023-01-06):
Add a new paragraph after 11.4.5.1 [class.ctor.general] paragraph 7 as follows:
A constructor shall not be a coroutine.
A constructor shall not have an explicit object parameter (9.3.4.6 [dcl.fct]).
[Accepted as a DR at the February, 2023 meeting.]
Subclause 11.4.6 [class.copy.assign] paragraph 13 does not specify the semantics of a defaulted move assignment operator:
The implicitly-defined copy assignment operator for a union X copies the object representation (6.8.1 [basic.types.general]) of X. ...
In contrast, the corresponding rule for move constructors is present in 11.4.5.3 [class.copy.ctor] paragraph 15:
The implicitly-defined copy/move constructor for a union X copies the object representation (6.8.1 [basic.types.general]) of X. ...
Proposed resolution (approved by CWG 2023-01-27):
Change in 11.4.6 [class.copy.assign] paragraph 13 as follows:
The implicitly-defined copy/move assignment operator for a union X copies the object representation (6.8.1 [basic.types.general]) of X. ...
[Accepted as a DR at the February, 2023 meeting.]
Issue 600 was resolved by P1787R6, but no example was added.
Proposed resolution (approved by CWG 2023-01-06):
Change in 11.8.1 [class.access.general] paragraph 4 as follows:
... When a using-declarator is named, access control is applied to it, not to the declarations that replace it. For an overload set, access control is applied only to the function selected by overload resolution.[ Example:
struct S { void f(int); private: void f(double); }; void g(S* sp) { sp->f(2); // OK, access control applied after overload resolution }
-- end example ]
[Accepted as a DR at the February, 2023 meeting.]
Consider:
struct MyType { int i; double d; std::strong_ordering operator<=> (const MyType& c) const = default; };
The defaulted three-way comparison operator is defined only if it is used, per 11.10.1 [class.compare.default] paragraph 1:
A comparison operator function for class C that is defaulted on its first declaration and is not defined as deleted is implicitly defined when it is odr-used or needed for constant evaluation.
The current rules make an odr-use of the three-way comparison operator ill-formed, but it would be preferable if it were deleted instead. In particular, 11.10.3 [class.spaceship] bullet 2.2 specifies
If the synthesized three-way comparison of type R between any objects xi and xi is not defined, the operator function is defined as deleted.
This refers to bullets 1.2 and 1.3 of 11.10.3 [class.spaceship] paragraph 1:
The synthesized three-way comparison of type R (17.12.2 [cmp.categories]) of glvalues a and b of the same type is defined as follows:
- If a <=> b is usable (11.10.1 [class.compare.default]), static_cast<R>(a <=> b).
- Otherwise, if overload resolution for a <=> b is performed and finds at least one viable candidate, the synthesized three-way comparison is not defined.
- Otherwise, if R is not a comparison category type, or either the expression a == b or the expression a < b is not usable, the synthesized three-way comparison is not defined.
- Otherwise, ...
However, a <=> b is actually usable, because 11.10.1 [class.compare.default] paragraph 3 defines:
A binary operator expression a @ b is usable if eitherMyType().d <=> MyType().d is a valid expression.
- a or b is of class or enumeration type and overload resolution (12.2 [over.match]) as applied to a @ b results in a usable candidate, or
- neither a nor b is of class or enumeration type and a @ b is a valid expression.
Proposed resolution (approved by CWG 2022-11-11) [SUPERSEDED]:
The synthesized three-way comparison of type R (17.12.2 [cmp.categories]) of glvalues a and b of the same type is defined as follows:
- If a <=> b is usable (11.10.1 [class.compare.default]) and
- if overload resolution for a direct-initialization of an object or reference of type R from a <=> b results in a usable candidate, static_cast<R>(a <=> b),
- otherwise, the synthesized three-way comparison is not defined.
- Otherwise, if overload resolution for a <=> b is performed and finds at least one viable candidate, the synthesized three-way comparison is not defined.
- Otherwise, if R is not a comparison category type, or either the expression a == b or the expression a < b is not usable, the synthesized three-way comparison is not defined.
- Otherwise, ...
CWG 2023-02-06
A simplification of the wording is sought.
Proposed resolution (approved by CWG 2023-02-07):
The synthesized three-way comparison of type R (17.12.2 [cmp.categories]) of glvalues a and b of the same type is defined as follows:
- If a <=> b is usable (11.10.1 [class.compare.default]) and can be explicitly converted to R using static_cast, static_cast<R>(a <=> b).
- Otherwise, if overload resolution for a <=> b is performed and finds at least one viable candidate, the synthesized three-way comparison is not defined.
- Otherwise, if R is not a comparison category type, or either the expression a == b or the expression a < b is not usable, the synthesized three-way comparison is not defined.
- Otherwise, ...
[ Resolved by paper P2797R0, adopted in February 2023. ]
Subclause 12.2.2.2.1 [over.match.call.general] paragraph 2 specifies:
If the postfix-expression is the address of an overload set, overload resolution is applied using that set as described above. If the function selected by overload resolution is a non-static member function, the program is ill-formed.
However, 7.6.2.2 [expr.unary.op] paragraph 3 states that the address of an explicit object member function is a plain pointer to function, not a pointer to member. The former can be invoked using the regular function call syntax (7.6.1.3 [expr.call]) without the need for a pointer-to-member expression (7.6.4 [expr.mptr.oper]). For example, absent any overloading and given some function f, the expression (&A::f)(A()) could be valid if f is a static member function or an explicit object member function. However, that expression cannot possibly be valid if f is an implicit object member function.
Proposed resolution (approved by CWG 2023-01-27):
Change in 12.2.2.2.1 [over.match.call.general] paragraph 2 as follows:
If the postfix-expression is the address of an overload set, overload resolution is applied using that set as described above. If the function selected by overload resolution isa non-statican implicit object member function, the program is ill-formed.
EWG 2023-02-06
Subclause 12.3 [over.over] paragraph 4 says that explicit-object member functions match both kinds of pointer type, and 11.4.3 [class.mfct.non.static] paragraph 2 seems to try to transform (&A::f)(A()) into (&(*this).A::f)(A()) if f is an explicit-object member function.
Editor's observation: 12.3 [over.over] paragraph 4 was a missed edit applying P0847R7; it was rectified with commit 0c9dd96bb on 2023-01-17.
Additional notes (March, 2023)
The concern about incorrectly applying the this transformation was addressed by P2797R0 (Proposed resolution for CWG2692 Static and explicit object member functions with the same parameter-type-lists).
[Accepted at the February, 2023 meeting as part of paper P2797R0.]
(Split off from issue 2687.)
Consider:
struct A { static void f(A); void f(this A); void g(); }; void A::g() { (&A::f)(A()); // #1 (&A::f)(); // #2 }
It is obvious that #2 is ill-formed, but what about #1? One possible answer is to make such declarations conflict.
Suggested resolution:
Change in 6.4.1 [basic.scope.scope] paragraph 3, adding bullets, a follows:
Two function templates have corresponding signatures if
- their template-parameter-lists have the same length,
- their corresponding template-parameters are equivalent,
- they have equivalent
- parameter-type-lists or non-object-parameter-type-lists and
- return types (if any), and,
- if both are non-static members, they have corresponding object parameters.
Change in 6.4.1 [basic.scope.scope] bullet 4.3.1 as follows:
- both declare functions with the same parameter-type-list or non-object-parameter-type-list [Footnote: ...], equivalent (13.7.7.2 [temp.over.link]) trailing requires-clauses (if any, except as specified in 13.7.5 [temp.friend]), and, if both are non-static members, they have corresponding object parameters, or
CWG 2023-01-27
Forward to EWG to determine whether such member declarations are considered sufficiently confusing to outweigh concerns of language orthogonality; see plusplus/papers#1455.
[Accepted as a DR at the November, 2022 meeting.]
Contrary to the note in the subject paragraph, overload resolution in selecting a surrogate call function can prefer a different conversion operator for the implicit conversion sequence because the conversion function from which the surrogate call function was derived is not the best viable function.
For example, noting that surrogate call functions are not generated from conversion function templates, the single surrogate call function derived from the (non-template) conversion function below is the sole candidate for the call; however, the specialization of the conversion function template is a better candidate f or the implicit conversion sequence during overload resolution:
using ff = int (*)(int); constexpr int ffimpl0(int x) { return x; } constexpr int ffimpl1(int x) { return x + 1; } struct A { template <typename T> constexpr operator T() const { return ffimpl0; } constexpr operator ff() const volatile { return ffimpl1; } }; char x[A()(42.f)]; extern char x[43];
Proposed resolution (approved CWG 2022-11-08):
Change in 12.2.2.2.3 [over.call.object] paragraph 3 as follows:
[Note 1: When comparing the call against the function call operators, the implied object argument is compared against the object parameter of the function call operator. When comparing the call against a surrogate call function, the implied object argument is compared against the first parameter of the surrogate call function.The conversion function from which the surrogate call function was derived will be used in the conversion sequence for that parameter since it converts the implied object argument to the appropriate function pointer or reference required by that first parameter.—end note]
[Accepted as a DR at the February, 2023 meeting.]
Consider:
#include <compare> enum class E : int { Lo = 0, Hi = 1 }; constexpr auto operator<=>(E lhs, E rhs) -> std::strong_ordering { return (int)rhs <=> (int)lhs; } // everybody agrees this is true static_assert((E::Lo <=> E::Hi) == std::strong_ordering::greater); // gcc rejects this, msvc and clang accept static_assert(E::Lo > E::Hi); // #1
The intent here is for the user-provided operator<=> to suppress the built-in operator<=> for E. And gcc, clang, and msvc all agree that this does happen when the comparison expression explicitly uses a <=> b.
But when the comparison expression is a @ b for one of the relational operators, gcc disagrees, conforming to 12.2.2.3 [over.match.oper] bullet 3.3:
For all other operators, the built-in candidates include all of the candidate operator functions defined in 12.5 [over.built] that, compared to the given operator, ... do not have the same parameter-type-list as any non-member candidate that is not a function template specialization.
The issue is that, for #1, the user-provided operator<=> is not a non-member candidate, but a rewritten candidate. A similar situation arises for a user-declared operator==, which will be called for e1 == e2, but not for e1 != e2. Again, clang and MSVC disagree.
Proposed resolution (January, 2023) [SUPERSEDED]:
Change 12.2.2.3 [over.match.oper] bullet 3.3.4 as follows:
Proposed resolution (approved by CWG 2023-02-10):
Change 12.2.2.3 [over.match.oper] bullet 3.3.4 as follows:
[Accepted as a DR at the February, 2023 meeting.]
Subclause 12.2.2.9 [over.match.class.deduct] paragraph 3 has an exception only for deduction failure for non-deduced contexts when deducing the return type from the defining-type-id, but not for other cases where deduction fails according to 13.10.3.6 [temp.deduct.type] paragraph 2. For example,
template <class S1, class S2> struct C { C(...); }; template<class T1> C(T1) -> C<T1, T1>; template<class T1, class T2> C(T1, T2) -> C<T1 *, T2>; template<class V1, class V2> using A = C<V1, V2>; C c1{""}; A a1{""}; C c2{"", 1}; A a2{"", 1};
resulting in A having neither of these deduction guides. There is implementation divergence in the handling of this example.
Suggested resolution:
We could say that cases where P involves a template parameter and A is not of the same form (under 13.10.3.6 [temp.deduct.type] paragraph 8) are non-deduced contexts for the purpose of these deductions. That should be enough to make it clear what happens for a2, where we'd deduce T2 = V2, and not deduce anything for T1, but wouldn't fix a1 due to the inconsistent deductions for T1; maybe this is what MSVC is doing. We could further fix a1 by allowing inconsistent deductions and treating them as if no value was deduced. Another option might be to do independent deductions for each template argument of the simple-template-id, and then try to merge the results for template arguments where deduction was successful; that'd be clearer that deduction can't fail, but would deduce less.
CWG 2023-02-08
In the example, A is the most trivial alias template imaginable; having this cause issues depending on the details of C is concerning.
Proposed resolution (approved by CWG 2023-02-09):
Change in 12.2.2.9 [over.match.class.deduct] paragraph 3 as follows:
... For each function or function template f in the guides of the template named by the simple-template-id of the defining-type-id, the template arguments of the return type of f are deduced from the defining-type-id of A according to the process in 13.10.3.6 [temp.deduct.type] with the exception that deduction does not fail if not all template arguments are deduced. If deduction fails for another reason, proceed with an empty set of deduced template arguments Let g denote the result of substituting these deductions into f. ...
[Accepted as a DR at the February, 2023 meeting.]
Consider:
template<typename T, std::size_t N> struct A { T array[N]; }; A a = { "meow" };
The current wording says in 12.2.2.9 [over.match.class.deduct] bullet 1.8:
- if ei is of array type and xi is a braced-init-list or string-literal, Ti is an rvalue reference to the declared type of ei, and
- ...
This will fail overload resolution, because a string literal (which is an lvalue) does not match a parameter of type T (&&)[N].
Proposed resolution (approved by CWG 2023-02-07):
Change in 12.2.2.9 [over.match.class.deduct] paragraph 1.8 as follows:
- if ei is of array type and xi is a braced-init-list
or string-literal, Ti is an rvalue reference to the declared type of ei, and- if ei is of array type and xi is a string-literal, Ti is an lvalue reference to the const-qualified declared type of ei, and
- ...
Append to the example in 12.2.2.9 [over.match.class.deduct] paragraph 2 as follows:
G g(true, 'a', 1); // OK, deduces G<char, bool>
template<class T, std::size_t N> struct H { T array[N]; }; template<class T, std::size_t N> struct I { volatile T array[N]; }; template<std::size_t N> struct J { unsigned char array[N]; }; H h = { "abc" }; // OK, deduces H<char, 4> (not T = const char) I i = { "def" }; // OK, deduces I<char, 4> J j = { "ghi" }; // error: cannot bind reference to array of unsigned char to array of char in deduction
[Accepted as a DR at the February, 2023 meeting.]
Consider:
template <class T> struct A { T ar[4]; }; A a = { "foo" };
Subclause 12.2.2.9 [over.match.class.deduct] bullet 1.5 specifies:
For each xi, let ei be the corresponding aggregate element of C or of one of its (possibly recursive) subaggregates that would be initialized by xi (9.5.2 [dcl.init.aggr]) if
- brace elision is not considered for any aggregate element that has a dependent non-array type or an array type with a value-dependent bound, and
- ...
The normative rule does not properly consider arrays with dependent element type, initialized by a string literal. MSVC accepts, gcc and clang reject the example.
Suggested resolution [SUPERSEDED]:
Change in 12.2.2.9 [over.match.class.deduct] bullet 1.5 as follows:
For each xi, let ei be the corresponding aggregate element of C or of one of its (possibly recursive) subaggregates that would be initialized by xi (9.5.2 [dcl.init.aggr]) if
- brace elision is not considered for any aggregate element that has
- a dependent non-array type
or,- an array type with a value-dependent bound, or
- a string literal as the corresponding initializer and that has an array type whose element type is a (possibly cv-qualified) template parameter or is a dependent type specified with a qualified-id whose nested-name-specifier is dependent or with a decltype-specifier; and
- ...
Proposed resolution (approved by CWG 2023-02-09):
Change in 12.2.2.9 [over.match.class.deduct] bullet 1.5 as follows:
For each xi, let ei be the corresponding aggregate element of C or of one of its (possibly recursive) subaggregates that would be initialized by xi (9.5.2 [dcl.init.aggr]) if
- brace elision is not considered for any aggregate element that has
- a dependent non-array type
or,- an array type with a value-dependent bound, or
- an array type with a dependent array element type and xi is a string literal; and
- ...
[Accepted as a DR at the November, 2022 meeting.]
Post-Prague "editorial" change cplusplus/draft#3625 removed the normative text supporting the interpretation that surrogate call functions, when chosen, call the functions they were formed from.
Proposed resolution (approved by CWG 2022-11-08):
Change in 12.4.4 [over.call] paragraph 1 as follows:
If a surrogate call functionfor a conversion function named operator conversion-type-idis selected, let e be the result of invoking the corresponding conversion operator function on the postfix-expression;is invokedthe expression is interpreted asOtherwise, ...postfix-expression . operator conversion-type-id ()e ( expression-listopt )
[Accepted as a DR at the February, 2023 meeting.]
The example in 12.6 [over.literal] paragraph 8 has the following lines:
double operator""_Bq(long double); // OK: does not use the reserved identifier _Bq (5.10) double operator"" _Bq(long double); // ill-formed, no diagnostic required: // uses the reserved identifier _Bq (5.10)
The referenced rule in 5.11 [lex.name] is in bullet 3.1:
Each identifier that contains a double underscore __ or begins with an underscore followed by an uppercase letter is reserved to the implementation for any use.
The distinction being drawn in the user-defined literal example apparently relies on the grammar for literal-operator-id at the beginning of 12.6 [over.literal]:
The second production does not mention the syntactic non-terminal identifier, so the literal-operator-id operator""_Bq presumably does not run afoul of the restriction in 5.11 [lex.name]. However, the grammar for user-defined-string-literal in 5.13.9 [lex.ext] is:
There doesn't seem to be a rule that exempts the identifier that is the ud-suffix of a user-defined-string-literal from the restriction in 5.11 [lex.name]. Either the example is incorrect or there needs to be a refinement of the rule in 5.11 [lex.name].
CWG 2022-11-11
CWG feels that the ostensible significance of whitespace in this context is unfortunate. In addition, since the normative rule is not consistent with the example, CWG solicits EWG input on the handling of this issue via cplusplus/papers#1367.
EWG 2023-02-06
EWG had consensus on "The form of User Defined Literals that permits a space between the quotes and the name of the literal should be deprecated, and eventually removed. Additionally, the UDL name should be excluded from the restriction in 5.11 [lex.name] in the non-deprecated form (sans space)."
Proposed resolution (February, 2023) [SUPERSEDED]:
Change in 5.11 [lex.name] paragraph 3 as follows:
In addition, some identifiers appearing as a token or pp-token are reserved for use by C++ implementations and shall not be used otherwise; no diagnostic is required.
Change in 12.6 [over.literal] paragraph 1 as follows:
The string-literal or user-defined-string-literal in a literal-operator-id shall have no encoding-prefix and shall contain no characters other than the implicit terminating '\0'. The ud-suffix of the user-defined-string-literal or the identifier in a literal-operator-id is called a literal suffix identifier. The first form of literal-operator-id is deprecated. Some literal suffix identifiers are reserved for future standardization; see 16.4.5.3.6 [usrlit.suffix]. A declaration whose literal-operator-id uses such a literal suffix identifier is ill-formed, no diagnostic required.
Change in 12.6 [over.literal] paragraph 8 as follows:
void operator ""_km(long double); // OK string operator "" _i18n(const char*, std::size_t); // OK, deprecated template <char...> double operator ""_\u03C0(); // OK, UCN for lowercase pi float operator ""_e(const char*); // OK float operator ""E(const char*); // ill-formed, no diagnostic required: // reserved literal suffix ([usrlit.suffix], [lex.ext]) double operator""_Bq(long double); // OK, does not use the reserved identifier _Bq ([lex.name]) double operator"" _Bq(long double); // ill-formed, no diagnostic required: // uses the reserved identifier _Bq ([lex.name]) float operator " "B(const char*); // error: non-empty string-literal string operator ""5X(const char*, std::size_t); // error: invalid literal suffix identifier double operator ""_miles(double); // error: invalid parameter-declaration-clause template <char...> int operator ""_j(const char*); // error: invalid parameter-declaration-clause extern "C" void operator ""_m(long double); // error: C language linkage
Add a new subclause after D.6 [depr.impldec]:
D.9 Literal operator function declarations using an identifier [depr.lit]
A literal-operator-id (12.6 [over.literal]) of the form
operator string-literal identifieris deprecated.
CWG 2023-02-07
Some implementers are concerned about the lack of space for implementation extensions. The suggestion is to reserve literal suffix identifiers starting with two underscores for the implementation in 16.4.5.3.6 [usrlit.suffix]. EWG is invited to comment on that direction.
EWG / LEWG 2023-02-07
EWG and LEWG resolved to amend the proposed resolution for CWG2521 to reserve literal suffix identifiers with double underscores anywhere for implementation use.
Proposed resolution (approved by CWG 2023-02-09):
Change in 5.11 [lex.name] paragraph 3 as follows:
In addition, some identifiers appearing as a token or pp-token are reserved for use by C++ implementations and shall not be used otherwise; no diagnostic is required.
In 5.13.9 [lex.ext], modify all occurrences as follows:
operator ""X
Change in 5.13.9 [lex.ext] paragraph 7 as follows:
long double operator ""_w(long double); std::string operator ""_w(const char16_t*, std::size_t); unsigned operator ""_w(const char*); int main() { 1.2_w; // calls operator ""_w(1.2L) u"one"_w; // calls operator ""_w(u"one", 3) 12_w; // calls operator ""_w("12") "two"_w; // error: no applicable literal operator }
Change in 12.6 [over.literal] paragraph 1 as follows:
The string-literal or user-defined-string-literal in a literal-operator-id shall have no encoding-prefix and shall contain no characters other than the implicit terminating '\0'. The ud-suffix of the user-defined-string-literal or the identifier in a literal-operator-id is called a literal suffix identifier. The first form of literal-operator-id is deprecated. Some literal suffix identifiers are reserved for future standardization; see 16.4.5.3.6 [usrlit.suffix]. A declaration whose literal-operator-id uses such a literal suffix identifier is ill-formed, no diagnostic required.
Change in 12.6 [over.literal] paragraph 8 as follows:
void operator ""_km(long double); // OK string operator "" _i18n(const char*, std::size_t); // OK, deprecated template <char...> double operator ""_\u03C0(); // OK, UCN for lowercase pi float operator ""_e(const char*); // OK float operator ""E(const char*); // ill-formed, no diagnostic required: // reserved literal suffix ([usrlit.suffix], [lex.ext]) double operator""_Bq(long double); // OK, does not use the reserved identifier _Bq ([lex.name]) double operator"" _Bq(long double); // ill-formed, no diagnostic required: // uses the reserved identifier _Bq ([lex.name]) float operator " "B(const char*); // error: non-empty string-literal string operator ""5X(const char*, std::size_t); // error: invalid literal suffix identifier double operator ""_miles(double); // error: invalid parameter-declaration-clause template <char...> int operator ""_j(const char*); // error: invalid parameter-declaration-clause extern "C" void operator ""_m(long double); // error: C language linkage
Change in 16.4.5.3.6 [usrlit.suffix] as follows:
Literal suffix identifiers (12.6 [over.literal]) that do not start with an underscore are reserved for future standardization. Literal suffix identifiers that contain a double underscore __ are reserved for use by C++ implementations.
Add a new subclause after D.6 [depr.impldec]:
D.9 Literal operator function declarations using an identifier [depr.lit]
A literal-operator-id (12.6 [over.literal]) of the form
operator string-literal identifieris deprecated.
[Accepted as a DR at the February, 2023 meeting.]
In 13.1 [temp.pre] paragraph 8, the phrase "templated entity" is defined. The derived term "templated function" is never actually defined, but is intended to apply to function templates as well as non-template members of class templates. Similarly, the phrases "templated variable" and "templated class" should be properly defined.
Proposed resolution (approved by CWG 2023-01-27):
Change in 9.2.9.7.1 [dcl.spec.auto.general] paragraph 12 as follows:
Return type deduction for a templatedentity that is afunctionor function templatewith a placeholder in its declared type occurs when the definition is instantiated even if the function body contains a return statement with a non-type-dependent operand.
Change in 13.1 [temp.pre] paragraph 8 as follows:
[Note 6: A local class, a local or block variable, or a friend function defined in a templated entity is a templated entity. —end note]
A templated function is a function template or a function that is templated. A templated class is a class template or a class that is templated. A templated variable is a variable template or a variable that is templated.
[Accepted as a DR at the November, 2022 meeting.]
13.7.4 [temp.variadic] paragraph 10 expands a fold-expression (including its enclosing parentheses) to an unparenthesized expression. If interpreted literally, this could result in reassociation and misinterpretation of the expression. For example, given:
template<int ...N> int k = 2 * (... + N);
... k<1, 2, 3> is specified as expanding to int k<1, 2, 3> = 2 * 1 + (2 + 3); resulting in a value of 7 rather than the intended value of 12.
Further, there is implementation divergence for the following example:
#include <type_traits> template<class ...TT> void f(TT ...tt) { static_assert(std::is_same_v<decltype((tt, ...)), int&>); } template void f(int /*,int*/);
gcc and MSVC apply the general expression interpretation
of decltype
, whereas clang and icc apply
the identifier special case.
Proposed resolution (approved by CWG 2022-08-26):
Change in 13.7.4 [temp.variadic] paragraph 10 as follows:The instantiation of a fold-expression (7.5.7 [expr.prim.fold]) produces:...
- ( ((E1 op E2 ) op . . . ) op EN ) for a unary left fold,
- ( E1 op (. . . op (EN-1 op EN )) ) for a unary right fold,
- ( (((E op E1 ) op E2 ) op . . . ) op EN ) for a binary left fold, and
- ( E1 op (. . . op (EN-1 op (EN op E))) ) for a binary right fold.
[Accepted as a DR at the November, 2022 meeting.]
In C++20, 13.7.7.2 [temp.over.link] paragraph 7 defined equivalence for function templates in terms of equivalence of several of its components; functional equivalence for them was similar in that it was defined recursively for their "return types and parameter lists", but differed with regard to constraints in that it required that they "accept and are satisfied by the same set of template argument lists". P1787R6 simplified the treatment by relying entirely on the "depends on whether two constructs are equivalent, and they are functionally equivalent but not equivalent" rule to make the correspondence check between the function templates ill-formed, no diagnostic required.
This created a situation where moving a constraint between a template-head and a requires-clause makes a function template truly different (because there is no reasonable way to read 6.4.1 [basic.scope.scope] bullet 4.3.2's "equivalent [...], template-heads, and trailing requires-clauses (if any)" as requiring a joint check for functional equivalence), even if overload resolution would never be able to distinguish them.
Suggested resolution [SUPERSEDED]:
Change in 13.7.7.2 [temp.over.link] paragraph 7 as follows:
If the validity or meaning of the program depends on whether two constructs are equivalent, and they are functionally equivalent but not equivalent, the program is ill-formed, no diagnostic required. Furthermore, if two function templates do not correspond, but accept and are satisfied by the same set of template argument lists, the program is ill-formed, no diagnostic required.
Suggested resolution (August, 2022) [SUPERSEDED]:
Append to 6.4.1 [basic.scope.scope] paragraph 3 as follows:
Change in 6.4.1 [basic.scope.scope] paragraph 4 as follows:
Change in 13.7.7.2 [temp.over.link] paragraph 7 as follows:
If the validity or meaning of the program depends on whether two constructs are equivalent, and they are functionally equivalent but not equivalent, the program is ill-formed, no diagnostic required. Furthermore, if two function templates with corresponding signatures do not correspond, but accept and are satisfied by the same set of template argument lists, the program is ill-formed, no diagnostic required.
Proposed resolution (approved by CWG 2022-09-09):
Append to 6.4.1 [basic.scope.scope] paragraph 3 as follows:
Change in 6.4.1 [basic.scope.scope] paragraph 4 as follows:
Change in 13.7.7.2 [temp.over.link] paragraph 7 as follows:
If the validity or meaning of the program depends on whether two constructs are equivalent, and they are functionally equivalent but not equivalent, the program is ill-formed, no diagnostic required. Furthermore, if two function templates that do not correspondthe program is ill-formed, no diagnostic required.
- have the same name,
- have corresponding signatures (6.4.1 [basic.scope.scope]),
- would declare the same entity (6.6 [basic.link]) considering them to correspond, and
- accept and are satisfied by the same set of template argument lists,
[Accepted as a DR at the November, 2022 meeting.]
The grammar for a concept-definition does not include an attribute-specifier-seqopt, making it impossible to deprecate a concept. This seems like an oversight.
CWG telecon 2022-10-07:
Agreed.
Proposed resolution (approved by CWG 2022-10-21):
Change in 9.13.4 [dcl.attr.deprecated] paragraph 2 as follows:
The attribute may be applied to the declaration of a class, a typedef-name, a variable, a non-static data member, a function, a namespace, an enumeration, an enumerator, a concept, or a template specialization.
Change in 13.7.9 [temp.concept] paragraph 1 as follows:
A concept is a template that defines constraints on its template arguments.concept-definition: concept concept-name attribute-specifier-seqopt = constraint-expression ; concept-name: identifierA concept-definition declares a concept. Its identifier becomes a concept-name referring to that concept within its scope. The optional attribute-specifier-seq appertains to the concept.
[Accepted as a DR at the November, 2022 meeting.]
The status of an example like the following is unclear:
template<typename T> T T(T) {}
According to 13.8.2 [temp.local] paragraph 6,
The name of a template-parameter shall not be bound to any following declaration contained by the scope to which the template-parameter belongs. [Example 5:
... template<class X> class X; // error: hidden by template-parameter
—end example]
The intent would appear to be that the function template could not have the same name as the template parameter. However, according to 6.4.9 [basic.scope.temp] paragraph 2,
Each template-declaration D introduces a template parameter scope that extends from the beginning of its template-parameter-list to the end of the template-declaration. Any declaration outside the template-parameter-list that would inhabit that scope instead inhabits the same scope as D.
This would indicate that the function template inhabits the namespace scope, not the template parameter scope, so the prohibition against use of the template parameter name would not apply.
To reject both the function and class template examples, 13.8.2 [temp.local] paragraph 6 could be changed to read:
The name of a template-parameter shall not be bound to any following declaration whose locus is contained by the scope to which the template-parameter belongs.
To accept both examples, the change could be:
The name of a template-parameter shall not be bound to any following declaration that inhabits a scope contained by the scope to which the template-parameter belongs.
Notes from the December, 2021 teleconference:
The consensus of CWG was to reject both examples, i.e., the first option.
Additional note (December, 2021):
It was observed that this issue is, strictly speaking, not a defect: the word “contains” is used in 6.4.1 [basic.scope.scope] paragraph 1 in its usual English sense to refer to the lexical nesting of scopes, so the template parameter scope of T “contains” the declaration of the function T. However, the use of the term “locus” would make the intent clearer.
Proposed resolution (December, 2021):
Change 13.8.2 [temp.local] paragraph 6 as follows:
The name of a template-parameter shall not be bound to any following declaration whose locus is contained by the scope to which the template-parameter belongs.
[Resolved by issue 2631, which was accepted as a DR at the November, 2022 meeting.]
Non-static data member initializers get the same late parsing as member functions and default arguments, but are they also instantiated as needed like them? And when is their validity checked?
Notes from the October, 2012 meeting:
CWG agreed that non-static data member initializers should be handled like default arguments.
Additional note (March, 2013):
Determining whether a defaulted constructor is constexpr or not requires parsing the class's non-static data member initializers; see also issue 1360.
CWG 2022-11-11
Resolved by issue 2631.
[Accepted as a DR at the February, 2023 meeting.]
Default argument instantiation is described in 13.9.2 [temp.inst] paragraph 12, and although instantiation of default arguments for member functions of class templates is mentioned elsewhere a number of times, this paragraph only describes default argument instantiation for function templates.
Proposed resolution (approved by CWG 2023-02-06):
Change in 13.9.2 [temp.inst] paragraph 12 as follows:
If a templated functiontemplatef is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point, except that the scope in which a closure type is declared (7.5.6.2 [expr.prim.lambda.closure]) --- and therefore its associated namespaces --- remain as determined from the context of the definition for the default argument. This analysis is called default argument instantiation. The instantiated default argument is then used as the argument of f.
[Accepted as a DR at the February, 2023 meeting.]
According to 13.9.4 [temp.expl.spec] paragraph 16,
A member or a member template of a class template may be explicitly specialized for a given implicit instantiation of the class template, even if the member or member template is defined in the class template definition. An explicit specialization of a member or member template is specified using the syntax for explicit specialization.
The relationship between this construct and paragraph 14 is not clear:
Whether an explicit specialization of a function or variable template is inline, constexpr, or an immediate function is determined by the explicit specialization and is independent of those properties of the template.
(See also 9.2.6 [dcl.constexpr] paragraph 1, note 1.) Is this intended to apply to explicit specializations of members of implicitly-instantiated class templates? For example:
template<typename T> struct S { int f(); constexpr int g(); }; template<> constexpr int S<int>::f() { // OK, constexpr? return 0; } template<> int S<int>::g() { // OK, not constexpr? return 0; }
There is implementation divergence on the treatment of this example. This divergence may relate to interpretation of the requirement in 9.2.6 [dcl.constexpr] paragraph 1,
If any declaration of a function or function template has a constexpr or consteval specifier, then all its declarations shall contain the same specifier.
Is an explicit specialization of a member of an implicitly-instantiated class template a declaration of that member? A similar question also applies to the constinit specifier as specified in 9.2.7 [dcl.constinit] paragraph 1:
If the specifier is applied to any declaration of a variable, it shall be applied to the initializing declaration.
(Note that constinit is not mentioned in 13.9.4 [temp.expl.spec] paragraph 14.) For example:
template<typename T> struct S { static constinit T x; }; template<> int S<int>::x = 10; // constinit required? extern char c; template<> short S<char>::x = c; // error, c not constant?
(Possibly relevant is the fact that default arguments are prohibited in explicit specializations of member functions of implicitly-instantiated class templates, per 13.9.4 [temp.expl.spec] bullet 21.3.)
CWG 2022-11-10
A specialization of a member of a class template redeclares the member of the primary template and thus the redeclaration rules apply. In passing, it was noticed that the rule governing explicit specializations in general omitted treatment of constinit, which was considered an oversight.
Proposed resolution (approved 2023-02-09):
Change in 13.9.4 [temp.expl.spec] paragraph 13 as follows:
Whether an explicit specialization of a function or variable template is inline, constexpr, constinit, or constevalan immediate functionis determined by the explicit specialization and is independent of those properties of the template.
[Accepted as a DR at the November, 2022 meeting.]
It is unclear whether an explicit template specialization "inherits" the attributes written on the primary template, or whether the specialization has to repeat the attributes. For example:
template <typename Ty>
[[noreturn]] void func(Ty);
template <>
void func<int>(int) {
// Warning about returning from a noreturn function or not?
}
A similar question arises for attributes written on the parameters of the primary function template. For example:
template <typename Ty>
void func([[maybe_unused]] int i);
template <>
void func<int>(int i) {
// i is not used, should it be warned on or not?
}
There is implementation divergence for the example.
Suggested resolution [SUPERSEDED]:
Change in 13.9.4 [temp.expl.spec] paragraph 13 as follows:
Any attributes applying to any part of the declaration of an explicit specialization of a function or variable template, as well asWhetherwhether such an explicit specializationof a function or variable templateis inline, constexpr, or an immediate function, is determined by the explicit specialization and is independent of those properties of the template. [ Note: Attributes that would affect the association of the declaration of an explicit specialization with the declaration of the primary template need to match. -- end note ]
Proposed resolution (approved by CWG 2022-11-09):
Change 13.9.4 [temp.expl.spec] paragraph 13 as follows:
Whether an explicit specialization of a function or variable template is inline, constexpr, or an immediate function is determined by the explicit specialization and is independent of those properties of the template. Similarly, attributes appearing in the declaration of a template have no effect on an explicit specialization of that template. [Example 7:template<class T> void f(T) { /* ... */ } template<class T> inline T g(T) { /* ... */ } template<> inline void f<>(int) { /* ... */ } // OK, inline template<> int g<>(int) { /* ... */ } // OK, not inline template<typename> [[noreturn]] void h([[maybe_unused]] int i); template<> void h<int>(int i) { // Implementations are expected not to warn that the function returns but can // warn about the unused parameter. }—end example]
[Accepted as a DR at the November, 2022 meeting.]
Subclause 13.10.3.1 [temp.deduct.general] paragraph 7 specifies:
The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions. The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered. If substitution into different declarations of the same function template would cause template instantiations to occur in a different order or not at all, the program is ill-formed; no diagnostic required.
[Note 4: The equivalent substitution in exception specifications is done only when the noexcept-specifier is instantiated, at which point a program is ill-formed if the substitution results in an invalid type or expression. —end note]
The note says that substitution into the noexcept-specifier occurs late, but the normative text does not support that, because the exception specification is part of the function type.
Subclause 13.10.3.1 [temp.deduct.general] paragraph 8 specifies:
Only invalid types and expressions in the immediate context of the function type, its template parameter types, and its explicit-specifier can result in a deduction failure.
However, paragraph 7 does not mention the explicit-specifier when describing the substitution.
Proposed resolution (approved by CWG 2022-09-09):
Change in 13.10.3.1 [temp.deduct.general] paragraph 7 as follows:
The deduction substitution loci are
The substitution occurs in all types and expressions that are used in the
- the function type outside of the noexcept-specifier,
- the explicit-specifier, and
- the template parameter declarations.
function type and in template parameter declarationsdeduction substitution loci. ...
Change in 13.10.3.1 [temp.deduct.general] paragraph 8 as follows:
...Only invalidInvalid types and expressions can result in a deduction failure only in the immediate context of thefunction type, its template parameter types, and its explicit-specifierdeduction substitution locican result in a deduction failure.
[Accepted as a DR at the November, 2022 meeting.]
The example intends to illustrate that a class type cannot be the type of a non-type template parameter (although the example is still ill-formed because "T()" is interpreted as a function type).
Proposed resolution (approved by CWG 2022-11-08):
Change in 13.10.3.1 [temp.deduct.general] bullet 11.8 as follows:
template <class T, T> struct S {}; template <class T> int f(S<T,-- end example ]T()T{}>*); // #1 class X { int m; }; int i0 = f<X>(0); // #1 uses a value of non-structural type X as a non-type template argument
[Accepted as a DR at the November, 2022 meeting.]
The rule in 13.10.3.4 [temp.deduct.conv] bullet 5.2 seems to allow
template<class T,bool B> using get=T(*)() noexcept(B); struct A { template<class T> operator get<T,false>() const; }; auto *p=A().operator get<int,true>();
Proposed resolution (approved by CWG 2022-11-09):
Change in 13.10.3.4 [temp.deduct.conv] paragraph 1 as follows:
... If the conversion-function-id is constructed during overload resolution ([over.match.funcs]), thefollowing transformationsrules in the remainder of this subclause apply.
Change in 13.10.3.4 [temp.deduct.conv] bullet 5.2 as follows:
However, certain attributes of A may be ignored:
- ...
- If the original A is a function pointer or pointer-to-member-function type with a potentially-throwing exception specification (14.5 [except.spec]),
its noexceptthe exception specification.- ...
[Accepted as a DR at the November, 2022 meeting.]
Subclause 14.3 [except.ctor] paragraph 3 specifies:
If the initialization or destruction of an object other than by delegating constructor is terminated by an exception, the destructor is invoked for each of the object's direct subobjects and, for a complete object, virtual base class subobjects, whose initialization has completed (9.5 [dcl.init]) and whose destructor has not yet begun execution, except that in the case of destruction, the variant members of a union-like class are not destroyed.
A traditional implementation has no way of knowing which subobjects are in the state that their initialization has completed but their destructor has not yet begun execution. For example, the program might call the destructor explicitly, and the implementation does not track whether that has happened. The intent here is that it only matters whether the implied destructor call generated implicitly as part of the class object's destructor has begun yet, but it does not say that, and the reference to variant members reinforces the interpretation that the set of subobjects that are destroyed is determined dynamically based on which objects are within their lifetimes. Also, combining construction and destruction rules here confuses the matter further -- in "whose initialization has completed and whose destructor has not yet begun" we care exclusively about the first part in constructors and exclusively about the second part in destructors.
The set of things that we actually want to destroy here is the things that were initialized by the initialization (constructor or aggregate initializer) itself, not the things that have been constructed and not destroyed by evaluations that the initialization happens to perform. For example:
struct A { union { T x; U y; }; A() { throw "does not destroy x"; } A(int) : x() { throw "does destroy x"; } A(float) : x() { x.~T(); throw "still destroys x, oops"; } A(double) : x() { x.~T(); new(&y) U(); throw "destroys x, does not destroy y"; } };
and similarly for aggregate initialization:
struct B { union { T x; U y; }; int a; }; B b = { .x = {}, .a = (b.x.~T(), new (&b.y) U(), throw "destroys x not y")};
Destruction is completely different: we just want to destroy all the things that the destructor was going to destroy anyway and hasn't already started destroying.
Suggested resolution [SUPERSEDED]:
Change in 14.3 [except.ctor] paragraph 3 as follows:
If the initialization
or destructionof an object other than by delegating constructor is terminated by an exception, the destructor is invoked for each of the object'sdirect subobjects and, for a complete object, virtual base classsubobjects that were directly initialized by the object's initialization and,whose initialization has completed (9.5 [dcl.init])and whose destructor has not yet begun execution, except that in the case of destruction, the variant members of a union-like class are not destroyed. A subobject is directly initialized if its initialization is specified in 11.9.3 [class.base.init] for initialization by constructor, in 11.9.4 [class.inhctor.init] for initialization by inherited constructor, in 9.5.2 [dcl.init.aggr] for aggregate initialization, or in 9.5.1 [dcl.init.general] for default-initialization, value-initialization, or direct-initialization of an array. [Note: This includes virtual base class subobjects if the initialization is for a complete object, and can include variant members that were nominated explicitly by a mem-initializer or designated-initializer-clause or that have a default member initializer. -- end note]If the destructor of an object is terminated by an exception, each destructor invocation that would be performed after executing the body of the destructor (11.4.7 [class.dtor]) and that has not yet begun execution is performed. [Note: This includes virtual base class subobjects if the destructor was invoked for a complete object. -- end note ]
Proposed resolution (CWG telecon 2022-08-12) [SUPERSEDED]:
Change in 14.3 [except.ctor] paragraph 3 as follows:
If the initialization
or destructionof an object other than by delegating constructor is terminated by an exception, the destructor is invoked for each of the object'sdirect subobjects and, for a complete object, virtual base classsubobjects that were known to be initialized by the object's initialization and,whose initialization has completed (9.5 [dcl.init])and whose destructor has not yet begun execution, except that in the case of destruction, the variant members of a union-like class are not destroyed. A subobject is known to be initialized if its initialization is specified in 11.9.3 [class.base.init] for initialization by constructor, in 11.9.4 [class.inhctor.init] for initialization by inherited constructor, in 9.5.2 [dcl.init.aggr] for aggregate initialization, or in 9.5.1 [dcl.init.general] for default-initialization, value-initialization, or direct-initialization of an array. [Note: This includes virtual base class subobjects if the initialization is for a complete object, and can include variant members that were nominated explicitly by a mem-initializer or designated-initializer-clause or that have a default member initializer. -- end note]If the destructor of an object is terminated by an exception, each destructor invocation that would be performed after executing the body of the destructor (11.4.7 [class.dtor]) and that has not yet begun execution is performed. [Note: This includes virtual base class subobjects if the destructor was invoked for a complete object. -- end note ]
Additional notes (August, 2022):
The proposed resolution above does not handle the situation where the initialization of a closure object is terminated by an exception during the evaluation of a lambda expression. It also does not handle 11.4.5.3 [class.copy.ctor] bullet 14.1 (array copies in defaulted constructors).
Proposed resolution (approved by CWG telecon 2022-09-09):
Change in 14.3 [except.ctor] paragraph 3 as follows:
If the initialization
or destructionof an object other than by delegating constructor is terminated by an exception, the destructor is invoked for each of the object'sdirect subobjects and, for a complete object, virtual base classsubobjects that were known to be initialized by the object's initialization and,whose initialization has completed (9.5 [dcl.init])and whose destructor has not yet begun execution, except that in the case of destruction, the variant members of a union-like class are not destroyed. [Note: If such an object has a reference member that extends the lifetime of a temporary object, this ends the lifetime of the reference member, so the lifetime of the temporary object is effectively not extended. —end note] A subobject is known to be initialized if its initialization is specified[Note: This includes virtual base class subobjects if the initialization is for a complete object, and can include variant members that were nominated explicitly by a mem-initializer or designated-initializer-clause or that have a default member initializer. —end note]
- in 11.9.3 [class.base.init] for initialization by constructor,
- in 11.4.5.3 [class.copy.ctor] for initialization by defaulted copy/move constructor,
- in 11.9.4 [class.inhctor.init] for initialization by inherited constructor,
- in 9.5.2 [dcl.init.aggr] for aggregate initialization,
- in 7.5.6.3 [expr.prim.lambda.capture] for the initialization of the closure object when evaluating a lambda-expression,
- in 9.5.1 [dcl.init.general] for default-initialization, value-initialization, or direct-initialization of an array.
If the destructor of an object is terminated by an exception, each destructor invocation that would be performed after executing the body of the destructor (11.4.7 [class.dtor]) and that has not yet begun execution is performed. [Note: This includes virtual base class subobjects if the destructor was invoked for a complete object. —end note]
The subobjects are destroyed in the reverse order of the completion of their construction. Such destruction is sequenced before entering a handler of the function-try-block of the constructor or destructor, if any.
[Accepted at the November, 2022 meeting.]
Paper P1774R8 (accepted in July, 2022) adds a new attribute assume, but neglects to update table 22 in 15.2 [cpp.cond].
Proposed resolution (accepted by CWG 2022-08-26):
In 15.2 [cpp.cond], add a row to table tab:cpp.cond.ha as follows:
Attribute Value assume 202207L
[Accepted as a DR at the February, 2023 meeting.]
It should be clarified via an example or a note that named module imports do not make macros available.
Proposed resolution (approved by CWG 2023-01-06):
Change in 10.3 [module.import] paragraph 7 as follows:
... These rules can in turn lead to the importation of yet more translation units. [ Note: Such indirect importation does not make macros available, because a translation unit is a sequence of tokens in translation phase 7 (5.2 [lex.phases]). Macros can be made available by directly importing header units as described in 15.6 [cpp.import]. -- end note ]
Add to the example in 15.6 [cpp.import] paragraph 8 as follows:
import "a.h"; // point of definition of #1, #2, and #3, point of undefinition of #1 in "e.h" import "d.h"; // point of definition of #4 and #5 in "e.h" int a = Y; // OK, active macro definitions #2 and #4 are valid redefinitions int c = Z; // error: active macro definitions #3 and #5 are not valid redefinitions of ZModule unit f:export module f; export import "a.h"; int a = Y; // OK
Translation unit #1:import f; int x = Y; // error: Y is neither a defined macro nor a declared name-- end example ]
[ Resolved by issue 2518, adopted in February, 2023. ]
C99 is very clear that a #error directive causes a translation to fail: Clause 4 paragraph 4 says,
The implementation shall not successfully translate a preprocessing translation unit containing a #error preprocessing directive unless it is part of a group skipped by conditional inclusion.
C++, on the other hand, simply says that a #error directive “renders the program ill-formed” (15.9 [cpp.error]), and the only requirement for an ill-formed program is that a diagnostic be issued; the translation may continue and succeed. (Noted in passing: if this difference between C99 and C++ is addressed, it would be helpful for synchronization purposes in other contexts as well to introduce the term “preprocessing translation unit.”)
See also issue 2518.
[Accepted at the November, 2022 meeting.]
The wording for the predefined macro __STDCPP_BFLOAT16_T__ added by P1467 can be interpreted more broadly than was intended.
Proposed resolution (approved by CWG 2022-11-08):
Change in 15.12 [cpp.predefined] paragraph 1 as follows:
__STDCPP_BFLOAT16_T__
Defined as the integer literal 1 if and only if the implementation supports an extended floating-point type with the properties of the typedef-name std::bfloat16_t as described in 6.8.3 [basic.extended.fp].
[Accepted at the February, 2023 meeting.]
P2720R0 comment DE 038Paper P2718R0 (Wording for P2644R1 Fix for Range-based for Loop), adopted at the November, 2022 meeting, omitted a consideration of a feature-test macro, although one is desirable.
Proposed resolution (approved by CWG 2023-01-06):
Change in 15.12 [cpp.predefined] table 23 as follows:
__cpp_range_based_for201603L202211L
[Accepted as a DR at the November, 2022 meeting.]
Clause Annex B [implimits] bullet 2.3 specifies:
This omits function types as the to-be-modified type, and ignores pointer-to-member declarators.
Proposed resolution (approved by CWG 2022-09-23):
Change in Clause Annex B [implimits] bullet 2.3 as follows:
[Accepted as a DR at the November, 2022 meeting.]
The changes from P1185R2 need an entry in Annex C, because they affect the interpretation of existing well-formed code. For example, given:
struct A { operator int() const { return 10; } }; bool operator==(A, int); // #1 //built-in: bool operator==(int, int); // #2 A a, b;
The expression 10 == a resolves to #2 in C++17 but now to #1. In addition, a == b is now ambiguous, because #1 has a user-defined conversion on the second argument, while the reversed order has it on the first argument. Similarly for operator!=.
Notes from the March, 2019 teleconference:
The ambiguity in 10 == a arises from the consideration of the reverse ordering of the operands.
CWG found this breakage surprising and asked for EWG's opinion before updating Annex C.
Proposed resolution (April, 2019) [SUPERSEDED]
Add the following as a new subclause in C.3 [diff.cpp17]:
C.5.6 Clause 12: Overloading
Affected subclause: 12.2.2.3 [over.match.oper]
Change: Overload resolution may change for equality operators 7.6.10 [expr.eq].
Rationale: Support calling operator== with reversed order of arguments.
Effect on original feature: Valid C++ 2017 code that uses equality operators with conversion functions may be ill-formed or have different semantics in this International Standard.struct A { operator int() const { return 10; } }; bool operator==(A, int); // #1 // built-in: bool operator==(int, int); // #2 bool b = 10 == A(); // uses #1 with reversed order of arguments; previously used #2
Proposed resolution:
Add the following as a new subclause in C.3 [diff.cpp17]:
C.5.6 Clause 12: Overloading
Affected subclause: 12.2.2.3 [over.match.oper]
Change: Overload resolution may change for equality operators 7.6.10 [expr.eq].
Rationale: Support calling operator== with reversed order of arguments.
Effect on original feature: Valid C++ 2017 code that uses equality operators with conversion functions may be ill-formed or have different semantics in this International Standard.struct A { operator int() const { return 10; } }; bool operator==(A, int); // #1 // built-in: bool operator==(int, int); // #2 bool b = 10 == A(); // uses #1 with reversed order of arguments; previously used #2 struct B { bool operator==(const B&); // member function with no cv-qualifier }; B b1; bool eq = (b1 == b1); // ambiguous; previously well-formed
[Accepted as a DR at the November, 2022 meeting.]
Unicode 15.0 UAX #31 clarified that rule R3 was, in fact, intended to apply to programming languages. WG21's prior understanding was that programming languages are not in scope of that rule. The proposed resolution updates E.4 [uaxid.pattern] to the revised understanding. See paper P2653R1 (Update Annex E based on Unicode 15.0 UAX 31) for more details.
Proposed resolution (approved by CWG 2022-10-21):
Change in E.4 [uaxid.pattern] as follows:
UAX #31 describes how formal languages
that use or interpret patterns of characters, such as regular expressions or number formats, may describe that syntax with Unicode propertiessuch as computer languages should describe and implement their use of whitespace and syntactically significant characters during the processes of lexing and parsing.C++ does not
do this as part of the language, deferring to library components for such usage of patterns. This requirement does not apply to C++claim conformance with this requirement.