Date: | 2023-03-24 |
Project: | Programming Language C++ |
Reference: | ISO/IEC IS 14882:2020 |
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 N4944.
[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 33 [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.8 [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 33 [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.8 [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 33 [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.8 [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 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.7.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.7.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.7.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.7.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 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 February, 2023 meeting.]
According to 7.5.7.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.7.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 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 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.3 [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.13.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.3 [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.3 [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.4 [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 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 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.5.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.5.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.5.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 February, 2023 meeting.]
According to 9.11 [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.11 [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.11 [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 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.12.1 [dcl.attr.grammar] paragraph 6 as follows:
[Note 4: A program is ill-formed if it contains an attribute specified in 9.12 [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.12 [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 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.11.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.11.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.11.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, ...
[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.4.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.4.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.4.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 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.10 [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.10 [lex.name]. However, the grammar for user-defined-string-literal in 5.13.8 [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.10 [lex.name]. Either the example is incorrect or there needs to be a refinement of the rule in 5.10 [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.10 [lex.name] in the non-deprecated form (sans space)."
Proposed resolution (February, 2023) [SUPERSEDED]:
Change in 5.10 [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.8 [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.10 [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.8 [lex.ext], modify all occurrences as follows:
operator ""X
Change in 5.13.8 [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.8 [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.6.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 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.5.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 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.5 [cpp.import]. -- end note ]
Add to the example in 15.5 [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 ]
[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 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 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 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.11 [cpp.predefined] table 23 as follows:
__cpp_range_based_for201603L202211L
[Accepted as a DR at the November, 2022 meeting.]
Subclause 7.5.1 [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.8 [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.6 [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.7 [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.1 [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.6 [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.7 [lex.nullptr] paragraph 1 as follows:
The pointer literal is the keyword nullptr. Itis a prvalue ofhas type std::nullptr_t.
[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 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.3 [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.3 [basic.life] bullet 6.2 and 6.7.3 [basic.life] bullet 7.2. The rules ought to be consistent.
Proposed resolution (approved by CWG 2022-10-07):
Change in 6.7.3 [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.5.2 [expr.prim.lambda.closure]), an aggregate type (9.4.2 [dcl.init.aggr]), or has at least one constexpr constructor or constructor template (possibly inherited (9.9 [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.5.2 [expr.prim.lambda.closure]),
- is an aggregate type (9.4.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.9 [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.5.2 [expr.prim.lambda.closure]),
- is an aggregate type (9.4.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.9 [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.5.2 [expr.prim.lambda.closure]),
an aggregate type (9.4.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.9 [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 November, 2022 meeting.]
The comment in the example in 7.5.5.3 [expr.prim.lambda.capture] paragraph 6 refers to "local variable", but should refer to init-capture instead.
Possible resolution:
Change in 7.5.5.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 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.4.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.4.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.13.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.13.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 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.ass] 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.5 [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(27.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(27.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 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.4 [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.4 [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.4 [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.4 [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.4.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.4 [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.4.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.4 [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.4.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 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.6 [dcl.struct.bind]). If the decl-specifier-seq contains any decl-specifier other than static, thread_local, auto (9.2.9.6 [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.6.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.6 [dcl.struct.bind]).If the decl-specifier-seq contains any decl-specifier other than static, thread_local, auto (9.2.9.6 [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.6 [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.4 [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 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.9 [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.9 [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.9 [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.9 [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.9 [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 as a DR at the November, 2022 meeting.]
Subclause 9.4.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.4.1 [dcl.init.general] paragraph 9).
Proposed resolution (approved by CWG 2022-08-26):
Change in 9.4.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.4.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.4.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.4.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.4.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.4.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.4.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.4.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.4.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.4.2 [dcl.init.aggr]), is called copy-initialization.
Change in 9.4.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.4.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.4.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.4.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.4.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.5.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.5.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.5.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.5.4 [dcl.fct.def.coroutine]). Otherwise...
If needed, change 9.5.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.5.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.5.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.5.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.5.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.5.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.5.4 [dcl.fct.def.coroutine] paragraph 8 specifies:
A suspended coroutine can be resumed to continue execution by invoking a resumption member function (17.12.4.6 [coroutine.handle.resumption]) of a coroutine handle (17.12.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.5.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.12.4.6 [coroutine.handle.resumption]) of a coroutine handle (17.12.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.7.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.7.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.6 [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.7.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.6 [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.7.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.7.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.4 [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.7.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.7.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 November, 2022 meeting.]
Subclause 9.12.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.12.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.12.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.12 [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.12.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.12 [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 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.12.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.12.2 [dcl.align]),
- either both entities are declared with the no_unique_address attribute (9.12.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.6 [basic.align]),
- either both entities are declared with the no_unique_address attribute (9.12.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 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 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 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.6 [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 an attribute. This seems like an oversight.
CWG telecon 2022-10-07:
Agreed.
Proposed resolution (approved by CWG 2022-10-21):
Change in 9.12.5 [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 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.4 [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.4 [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.4.2 [dcl.init.aggr] for aggregate initialization, or in 9.4.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.4 [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.4.2 [dcl.init.aggr] for aggregate initialization, or in 9.4.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.4 [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.4.2 [dcl.init.aggr] for aggregate initialization,
- in 7.5.5.3 [expr.prim.lambda.capture] for the initialization of the closure object when evaluating a lambda-expression,
- in 9.4.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 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.2 [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.2 [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.
[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 [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 [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 [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 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 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 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.11 [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].
[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.5 [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.5 [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 [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.10 [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.10 [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.9 [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.4 [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.4 [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.4 [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.4 [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.9 [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.9 [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.9 [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.4 [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.4 [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.4 [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.7.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.11 [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.8.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.
[Moved to DR at 4/02 meeting.]
Jack Rouse: 6.7.3 [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.4.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.3 [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.4.2 [dcl.init.aggr]).]
[Voted into WP at April 2003 meeting.]
The wording in 6.7.3 [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.3 [basic.life] paragraph 6 change the third bullet:
[Voted into WP at March 2004 meeting.]
6.7.3 [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.3 [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.3 [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.4.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.5.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.5.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.5.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.5.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.5.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.5.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.5.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.5.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.5.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.5.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 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.6 [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.12.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.6 [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]
[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.4 [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.4 [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.5.2 [basic.stc.static], 6.7.5.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.ass] 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.8 [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.4 [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.4.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.8 [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.13 [support.runtime] paragraph 6).
Proposed Resolution (November, 2006):
Change the footnote in 17.13 [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.ass] paragraph 1 says,
The result of the assignment operation is the value stored in the left operand after the assignment has taken place; the result is an lvalue.
What is the normative effect of the words "after the assignment has taken place"? I think that phrase ought to mean that in addition to whatever constraints the rules about sequence points might impose on the implementation, assignment operators on built-in types have the additional constraint that they must store the left-hand side's new value before returning a reference to that object as their result.
One could argue that as the C++ standard currently stands, the effect of x = y = 0; is undefined. The reason is that it both fetches and stores the value of y, and does not fetch the value of y in order to compute its new value.
I'm suggesting that the phrase "after the assignment has taken place" should be read as constraining the implementation to set y to 0 before yielding the value of y as the result of the subexpression y = 0.
Francis Glassborow:
My understanding is that for a single variable:
It is the 3) that is often ignored because in practice the compiler hardly ever codes for the read because it already has that value but in complicated evaluations with a shortage of registers, that is not always the case. Without getting too close to the hardware, I think we both know that a read too close to a write can be problematical on some hardware.
So, in x = y = 0;, the implementation must NOT fetch a value from y, instead it has to "know" what that value will be (easy because it has just computed that in order to know what it must, at some time, store in y). From this I deduce that computing the lvalue (to know where to store) and the rvalue to know what is stored are two entirely independent actions that can occur in any order commensurate with the overall requirements that both operands for an operator be evaluated before the operator is.
Erwin Unruh:
C distinguishes between the resulting value of an assignment and putting the value in store. So in C a compiler might implement the statement x=y=0; either as x=0;y=0; or as y=0;x=0; In C the statement (x += 5) += 7; is not allowed because the first += yields an rvalue which is not allowed as left operand to +=. So in C an assignment is not a sequence of write/read because the result is not really "read".
In C++ we decided to make the result of assignment an lvalue. In this case we do not have the option to specify the "value" of the result. That is just the variable itself (or its address in a different view). So in C++, strictly speaking, the statement x=y=0; must be implemented as y=0;x=y; which makes a big difference if y is declared volatile.
Furthermore, I think undefined behaviour should not be the result of a single mentioning of a variable within an expression. So the statement (x +=5) += 7; should NOT have undefined behaviour.
In my view the semantics could be:
Jerry Schwarz:
My recollection is different from Erwin's. I am confident that the intention when we decided to make assignments lvalues was not to change the semantics of evaluation of assignments. The semantics was supposed to remain the same as C's.
Ervin seems to assume that because assignments are lvalues, an assignment's value must be determined by a read of the location. But that was definitely not our intention. As he notes this has a significant impact on the semantics of assignment to a volatile variable. If Erwin's interpretation were correct we would have no way to write a volatile variable without also reading it.
Lawrence Crowl:
For x=y=0, lvalue semantics implies an lvalue to rvalue conversion on the result of y=0, which in turn implies a read. If y is volatile, lvalue semantics implies both a read and a write on y.
The standard apparently doesn't state whether there is a value dependence of the lvalue result on the completion of the assignment. Such a statement in the standard would solve the non-volatile C compatibility issue, and would be consistent with a user-implemented operator=.
Another possible approach is to state that primitive assignment operators have two results, an lvalue and a corresponding "after-store" rvalue. The rvalue result would be used when an rvalue is required, while the lvalue result would be used when an lvalue is required. However, this semantics is unsupportable for user-defined assignment operators, or at least inconsistent with all implementations that I know of. I would not enjoy trying to write such two-faced semantics.
Erwin Unruh:
The intent was for assignments to behave the same as in C. Unfortunately the change of the result to lvalue did not keep that. An "lvalue of type int" has no "int" value! So there is a difference between intent and the standard's wording.
So we have one of several choices:
I think the last one has the least impact on existing programs, but it is an ugly solution.
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.ass] 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.13 [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.4 [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.7.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.10 [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.11 [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.ass] 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.ass] 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.4.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.5.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.5.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.5.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.5.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.5.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.4 [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.4 [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.5.2 [basic.stc.static]) or thread storage duration (3.7.2) shall be zero-initialized (9.4 [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.5.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.5.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.5.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.5.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.9 [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.9 [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.4 [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.4 [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.4 [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.6 [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.5 [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.5 [expr.prim.lambda], 9.2.4 [dcl.typedef], 9.12.2 [dcl.align], 9.7.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.5.5.2 [basic.stc.dynamic.allocation] paragraph 1 third sentence, replace "size_t" by "std::size_t".
In 6.7.5.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.5.5.2 [basic.stc.dynamic.allocation] paragraph 4, replace "type_info" by "std::type_info" in the note.
In 6.7.5.5.3 [basic.stc.dynamic.deallocation] paragraph 3, replace all four instances of "size_t" by "std::size_t".
In 6.7.3 [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.5 [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 14.6.3 [except.uncaught], replace "uncaught_exception" by "std::uncaught_exception".
In 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.4 [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.4 [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.11 [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.4 [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.4 [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.4 [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.4 [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.4 [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.4 [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.4.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.4 [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.4 [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.4 [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.4 [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.4 [dcl.init].
Add a footnote to the indicated bullet in 9.4 [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.4 [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.4 [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.4 [dcl.init].
This sounds as if 9.4 [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.4 [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.4 [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.4 [dcl.init] determines the conditions under which it is used).
Change 9.4 [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.4 [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.4 [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.4.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.4.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.4.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.4.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.4.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.4.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.4.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.4.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.4.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.4.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.4.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.4.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.7.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.7.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.7.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.7.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.7.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.7.1 [dcl.enum]): int, unsigned int, long, or unsigned long.
[Voted into WP at April 2003 meeting.]
9.7.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.7.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.7.1 [dcl.enum] paragraph 1.
Proposed resolution (October, 2005):
Change the grammar in 9.7.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.7.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.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.7.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.7.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.9 [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.9 [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.9 [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.9 [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.9 [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.9 [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.9 [namespace.udecl] paragraph 5:
A using-declaration shall not name a namespace;
[Moved to DR at 4/01 meeting.]
9.11 [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.11 [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.11 [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.11 [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.11 [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.11 [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.5 [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.11 [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.9 [namespace.udecl]), or a using-directive (9.8.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.4 [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.4.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.4.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.4.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.6.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.6.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.4.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.4.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.4.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.4.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.4.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.4.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.4.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.4.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.4.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.4.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.4.2 [dcl.init.aggr] paragraph 7 as indicated:
If there are fewerinitializersinitializer-clauses in the list than there are members...
Replace 9.4.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.4.2 [dcl.init.aggr] paragraph 10 as indicated:
When initializing a multi-dimensional array, theinitializersinitializer-clauses initialize the elements...
Change 9.4.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.4.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.4.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.4.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.4.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.4.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.4.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.7 [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.4.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.4.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.8 [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.7.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.4.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.4.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.5 [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.5 [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.5 [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.5 [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.5 [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.5 [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.7.1 [dcl.enum] paragraph 7 and 11.4.10 [class.bit] paragraph 4 that should be resolved.
Proposed resolution (April, 2006):
Replace 9.7.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.7.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(); }; vo