Date:  2024-07-20
Project:  Programming Language C++
Reference:  ISO/IEC IS 14882:2020
Reply to:  Jens Maurer
 jens.maurer@gmx.net


C++ Standard Core Language Defect Reports and Accepted Issues, Revision 115


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 N4986.


Issues with "DR" Status


2818. Use of predefined reserved identifiers

Section: 5.10  [lex.name]     Status: DR     Submitter: Jiang An     Date: 2023-01-18

[Accepted as a DR at the June, 2024 meeting.]

Subclause 5.10 [lex.name] paragraph 3 specifies:

In addition, some identifiers appearing as a token or preprocessing-token are reserved for use by C++ implementations and shall not be used otherwise; no diagnostic is required.

That implies that uses of standard-specified predefined macros (15.11 [cpp.predefined]) or feature-test macros (17.3.2 [version.syn]) make the program ill-formed. This does not appear to be the intent.

Proposed resolution (approved by CWG 2024-01-19) [SUPERSEDED]:

Change in 5.10 [lex.name] paragraph 3 and add bullets as follows:

In addition, some identifiers appearing as a token or preprocessing-token are reserved for use by C++ implementations and shall not be used otherwise; no diagnostic is required.

CWG 2024-03-20

The exceptions should not be specified using a specific list, which might become stale in the future.

Proposed resolution (approved by CWG 2024-03-20):

Change in 5.10 [lex.name] paragraph 3 and add bullets as follows:

In addition, some identifiers appearing as a token or preprocessing-token are reserved for use by C++ implementations and shall not be used otherwise; no diagnostic is required.



2870. Combining absent encoding-prefixes

Section: 5.13.5  [lex.string]     Status: DR     Submitter: Jan Schultke     Date: 2024-03-09

[Accepted as a DR at the June, 2024 meeting.]

(From submission #511.)

Subclause 5.13.5 [lex.string] paragraph 7 does not properly handle the case where both encoding-prefixes are absent:

The common encoding-prefix for a sequence of adjacent string-literals is determined pairwise as follows: If two string-literals have the same encoding-prefix , the common encoding-prefix is that encoding-prefix . If one string-literal has no encoding-prefix , the common encoding-prefix is that of the other string-literal. Any other combinations are ill-formed.

Possible resolution [SUPERSEDED]:

Change in 5.13.5 [lex.string] paragraph 7 as follows:

The common encoding-prefix for a sequence of adjacent string-literals is determined pairwise as follows: If two string-literals have the same encoding-prefix, the common encoding-prefix is that encoding-prefix. If one string-literal has no encoding-prefix, the common encoding-prefix is that of the other string-literal or is absent if the other string-literal has no encoding-prefix. Otherwise, both string-literals shall have the same encoding-prefix, and the common encoding-prefix is that encoding-prefix. Any other combinations are ill-formed.

CWG 2024-05-03

CWG preferred a holistic instead of a pairwise approach.

Proposed resolution (approved by CWG 2024-05-17):

Change in 5.13.5 [lex.string] paragraph 7 as follows:

The common encoding-prefix for a sequence of adjacent string-literals is determined pairwise as follows: If two string-literals have the same encoding-prefix , the common encoding-prefix is that encoding-prefix . If one string-literal has no encoding-prefix , the common encoding-prefix is that of the other string-literal. Any other combinations are ill-formed.

The string-literals in any sequence of adjacent string-literals shall have at most one unique encoding-prefix among them. The common encoding-prefix of the sequence is that encoding-prefix, if any.




2883. Definition of "odr-usable" ignores lambda scopes

Section: 6.3  [basic.def.odr]     Status: DR     Submitter: Hubert Tong     Date: 2024-03-20

[Accepted as a DR at the June, 2024 meeting.]

(From submission #523.)

Consider:

  void f() {
    int x;
    [&] {
      return x;       // #1
    };
  }

The odr-use of x is ill-formed, because x is not odr-usable in that scope, because there is a lambda scope (6.4.5 [basic.scope.lambda]) between #1 and the definition of x.

A more involved example exhibits implementation divergence:

  struct A {
    A() = default;
    A(const A &) = delete;
    constexpr operator int() { return 42; }
  };
  void f() {
    constexpr A a;
    [=]<typename T, int = a> {};   // OK, not odr-usable from a default template argument, and not odr-used
  }

Proposed resolution (approved by CWG 2024-05-31):

Change in 6.3 [basic.def.odr] paragraph 10 as follows:

A local entity (6.1 [basic.pre]) is odr-usable in a scope (6.4.1 [basic.scope.scope]) if: If a local entity is odr-used in a scope in which it is not odr-usable, the program is ill-formed.



2872. Linkage and unclear "can be referred to"

Section: 6.6  [basic.link]     Status: DR     Submitter: Brian Bi     Date: 2024-03-08

[Accepted as a DR at the June, 2024 meeting.]

It is unclear what "can be referred to" means in 6.6 [basic.link] paragraph 2. Paragraph 8 provides precise definitions for "same entity".

Possible resolution [SUPERSEDED]:

  1. Replace in 6.6 [basic.link] paragraph 2 as follows:

    A name is said to have linkage when it can denote the same object, reference, function, type, template, namespace or value as a name introduced by a declaration in another scope:
    • When a name has external linkage, the entity it denotes can be referred to by names from scopes of other translation units or from other scopes of the same translation unit.
    • When a name has module linkage, the entity it denotes can be referred to by names from other scopes of the same module unit (10.1 [module.unit]) or from scopes of other module units of that same module.
    • When a name has internal linkage, the entity it denotes can be referred to by names from other scopes in the same translation unit.
    • When a name has no linkage, the entity it denotes cannot be referred to by names from other scopes.
    A name can have external linkage, module linkage, internal linkage, or no linkage, as determined by the rules below. [ Note: The linkage of a name determines when corresponding declarations (6.4.1 [basic.scope.scope]) that introduce that name can declare the same entity. ---end note]
  2. Change in 11.6 [class.local] paragraph 3 as follows:

    If class X is a local class, a nested class Y may be declared in class X and later defined in the definition of class X or be later defined in the same scope as the definition of class X. A class nested within a local class is a local class. A member of a local class X shall be declared only in the definition of X or the nearest enclosing block scope of X.

CWG 2024-05-03

CWG opined to split off the second change (see issue 2890) and clarify the note.

Proposed resolution (approved by CWG 2024-05-17):

Replace in 6.6 [basic.link] paragraph 2 as follows:

A name is said to have linkage when it can denote the same object, reference, function, type, template, namespace or value as a name introduced by a declaration in another scope:
A name can have external linkage, module linkage, internal linkage, or no linkage, as determined by the rules below. [ Note: All declarations of an entity with a name with internal linkage appear in the same translation unit. All declarations of an entity with module linkage are attached to the same module. ---end note]



2886. Temporaries and trivial potentially-throwing special member functions

Section: 6.7.7  [class.temporary]     Status: DR     Submitter: Jiang An     Date: 2024-04-27

[Accepted as a DR at the June, 2024 meeting.]

(From submission #531.)

Paper P1286R2 (Contra CWG DR1778) allowed trivial potentially-throwing special member functions. Whether such a trivial special member function is actually invoked is thus observable via the noexcept operator. Issue 2820 clarified the situation for value-initialization and removed a special case for a trivial default constructor.

Subclause 6.7.7 [class.temporary] paragraph 4 appears to normatively avoid invoking a trivial constructor or destructor, something best left to the as-if rule. There is implementation divergence for this example:

  struct C {
    C() = default;
    ~C() noexcept(false) = default;
  };

  static_assert(noexcept(C()));

A related question arises from the introduction of temporaries for function parameters and return values in 6.7.7 [class.temporary] paragraph 3. However, this situation can never result in actually throwing an exception during forward evolution: when the constructor becomes non-trivial, the permission to create a temporary object evaporates.

Proposed resolution (approved by CWG 2024-05-31):

  1. Change in 6.7.7 [class.temporary] paragraph 3 as follows:

    When an object of class type X is passed to or returned from a potentially-evaluated function call, if X has at least one eligible copy or move constructor (11.4.4 [special]), each such constructor is trivial, and the destructor of X is either trivial or deleted, implementations are permitted to create a temporary object to hold the function parameter or result object. The temporary object is constructed from the function argument or return value, respectively, and the function's parameter or return object is initialized as if by using the eligible trivial constructor to copy the temporary (even if that constructor is inaccessible or would not be selected by overload resolution to perform a copy or move of the object).
  2. Change in 6.7.7 [class.temporary] paragraph 4 as follows:

    When an implementation introduces a temporary object of a class that has a non-trivial constructor (11.4.5.2 [class.default.ctor], 11.4.5.3 [class.copy.ctor]), it shall ensure that a constructor is called for the temporary object. Similarly, the destructor shall be called for a temporary with a non-trivial destructor (11.4.7 [class.dtor]). Temporary objects are destroyed as the last step in evaluating the full-expression (6.9.1 [intro.execution]) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception. The value computations and side effects of destroying a temporary object are associated only with the full-expression, not with any specific subexpression.



2836. Conversion rank of long double and extended floating-point types

Section: 6.8.6  [conv.rank]     Status: DR     Submitter: Joshua Cranmer     Date: 2023-11-20     Liaison: EWG

[Accepted as a DR at the June, 2024 meeting.]

Consider:

  auto f(long double x, std::float64_t y) {
    return x + y;
  }

What is the return type of f?

Suppose an implementation uses the same size and semantics for all of double, long double, and std::float64_t. C23 prefers the IEEE interchange type (i.e. std::float64_t) over long double and double. In contrast, C++ chooses long double, which has a higher rank than double, but std::float64_t is specified to have the same rank as double.

This outcome conflicts with the documented goal of P1467 that C++ and C conversion rules be aligned.

Suggested resolution [SUPERSEDED]:

Change in 6.8.6 [conv.rank] bullet 2.5 as follows:

Additional notes (December, 2023)

The suggested resolution would make a conversion from std::float64_t to double not implicit, which is not desirable.

David Olsen, one of the authors of P1467, asserts that the deviation from C is intentional, and was discussed with the C floating-point study group.

Forwarding to EWG via paper issue 1699 to confirm that the deviation from C is intentional and thus an Annex C entry should be created.

EWG 2024-03-18

This issue should be closed as NAD.

Possible resolution [SUPERSEDED]:

Add a new paragraph in C.7.3 [diff.basic] as follows:

Affected subclause: 6.8.6 [conv.rank]
Change: Conversion rank of same-sized long double vs. std::float64_t
Rationale: Provide implicit conversion from std::float64_t to double.
Effect on original feature: Change of semantically well-defined feature.
Difficulty of converting: Trivial: explicitly convert to the desired type.
How widely used: Rarely.

CWG 2024-04-19

The name std::float64_t is not valid C. CWG resolved to adding a note to 6.8.6 [conv.rank] bullet 2.5 instead.

Proposed resolution (approved by CWG 2024-06-26):

Change in 6.8.6 [conv.rank] bullet 2.5 as follows:




2892. Unclear usual arithmetic conversions

Section: 7.4  [expr.arith.conv]     Status: DR     Submitter: Lauri Vasama     Date: 2024-05-06

[Accepted as a DR at the June, 2024 meeting.]

(From submission #533.)

Subclause 7.4 [expr.arith.conv] bullet 1.4.1 specifies:

What does "needed" mean?

Proposed resolution (approved by CWG 2024-05-31):

Change in 7.4 [expr.arith.conv] bullet 1.4.1 as follows:




2869. this in local classes

Section: 7.5.3  [expr.prim.this]     Status: DR     Submitter: Richard Smith     Date: 2024-03-14

[Accepted as a DR at the June, 2024 meeting.]

(From submission #515.)

Consider:

  struct A {
    static void f() {
      struct B {
        void *g() { return this; }
       };
     }
  };

According to 7.5.3 [expr.prim.this] paragraph 3, this example is ill-formed, because this "appears within" the declaration of a static member function. The qualification "of the current class" can be read as attaching to explicit object member functions only.

Suggested resolution [SUPERSEDED]:

Change in 7.5.3 [expr.prim.this] paragraph 3 as follows:

If a declaration declares a member function or member function template of a class X, the expression this is a prvalue of type “pointer to cv-qualifier-seq X” wherever X is the current class between the optional cv-qualifier-seq and the end of the function-definition, member-declarator , or declarator. It shall not appear within the The declaration of either that determines the type of this shall declare neither a static member function or nor an explicit object member function of the current class (although its type and value category are defined within such member functions as they are within an implicit object member function).

CWG 2024-05-03

CWG preferred a smaller surgery to avoid the English parsing issue.

Proposed resolution (approved by CWG 2024-05-17):

Change in 7.5.3 [expr.prim.this] paragraph 3 as follows:

If a declaration declares a member function or member function template of a class X, the expression this is a prvalue of type “pointer to cv-qualifier-seq X” wherever X is the current class between the optional cv-qualifier-seq and the end of the function-definition, member-declarator, or declarator. It shall not appear within the declaration of either a static member function or an explicit object member function of the current class (although its type and value category are defined within such member functions as they are within an implicit object member function).



2561. Conversion to function pointer for lambda with explicit object parameter

Section: 7.5.6.2  [expr.prim.lambda.closure]     Status: DR     Submitter: Barry Revzin     Date: 2022-02-14     Liaison: EWG

[Accepted as a DR at the June, 2024 meeting.]

P0847R7 (Deducing this) (approved October, 2021) added explicit-object member functions. Consider:

  struct C {
    C(auto) { }
  };

  void foo() {
    auto l = [](this C) { return 1; };
    void (*fp)(C) = l;
    fp(1); // same effect as decltype(l){}() or decltype(l){}(1) ?
  }

Subclause 7.5.6.2 [expr.prim.lambda.closure] paragraph 8 does not address explicit object member functions:

The closure type for a non-generic lambda-expression with no lambda-capture whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage (9.11 [dcl.link]) having the same parameter and return types as the closure type's function call operator. The conversion is to “pointer to noexcept function” if the function call operator has a non-throwing exception specification. The value returned by this conversion function is the address of a function F that, when invoked, has the same effect as invoking the closure type's function call operator on a default-constructed instance of the closure type. F is a constexpr function if...

Suggested resolution [SUPERSEDED]:

  1. Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 8 as follows:

    ... The value returned by this conversion function is
    • for a lambda-expression whose parameter-declaration-clause has an explicit object parameter, the address of the function call operator (7.6.2.2 [expr.unary.op];
    • otherwise, the address of a function F that, when invoked, has the same effect as invoking the closure type's function call operator on a default-constructed instance of the closure type.
    F is a constexpr function if... is an immediate function.

    [ Example:

      struct C {
        C(auto) { }
      };
    
      void foo() {
        auto a = [](C) { return 0; };
        int (*fp)(C) = a;   // OK
        fp(1);              // same effect as decltype(a){}(1)
        auto b = [](this C) { return 1; };
        fp = b;             // OK
        fp(1);              // same effect as (&decltype(b)::operator())(1)
      }
    

    -- end example ]

  2. Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 11 as follows:

    The value returned by any given specialization of this conversion function template is
    • for a lambda-expression whose parameter-declaration-clause has an explicit object parameter, the address of the corresponding function call operator template specialization (7.6.2.2 [expr.unary.op]);
    • otherwise, the address of a function F that, when invoked, has the same effect as invoking the generic lambda's corresponding function call operator template specialization on a default-constructed instance of the closure type.
    F is a constexpr function if...

CWG 2023-06-17

Requesting guidance from EWG with paper issue 1689.

Additional notes (October, 2023)

Additional examples demonstrating implementation divergence between clang and MSVC:

  struct Any { Any(auto) {} };
  auto x = [](this auto self, int x) { return x; };
  auto y = [](this Any self, int x) { return x; };
  auto z = [](this int (*self)(int), int x) { return x; };
  int main() {
    x(1);
    y(1);
    z(1);
    int (*px)(int) = +x; // MSVC
    int (*py1)(int) = +y; // MSVC
    int (*py2)(Any, int) = +y; // Clang
    int (*pz1)(int) = +z; // MSVC
    int (*pz2)(int (*)(int), int) = +z; // Clang
  }

Additional notes (November, 2023)

Additional example:

  auto c2 = [](this auto self) { return sizeof(self); };
  struct Derived2 : decltype(c) { int value; } d2;
  struct Derived3 : decltype(c) { int value[10]; } d3;

For MSVC, d2() == 4 and d3() == 40, but +d2 and +d3 both point to functions returning 1.

EWG 2023-11-07

Move forward with option 1 "punt" from D3031 for C++26. A future paper can explore other solutions.

Proposed resolution (approved by CWG 2024-04-19):

  1. Change the example in 7.5.6.1 [expr.prim.lambda.general] paragraph 6 as follows:

      int i = [](int i, auto a) { return i; }(3, 4);  // OK, a generic lambda
      int j = []<class T>(T t, int i) { return i; }(3, 4);  // OK, a generic lambda
      auto x = [](int i, auto a) { return i; };             // OK, a generic lambda
      auto y = [](this auto self, int i) { return i; };      // OK, a generic lambda
      auto z = []<class T>(int i) { return i; };             // OK, a generic lambda
    
    
  2. Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 9 as follows:

    The closure type for a non-generic lambda-expression with no lambda-capture and no explicit object parameter (9.3.4.6 [dcl.fct]) whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage (9.11 [dcl.link]) having the same parameter and return types as the closure type's function call operator. ...
  3. Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 10 as follows:

    For a generic lambda with no lambda-capture and no explicit object parameter (9.3.4.6 [dcl.fct]), the closure type has a conversion function template to pointer to function. ...

CWG 2023-11-09

Keeping in review status in anticipation of a paper proposing reasonable semantics for the function pointer conversions.

EWG 2024-03-18

Progress with option #1 of P3031R0, affirming the direction of the proposed resolution.




2881. Type restrictions for the explicit object parameter of a lambda

Section: 7.5.6.2  [expr.prim.lambda.closure]     Status: DR     Submitter: Richard Smith     Date: 2024-04-19

[Accepted as a DR at the June, 2024 meeting.]

Subclause 7.5.6.2 [expr.prim.lambda.closure] paragraph 5 restricts the type of an explicit object parameter of a lambda to the closure type or classes derived from the closure type. It neglects to consider ambiguous or private derivation scenarios.

Proposed resolution (approved by CWG 2024-06-26):

  1. Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 5 as follows:

    Given a lambda with a lambda-capture, the type of the explicit object parameter, if any, of the lambda's function call operator (possibly instantiated from a function call operator template) shall be either:
    • the closure type
    • a class type publicly and unambiguously derived from the closure type, or
    • a reference to a possibly cv-qualified such type.
  2. Add a new bullet after 13.10.3.1 [temp.deduct.general] bullet 11.10:

    • ...
    • Attempting to create a function type in which a parameter has a type of void, or in which the return type is a function type or array type.
    • Attempting to give to an explicit object parameter of a lambda's function call operator a type not permitted for such (7.5.6.2 [expr.prim.lambda.closure]).

CWG 2024-06-26

The following example is not supported by the proposed resolution and remains ill-formed:

  int main() {
    int x = 0;
    auto lambda = [x] (this auto self) { return x; };
    using Lambda = decltype(lambda);
    struct D : private Lambda {
      D(Lambda l) : Lambda(l) {}
      using Lambda::operator();
      friend Lambda;
    } d(lambda);
    d();
  } 



2861. dynamic_cast on bad pointer value

Section: 7.6.1.7  [expr.dynamic.cast]     Status: DR     Submitter: Jim X     Date: 2024-02-06

[Accepted as a DR at the June, 2024 meeting.]

(From submission #497.)

Base-to-derived casts and cross-casts need to inspect the vtable of a polymorphic type. However, this is not defined as an "access" and there is no provision for undefined behavior analoguous to 7.2.1 [basic.lval] paragraph 11.

Proposed resolution (approved by CWG 2024-06-26):

  1. Add a new paragraph after 7.6.1.7 [expr.dynamic.cast] paragraph 6 as follows:

    If v is a null pointer value, the result is a null pointer value.

    If v has type "pointer to cv U" and v does not point to an object whose type is similar (7.3.6 [conv.qual]) to U and that is within its lifetime or within its period of construction or destruction (11.9.5 [class.cdtor]), the behavior is undefined. If v is a glvalue of type U and v does not refer to an object whose type is similar to U and that is within its lifetime or within its period of construction or destruction, the behavior is undefined.

  2. Change in 7.3.12 [conv.ptr] paragraph 3 as follows:

    A prvalue v of type “pointer to cv D”, where D is a complete class type, can be converted to a prvalue of type “pointer to cv B”, where B is a base class (11.7 [class.derived]) of D. If B is an inaccessible (11.8 [class.access]) or ambiguous (6.5.2 [class.member.lookup]) base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion is a pointer to the base class subobject of the derived class object. The null pointer value is converted to the null pointer value of the destination type. If v is a null pointer value, the result is a null pointer value. Otherwise, if B is a virtual base class of D and v does not point to an object whose type is similar (7.3.6 [conv.qual]) to D and that is within its lifetime or within its period of construction or destruction (11.9.5 [class.cdtor]), the behavior is undefined. Otherwise, the result is a pointer to the base class subobject of the derived class object.



2882. Unclear treatment of conversion to void

Section: 7.6.1.9  [expr.static.cast]     Status: DR     Submitter: Richard Smith     Date: 2024-04-24

[Accepted as a DR at the June, 2024 meeting.]

The ordering of paragraphs in 7.6.1.9 [expr.static.cast] appears to imply that an attempt is made to form an implicit conversion sequence for conversions to void, possibly considering user-defined conversion functions to void. This is not intended.

Proposed resolution (approved by CWG 2024-05-31):

  1. Move 7.6.1.9 [expr.static.cast] paragraph 6 to before paragraph 4, strike paragraph 5, and adjust paragraph 7:

    Any expression can be explicitly converted to type cv void, in which case the operand is a discarded-value expression (7.2 [expr.prop]). [Note 3: Such a static_cast has no result as it is a prvalue of type void; see 7.2.1 [basic.lval]. —end note] [Note 4: However, if the value is in a temporary object (6.7.7 [class.temporary]), the destructor for that object is not executed until the usual time, and the value of the object is preserved for the purpose of executing the destructor. —end note]

    An Otherwise, an expression E can be explicitly converted to a type T if there is an implicit conversion sequence (12.2.4.2 [over.best.ics]) from E to T, if overload resolution ...

    Otherwise, the static_cast shall perform one of the conversions listed below. No other conversion shall be performed explicitly using a static_cast.

    Any expression can be explicitly converted to type cv void, in which case the operand is a discarded-value expression (7.2 [expr.prop]). [Note 3: Such a static_cast has no result as it is a prvalue of type void; see 7.2.1 [basic.lval]. —end note] [Note 4: However, if the value is in a temporary object (6.7.7 [class.temporary]), the destructor for that object is not executed until the usual time, and the value of the object is preserved for the purpose of executing the destructor. —end note]

    The Otherwise, the inverse of any a standard conversion sequence (7.3 [conv]) not containing an lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), function-to-pointer (7.3.4 [conv.func]), null pointer (7.3.12 [conv.ptr]), null member pointer (7.3.13 [conv.mem]), boolean (7.3.15 [conv.bool]), or function pointer (7.3.14 [conv.fctptr]) conversion, can be performed explicitly using static_cast. A program is ill-formed if it uses static_cast to perform the inverse of an ill-formed standard conversion sequence.

  2. Add a new paragraph after 7.6.1.9 [expr.static.cast] paragraph 14:

    A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T”, ... Otherwise, the pointer value is unchanged by the conversion. [Example 3: ... —end example]

    No other conversion can be performed using static_cast.




2728. Evaluation of conversions in a delete-expression

Section: 7.6.2.9  [expr.delete]     Status: DR     Submitter: Jiang An     Date: 2023-05-05

[Accepted as a DR at the June, 2024 meeting.]

Subclause 7.6.2.9 [expr.delete] paragraph 4 specifies:

The cast-expression in a delete-expression shall be evaluated exactly once.

Due to the reference to the syntactic non-terminal cast-expression, it is unclear whether that includes the conversion to pointer type specified in 7.6.2.9 [expr.delete] paragraph 2.

Proposed resolution (approved by CWG 2024-03-01) [SUPERSEDED]:

  1. Change in 7.6.2.9 [expr.delete] paragraph 1 and paragraph 2 as follows:

    ... The operand shall be of class type or a prvalue of pointer to object type or of class type. If of class type, the operand is contextually implicitly converted (7.3 [conv]) to a prvalue pointer to object type. [ Footnote: ... ] The converted operand is used in place of the original operand for the remainder of this subclause. The delete-expression has type void.

    If the operand has a class type, the operand is converted to a pointer type by calling the above-mentioned conversion function, and the converted operand is used in place of the original operand for the remainder of this subclause. ...

  2. Change in 7.6.2.9 [expr.delete] paragraph 4 as follows:

    The cast-expression in operand of a delete-expression shall be evaluated exactly once.

Proposed resolution (approved by CWG 2024-03-20):

  1. Change in 7.6.2.9 [expr.delete] paragraph 1 and paragraph 2 as follows:

    ... The first alternative is a single-object delete expression, and the second is an array delete expression. Whenever the delete keyword is immediately followed by empty square brackets, it shall be interpreted as the second alternative. [ Footnote: ... ] If the operand is of class type, it is contextually implicitly converted (7.3 [conv]) to a pointer to object type and the converted operand is used in place of the original operand for the remainder of this subclause. [ Footnote: ...] Otherwise, it shall be a prvalue of pointer to object type. The delete-expression has type void.

    If the operand has a class type, the operand is converted to a pointer type by calling the above-mentioned conversion function, and the converted operand is used in place of the original operand for the remainder of this subclause. ...

  2. Delete 7.6.2.9 [expr.delete] paragraph 4:

    The cast-expression in a delete-expression shall be evaluated exactly once.



2865. Regression on result of conditional operator

Section: 7.6.16  [expr.cond]     Status: DR     Submitter: Christof Meerwald     Date: 2024-01-14

[Accepted as a DR at the June, 2024 meeting.]

Consider:

  #include <concepts>

  template <class T> T get();

  template <class T>
  using X = decltype(true ? get<T const&>() : get<T>());

  struct C { };

  static_assert(std::same_as<X<int>, int>);
  static_assert(std::same_as<X<C>, C const>);  // #1

Before Issue 1895, #1 was well-formed. With the reformulation based on conversion sequences, #1 is now ill-formed because both conversion sequences can be formed.

Proposed resolution (approved by CWG 2024-05-03):

Change in 7.6.16 [expr.cond] bullet 4.3 as follows:




2874. Qualified declarations of partial specializations

Section: 9.2.9.5  [dcl.type.elab]     Status: DR     Submitter: Krystian Stasiowski     Date: 2024-03-19

[Accepted as a DR at the June, 2024 meeting.]

(From submission #521.)

Consider:

  namespace N {
    template<typename T>
    struct A;
  }

  template<>
  struct N::A<int>;     // OK

  template<typename T>
  struct N::A<T*>;      // error: invalid use of elaborated-type-specifier with a qualified-id

However, all major implementations support this.

Proposed resolution (approved by CWG 2024-05-17):

Change in 9.2.9.5 [dcl.type.elab] paragraph 2 as follows:

If an elaborated-type-specifier is the sole constituent of a declaration, the declaration is ill-formed unless it is an explicit specialization (13.9.4 [temp.expl.spec]), a partial specialization (13.7.6 [temp.spec.partial]), an explicit instantiation (13.9.3 [temp.explicit]), or it has one of the following forms:
  class-key attribute-specifier-seqopt identifier ;
  class-key attribute-specifier-seqopt simple-template-id ;
In the first case, the elaborated-type-specifier declares the identifier as a class-name. The second case shall appear only in an explicit-specialization (13.9.4 [temp.expl.spec]) or in a template-declaration (where it declares a partial specialization (13.7 [temp.decls]). The attribute-specifier-seq, if any, appertains to the class or template being declared.



2859. Value-initialization with multiple default constructors

Section: 9.4.1  [dcl.init.general]     Status: DR     Submitter: Brian Bi     Date: 2024-02-09

[Accepted as a DR at the June, 2024 meeting.]

(From submission #501.)

Subclause 9.4.1 [dcl.init.general] paragraph 9 specifies (as modified by issue 2820):

To value-initialize an object of type T means:

Per the specification, no zero-initialization occurs if the class has any user-provided default constructor, even if that constructor is not actually selected for the default initialization. This is observable in a constant expression, where reads of uninitialized data members are ill-formed.

Proposed resolution (approved by CWG 2024-04-05):

  1. Change in 6.7.3 [basic.life] paragraph 1 as follows:

    The lifetime of an object or reference is a runtime property of the object or reference. A variable is said to have vacuous initialization if it is default-initialized, no other initialization is performed, and, if it is of class type or a (possibly multidimensional) array thereof, a trivial constructor of that class type has a trivial default constructor is selected for the default-initialization. The lifetime of an object of type T begins when: ...
  2. Change in 9.4.1 [dcl.init.general] paragraph 9 as follows:

    To value-initialize an object of type T means:
    • If T is a (possibly cv-qualified) class type (Clause 11 [class]), then
      • if T has either no default constructor (11.4.5.2 [class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
      • otherwise, the object is zero-initialized and then default-initialized.
      let C be the constructor selected to default-initialize the object, if any. If C is not user-provided, the object is first zero-initialized. In all cases, the object is then default-initialized.
    • ...



2895. Initialization should ignore the destination type's cv-qualification

Section: 9.4.1  [dcl.init.general]     Status: DR     Submitter: Brian Bi     Date: 2024-05-29

[Accepted as a DR at the June, 2024 meeting.]

(From submission #541.)

Initialization of cv T follows the same rules as initialization of T. Issue 2830 clarified this for list-initialization. Further amendments are needed to properly handle cv-qualified versions of char8_t and char16_t as well as bool.

Proposed resolution (approved by CWG 2024-06-14):

  1. Change in 9.4.1 [dcl.init.general] paragraph 16 as follows:

    The semantics of initializers are as follows. The destination type is the cv-unqualified type of the object or reference being initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the source type is not defined.
  2. Change in 9.4.1 [dcl.init.general] bullet 16.6 as follows:

    • Otherwise, if the destination type is a (possibly cv-qualified) class type:
      • If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of as the destination type, the initializer expression is used to initialize the destination object. [Example 2: T x = T(T(T())); value-initializes x. —end example]
      • Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of as or is derived from the destination type, constructors are considered. The applicable constructors are enumerated (12.2.2.4 [over.match.ctor]), and the best one is chosen through overload resolution (12.2 [over.match]). Then:
  3. Change in 9.4.1 [dcl.init.general] bullet 16.9 as follows:

    • Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression. A standard conversion sequence (7.3 [conv]) is used to convert the initializer expression to a prvalue of the cv-unqualified version of the destination type; no user-defined conversions are considered. If the conversion cannot be done, the initialization is ill-formed. When initializing a bit-field with a value that it cannot represent, the resulting value of the bit-field is implementation-defined. [ Note: ... ]



233. References vs pointers in UDC overload resolution

Section: 9.4.4  [dcl.init.ref]     Status: DR     Submitter: Matthias Meixner     Date: 9 Jun 2000

[Accepted as a DR at the June, 2024 meeting.]

There is an inconsistency in the handling of references vs pointers in user defined conversions and overloading. The reason for that is that the combination of 9.4.4 [dcl.init.ref] and 7.3.6 [conv.qual] circumvents the standard way of ranking conversion functions, which was probably not the intention of the designers of the standard.

Let's start with some examples, to show what it is about:

    struct Z { Z(){} };

    struct A {
       Z x;

       operator Z *() { return &x; }
       operator const Z *() { return &x; }
    };

    struct B {
       Z x;

       operator Z &() { return x; }
       operator const Z &() { return x; }
    };

    int main()
    {
       A a;
       Z *a1=a;
       const Z *a2=a; // not ambiguous

       B b;
       Z &b1=b;
       const Z &b2=b; // ambiguous
    }

So while both classes A and B are structurally equivalent, there is a difference in operator overloading. I want to start with the discussion of the pointer case (const Z *a2=a;): 12.2.4 [over.match.best] is used to select the best viable function. Rule 4 selects A::operator const Z*() as best viable function using 12.2.4.3 [over.ics.rank] since the implicit conversion sequence const Z* -> const Z* is a better conversion sequence than Z* -> const Z*.

So what is the difference to the reference case? Cv-qualification conversion is only applicable for pointers according to 7.3.6 [conv.qual]. According to 9.4.4 [dcl.init.ref] paragraphs 4-7 references are initialized by binding using the concept of reference-compatibility. The problem with this is, that in this context of binding, there is no conversion, and therefore there is also no comparing of conversion sequences. More exactly all conversions can be considered identity conversions according to 12.2.4.2.5 [over.ics.ref] paragraph 1, which compare equal and which has the same effect. So binding const Z* to const Z* is as good as binding const Z* to Z* in terms of overloading. Therefore const Z &b2=b; is ambiguous. [12.2.4.2.5 [over.ics.ref] paragraph 5 and 12.2.4.3 [over.ics.rank] paragraph 3 rule 3 (S1 and S2 are reference bindings ...) do not seem to apply to this case]

There are other ambiguities, that result in the special treatment of references: Example:

    struct A {int a;};
    struct B: public A { B() {}; int b;};

    struct X {
       B x;
       operator A &() { return x; }
       operator B &() { return x; }
    };

    main()
    {
       X x;
       A &g=x; // ambiguous
    }

Since both references of class A and B are reference compatible with references of class A and since from the point of ranking of implicit conversion sequences they are both identity conversions, the initialization is ambiguous.

So why should this be a defect?

So overall I think this was not the intention of the authors of the standard.

So how could this be fixed? For comparing conversion sequences (and only for comparing) reference binding should be treated as if it was a normal assignment/initialization and cv-qualification would have to be defined for references. This would affect 9.4.4 [dcl.init.ref] paragraph 6, 7.3.6 [conv.qual] and probably 12.2.4.3 [over.ics.rank] paragraph 3.

Another fix could be to add a special case in 12.2.4 [over.match.best] paragraph 1.

CWG 2023-06-13

It was noted that the second example is not ambiguous, because a derived-to-base conversion is compared against an identity conversion. However, 12.2.4.2.5 [over.ics.ref] paragraph 1 needs a wording fix so that it applies to conversion functions as well. CWG opined that the first example be made valid, by adding a missing tie-breaker for the conversion function case.

Proposed resolution (approved by CWG 2024-04-19):

  1. Change in 12.2.4.1 [over.match.best.general] bullet 2.2 as follows:

    • ...
    • the context is an initialization by user-defined conversion (see 9.4 [dcl.init], 12.2.2.6 [over.match.conv], and 12.2.2.7 [over.match.ref]) and the standard conversion sequence from the return type result of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type result of F2 to the destination type
    • ...
  2. Add a new sub-bullet to 12.2.4.3 [over.ics.rank] bullet 3.2 as follows:

    • ...
    • S1 and S2 include reference bindings (9.4.4 [dcl.init.ref]), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers. [ Example: ... -- end example ] or, if not that,
    • S1 and S2 bind the same reference type "reference to T" and have source types V1 and V2, respectively, where the standard conversion sequence from V1* to T* is better than the standard conversion sequence from V2* to T*. [ Example:
        struct Z {};
      
        struct A {
          operator Z&();
          operator const Z&();       // #1
        };
      
        struct B {
          operator Z();
          operator const Z&&();      // #2
        };
      
        const Z& r1 = A();          // OK, uses #1
        const Z&& r2 = B();         // OK, uses #2
      
      --- end example]



2864. Narrowing floating-point conversions

Section: 9.4.5  [dcl.init.list]     Status: DR     Submitter: Brian Bi     Date: 2023-11-04

[Accepted as a DR at the June, 2024 meeting.]

Consider:

  float f = {1e100};

This is rejected as narrowing on all implementations. Issue 2723 made the example non-narrowing, which seems incorrect on an IEEE platform.

Proposed resolution (approved by CWG 2024-04-19):

Change in 9.4.5 [dcl.init.list] paragraph 7 as follows:

A narrowing conversion is an implicit conversion

Additional notes (April, 2024)

According to the proposed wording, since NaNs are not finite, conversion of NaNs is always non-narrowing. However, the payload of the NaN might not be preserved when converting to a smaller floating-point type.




2144. Function/variable declaration ambiguity

Section: 9.5.1  [dcl.fct.def.general]     Status: DR     Submitter: Richard Smith     Date: 2015-06-19

[Accepted as a DR at the June, 2024 meeting.]

The following fragment,

  int f() {};

is syntactically ambiguous. It could be either a function-definition followed by an empty-declaration, or it could be a simple-declaration whose init-declarator has the brace-or-equal-initializer {}. The same is true of a variable declaration

  int a {};

since function-definition simply uses the term declarator in its production.

Additional notes (May, 2024)

Issue 2876 introduced a framework to distinguish the parsing. Its resolution was extended to also resolve this issue.




2867. Order of initialization for structured bindings

Section: 9.6  [dcl.struct.bind]     Status: DR     Submitter: Richard Smith     Date: 2023-02-03

[Accepted as a DR at the June, 2024 meeting.]

Consider:

  auto [a, b] = f(X{});

If X is a tuple-like type, this is transformed to approximately the following:

  auto e = f(X{});
  T1 &a = get<0>(std::move(e));
  T2 &b = get<1>(std::move(e));

However, the sequencing of the initializations of e, a, and b is not specified. Further, the temporary X{} should be destroyed after the initializations of a and b.

Possible resolution [SUPERSEDED]:

  1. Change in 6.9.1 [intro.execution] paragraph 5 as follows:

    A full-expression is
    • an unevaluated operand (7.2.3 [expr.context]),
    • a constant-expression (7.7 [expr.const]),
    • an immediate invocation (7.7 [expr.const]),
    • an init-declarator (9.3 [dcl.decl]) or a mem-initializer (11.9.3 [class.base.init]), including the constituent expressions of the initializer,
    • the initializers for all uniquely-named variables introduced by a structured binding declaration (9.6 [dcl.struct.bind]), including the constituent expressions of all initializers,
    • an invocation of a destructor generated at the end of the lifetime of an object other than a temporary object (6.7.7 [class.temporary]) whose lifetime has not been extended, or
    • an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.
    ...
  2. Change in 9.6 [dcl.struct.bind] paragraph 4 as follows:

    ... Each vi is the name of an lvalue of type Ti that refers to the object bound to ri; the referenced type is Ti. The initialization of e is sequenced before the initialization of any ri. The initialization of ri is sequenced before the initialization of rj if i < j.

CWG 2024-05-03

CWG tentatively agreed that all temporaries in a structured binding (including those from default arguments of get invocations) should persist until the semicolon on the declaration as written.

Possible resolution [SUPERSEDED]:

  1. Change in 6.9.1 [intro.execution] paragraph 5 as follows:

    A full-expression is
    • an unevaluated operand (7.2.3 [expr.context]),
    • a constant-expression (7.7 [expr.const]),
    • an immediate invocation (7.7 [expr.const]),
    • an init-declarator (9.3 [dcl.decl]) , an initializer of a structured binding declaration (9.1 [dcl.pre]), or a mem-initializer (11.9.3 [class.base.init]), including the constituent expressions of the initializer,
    • an invocation of a destructor generated at the end of the lifetime of an object other than a temporary object (6.7.7 [class.temporary]) whose lifetime has not been extended, or
    • an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.
    ...
  2. Change in 9.6 [dcl.struct.bind] paragraph 4 as follows:

    ... Each vi is the name of an lvalue of type Ti that refers to the object bound to ri; the referenced type is Ti. The initialization of e is sequenced before the initialization of any ri. The initialization of each ri is sequenced before the initialization of any rj where i < j.
  3. Append a new paragraph at the end of 9.6 [dcl.struct.bind] as follows:

    ... [ Example: ... The type of the id-expression x is "int", the type of the id-expression y is "const volatile double". -- end example ]

    The initialization of e and of any ri are sequenced before the destruction of any temporary object introduced by the initializer for e or by the initializers for the ri. The temporary objects are destroyed in the reverse order of their construction.

CWG 2024-06-14

The specification for the lifetime of temporaries should be moved to 6.7.7 [class.temporary].)

Proposed resolution (approved by CWG 2024-06-28):

  1. Change in 6.7.7 [class.temporary] paragraph 5 as follows:

    There are four five contexts in which temporaries are destroyed at a different point than the end of the full-expression. ...
  2. Insert a new paragraph after 6.7.7 [class.temporary] paragraph 7 as follows:

    The fourth context is when a temporary object is created in the for-range-initializer of a range-based for statement. If such a temporary object would otherwise be destroyed at the end of the for-range-initializer full-expression, the object persists for the lifetime of the reference initialized by the for-range-initializer.

    The fifth context is when a temporary object is created in a structured binding declaration (9.6 [dcl.struct.bind]). Any temporary objects introduced by the initializers for the variables with unique names are destroyed at the end of the structured binding declaration.

  3. Change in 6.9.1 [intro.execution] paragraph 5 as follows:

    A full-expression is
    • an unevaluated operand (7.2.3 [expr.context]),
    • a constant-expression (7.7 [expr.const]),
    • an immediate invocation (7.7 [expr.const]),
    • an init-declarator (9.3 [dcl.decl]) (including such introduced by a structured binding (9.6 [dcl.struct.bind])) or a mem-initializer (11.9.3 [class.base.init]), including the constituent expressions of the initializer,
    • an invocation of a destructor generated at the end of the lifetime of an object other than a temporary object (6.7.7 [class.temporary]) whose lifetime has not been extended, or
    • an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.
    ...
  4. Change in 9.6 [dcl.struct.bind] paragraph 4 as follows:

    ... Each vi is the name of an lvalue of type Ti that refers to the object bound to ri; the referenced type is Ti. The initialization of e is sequenced before the initialization of any ri. The initialization of each ri is sequenced before the initialization of any rj where i < j.



2877. Type-only lookup for using-enum-declarator

Section: 9.7.2  [enum.udecl]     Status: DR     Submitter: Richard Smith     Date: 2024-04-07

[Accepted as a DR at the June, 2024 meeting.]

Issue 2621 claimed to ask the question whether lookup for using enum declarations was supposed to be type-only, but the example actually highlighted the difference between elaborated-type-specifier lookup (where finding nothing but typedef names is ill-formed) and ordinary lookup.

However, consider:

  enum A {
    x, y
  };
  void f() {
    int A;
    using enum A;      // #1, error: names non-type int A
    using T = enum A;  // #2, OK, names ::A
  }

The two situations should both be type-only lookups for consistency, although #2 would not find typedefs.

Proposed resolution (reviewed by CWG 2024-05-17) [SUPERSEDED]:

Change in 9.7.2 [enum.udecl] paragraph 1 as follows:

A using-enum-declarator names the set of declarations found by type-only lookup (6.5.1 [basic.lookup.general] 6.5.3 [basic.lookup.unqual], 6.5.5 [basic.lookup.qual]) for the using-enum-declarator (6.5.3 [basic.lookup.unqual], 6.5.5 [basic.lookup.qual]). The using-enum-declarator shall designate a non-dependent type with a reachable enum-specifier.

Additional notes (May, 2024)

An example is desirable. Also, the treatment of the following example is unclear:

  template<class T> using AA = T;
  enum AA<E> e; // ill-formed elaborated-type-specifier
  using enum AA<E>; // Clang and MSVC reject, GCC and EDG accept

Proposed resolution (approved by CWG 2024-06-26):

Change in 9.7.2 [enum.udecl] paragraph 1 as follows:

A using-enum-declarator names the set of declarations found by type-only lookup (6.5.1 [basic.lookup.general] 6.5.3 [basic.lookup.unqual], 6.5.5 [basic.lookup.qual]) for the using-enum-declarator (6.5.3 [basic.lookup.unqual], 6.5.5 [basic.lookup.qual]). The using-enum-declarator shall designate a non-dependent type with a reachable enum-specifier. [ Example:
enum E { x };
void f() {
  int E;
  using enum E;   // OK
}
using F = E;
using enum F;     // OK
template<class T> using EE = T;
void g() {
  using enum EE<E>;  // OK
}
-- end example ]



2871. User-declared constructor templates inhibiting default constructors

Section: 11.4.5.2  [class.default.ctor]     Status: DR     Submitter: Jim X     Date: 2024-03-03

[Accepted as a DR at the June, 2024 meeting.]

(From submission #510.)

Subclause 11.4.5.2 [class.default.ctor] paragraph 1 does not, but should, consider user-declared constructor templates.

Proposed resolution (approved by CWG 2024-04-05):

Change in 11.4.5.2 [class.default.ctor] paragraph 1 as follows:

... If there is no user-declared constructor or constructor template for class X, a non-explicit constructor having no parameters is implicitly declared as defaulted (9.5 [dcl.fct.def]). ...



2588. friend declarations and module linkage

Section: 11.8.4  [class.friend]     Status: DR     Submitter: Nathan Sidwell     Date: 2022-05-26     Liaison: EWG

[Accepted as a DR at the June, 2024 meeting.]

Consider:

  export module Foo;
  class X {
    friend void f(X); // #1 linkage?
  };

Subclause 11.8.4 [class.friend] paragraph 4 gives #1 external linkage:

A function first declared in a friend declaration has the linkage of the namespace of which it is a member (6.6 [basic.link]).

(There is no similar provision for friend classes first declared in a class.)

However, 6.6 [basic.link] bullet 4.8 gives it module linkage:

... otherwise, if the declaration of the name is attached to a named module (10.1 [module.unit]) and is not exported (10.2 [module.interface]), the name has module linkage;

Subclause 10.2 [module.interface] paragraph 2 does not apply:

A declaration is exported if it is declared within an export-declaration and inhabits a namespace scope or it is

Also consider this related example:

  export module Foo;
  export class Y;
  // maybe many lines later, or even a different partition of Foo
  class Y {
    friend void f(Y); // #2 linkage?
  };

See issue 2607 for a similar question about enumerators.

Additional note (May, 2022):

Forwarded to EWG with paper issue 1253, by decision of the CWG chair.

EWG telecon 2022-06-09

Consensus: "A friend's linkage should be affected by the presence/absence of export on the containing class definition itself, but ONLY if the friend is a definition", pending confirmation by electronic polling.

Proposed resolution (June, 2022) (approved by CWG 2023-01-27) [SUPERSEDED]:

  1. Change in 6.6 [basic.link] paragraph 4 as follows:

    ... The name of an entity that belongs to a namespace scope that has not been given internal linkage above and that is the name of
    • a variable; or
    • a function; or
    • a named class (11.1 [class.pre]), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes (9.2.4 [dcl.typedef]); or
    • a named enumeration (9.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]); or
    • an unnamed enumeration that has an enumerator as a name for linkage purposes (9.7.1 [dcl.enum]); or
    • a template
    has its linkage determined as follows:
    • if the entity is a function or function template first declared in a friend declaration and that declaration is a definition, the name has the same linkage, if any, as the name of the enclosing class (11.8.4 [class.friend]);
    • otherwise, if the entity is a function or function template declared in a friend declaration and a corresponding non-friend declaration is reachable, the name has the linkage determined from that prior declaration,
    • otherwise, if the enclosing namespace has internal linkage, the name has internal linkage;
    • otherwise, if the declaration of the name is attached to a named module (10.1 [module.unit]) and is not exported (10.2 [module.interface]), the name has module linkage;
    • otherwise, the name has external linkage.
  2. Remove 11.8.4 [class.friend] paragraph 4:

    A function first declared in a friend declaration has the linkage of the namespace of which it is a member (6.6 [basic.link]). Otherwise, the function retains its previous linkage (9.2.2 [dcl.stc]).

EWG electronic poll 2022-06

Consensus for "A friend's linkage should be affected by the presence/absence of export on the containing class definition itself, but ONLY if the friend is a definition (option #2, modified by Jason's suggestion). This resolves CWG2588." See vote.

Proposed resolution (approved by CWG 2024-03-20):

  1. Change in 6.6 [basic.link] paragraph 4 as follows:

    ... The name of an entity that belongs to a namespace scope that has not been given internal linkage above and that is the name of
    • a variable; or
    • a function; or
    • a named class (11.1 [class.pre]), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes (9.2.4 [dcl.typedef]); or
    • a named enumeration (9.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]); or
    • an unnamed enumeration that has an enumerator as a name for linkage purposes (9.7.1 [dcl.enum]); or
    • a template
    has its linkage determined as follows:
    • if the entity is a function or function template first declared in a friend declaration and that declaration is a definition and the enclosing class is defined within an export-declaration, the name has the same linkage, if any, as the name of the enclosing class (11.8.4 [class.friend]);
    • otherwise, if the entity is a function or function template declared in a friend declaration and a corresponding non-friend declaration is reachable, the name has the linkage determined from that prior declaration,
    • otherwise, if the enclosing namespace has internal linkage, the name has internal linkage;
    • otherwise, if the declaration of the name is attached to a named module (10.1 [module.unit]) and is not exported (10.2 [module.interface]), the name has module linkage;
    • otherwise, the name has external linkage.
  2. Remove 11.8.4 [class.friend] paragraph 4:

    A function first declared in a friend declaration has the linkage of the namespace of which it is a member (6.6 [basic.link]). Otherwise, the function retains its previous linkage (9.2.2 [dcl.stc]).



2891. Normative status of implementation limits

Section: Clause Annex B  [implimits]     Status: DR     Submitter: ISO/CS     Date: 2024-05-17

[Accepted as a DR at the June, 2024 meeting.]

It is unclear whether Clause Annex B [implimits] is normative, and what its normative content is. The Annex was editorially switched from "informative" to "normative" in September 2020 with this change description:

[intro.compliance.general, implimits] Cite Annex B normatively.

This change also promotes Annex B [implimits] to a "normative" annex. The existing wording in the annex is already normative in character.

On the other hand, this sentence in Clause Annex B [implimits] paragraph 2 seems to say otherwise:

... However, these quantities are only guidelines and do not determine compliance.

If it is indeed intentional that the actual quantitative limits are just guidelines, then the normative (conformance-related) content is limited to Clause Annex B [implimits] paragraph 1, which establishes documentation requirements only:

Because computers are finite, C++ implementations are inevitably limited in the size of the programs they can successfully process. Every implementation shall document those limitations where known. This documentation may cite fixed limits where they exist, say how to compute variable limits as a function of available resources, or say that fixed limits do not exist or are unknown.

However, those general documentation requirements are best expressed in 4.1.1 [intro.compliance.general], leaving Clause Annex B [implimits] with just an informative list of minimum suggested quantities.

Possible resolution [SUPERSEDED]:

  1. Change in 4.1.1 [intro.compliance.general] bullet 2.1 as follows:

    If a program contains no violations of the rules in Clause 5 through Clause 33 and Annex D, a conforming implementation shall, within its resource limits as described in Annex B (see below), accept and correctly execute that program.
  2. Insert a new paragraph before 4.1.1 [intro.compliance.general] paragraph 8 as follows:

    An implementation shall document its limitations in the size of the programs it can successfully process, where known. This documentation may cite fixed limits where they exist, say how to compute variable limits as a function of available resources, or say that fixed limits do not exist or are unknown. Clause Annex B [implimits] lists some quantities that can be subject to limitations, and recommends a minimum value for each quantity.

    A conforming implementation may have extensions (including additional library functions), ...

  3. Change in Clause Annex B [implimits] paragraph 1 and paragraph 2 as follows:

    Annex B
    (normativeinformative)

    Because computers are finite, C++ implementations are inevitably limited in the size of the programs they can successfully process. Every implementation shall document those limitations where known. This documentation may cite fixed limits where they exist, say how to compute variable limits as a function of available resources, or say that fixed limits do not exist or are unknown.

    The limits may constrain quantities that include those described below or others. Implementations can exhibit limitations, among others for the quantities in the following list. The bracketed number following each quantity is recommended as the minimum value for that quantity. However, these quantities are only guidelines and do not determine compliance.

CWG 2024-05-17

CWG was divided whether to relax the documentation requirement or not. As a next step, the wording for a relaxation should be reviewed.

Proposed resolution (approved by CWG 2024-06-26):

  1. Change in 4.1.1 [intro.compliance.general] bullet 2.1 as follows:

    If a program contains no violations of the rules in Clause 5 through Clause 33 and Annex D, a conforming implementation shall, within its resource limits as described in Annex B, accept and correctly execute that program, except when the implementation's limitations (see below) are exceeded.
  2. Insert a new paragraph before 4.1.1 [intro.compliance.general] paragraph 8 as follows:

    An implementation is encouraged to document its limitations in the size or complexity of the programs it can successfully process, if possible and where known. Clause Annex B [implimits] lists some quantities that can be subject to limitations and a potential minimum supported value for each quantity.

    A conforming implementation may have extensions (including additional library functions), ...

  3. Change in Clause Annex B [implimits] paragraph 1 and paragraph 2 as follows:

    Annex B
    (normativeinformative)

    Because computers are finite, C++ implementations are inevitably limited in the size of the programs they can successfully process. Every implementation shall document those limitations where known. This documentation may cite fixed limits where they exist, say how to compute variable limits as a function of available resources, or say that fixed limits do not exist or are unknown.

    The limits may constrain quantities that include those described below or others. Implementations can exhibit limitations for various quantities; some possibilities are presented in the following list. The bracketed number following each quantity is recommended as the a potential minimum value for that quantity. However, these quantities are only guidelines and do not determine compliance.




2887. Missing compatibility entries for xvalues

Section: C.6.3  [diff.cpp03.expr]     Status: DR     Submitter: Jiang An     Date: 2024-04-18

[Accepted as a DR at the June, 2024 meeting.]

(From submission #528.)

Paper N3055 (A Taxonomy of Expression Value Categories) introduced xvalues. This changed the behavior of well-formed code for typeid and the conditional operator, but corresponding entries in Annex C are missing.

Proposed resolution (approved by CWG 2024-05-31):

  1. Add a new paragraph to C.6.3 [diff.cpp03.expr] as follows:

    Affected subclause: 7.6.1.8 [expr.typeid]
    Change: Evaluation of operands in typeid.
    Rationale: Introduce additional expression value categories.
    Effect on original feature: Valid C++ 2003 code that uses xvalues as operands for typeid may change behavior. For example,
      void f() {
        struct B {
          B() {}
          virtual ~B() { }
        };
    
        struct C { B b; };
        typeid(C().b); // unevaluated in C++03, evaluated in C++11
      }
    
  2. Add a new paragraph to C.6.3 [diff.cpp03.expr] as follows:

    Affected subclause: 7.6.16 [expr.cond]
    Change: Fewer copies in the conditional operator.
    Rationale: Introduce additional expression value categories.
    Effect on original feature: Valid C++ 2003 code that uses xvalues as operands for the conditional operator may change behavior. For example,
      void f() {
        struct B {
          B() {}
          B(const B&) { }
        };
        struct D : B {};
    
        struct BB { B b; };
        struct DD { D d; };
    
        true ? BB().b : DD().d; // additional copy in C++03, no copy or move in C++11
      }
    





Issues with "accepted" Status


2858. Declarative nested-name-specifiers and pack-index-specifiers

Section: 7.5.5.3  [expr.prim.id.qual]     Status: accepted     Submitter: Krystian Stasiowski     Date: 2024-02-06

[Accepted at the June, 2024 meeting.]

(From submission #499.)

Subclause 7.5.5.3 [expr.prim.id.qual] paragraph 2 specifies:

... A declarative nested-name-specifier shall not have a decltype-specifier. ...

This should have been adjusted by P2662R3 (Pack Indexing) to read computed-type-specifier.

Proposed resolution (approved by CWG 2024-04-05):

Change in 7.5.5.3 [expr.prim.id.qual] paragraph 2 as follows:

... A declarative nested-name-specifier shall not have a decltype-specifier computed-type-specifier. ...

CWG 2024-06-26

This is not a DR.




2819. Cast from null pointer value in a constant expression

Section: 7.7  [expr.const]     Status: accepted     Submitter: Jason Merrill     Date: 2023-10-19     Liaison: EWG

[Accepted at the June, 2024 meeting.]

Subclause 7.7 [expr.const] bullet 5.14 was amended by P2738R1 to support certain casts from void* to object pointer types. The bullet specifies:

This wording does not, but should, support null pointer values. The implementation burden is negligible.

Proposed resolution (approved by CWG 2023-12-01):

Change in 7.7 [expr.const] bullet 5.14 as follows:

CWG 2023-12-01

CWG seeks approval from EWG for the design direction. See paper issue 1698.

EWG 2024-03-18

EWG approves.

CWG 2024-04-19

This issue is not a DR.




2876. Disambiguation of T x = delete("text")

Section: 9.5.1  [dcl.fct.def.general]     Status: accepted     Submitter: Richard Smith     Date: 2024-03-22

[Accepted at the June, 2024 meeting.]

P2573R2 (= delete("should have a reason");), adopted in Tokyo, does not disambiguate the following syntax:

  using T = void ();
  using U = int;

  T a = delete ("hello");
  U b = delete ("hello");

Either may be parsed as a (semantically ill-formed) simple-declaration whose initializer is a delete-expression or as a function-definition.

Proposed resolution (approved by CWG 2024-05-31):

Change and split 9.1 [dcl.pre] paragraph 9 as follows:

An object definition causes storage of appropriate size and alignment to be reserved and any appropriate initialization (9.4 [dcl.init]) to be done.

Syntactic components beyond those found in the general form of simple-declaration are added to a function declaration to make a function-definition. A token sequence starting with { or = is treated as a function-body (9.5.1 [dcl.fct.def.general]) if the type of the declarator-id (9.3.4.1 [dcl.meaning.general]) is a function type, and is otherwise treated as a brace-or-equal-initializer (9.4.1 [dcl.init.general]). [ Note: If the declaration acquires a function type through template instantiation, the program is ill-formed; see 13.9.1 [temp.spec.general]. The function type of a function definition cannot be specified with a typedef-name (9.3.4.6 [dcl.fct]). --end note ] An object declaration, however, is also a definition unless it contains the extern specifier and has no initializer (6.2 [basic.def]). An object definition causes storage of appropriate size and alignment to be reserved and any appropriate initialization (9.4 [dcl.init]) to be done.

This drafting also resolves issue 2144.






Issues with "DRWP" Status


2700. #error disallows existing implementation practice

Section: 4.1.1  [intro.compliance.general]     Status: DRWP     Submitter: Richard Smith     Date: 2023-02-13     Liaison: WG14

[Accepted as a DR at the March, 2024 meeting.]

The resolution for issue 2518 disallows existing implementation practice, as detailed below:

Suggested resolution [SUPERSEDED]:

  1. Change in 4.1.1 [intro.compliance.general] paragraph 2 as follows:

    Furthermore, a conforming implementation
    • shall not accept a preprocessing translation unit containing a #error preprocessing directive (15.8 [cpp.error]), and
    • 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]).
  2. Change in 5.1 [lex.separate] paragraph 1 as follows:

    The text of the program is kept in units called source files in this document. A source file together with all the headers (16.4.2.3 [headers]) and source files included (15.3 [cpp.include]) via the preprocessing directive #include, less any source lines skipped by any of the conditional inclusion (15.2 [cpp.cond]) preprocessing directives or by the implementation-defined behavior of any conditionally-supported-directives (15.1 [cpp.pre]), is called a preprocessing translation unit.

CWG 2023-03-03

Permit that #warning can be ignored if another diagnostic is produced.

Proposed resolution (approved by CWG 2024-01-19):

  1. Change in 4.1.1 [intro.compliance.general] bullet 2.3 as follows:

    • ...
    • Otherwise, if a program contains
      • a violation of any diagnosable rule or,
      • a preprocessing translation unit with a #warning preprocessing directive (15.8 [cpp.error]), 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.
  2. Change in 4.1.1 [intro.compliance.general] paragraph 2 as follows:

    Furthermore, a conforming implementation shall not accept
    • a preprocessing translation unit containing a #error preprocessing directive (15.8 [cpp.error]), or
    • 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]).
  3. Change in 5.1 [lex.separate] paragraph 1 as follows:

    The text of the program is kept in units called source files in this document. A source file together with all the headers (16.4.2.3 [headers]) and source files included (15.3 [cpp.include]) via the preprocessing directive #include, less any source lines skipped by any of the conditional inclusion (15.2 [cpp.cond]) preprocessing directives, as modified by the implementation-defined behavior of any conditionally-supported-directives (15.1 [cpp.pre]) and pragmas (15.9 [cpp.pragma]), if any, is called a preprocessing translation unit.
  4. Change in 15.8 [cpp.error] as follows:

    A preprocessing directive of either of the following forms the form
    # error pp-tokensopt new-line
    
    renders the program ill-formed. A preprocessing directive of the form
    # warning pp-tokensopt new-line
    
    causes requires the implementation to produce a at least one diagnostic message for the preprocessing translation unit (4.1.1 [intro.compliance.general]) that.

    Recommended practice: Any diagnostic message caused by either of these directives should include the specified sequence of preprocessing tokens; the #error directive renders the program ill-formed.




1698. Files ending in \

Section: 5.2  [lex.phases]     Status: DRWP     Submitter: David Krauss     Date: 2013-06-10

[Accepted as a DR at the November, 2023 meeting.]

The description of how to handle file not ending in a newline in 5.2 [lex.phases] paragraph 1, phase 2, is:

  1. Each instance of a backslash character (\) immediately followed by a new-line character is deleted, splicing physical source lines to form logical source lines. Only the last backslash on any physical source line shall be eligible for being part of such a splice. If, as a result, a character sequence that matches the syntax of a universal-character-name is produced, the behavior is undefined. A source file that is not empty and that does not end in a new-line character, or that ends in a new-line character immediately preceded by a backslash character before any such splicing takes place, shall be processed as if an additional new-line character were appended to the file.

This is not clear regarding what happens if the last character in the file is a backslash. In such a case, presumably the result of adding the newline should not be a line splice but rather a backslash preprocessing-token (that will be diagnosed as an invalid token in phase 7), but that should be spelled out.

CWG 2023-07-14

Addressed by the resolution for issue 2747.




2573. Undefined behavior when splicing results in a universal-character-name

Section: 5.2  [lex.phases]     Status: DRWP     Submitter: US     Date: 2019-10-23     Liaison: SG12

[ Resolved by paper P2621R2 (Undefined behavior in the lexer), adopted in June, 2023. ]

(From National Body comments US 024 and US 025 on the C++20 DIS.)

Subclause 5.2 [lex.phases] bullet 1.2 specifies:

Except for splices reverted in a raw string literal, if a splice results in a character sequence that matches the syntax of a universal-character-name, the behavior is undefined.

Undefined behavior during lexing is not acceptable. The behavior ought to be well-defined, ill-formed, or conditionally-supported.

Additional notes (January, 2023):

Forwarded to SG12 with paper issue 1405, by decision of the CWG and SG12 chairs.




2747. Cannot depend on an already-deleted splice

Section: 5.2  [lex.phases]     Status: DRWP     Submitter: Jim X     Date: 2021-09-14

[Accepted as a DR at the November, 2023 meeting.]

(From editorial issue 4903.)

Subclause 5.2 [lex.phases] paragraph 2 specifies:

... Each sequence of a backslash character (\) immediately followed by zero or more whitespace characters other than new-line followed by a new-line character is deleted, splicing physical source lines to form logical source lines. ... A source file that is not empty and that does not end in a new-line character, or that ends in a splice, shall be processed as if an additional new-line character were appended to the file.

This is confusing, because the first sentence deletes all splices, and then the last sentence checks for a splice that has already been deleted.

Proposed resolution (approved by CWG 2023-07-14):

Change in 5.2 [lex.phases] paragraph 2 as follows:

... Each sequence of a backslash character (\) immediately followed by zero or more whitespace characters other than new-line followed by a new-line character is deleted, splicing physical source lines to form logical source lines. ... A source file that is not empty and that (after splicing) does not end in a new-line character, or that ends in a splice, shall be processed as if an additional new-line character were appended to the file.

CWG 2023-07-14

CWG noted that a lone backslash at the end of a file remains (in the status quo and with the proposed change) and turns into an ill-formed preprocessing-token. The wording as amended seems sufficiently clear to consider issue 1698 resolved.




2574. Undefined behavior when lexing unmatched quotes

Section: 5.4  [lex.pptoken]     Status: DRWP     Submitter: US     Date: 2019-10-23     Liaison: SG12

[ Resolved by paper P2621R2 (Undefined behavior in the lexer), adopted in June, 2023. ]

(From National Body comment US 027 on the C++20 DIS.)

Subclause 5.4 [lex.pptoken] paragraph 2 specifies:

If a U+0027 apostrophe or a U+0022 quotation mark character matches the last category, the behavior is undefined.

Undefined behavior during lexing is not acceptable. This ought to be ill-formed.

Additional notes (January, 2023):

Forwarded to SG12 with paper issue 1406, by decision of the CWG and SG12 chairs.




2698. Using extended integer types with z suffix

Section: 5.13.2  [lex.icon]     Status: DRWP     Submitter: Mike Miller     Date: 2023-02-17     Liaison: WG14, EWG

[Accepted as a DR at the June, 2023 meeting.]

Subclause 5.13.2 [lex.icon] paragraph 4 specifies:

If an integer-literal cannot be represented by any type in its list and an extended integer type (6.8.2 [basic.fundamental]) can represent its value, it may have that extended integer type. If all of the types in the list for the integer-literal are signed, the extended integer type shall be signed. If all of the types in the list for the integer-literal are unsigned, the extended integer type shall be unsigned. If the list contains both signed and unsigned types, the extended integer type may be signed or unsigned. A program is ill-formed if one of its translation units contains an integer-literal that cannot be represented by any of the allowed types.

This implies that an integer-literal with a z suffix can be of extended integer type, if the literal is larger than what is representable in std::size_t.

According to the author of the paper P0330R8 (Literal Suffix for (signed) size_t) introducing the feature, this is unintentional; z should only yield std::size_t and its corresponding signed integer type.

See also the corresponding WG14 paper N2998 Literal Suffixes for size_t.

Proposed resolution (reviewed by CWG 2023-03-03, approved by CWG 2023-05-12):

Change in 5.13.2 [lex.icon] paragraph 4 as follows:

If Except for integer-literals containing a size-suffix, if the value of an integer-literal cannot be represented by any type in its list and an extended integer type (6.8.2 [basic.fundamental]) can represent its value, it may have that extended integer type. If all of the types in the list for the integer-literal are signed, the extended integer type shall be is signed. If all of the types in the list for the integer-literal are unsigned, the extended integer type shall be is unsigned. If the list contains both signed and unsigned types, the extended integer type may be signed or unsigned. A program is ill-formed if one of its translation units contains If an integer-literal that cannot be represented by any of the allowed types, the program is ill-formed. [ Note: An integer-literal with a z or Z suffix is ill-formed if it cannot be represented by std::size_t. -- end note ]

Additional notes (February, 2023)

Alerted the chair of SG22 (C/C++ Liaison).

Forwarded to EWG at the request of the EWG chair via cplusplus/papers#1467.

EWG 2023-05-11

The "z" suffixes mean std::size_t (or its corresponding signed type) only. The proposed resolution is accepted by EWG.




2745. Dependent odr-use in generic lambdas

Section: 6.3  [basic.def.odr]     Status: DRWP     Submitter: Shafik Yaghmour     Date: 2022-12-13

[Accepted as a DR at the March, 2024 meeting.]

Default template arguments of generic lambdas can refer to local variables. It is unclear whether the potential odr-use is checked when parsing the template definition or when instantiating the template.

There is wide implementation divergence.

Proposed resolution (approved by CWG 2024-03-20):

Insert a new paragraph before 6.3 [basic.def.odr] paragraph 11:

[ Example:

  void g() {
    constexpr int x = 1;
    auto lambda = [] <typename T, int = ((T)x, 0)> {};  // OK
    lambda.operator()<int, 1>();         // OK, does not consider x at all
    lambda.operator()<int>();            // OK, does not odr-use x
    lambda.operator()<const int&>();     // error: odr-uses x from a context where x is not odr-usable
  }

  void h() {
    constexpr int x = 1;
    auto lambda = [] <typename T> { (T)x; };  // OK
    lambda.operator()<int>();            // OK, does not odr-use x
    lambda.operator()<void>();           // OK, does not odr-use x
    lambda.operator()<const int&>();     // error: odr-uses x from a context where x is not odr-usable
  }

-- end example ]

Every program shall contain at least one definition of every function or variable ...




2764. Use of placeholders affecting name mangling

Section: 6.4.1  [basic.scope.scope]     Status: DRWP     Submitter: Hubert Tong     Date: 2023-07-05

[Accepted as a DR at the November, 2023 meeting.]

Paper P2169R4 (A nice placeholder with no name), as approved by WG21 in Varna, added a placeholder facility. The intent was that the use of placeholders is sufficiently limited such that they never need to be mangled. Quote from 6.4.1 [basic.scope.scope] paragraph 5 as modified by the paper:

A declaration is name-independent if its name is _ and it declares a variable with automatic storage duration, a structured binding not inhabiting a namespace scope, the variable introduced by an init-capture, or a non-static data member.

The following example does not seem to follow that intent:

  struct A { A(); };
  inline void f() {
    static union { A _{}; };
    static union { A _{}; };
  }
  void g() { return f(); }

The preceding example needs handling similar to the following example, which is unrelated to the placeholder feature:

  struct A { A(); };
  inline void f() {
    { static union { A a{}; }; }
    { static union { A a{}; }; }
  }
  void g() { return f(); }

A similar problem may arise for static or thread_local structured bindings at block scope.

Finally, another example involving placeholders in anonymous unions:

  static union { int _ = 42; };
  int &ref = _;
  int foo() { return 13; }
  static union { int _ = foo(); };
  int main() { return ref; }

Possible resolution (reviewed by CWG 2023-08-25) [SUPERSEDED]:

Change in 6.4.1 [basic.scope.scope] paragraph 5 and add bullets as follows:

A class is name-dependent if it is an anonymous union declared at namespace scope or with a storage-class-specifier (11.5.2 [class.union.anon]). A declaration is name-independent if its name is _ and it declares

Proposed resolution (approved by CWG 2023-09-15):

Change in 6.4.1 [basic.scope.scope] paragraph 5 and add bullets as follows:

A declaration is name-independent if its name is _ and it declares



2793. Block-scope declaration conflicting with parameter name

Section: 6.4.3  [basic.scope.block]     Status: DRWP     Submitter: Jason Merrill     Date: 2023-08-31

[Accepted as a DR at the November, 2023 meeting.]

Consider:

  void f(int i) { extern int i; } 

According to 6.4.3 [basic.scope.block] paragraph 2, the target scope of the declaration is relevant (which would be the global scope), but not the scope in which the name is bound. That seems wrong. For comparison, template parameter names use the latter rule (13.8.2 [temp.local] paragraph 6).

Proposed resolution (approved by CWG 2023-09-15):

If a declaration that is not a name-independent declaration and whose target scope is that binds a name in the block scope S of a potentially conflicts with a declaration whose target scope is the parent scope of S, the program is ill-formed.



2857. Argument-dependent lookup with incomplete class types

Section: 6.5.4  [basic.lookup.argdep]     Status: DRWP     Submitter: Lewis Baker     Date: 2024-02-08

[Accepted as a DR at the March, 2024 meeting.]

Subclause 6.5.4 [basic.lookup.argdep] bullet 3.2 specifies:

It is unclear what happens if T is incomplete, for example because it was instantiated from a template whose definition was not (yet) available.

Proposed resolution (approved by CWG 2024-03-01)

Change in 6.5.4 [basic.lookup.argdep] bullet 3.2 as follows:




2753. Storage reuse for string literal objects and backing arrays

Section: 6.7.2  [intro.object]     Status: DRWP     Submitter: Brian Bi     Date: 2023-06-29

[Accepted as a DR at the November, 2023 meeting.]

Subclause 6.7.2 [intro.object] paragraph 9 specifies the general principle that two objects with overlapping lifetimes have non-overlapping storage, which can be observed by comparing addresses:

Unless an object is a bit-field or a subobject of zero size, the address of that object is the address of the first byte it occupies. Two objects with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a subobject of zero size and they are of different types; otherwise, they have distinct addresses and occupy disjoint bytes of storage.

After P2752, there are two exceptions: string literal objects and backing arrays for initializer lists.

Subclause 5.13.5 [lex.string] paragraph 9 specifies:

Evaluating a string-literal results in a string literal object with static storage duration (6.7.5 [basic.stc]). Whether all string-literals are distinct (that is, are stored in nonoverlapping objects) and whether successive evaluations of a string-literal yield the same or a different object is unspecified.

Subclause 9.4.4 [dcl.init.ref] paragraph 5, after application of P2752R3 (approved in June, 2023), specifies:

Whether all backing arrays are distinct (that is, are stored in non-overlapping objects) is unspecified.

It is unclear whether a backing array can overlap with a string literal object.

Furthermore, it is unclear whether any such object can overlap with named objects or temporaries, for example:

  const char (&r) [] = "foo";
  const char a[] = {'f', 'o', 'o', '\0'};

  int main() {  
    assert(&r == &a);   // allowed not to fail?
  }

Proposed resolution (approved by CWG 2023-11-09):

  1. Add a new paragraph before 6.7.2 [intro.object] paragraph 9 and change the latter as follows:

    An object is a potentially non-unique object if it is a string literal object (5.13.5 [lex.string]), the backing array of an initializer list (9.4.4 [dcl.init.ref]), or a subobject thereof.

    Unless an object is a bit-field or a subobject of zero size, the address of that object is the address of the first byte it occupies. Two objects with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a subobject of zero size and they are of different types, or if they are both potentially non-unique objects; otherwise, they have distinct addresses and occupy disjoint bytes of storage.

    [Example 2:

      static const char test1 = 'x';
      static const char test2 = 'x';
      const bool b = &test1 != &test2;  // always true
    
      static const char (&r) [] = "x";
      static const char *s = "x";  
      static std::initializer_list<char> il = { 'x' };
      const bool b2 = r != il.begin();        // unspecified result
      const bool b3 = r != s;                 // unspecified result
      const bool b4 = il.begin() != &test1;   // always true
      const bool b5 = r != &test1;            // always true
    

    -- end example]

  2. Change in subclause 5.13.5 [lex.string] paragraph 9 as follows:

    Evaluating a string-literal results in a string literal object with static storage duration (6.7.5 [basic.stc]). [ Note: String literal objects are potentially non-unique (6.7.2 [intro.object]). Whether all string-literals are distinct (that is, are stored in nonoverlapping objects) and whether successive evaluations of a string-literal yield the same or a different object is unspecified. -- end note ]
  3. Change in subclause 9.4.4 [dcl.init.ref] paragraph 5, after application of P2752R3 (approved in June, 2023), as follows:

    Whether all backing arrays are distinct (that is, are stored in non-overlapping objects) is unspecified. [ Note: Backing arrays are potentially non-unique objects (6.7.2 [intro.object]). -- end note ]

CWG 2023-07-14

CWG resolved that a named or temporary object is always disjoint from any other object, and thus cannot overlap with a string literal object or a backing array. The lines b4 and b5 in the example highlight that outcome.

Backing arrays and string literals can arbitrarily overlap among themselves; CWG believes the proposed wording achieves that outcome.

The ancillary question how address comparisons between potentially non-unique objects are treated during constant evaluation is handled in issue 2765.




2795. Overlapping empty subobjects with different cv-qualification

Section: 6.7.2  [intro.object]     Status: DRWP     Submitter: Jonathan Caves     Date: 2023-09-04

[Accepted as a DR at the November, 2023 meeting.]

Subclause 6.7.2 [intro.object] paragraph 9 specifies:

... Two objects with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a subobject of zero size and they are of different types; otherwise, they have distinct addresses and occupy disjoint bytes of storage. [ Footnote: ... ]

Types T and const T are different types, but it is unlikely the rule is intending to differentiate along that line.

Suggested resolution [SUPERSEDED]:

Change in 6.7.2 [intro.object] paragraph 9 as follows:

... Two objects with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a subobject of zero size and they are of different types (ignoring top-level cv-qualifiers); otherwise, they have distinct addresses and occupy disjoint bytes of storage. [ Footnote: ... ]

Proposed resolution (approved by CWG 2023-09-15):

(Hypothetically, pointer-to-member types can be empty, but might differ in non-top-level cv-qualification.)

Change in 6.7.2 [intro.object] paragraph 9 as follows:

... Two objects with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a subobject of zero size and they are not of different similar types (7.3.6 [conv.qual]); otherwise, they have distinct addresses and occupy disjoint bytes of storage. [ Footnote: ... ]



2721. When exactly is storage reused?

Section: 6.7.3  [basic.life]     Status: DRWP     Submitter: Richard Smith     Date: 2023-03-23

[Accepted as a DR at the June, 2023 meeting.]

Subclause 6.7.3 [basic.life] bullet 1.5 specifies:

The lifetime of an object o of type T ends when:

Consider the expression new (p) T(x). Does the lifetime of *p end when p is returned from the allocation function, before x is evaluated? Or does the lifetime end when the constructor body starts executing, after x is evaluated?

The second option is conceivable for initialization by constructor and non-class types; for aggregate initialization, the first option must be used, because evaluation of x directly initializes a part of the resulting object. The first option is simpler to implement for constant evaluation.

Proposed resolution (approved by CWG 2023-05-12):

Change in 6.7.3 [basic.life] paragraph 1 as follows:

The lifetime of an object o of type T ends when: When evaluating a new-expression, storage is considered reused after it is returned from the allocation function, but before the evaluation of the new-initializer (7.6.2.8 [expr.new]). [ Example:
  struct S {
    int m;
  };

  void f() {
    S x{1};
    new(&x) S(x.m);  // undefined behavior
  }
-- end example ]



2533. Storage duration of implicitly created objects

Section: 6.7.5  [basic.stc]     Status: DRWP     Submitter: Andrey Erokhin     Date: 2022-02-17

[Accepted as a DR at the March, 2024 meeting.]

In subclause 6.7.2 [intro.object] paragraph 10, operations implicitly creating objects are defined:

Some operations are described as implicitly creating objects within a specified region of storage. For each operation that is specified as implicitly creating objects, that operation implicitly creates and starts the lifetime of zero or more objects of implicit-lifetime types (6.8.1 [basic.types.general]) in its specified region of storage if...

However, the standard does not specify the storage duration that such an implicitly-created object has; this new method of object creation is not mentioned in 6.7.5.1 [basic.stc.general] paragraph 2:

Static, thread, and automatic storage durations are associated with objects introduced by declarations (6.2 [basic.def]) and implicitly created by the implementation (6.7.7 [class.temporary]). The dynamic storage duration is associated with objects created by a new-expression (7.6.2.8 [expr.new]).

With the exception of malloc, the storage duration should probably be that of the object providing storage (if any), similar to the provision for subobjects in 6.7.5.6 [basic.stc.inherit]:

The storage duration of subobjects and reference members is that of their complete object (6.7.2 [intro.object]).

The storage duration of an object created by a non-allocating form of an allocation function (17.6.3.4 [new.delete.placement]) should be treated similarly.

Possible resolution [SUPERSEDED]:

  1. Change in 6.7.2 [intro.object] paragraph 13 as follows:

    Any implicit or explicit invocation of a function named operator new or operator new[] implicitly creates objects with dynamic storage duration in the returned region of storage and returns a pointer to a suitable created object.
  2. Change in 6.7.5.1 [basic.stc.general] paragraph 2 as follows:

    Static, thread, and automatic storage durations are associated with objects introduced by declarations (6.2 [basic.def]) and implicitly created by the implementation (6.7.7 [class.temporary]). The dynamic storage duration is associated with objects created by a new-expression (7.6.2.8 [expr.new]) in storage returned by an allocation function (6.7.5.5.2 [basic.stc.dynamic.allocation]) other than a non-allocating form (17.6.3.4 [new.delete.placement]) or by C library memory allocation (20.2.12 [c.malloc]).
  3. Change in 6.7.5.5.2 [basic.stc.dynamic.allocation] paragraph 3 as follows:

    For an allocation function other than a reserved placement allocation function other than a non-allocating form (17.6.3.4 [new.delete.placement]), the pointer returned on a successful call shall represent the address of storage that is aligned as follows:
  4. Change in 6.7.5.6 [basic.stc.inherit] paragraph 1 as follows:

    The storage duration of subobjects and reference members is that of their complete object. The storage duration of an object nested within another object x is the storage duration of x (6.7.2 [intro.object]).
  5. Change in 7.6.2.8 [expr.new] paragraph 9 as follows:

    An object created by a new-expression that invokes an allocation function with a non-allocating form (see below) has the storage duration of the object that used to occupy the region of storage where the new object is created. Objects Any other object created by a new-expression have has dynamic storage duration (6.7.5.5 [basic.stc.dynamic]). [Note 5: The lifetime of such an object is not necessarily restricted to the scope in which it is created. —end note]
  6. Change in 20.2.12 [c.malloc] paragraph 4 as follows:

    These functions implicitly create objects (6.7.2 [intro.object]) with dynamic storage duration in the returned region of storage and return a pointer to a suitable created object. In the case of calloc and realloc, the objects are created before the storage is zeroed or copied, respectively.

Additional note (December, 2023)

The approach outlined above is incomplete and the wrong direction. The concept of storage duration determines when an object is created and destroyed; for dynamic storage duration, the object is created and destroyed by explicit program action.

Proposed resolution (approved by CWG 2024-01-19):

  1. Change in 6.7.5.1 [basic.stc.general] paragraph 2 as follows:

    Static, thread, and automatic storage durations are associated with objects introduced by declarations (6.2 [basic.def]) and implicitly created by the implementation with temporary objects (6.7.7 [class.temporary]). The dynamic storage duration is associated with objects created by a new-expression (7.6.2.8 [expr.new]) or with implicitly created objects (6.7.2 [intro.object]).



2850. Unclear storage duration for function parameter objects

Section: 6.7.5  [basic.stc]     Status: DRWP     Submitter: Brian Bi     Date: 2024-02-03

[Accepted as a DR at the March, 2024 meeting.]

(From submission #490.)

Function parameter objects have automatic storage duration and are not temporary objects (see also issue 2849). However, it is unclear how long the storage for function parameter objects lasts.

Furthermore, for temporary objects that are destroyed at the end of the full-expression, it is unclear how the destruction is ordered with respect to temporary objects destroyed at the end of the full-expression.

Proposed resolution (approved by CWG 2024-03-20):

  1. Change in and combine 6.7.5.4 [basic.stc.auto] paragraph 1 and 2 as follows:

    Variables that belong to a block or parameter scope and are not explicitly declared static, thread_local, or extern have automatic storage duration. The storage for these entitiessuch variables lasts until the block in which they are created exits. [Note 1: These variables are initialized and destroyed as described in 8.8 [stmt.dcl]. -- end note]

    Variables that belong to a parameter scope also have automatic storage duration. The storage for a function parameter lasts until immediately after its destruction (7.6.1.3 [expr.call]).

  2. Change in 6.7.7 [class.temporary] paragraph 8 as follows:

    The destruction of a temporary whose lifetime is not extended beyond the full-expression in which it was created is sequenced before the destruction of every temporary which is constructed earlier in the same full-expression. Let x and y each be either a temporary object whose lifetime is not extended, or a function parameter. If the lifetimes of x and y end at the end of the same full-expression, and x is initialized before y, then the destruction of y is sequenced before that of x. If the lifetime of two or more temporaries with lifetimes extending beyond the full-expressions in which they were created ends at the same point, these temporaries are destroyed at that point in the reverse order of the completion of their construction. In addition, the destruction of such temporaries shall take into account the ordering of destruction of objects with static, thread, or automatic storage duration (6.7.5.2 [basic.stc.static], 6.7.5.3 [basic.stc.thread], 6.7.5.4 [basic.stc.auto]); that is, if obj1 is an object with the same storage duration as the temporary and created before the temporary is created the temporary shall be destroyed before obj1 is destroyed; if obj2 is an object with the same storage duration as the temporary and created after the temporary is created the temporary shall be destroyed after obj2 is destroyed.
  3. Change in 7.6.1.3 [expr.call] paragraph 6 as follows:

    ... It is implementation-defined whether the lifetime of a parameter ends is destroyed when the function in which it is defined returns exits (8.7.4 [stmt.return], 14.3 [except.ctor]) or at the end of the enclosing full-expression; parameters are always destroyed in the reverse order of their construction. The initialization and destruction of each parameter occurs within the context of the full-expression (6.9.1 [intro.execution]) where the function call appears.
  4. Change in 8.8 [stmt.dcl] paragraph 2 as follows:

    A block variable with automatic storage duration (6.7.5.4 [basic.stc.auto]) is active everywhere in the scope to which it belongs after its init-declarator . Upon each transfer of control (including sequential execution of statements) within a function from point P to point Q, all block variables with automatic storage duration that are active at P and not at Q are destroyed in the reverse order of their construction. Then, all block variables with automatic storage duration that are active at Q but not at P are initialized in declaration order; unless all such variables have vacuous initialization (6.7.3 [basic.life]), the transfer of control shall not be a jump. [ Footnote: ... ] When a declaration-statement is executed, P and Q are the points immediately before and after it; when a function returns, Q is after its body.



2822. Side-effect-free pointer zap

Section: 6.7.5.1  [basic.stc.general]     Status: DRWP     Submitter: Davis Herring     Date: 2023-11-06

[Accepted as a DR at the March, 2024 meeting.]

Subclause 6.7.5.1 [basic.stc.general] paragraph 4 seems to suggest that the end of duration of a region of storage causes actual modifications to pointer objects, causing questions about data races (in the abstract machine).

Proposed resolution (approved by CWG 2024-03-20):

  1. Append to 6.7.5.1 [basic.stc.general] paragraph 1:

    [ Note: After the duration of a region of storage has ended, the use of pointers to that region of storage is limited (6.8.4 [basic.compound]). -- end note ]
  2. Remove 6.7.5.1 [basic.stc.general] paragraph 4 as follows:

    When the end of the duration of a region of storage is reached, the values of all pointers representing the address of any part of that region of storage become invalid pointer values (6.8.4 [basic.compound]). Indirection through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior. Any other use of an invalid pointer value has implementation-defined behavior. [ Footnote: ... ]
  3. Change in 6.8.4 [basic.compound] paragraph 3 as follows:

    [Note 2: A pointer past the end of an object (7.6.6 [expr.add]) is not considered to point to an unrelated object of the object's type, even if the unrelated object is located at that address. A pointer value becomes invalid when the storage it denotes reaches the end of its storage duration; see 6.7.5 [basic.stc].end note]
  4. Insert a new paragraph after 6.8.4 [basic.compound] paragraph 3:

    A pointer value P is valid in the context of an evaluation E if P is a null pointer value, or if it is a pointer to or past the end of an object O and E happens before the end of the duration of the region of storage for O. If a pointer value P is used in an evaluation E and P is not valid in the context of E, then the behavior is undefined if E is an indirection (7.6.2.2 [expr.unary.op]) or an invocation of a deallocation function (6.7.5.5.3 [basic.stc.dynamic.deallocation]), and implementation-defined otherwise. [ Footnote: Some implementations might define that copying such a pointer value causes a system-generated runtime fault. -- end footnote ] [ Note: P can be valid in the context of E even if it points to a type unrelated to that of O or if O is not within its lifetime, although further restrictions apply to such pointer values (6.7.3 [basic.life], 7.2.1 [basic.lval], 7.6.6 [expr.add]). —end note]
  5. Change in 7.6.1.9 [expr.static.cast] paragraph 14 as follows:

    ... If the original pointer value represents the address A of a byte in memory and A does not satisfy the alignment requirement of T, then the resulting pointer value (6.8.4 [basic.compound]) is unspecified. ...
  6. Change in 7.6.1.10 [expr.reinterpret.cast] paragraph 5 as follows:

    A value of integral type or enumeration type can be explicitly converted to a pointer. A pointer converted to an integer of sufficient size (if any such exists on the implementation) and back to the same pointer type will have its original value (6.8.4 [basic.compound]); mappings between pointers and integers are otherwise implementation-defined.



2719. Creating objects in misaligned storage

Section: 6.7.6  [basic.align]     Status: DRWP     Submitter: Jiang An     Date: 2023-04-05

[Accepted as a DR at the June, 2023 meeting.]

A non-allocating form of operator new can be used to create an object in storage that is not suitably aligned for the type of the object. Such attempts ought to be undefined behavior.

Proposed resolution (approved by CWG 2023-04-28):

  1. Change in 6.7.6 [basic.align] paragraph 1 as follows:

    Object types have alignment requirements (6.8.2 [basic.fundamental], 6.8.4 [basic.compound]) which place restrictions on the addresses at which an object of that type may be allocated. An alignment is an implementation-defined integer value representing the number of bytes between successive addresses at which a given object can be allocated. An object type imposes an alignment requirement on every object of that type; stricter alignment can be requested using the alignment specifier (9.12.2 [dcl.align]). Attempting to create an object (6.7.2 [intro.object]) in storage that does not meet the alignment requirements of the object's type is undefined behavior.
  2. Change in 7.6.2.8 [expr.new] paragraph 22 as follows:

    [Note 11: When the allocation function returns a value other than null, it must be a pointer to a block of storage in which space for the object has been reserved. The block of storage is assumed to be appropriately aligned (6.7.6 [basic.align]) and of the requested size. The address of the created object will not necessarily be the same as that of the block if the object is an array. —end note]



2849. Parameter objects are not temporary objects

Section: 6.7.7  [class.temporary]     Status: DRWP     Submitter: Brian Bi     Date: 2024-01-20

[Accepted as a DR at the March, 2024 meeting.]

(From submission #490.)

Parameter objects are not temporary objects, according to 6.7.7 [class.temporary] paragraph 1. An exception hinting at this in 6.7.7 [class.temporary] paragraph 7 should be removed.

Proposed resolution (approved by CWG 2024-02-02):

Change in 6.7.7 [class.temporary] paragraph 7 as follows:

The fourth context is when a temporary object other than a function parameter object is created in the for-range-initializer of a range-based for statement. If such a temporary object would otherwise be destroyed at the end of the for-range-initializer full-expression, the object persists for the lifetime of the reference initialized by the for-range-initializer.



2519. Object representation of a bit-field

Section: 6.8.1  [basic.types.general]     Status: DRWP     Submitter: Jiang An     Date: 2022-01-20

[Accepted as a DR at the June, 2023 meeting.]

6.7.2 [intro.object] clearly implies that bit-fields are objects; paragraphs 8-9 contain phrases like “unless an object is a bit-field...” and “a non-bit-field subobject”. However, the definition of “object representation” in 6.8.1 [basic.types.general] paragraph 4 is,

The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T).

and thus fails to address bit-fields, which are not necessarily composed of a sequence of complete bytes.

The C Standard (6.2.6.1 paragraph 4) says,

Values stored in bit-fields consist of m bits, where m is the size specified for the bit-field. The object representation is the set of m bits the bit-field comprises in the addressable storage unit holding it.

Presumably similar wording could be adopted for C++.

Proposed resolution (approved by CWG 2023-01-06) [SUPERSEDED]:

Change in 6.8.1 [basic.types.general] paragraph 4 as follows:

The object representation of an object of a type T is the sequence of N unsigned char objects taken up by the a non-bit-field complete object of type T, where N equals sizeof(T). The value representation of an object of a type T is the set of bits in the object representation of T that participate in representing a value of type T. The object and value representation of a non-bit-field complete object of type T are the bytes and bits, respectively, of the object corresponding to the object and value representation of its type. The object representation of a bit-field object is the sequence of N bits taken up by the object, where N is the width of the bit-field (11.4.10 [class.bit]). The value representation of a bit-field object is the set of bits in the object representation that participate in representing its value. Bits in the object representation of a type or object that are not part of the value representation are padding bits. For trivially copyable types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values. [ Footnote: ... ]

CWG 2023-02-06

Additional drafting is needed to constrain the definition to complete object types.

Proposed resolution (approved for C++26 by CWG 2023-02-06):

Change in 6.8.1 [basic.types.general] paragraph 4 as follows:

The object representation of an object of a complete object type T is the sequence of N unsigned char objects taken up by the a non-bit-field complete object of type T, where N equals sizeof(T). The value representation of an object of a type T is the set of bits in the object representation of T that participate in representing a value of type T. The object and value representation of a non-bit-field complete object of type T are the bytes and bits, respectively, of the object corresponding to the object and value representation of its type. The object representation of a bit-field object is the sequence of N bits taken up by the object, where N is the width of the bit-field (11.4.10 [class.bit]). The value representation of a bit-field object is the set of bits in the object representation that participate in representing its value. Bits in the object representation of a type or object that are not part of the value representation are padding bits. For trivially copyable types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values. [ Footnote: ... ]



2689. Are cv-qualified std::nullptr_t fundamental types?

Section: 6.8.2  [basic.fundamental]     Status: DRWP     Submitter: Anoop Rana     Date: 2022-12-08

[Accepted as a DR at the March, 2024 meeting.]

It is unclear whether cv std::nullptr_t is a fundamental type, given that it is declared in a library header and cv-qualifications are not mentioned in 6.8.2 [basic.fundamental] paragraph 15.

Proposed resolution (approved by CWG 2023-12-01):

Change in 6.8.2 [basic.fundamental] paragraph 15 as follows:

The types denoted by cv std::nullptr_t are distinct types. A value of type std::nullptr_t is a null pointer constant (7.3.12 [conv.ptr]). Such values participate in the pointer and the pointer-to-member conversions (7.3.12 [conv.ptr], 7.3.13 [conv.mem]). sizeof(std::nullptr_t) shall be equal to sizeof(void*).

The types described in this subclause are called fundamental types. [Note 11: Even if the implementation defines two or more fundamental types to have the same value representation, they are nevertheless different types. —end note]




2723. Range of representable values for floating-point types

Section: 6.8.2  [basic.fundamental]     Status: DRWP     Submitter: Jiang An     Date: 2023-04-21

[Accepted as a DR at the June, 2023 meeting.]

The range of representable values is defined for integer types, but not for floating-point types. This term is used in 5.13.4 [lex.fcon] paragraph 3 as well as in the library, e.g. in 22.13.3 [charconv.from.chars] and 30.4.3.2.3 [facet.num.get.virtuals].

The C standard contains a suitable definition that we should inherit.

Proposed resolution (approved by CWG 2023-05-12):

Add a new paragraph after 6.8.2 [basic.fundamental] paragraph 12 as follows:

... Except as specified in 6.8.3 [basic.extended.fp], the object and value representations and accuracy of operations of floating-point types are implementation-defined.

The minimum range of representable values for a floating-point type is the most negative finite floating-point number representable in that type through the most positive finite floating-point number representable in that type. In addition, if negative infinity is representable in a type, the range of that type is extended to all negative real numbers; likewise, if positive infinity is representable in a type, the range of that type is extended to all positive real numbers. [ Note: Since negative and positive infinity are representable in ISO/IEC/IEEE 60559 formats, all real numbers lie within the range of representable values of a floating-point type adhering to ISO/IEC/IEEE 60559. ]




2811. Clarify "use" of main

Section: 6.9.3.1  [basic.start.main]     Status: DRWP     Submitter: Jan Schultke     Date: 2023-10-12

[Accepted as a DR at the March, 2024 meeting.]

Subclause 6.9.3.1 [basic.start.main] paragraph 3 specifies:

The function main shall not be used within a program. ...

It is unclear what "use" means. N3214 excluded this appearance from the clarifications of "use" that were turned into "odr-use". For example, it is unclear whether decltype(main) is allowed or not.

CWG 2023-12-01

CWG favored to ban any mention of main.

Proposed resolution (approved by CWG 2023-12-15):

Change in 6.9.3.1 [basic.start.main] paragraph 3 as follows:

The function main shall not be used within a program named by an expression. ...



2485. Bit-fields in integral promotions

Section: 7.3.7  [conv.prom]     Status: DRWP     Submitter: Richard Smith     Date: 2021-04-01

[Accepted as a DR at the June, 2023 meeting.]

According to 7.3.7 [conv.prom] paragraph 5,

A prvalue for an integral bit-field (11.4.10 [class.bit]) can be converted to a prvalue of type int if int can represent all the values of the bit-field; otherwise, it can be converted to unsigned int if unsigned int can represent all the values of the bit-field. If the bit-field is larger yet, no integral promotion applies to it. If the bit-field has an enumerated type, it is treated as any other value of that type for promotion purposes.

This description has several problems. First, the “bit-field” semantic property only makes sense for glvalue expressions, so it's unclear why these rules are described as applying to a prvalue. Perhaps this should be rephrased as something like “An expression that was a bit-field glvalue prior to the application of the lvalue-to-rvalue conversion”?

Second, suppose that char32_t is wider than int. Per paragraph 2, a char32_t prvalue promotes to unsigned long (because unsigned long is necessarily at least 32 bits wide). But per paragraph 5, a char32_t : 32 bitfield does not promote. This seems inconsistent.

Finally, it is not clear that the usual integral promotions are not applied to bit-fields. This should be made explicit.

Proposed resolution (approved by CWG 2023-02-07):

  1. Insert a paragraph before 7.3.7 [conv.prom] paragraph 1 as follows:

    For the purposes of 7.3.7 [conv.prom], a converted bit-field is a prvalue that is the result of an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) applied to a bit-field (11.4.10 [class.bit]).
  2. Change in 7.3.7 [conv.prom] paragraph 1 as follows:

    A prvalue of that is not a converted bit-field and has an integer type other than bool, char8_t, char16_t, char32_t, or wchar_t whose integer conversion rank (6.8.6 [conv.rank]) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.
  3. Change in 7.3.7 [conv.prom] paragraph 4 as follows:

    A prvalue of an unscoped enumeration type whose underlying type is fixed (9.7.1 [dcl.enum]) can be converted to a prvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, a prvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to a prvalue of the promoted underlying type. [ Note: A converted bit-field of enumeration type is treated as any other value of that type for promotion purposes. -- end note ]
  4. Change in 7.3.7 [conv.prom] paragraph 5 as follows:

    A prvalue for an integral bit-field (11.4.10 [class.bit]) converted bit-field of integral type can be converted to a prvalue of type int if int can represent all the values of the bit-field; otherwise, it can be converted to unsigned int if unsigned int can represent all the values of the bit-field. If the bit-field is larger yet, no integral promotion applies to it. If the bit-field has enumeration type, it is treated as any other value of that type for promotion purposes.
  5. Move 7.3.7 [conv.prom] paragraph 2 after paragraph 5 and change as follows:

    A prvalue of type char8_t, char16_t, char32_t, or wchar_t (6.8.2 [basic.fundamental]) (including a converted bit-field that was not already promoted to int or unsigned int according to the rules above) can be converted to a prvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int. If none of the types in that list can represent all the values of its underlying type, a prvalue of type char8_t, char16_t, char32_t, or wchar_t can be converted to a prvalue of , or its underlying type.



170. Pointer-to-member conversions

Section: 7.3.13  [conv.mem]     Status: DRWP     Submitter: Mike Stump     Date: 16 Sep 1999

[Accepted as a DR at the June, 2023 meeting.]

The descriptions of explicit (7.6.1.9 [expr.static.cast] paragraph 9) and implicit (7.3.13 [conv.mem] paragraph 2) pointer-to-member conversions differ in two significant ways:

  1. In a static_cast, a conversion in which the class in the target pointer-to-member type is a base of the class in which the member is declared is permitted and required to work correctly, as long as the resulting pointer-to-member is eventually dereferenced with an object whose dynamic type contains the member. That is, the class of the target pointer-to-member type is not required to contain the member referred to by the value being converted. The specification of implicit pointer-to-member conversion is silent on this question.

    (This situation cannot arise in an implicit pointer-to-member conversion where the source value is something like &X::f, since you can only implicitly convert from pointer-to-base-member to pointer-to-derived-member. However, if the source value is the result of an explicit "up-cast," the target type of the conversion might still not contain the member referred to by the source value.)

  2. The target type in a static_cast is allowed to be more cv-qualified than the source type; in an implicit conversion, however, the cv-qualifications of the two types are required to be identical.

The first difference seems like an oversight. It is not clear whether the latter difference is intentional or not.

(See also issue 794.)

CWG 2022-11-09

The second concern is NAD; implicit conversions allow chaining a pointer-to-member conversion with a qualification conversion.

Proposed resolution (approved by CWG 2023-02-06):

  1. Change in 7.3.13 [conv.mem] paragraph 2 as follows:

    A prvalue of type “pointer to member of B of type cv T”, where B is a class type, can be converted to a prvalue of type “pointer to member of D of type cv T”, where D is a complete class derived (11.7 [class.derived]) from B. If B is an inaccessible (11.8 [class.access]), ambiguous (6.5.2 [class.member.lookup]), or virtual (11.7.2 [class.mi]) base class of D, or a base class of a virtual base class of D, a program that necessitates this conversion is ill-formed. If class D does not contain the original member and is not a base class of the class containing the original member, the behavior is undefined. Otherwise, The the result of the conversion refers to the same member as the pointer to member before the conversion took place, but it refers to the base class member as if it were a member of the derived class. The result refers to the member in D's instance of B. Since the result has type “pointer to member of D of type cv T”, indirection through it with a D object is valid. The result is the same as if indirecting through the pointer to member of B with the B subobject of D. The null member pointer value is converted to the null member pointer value of the destination type. [ Footnote: ... ]
  2. Change in 7.6.1.9 [expr.static.cast] paragraph 13 as follows:

    ... If class B contains the original member, or is a base or derived class of the class containing the original member, the resulting pointer to member points to the original member. ...



1973. Which parameter-declaration-clause in a lambda-expression?

Section: 7.5.6.2  [expr.prim.lambda.closure]     Status: DRWP     Submitter: Dinka Ranns     Date: 2014-07-16

[Accepted as a DR at the June, 2023 meeting.]

According to 7.5.6.2 [expr.prim.lambda.closure] paragraph 3,

The closure type for a lambda-expression has a public inline function call operator (for a non-generic lambda) or function call operator template (for a generic lambda) (12.4.4 [over.call]) whose parameters and return type are described by the lambda-expression's parameter-declaration-clause and trailing-return-type respectively, and whose template-parameter-list consists of the specified template-parameter-list, if any.

This is insufficiently precise because the trailing-return-type might itself contain a parameter-declaration-clause.

Suggested resolution [SUPERSEDED]:

Change in 7.5.6.1 [expr.prim.lambda.general] paragraph 5 as follows:

If a lambda-declarator does not include a start with a parenthesized parameter-declaration-clause, it is as if () were inserted at the start of the lambda-declarator. A lambda-expression's parameter-declaration-clause is the (possibly empty) parameter-declaration-clause of the lambda-expression's lambda-declarator. If the lambda-declarator does not include a trailing-return-type, the lambda return type is auto, which is deduced from return statements as described in 9.2.9.7 [dcl.spec.auto].

Proposed resolution (approved by CWG 2023-02-06):

  1. Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 3 as follows:

    The closure type for a lambda-expression has a public inline function call operator (for a non-generic lambda) or function call operator template (for a generic lambda) (12.4.4 [over.call]) whose parameters and return type are described by those of the lambda-expression's parameter-declaration-clause and trailing-return-type respectively, and whose template-parameter-list consists of the specified template-parameter-list, if any.
  2. Change in 7.5.6.1 [expr.prim.lambda.general] paragraph 5 as follows:

    If a lambda-declarator does not include a parameter-declaration-clause, it is as if () were inserted at the start of the lambda-declarator. A lambda-expression's parameter-declaration-clause is the parameter-declaration-clause of the lambda-expression's lambda-declarator, if any, or empty otherwise. If the lambda-declarator does not include a trailing-return-type, the lambda return type is auto, which is deduced from return statements as described in 9.2.9.7 [dcl.spec.auto].



2542. Is a closure type a structural type?

Section: 7.5.6.2  [expr.prim.lambda.closure]     Status: DRWP     Submitter: Zhihao Yuan     Date: 2022-03-01

[Accepted as a DR at the June, 2023 meeting.]

Consider:

  template <auto V>
  void foo() {}

  void bar() {
    foo<[i = 3] { return i; }>();
  }

It is unclear whether the data members of a closure type are public or private. This makes a difference, since it affects whether a closure type is a structural type or not (13.2 [temp.param] paragraph 7:

A structural type is one of the following:

Proposed resolution (approved by CWG 2023-03-30):

Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 2 as follows:

... The closure type is not an aggregate type (9.4.2 [dcl.init.aggr]) and not a structural type (13.2 [temp.param]). ...



2845. Make the closure type of a captureless lambda a structural type

Section: 7.5.6.2  [expr.prim.lambda.closure]     Status: DRWP     Submitter: Barry Revzin     Date: 2023-12-29

[Accepted as a DR at the March, 2024 meeting.]

Issue 2542 (approved in June, 2023) made all closure types not be structural types, i.e. unsuitable for use as non-type template parameters. This causes an inconsistency with the treatment of the pointer-to-function conversion for closure types with no captures:

  template <auto V>
  void foo() {}

  void bar() {
    foo<[i = 3] { return i; }>();   // #1: error
    foo<[]{}>();                    // #2: error
    foo<+[]{}>();                   // #3: OK, a function pointer is a structural type
  }

Proposed resolution (approved by CWG 2024-02-02):

  1. Change in 7.5.6.2 [expr.prim.lambda.closure] paragraph 3 as follows:

    The closure type is not an aggregate type (9.4.2 [dcl.init.aggr]) and not ; it is a structural type (13.2 [temp.param]) if and only if the lambda has no lambda-capture. An implementation may define the closure type differently from ...
  2. Change in 13.6 [temp.type] paragraph 2 as follows:

    Two values are template-argument-equivalent if they are of the same type and
    • ...
    • they are of a closure type (7.5.6.2 [expr.prim.lambda.closure]), or
    • they are of class type and their corresponding direct subobjects and reference members are template-argument-equivalent.



2560. Parameter type determination in a requirement-parameter-list

Section: 7.5.8.1  [expr.prim.req.general]     Status: DRWP     Submitter: Daveed Vandevoorde     Date: 2020-01-21     Liaison: EWG

[Accepted as a DR at the March, 2024 meeting.]

Consider:

  template<typename T>
    requires requires (T p[10]) { (decltype(p))nullptr; }
  int v = 42;
  auto r = v<int>; // well-formed? 

This example is only well-formed if the type of the parameter p is adjusted to T*, but the provisions in 9.3.4.6 [dcl.fct] paragraph 5 cover function parameters only.

One option is to specify application of the same adjustments as for function parameters. Another option is to specify rules that arguably are more useful in a requires-expression.

Proposed resolution (approved by CWG 2023-11-07):

Change in 7.5.8.1 [expr.prim.req.general] paragraph 3 as follows:

A requires-expression may introduce local parameters using a parameter-declaration-clause (9.3.4.6 [dcl.fct]). A local parameter of a requires-expression shall not have a default argument. The type of such a parameter is determined as specified for a function parameter in 9.3.4.6 [dcl.fct]. These parameters have no linkage, storage, or lifetime; they are only used as notation for the purpose of defining requirements. The parameter-declaration-clause of a requirement-parameter-list shall not terminate with an ellipsis.
[Example 2:
  template<typename T>
  concept C = requires(T t, ...) {  // error: terminates with an ellipsis
    t;
  };
  template<typename T>
  concept C2 = requires(T p[2]) {
    (decltype(p))nullptr;           // OK, p has type "pointer to T"
  };
end example]

CWG 2023-06-17

There are arguments in favor of both options. Forwarded to EWG with paper issue 1582.

EWG 2023-11-07

Accept the proposed resolution and forward to CWG for inclusion in C++26.




1642. Missing requirements for prvalue operands

Section: 7.6  [expr.compound]     Status: DRWP     Submitter: Joseph Mansfield     Date: 2013-03-15

[Accepted as a DR at the June, 2023 meeting.]

Although the note in 7.2.1 [basic.lval] paragraph 1 states that

The discussion of each built-in operator in Clause 7 [expr] indicates the category of the value it yields and the value categories of the operands it expects

in fact, many of the operators that take prvalue operands do not make that requirement explicit. Possible approaches to address this failure could be a blanket statement that an operand whose value category is not stated is assumed to be a prvalue; adding prvalue requirements to each operand description for which it is missing; or changing the description of the usual arithmetic conversions to state that they imply the lvalue-to-rvalue conversion, which would cover the majority of the omissions.

(See also issue 1685, which deals with an inaccurately-specified value category.)

Proposed resolution (approved by CWG 2023-04-28):

  1. Change in 7.2.1 [basic.lval] paragraph 6 as follows:

    Whenever a glvalue appears as an operand of an operator that expects requires a prvalue for that operand, the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), or function-to-pointer (7.3.4 [conv.func]) standard conversions are applied to convert the expression to a prvalue. ...
  2. Change in 7.3.1 [conv.general] paragraph 1 as follows:

    ... A standard conversion sequence will be applied to an expression if necessary to convert it to an expression having a required destination type and value category. ...
  3. Add to the bulleted list in 7.4 [expr.arith.conv] paragraph 1 as follows:

    ... This pattern is called the usual arithmetic conversions, which are defined as follows:
    • The lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is applied to each operand and the resulting prvalues are used in place of the original operands for the remainder of this section.
    • If either operand is of scoped enumeration type (9.7.1 [dcl.enum]), no conversions are performed; if the other operand does not have the same type, the expression is ill-formed.
    • ...
  4. Change in 7.6.1.3 [expr.call] paragraph 1 as follows:

    ... For a call to a non-member function or to a static member function, the postfix expression shall be either be an lvalue that refers to a function (in which case the function-to-pointer standard conversion (7.3.4 [conv.func]) is suppressed on the postfix expression), or have a prvalue of function pointer type.
  5. Change in 7.6.2.2 [expr.unary.op] paragraph 7 as follows:

    The operand of the unary + operator shall have be a prvalue of arithmetic, unscoped enumeration, or pointer type and the result is the value of the argument. Integral promotion is performed on integral or enumeration operands. ...
  6. Change in 7.6.2.2 [expr.unary.op] paragraph 8 as follows:

    The operand of the unary - operator shall have be a prvalue of arithmetic or unscoped enumeration type and the result is the negative of its operand. Integral promotion is performed on integral or enumeration operands. ...
  7. Change in 7.6.2.2 [expr.unary.op] paragraph 10 as follows:

    The operand of the ~ operator shall have be a prvalue of integral or unscoped enumeration type. Integral promotions are performed. ...
  8. Change in 7.6.2.9 [expr.delete] paragraph 1 as follows:

    ... The operand shall be of pointer to object type or of class type. If the operand is of class type, the operand it is contextually implicitly converted (7.3 [conv]) to a pointer to object type. [ Footnote: ... ] Otherwise, it shall be a prvalue of pointer to object type. The delete-expression has type void.
  9. Change in 7.6.4 [expr.mptr.oper] paragraph 2 and 3 as follows:

    The binary operator .* binds its second operand, which shall be a prvalue of type “pointer to member of T” to its first operand, which shall be ...

    The binary operator ->* binds its second operand, which shall be a prvalue of type “pointer to member of T” to its first operand, which shall be ...

  10. Change in 7.6.6 [expr.add] paragraph 1 as follows:

    The additive operators + and - group left-to-right. The Each operand shall be a prvalue. If both operands have arithmetic or unscoped enumeration type, the usual arithmetic conversions (7.4 [expr.arith.conv]) are performed for operands of arithmetic or enumeration type. Otherwise, if one operand has arithmetic or unscoped enumeration type, integral promotion is applied (7.3.7 [conv.prom]) to that operand. A converted or promoted operand is used in place of the corresponding original operand for the remainder of this section. ... For addition, either both operands shall have arithmetic or unscoped enumeration type, or one operand shall be a pointer to a completely-defined object type and the other shall have integral or unscoped enumeration type.
  11. Change in 7.6.6 [expr.add] paragraph 2 as follows:

    For subtraction, one of the following shall hold:
    • both operands have arithmetic or unscoped enumeration type; or
    • both operands are pointers to cv-qualified or cv-unqualified versions of the same completely-defined object type; or
    • the left operand is a pointer to a completely-defined object type and the right operand has integral or unscoped enumeration type.
  12. Change in 7.6.7 [expr.shift] paragraph 1 as follows:

    ... The operands shall be prvalues of integral or unscoped enumeration type and integral promotions are performed. ...
  13. Change in 9.4.1 [dcl.init.general] bullet 16.9 as follows:

    • ...
    • Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression. A standard conversion sequence (7.3 [conv]) will be is used, if necessary, to convert the initializer expression to a prvalue of the cv-unqualified version of the destination type; no user-defined conversions are considered. ...
    • ...



2715. "calling function" for parameter initialization may not exist

Section: 7.6.1.3  [expr.call]     Status: DRWP     Submitter: Brian Bi     Date: 2023-04-02

[Accepted as a DR at the June, 2023 meeting.]

Subclause 7.6.1.3 [expr.call] paragraph 6 specifies:

... The initialization and destruction of each parameter occurs within the context of the calling function. [Example 2: The access of the constructor, conversion functions or destructor is checked at the point of call in the calling function. If a constructor or destructor for a function parameter throws an exception, the search for a handler starts in the calling function; in particular, if the function called has a function-try-block (14.1 [except.pre]) with a handler that can handle the exception, this handler is not considered. —end example]

However, there is no calling function in the case where a function call appears in the initializer of a namespace-scope variable. Likewise, some constant expressions appearing in a type-id do not have calling functions, either. For example:

  class C {
   private:
    constexpr int C(int) {}
    friend void foo(int (*a)[1]) noexcept;
  };

  constexpr int bar(C) { return 1; }

  void foo(int (&a)[bar(1)]) noexcept(bar(2) > 0); // presumably OK because of friendship

Proposed resolution (approved by CWG 2023-04-28):

Change in 7.6.1.3 [expr.call] paragraph 6 as follows:

... The initialization and destruction of each parameter occurs within the context of the calling function full-expression (6.9.1 [intro.execution]) where the function call appears. [Example 2: The access (11.8.1 [class.access.general]) of the constructor, conversion functions, or destructor is checked at the point of call in the calling function. If a constructor or destructor for a function parameter throws an exception, the search for a handler starts in the calling function; in particular, if the function called has a any function-try-block (14.1 [except.pre]) of the called function with a handler that can handle the exception, this handler is not considered. —end example]



2725. Overload resolution for non-call of class member access

Section: 7.6.1.5  [expr.ref]     Status: DRWP     Submitter: Richard Smith     Date: 2023-04-26

[Accepted as a DR at the November, 2023 meeting.]

Consider:

  struct A {
    static void f();
    static void f(int);
  } x;
  void (*p)() = x.f;   // error

This is ill-formed as confirmed by issue 61. Various other changes (see issue 2241) have put the following example into the same category:

  struct B {
    static void f();
  } y;
  void (*q)() = y.f;   // error

If this is the intended outcome (although major implementations disagree), then the rules in 7.6.1.5 [expr.ref] should be clarified accordingly.

Proposed resolution (approved by CWG 2023-06-13):

Change in 7.6.1.5 [expr.ref] bullet 6.3 as follows:

This also addresses issue 1038.




2748. Accessing static data members via null pointer

Section: 7.6.1.5  [expr.ref]     Status: DRWP     Submitter: Tomasz Kamiński     Date: 2023-06-13

[Accepted as a DR at the March, 2024 meeting.]

Consider:

  struct C { static int foo; };
  C* c = nullptr;

The behavior of (*c).foo is clearly undefined per 7.6.1.5 [expr.ref] paragraph 1:

The postfix expression before the dot or arrow is evaluated ...

However, the treatment of c->foo is less clear, because the transformation to the form (*(E1)).E2 occurs later.

Proposed resolution (approved by CWG 2023-12-15):

Move a part of 7.6.1.5 [expr.ref] paragraph 1 to before paragraph 3 and edit as follows:

A postfix expression followed by a dot . or an arrow ->, optionally followed by the keyword template, and then followed by an id-expression, is a postfix expression. The postfix expression before the dot or arrow is evaluated; [ Footnote: ... ] the result of that evaluation, together with the id-expression, determines the result of the entire postfix expression. [Note 1: If the keyword template is used, the following unqualified name is considered to refer to a template (13.3 [temp.names]). If a simple-template-id results and is followed by a ::, the id-expression is a qualified-id. —end note]

For the first option (dot) the first expression shall be a glvalue. For the second option (arrow) the first expression shall be a prvalue having pointer type. The expression E1->E2 is converted to the equivalent form (*(E1)).E2; the remainder of 7.6.1.5 [expr.ref] will address only the first option (dot). [ Footnote: ... ]

The postfix expression before the dot is evaluated; [ Footnote: ... ] the result of that evaluation, together with the id-expression, determines the result of the entire postfix expression.




2813. Class member access with prvalues

Section: 7.6.1.5  [expr.ref]     Status: DRWP     Submitter: Barry Revzin     Date: 2023-08-28

[Accepted as a DR at the March, 2024 meeting.]

Subclause 7.6.1.5 [expr.ref] paragraph 2 specifies:

For the first option (dot) the first expression shall be a glvalue. ...

This provision forces a temporary materialization conversion (i.e. a copy) per 7.2.1 [basic.lval] paragraph 7:

Whenever a prvalue appears as an operand of an operator that expects a glvalue for that operand, the temporary materialization conversion (7.3.5 [conv.rval]) is applied to convert the expression to an xvalue.

However, this is limiting in the case that an explicit object member function with a by-value object parameter is invoked for a non-copyable class object, for example:

  struct X {
    X() = default;

    X(const X&) = delete;
    X& operator=(const X&) = delete;

    void f(this X self) { }
  };

  void f() {
    X{}.f();   // OK?
  }

The example ought to be well-formed.

Proposed resolution (reviewed by CWG 2024-02-16) [SUPERSEDED]:

  1. Change in 6.7.7 [class.temporary] paragraph 2 as follows:

    ... [Note 3: Temporary objects are materialized: end note]
  2. Change in 7.6.1.3 [expr.call] paragraph 6 as follows:

    When a function is called, each parameter (9.3.4.6 [dcl.fct]) is initialized (9.4 [dcl.init], 11.4.5.3 [class.copy.ctor]) with its corresponding argument. If the function is an explicit object member function and there is an implied object argument (12.2.2.2.2 [over.call.func]), the list of provided arguments is preceded by the implied object argument for the purposes of this correspondence. If there is no corresponding argument, the default argument for the parameter is used. [Example 1:
    template<typename ...T> int f(int n = 0, T ...t);
    int x = f<int>(); // error: no argument for second function parameter
    
    end example] If the function is a static member function invoked using class member access syntax, the object expression is a discarded-value expression (7.2.3 [expr.context]). If the function is an implicit object member function, the object expression of the class member access shall be a glvalue and the this parameter of the function (7.5.3 [expr.prim.this]) is initialized with a pointer to the object of the call, converted as if by an explicit type conversion (7.6.3 [expr.cast]). [Note 5: There is no access or ambiguity checking on this conversion; the access checking and disambiguation are done as part of the (possibly implicit) class member access operator. See 6.5.2 [class.member.lookup], 11.8.3 [class.access.base], and 7.6.1.5 [expr.ref]. —end note] When a function is called, the type of any parameter shall not be a class type that is either incomplete or abstract. [Note 6: This still allows a parameter to be a pointer or reference to such a type. However, it prevents a passed-by-value parameter to have an incomplete or abstract class type. —end note] It is implementation-defined whether ...
  3. Change in 7.6.1.5 [expr.ref] paragraph 2 as follows:

    For the first option (dot), if the id-expression is a data member, the first expression shall be a glvalue. For the second option (arrow), the first expression shall be a prvalue having pointer type. ...

CWG 2024-02-16

When a class member access refers to a static data member or a member enumerator, the object expression should also be treated as a discarded-value expression.

Proposed resolution (approved by CWG 2024-03-01):

  1. Change in 6.7.7 [class.temporary] paragraph 2 as follows:

    ... [Note 3: Temporary objects are materialized: end note]
  2. Change in 7.6.1.3 [expr.call] paragraph 6 as follows:

    When a function is called, each parameter (9.3.4.6 [dcl.fct]) is initialized (9.4 [dcl.init], 11.4.5.3 [class.copy.ctor]) with its corresponding argument. If the function is an explicit object member function and there is an implied object argument (12.2.2.2.2 [over.call.func]), the list of provided arguments is preceded by the implied object argument for the purposes of this correspondence. If there is no corresponding argument, the default argument for the parameter is used. [Example 1:
    template<typename ...T> int f(int n = 0, T ...t);
    int x = f<int>(); // error: no argument for second function parameter
    
    end example] If the function is an implicit object member function, the object expression of the class member access shall be a glvalue and the this parameter of the function (7.5.3 [expr.prim.this]) is initialized with a pointer to the object of the call, converted as if by an explicit type conversion (7.6.3 [expr.cast]). [Note 5: There is no access or ambiguity checking on this conversion; the access checking and disambiguation are done as part of the (possibly implicit) class member access operator. See 6.5.2 [class.member.lookup], 11.8.3 [class.access.base], and 7.6.1.5 [expr.ref]. —end note] When a function is called, the type of any parameter shall not be a class type that is either incomplete or abstract. [Note 6: This still allows a parameter to be a pointer or reference to such a type. However, it prevents a passed-by-value parameter to have an incomplete or abstract class type. —end note] It is implementation-defined whether ...
  3. Change in 7.6.1.5 [expr.ref] paragraph 2 as follows:

    For the first option (dot), if the id-expression names a static member or an enumerator, the first expression is a discarded-value expression (7.2.3 [expr.context]); if the id-expression names a non-static data member, the first expression shall be a glvalue. For the second option (arrow), the first expression shall be a prvalue having pointer type. ...



2855. Undefined behavior in postfix increment

Section: 7.6.1.6  [expr.post.incr]     Status: DRWP     Submitter: Lénárd Szolnoki     Date: 2023-12-12

[Accepted as a DR at the March, 2024 meeting.]

(From submission #479.)

Consider:

  int8_t x = 127;
  x++;

This has undefined behavior, because the resulting value is not representable as an int8_t. In contrast,

  int8_t x = 127;
  ++x;

is well-defined, because it is equivalent to x += 1, which is equivalent to x = (int)x + 1 after the usual arithmetic conversions (7.4 [expr.arith.conv]). No arithmetic overflow occurs. The presence or absence of undefined behavior is detectable in constant evaluation.

Proposed resolution (approved by CWG 2024-02-16):

  1. Change in 7.6.1.6 [expr.post.incr] paragraph 1 as follows:

    The value of a postfix ++ expression is the value of its operand. [Note 1: The value obtained is a copy of the original value. —end note] The operand shall be a modifiable lvalue. The type of the operand shall be an arithmetic type other than cv bool, or a pointer to a complete object type. An operand with volatile-qualified type is deprecated; see D.4 [depr.volatile.type]. The value of the operand object is modified (3.1 [defns.access]) by adding 1 to it as if it were the operand of the prefix ++ operator (7.6.2.3 [expr.pre.incr]). The value computation of the ++ expression is sequenced before the modification of the operand object. With respect to an indeterminately-sequenced function call, the operation of postfix ++ is a single evaluation. [Note 2: Therefore, a function call cannot intervene between the lvalue-to-rvalue conversion and the side effect associated with any single postfix ++ operator. —end note] The result is a prvalue. The type of the result is the cv-unqualified version of the type of the operand. If the operand is a bit-field that cannot represent the incremented value, the resulting value of the bit-field is implementation-defined. See also 7.6.6 [expr.add] and 7.6.19 [expr.ass].
  2. Change in 7.6.2.3 [expr.pre.incr] as follows:

    The operand of prefix ++ or -- is modified (3.1 [defns.access]) by adding 1. The operand shall be a modifiable lvalue. The type of the operand shall not be an arithmetic type other than of type cv bool, or a pointer to a completely-defined object type. An operand with volatile-qualified type is deprecated; see D.4 [depr.volatile.type]. The result is the updated operand; it is an lvalue, and it is a bit-field if the operand is a bit-field. The expression ++x is otherwise equivalent to x+=1 and the expression --x is otherwise equivalent to x-=1 . [Note 1: See the discussions of addition (7.6.6 [expr.add]) and assignment operators (7.6.19 [expr.ass]) for information on conversions. end note]

    The operand of prefix -- is modified (3.1 [defns.access]) by subtracting 1. The requirements on the operand of prefix -- and the properties of its result are otherwise the same as those of prefix ++. [Note 2: For postfix increment and decrement, see 7.6.1.6 [expr.post.incr]. —end note]




1954. typeid null dereference check in subexpressions

Section: 7.6.1.8  [expr.typeid]     Status: DRWP     Submitter: David Majnemer     Date: 2014-06-23

[Accepted as a DR at the March, 2024 meeting.]

According to 7.6.1.8 [expr.typeid] paragraph 2,

If the glvalue expression is obtained by applying the unary * operator to a pointer69 and the pointer is a null pointer value (7.3.12 [conv.ptr]), the typeid expression throws an exception (14.2 [except.throw]) of a type that would match a handler of type std::bad_typeid exception (17.7.5 [bad.typeid]).

The footnote makes clear that this requirement applies without regard to parentheses, but it is unspecified whether it applies when the dereference occurs in a subexpression of the operand (e.g., in the second operand of the comma operator or the second or third operand of a conditional operator). There is implementation divergence on this question.

Proposed resolution (approved by CWG 2023-11-09):

Insert a new paragraph before 7.6.1.8 [expr.typeid] paragraph 3 and change the latter as follows:

If an expression operand of typeid is a possibly-parenthesized unary-expression whose unary-operator is * and whose operand evaluates to a null pointer value (6.8.4 [basic.compound]), the typeid expression throws an exception (14.2 [except.throw]) of a type that would match a handler of type std::bad_typeid (17.7.5 [bad.typeid]). [ Note: In other contexts, evaluating such a unary-expression results in undefined behavior (7.6.2.2 [expr.unary.op]) -- end note ]

When typeid is applied to a glvalue whose type is a polymorphic class type (11.7.3 [class.virtual]), the result refers to a std::type_info object representing the type of the most derived object (6.7.2 [intro.object]) (that is, the dynamic type) to which the glvalue refers. If the glvalue is obtained by applying the unary * operator to a pointer [ Footnote: ... ] and the pointer is a null pointer value (6.8.4 [basic.compound]), the typeid expression throws an exception (14.2 [except.throw]) of a type that would match a handler of type std::bad_typeid exception (17.7.5 [bad.typeid]).




2718. Type completeness for derived-to-base conversions

Section: 7.6.1.9  [expr.static.cast]     Status: DRWP     Submitter: Jim X     Date: 2023-04-09

[Accepted as a DR at the June, 2023 meeting.]

Issue 2310 clarified class completeness requirements for derived-to-base pointer conversions, but neglected the corresponding lvalue conversion.

Proposed resolution (approved by CWG 2023-04-28):

Change in 7.6.1.9 [expr.static.cast] paragraph 2 as follows:

An lvalue of type “cv1 B”, where B is a class type, can be cast to type “reference to cv2 D”, where D is a complete class derived (11.7 [class.derived]) from B, if cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. ...



2780. reinterpret_cast to reference to function types

Section: 7.6.1.10  [expr.reinterpret.cast]     Status: DRWP     Submitter: Lauri Vasama     Date: 2023-08-07

[Accepted as a DR at the November, 2023 meeting.]

Subclause 7.6.1.10 [expr.reinterpret.cast] paragraph 11 specifies:

A glvalue of type T1, designating an object x, can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result is that of *reinterpret_cast<T2 *>(p) where p is a pointer to x of type “pointer to T1”. No temporary is created, no copy is made, and no constructors (11.4.5 [class.ctor]) or conversion functions (11.4.8 [class.conv]) are called. [ Footnote: ... ]

The wording does not cover references to function type, only references to object types. All major implementations accept the following example:

  void f() {}

  void(&g())(int) {
    return reinterpret_cast<void(&)(int)>(f);
  }

Proposed resolution (approved by CWG 2023-09-15):

Change in 7.6.1.10 [expr.reinterpret.cast] paragraph 11 as follows:

A glvalue of type T1, designating an object or function x, can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result is that of *reinterpret_cast<T2 *>(p) where p is a pointer to x of type “pointer to T1”. No temporary is created, no copy is made, and no constructors (11.4.5 [class.ctor]) or conversion functions (11.4.8 [class.conv]) are called. [ Footnote: ... ]



2823. Implicit undefined behavior when dereferencing pointers

Section: 7.6.2.2  [expr.unary.op]     Status: DRWP     Submitter: CWG     Date: 2023-11-06

[Accepted as a DR at the November, 2023 meeting.]

Subclause 7.6.2.2 [expr.unary.op] paragraph 1 specifies:

The unary * operator performs indirection. Its operand shall be a prvalue of type “pointer to T”, where T is an object or function type. The operator yields an lvalue of type T denoting the object or function to which the operand points.

It is unclear what happens if the operand does not point to an object or function.

Proposed resolution (approved by CWG 2023-11-08):

Change in 7.6.2.2 [expr.unary.op] paragraph 1 as follows:

The unary * operator performs indirection. Its operand shall be a prvalue of type “pointer to T”, where T is an object or function type. The operator yields an lvalue of type T denoting the object or function to which the operand points. If the operand points to an object or function, the result denotes that object or function; otherwise, the behavior is undefined except as specified in 7.6.1.8 [expr.typeid].



2668. co_await in a lambda-expression

Section: 7.6.2.4  [expr.await]     Status: DRWP     Submitter: Jim X     Date: 2022-12-12

[Accepted as a DR at the March, 2024 meeting.]

Subclause 7.6.2.4 [expr.await] paragraph 2 disallows an await-expression to appear in the body of a lambda-expression:

An await-expression shall appear only in a potentially-evaluated expression within the compound-statement of a function-body outside of a handler (14.1 [except.pre]). ...

This is probably unintended.

Proposed resolution (approved by CWG 2023-11-11):

Change in 7.6.2.4 [expr.await] paragraph 2 as follows:

An await-expression shall appear only in as a potentially-evaluated expression within the compound-statement of a function-body or lambda-expression, in either case outside of a handler (14.1 [except.pre]). ...



2722. Temporary materialization conversion for noexcept operator

Section: 7.6.2.7  [expr.unary.noexcept]     Status: DRWP     Submitter: Brian Bi     Date: 2023-04-24

[Accepted as a DR at the June, 2023 meeting.]

It is unclear whether noexcept(A()) applies the temporary materialization conversion to the prvalue A(). The resolution of issue 1354 suggests that it does so that the destructor is (notionally) invoked.

Proposed resolution (approved by CWG 2023-05-12):

Change in 7.6.2.7 [expr.unary.noexcept] paragraph 3 as follows:

If the operand is a prvalue, the temporary materialization conversion (7.3.5 [conv.rval]) is applied. The result of the noexcept operator is true unless the expression full-expression of the operand is potentially-throwing (14.5 [except.spec]).



2792. Clean up specification of noexcept operator

Section: 7.6.2.7  [expr.unary.noexcept]     Status: DRWP     Submitter: Jan Schultke     Date: 2023-08-30

[Accepted as a DR at the November, 2023 meeting.]

The introductory sentence "can throw an exception" is misleading, because it might be interpreted to cover exceptions thrown as the result of encountering undefined behavior.

Proposed resolution (approved by CWG 2023-10-06):

Change all of 7.6.2.7 [expr.unary.noexcept] as follows:

The noexcept operator determines whether the evaluation of its operand, which is an unevaluated operand (7.2.3 [expr.context]), can throw an exception (14.2 [except.throw]).

noexcept-expression:
        noexcept ( expression )

The operand of the noexcept operator is an unevaluated operand (7.2.3 [expr.context]). If the operand is a prvalue, the temporary materialization conversion (7.3.5 [conv.rval]) is applied.

The result of the noexcept operator is a prvalue of type bool. The result is false if the full-expression of the operand is potentially-throwing (14.5 [except.spec]), and true otherwise.

[Note 1: A noexcept-expression is an integral constant expression (7.7 [expr.const]). —end note]

If the operand is a prvalue, the temporary materialization conversion (7.3.5 [conv.rval]) is applied. The result of the noexcept operator is true unless the full-expression of the operand is potentially-throwing (14.5 [except.spec]).




2102. Constructor checking in new-expression

Section: 7.6.2.8  [expr.new]     Status: DRWP     Submitter: Richard Smith     Date: 2015-03-16

[Accepted as a DR at the November, 2023 meeting.]

According to 7.6.2.8 [expr.new] paragraph 25,

If the new-expression creates an object or an array of objects of class type, access and ambiguity control are done for the allocation function, the deallocation function (11.4.11 [class.free]), and the constructor (11.4.5 [class.ctor]).

The mention of “the constructor” here is strange. For the “object of class type” case, access and ambiguity control are done when we perform initialization in paragraph 17, and we might not be calling a constructor anyway (for aggregate initialization). This seems wrong.

For the “array of objects of class type” case, it makes slightly more sense (we need to check the trailing array elements can be default-initialized) but again (a) we aren't necessarily using a constructor, (b) we should say which constructor — and we may need overload resolution to find it, and (c) shouldn't this be part of initialization, so we can distinguish between the cases where we should copy-initialize from {} and the cases where we should default-initialize?

Additional notes (May, 2023):

It is unclear whether default-initialization is required to be well-formed even for an array with no elements.

Proposed resolution (approved by CWG 2023-06-16):

  1. Insert a new paragraph before 7.6.2.8 [expr.new] paragraph 9:

    If the allocated type is an array, the new-initializer is a braced-init-list, and the expression is potentially-evaluated and not a core constant expression, the semantic constraints of copy-initializing a hypothetical element of the array from an empty initializer list are checked (9.4.5 [dcl.init.list]). [ Note: The array can contain more elements than there are elements in the braced-init-list, requiring initialization of the remainder of the array elements from an empty initializer list. -- end note ]

    Objects created by a new-expression have dynamic storage duration (6.7.5.5 [basic.stc.dynamic]). ...

  2. Change in 7.6.2.8 [expr.new] paragraph 25 as follows:

    If the new-expression creates an object or an array of objects of class type, access and ambiguity control are done for the allocation function, the deallocation function (6.7.5.5.3 [basic.stc.dynamic.deallocation]), and the constructor (11.4.5 [class.ctor]) selected for the initialization (if any). If the new-expression creates an array of objects of class type, the destructor is potentially invoked (11.4.7 [class.dtor]).
  3. Change in 7.6.2.8 [expr.new] paragraph 28 as follows:

    A declaration of a placement deallocation function matches the declaration of a placement allocation function if it has the same number of parameters and, after parameter transformations (9.3.4.6 [dcl.fct]), all parameter types except the first are identical. If the lookup finds a single matching deallocation function, that function will be called; otherwise, no deallocation function will be called. If the lookup finds a usual deallocation function and that function, considered as a placement deallocation function, would have been selected as a match for the allocation function, the program is ill-formed. For a non-placement allocation function, the normal deallocation function lookup is used to find the matching deallocation function (7.6.2.9 [expr.delete]). In any case, the matching deallocation function (if any) shall be non-deleted and accessible from the point where the new-expression appears.
  4. Change in 9.4.1 [dcl.init.general] paragraph 7 as follows:

    To default-initialize an object of type T means:
    • ...
    • If T is an array type, the semantic constraints of default-initializing a hypothetical element shall be met and each element is default-initialized.
    • ...
  5. Change in 9.4.1 [dcl.init.general] paragraph 9 as follows:

    To value-initialize an object of type T means:
    • if If T is a (possibly cv-qualified) class type (Clause 11 [class]), then
      • if T has either no default constructor (11.4.5.2 [class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
      • otherwise, the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;.
    • if If T is an array type, the semantic constraints of value-initializing a hypothetical element shall be met and each element is value-initialized;.
    • otherwiseOtherwise, the object is zero-initialized.



2729. Meaning of new-type-id

Section: 7.6.2.8  [expr.new]     Status: DRWP     Submitter: Jim X     Date: 2023-02-06

[Accepted as a DR at the June, 2023 meeting.]

Subclause 7.6.2.8 [expr.new] paragraph 1 introduces the grammar non-terminal new-type-id, but never specifies its meaning.

Proposed resolution (approved by CWG 2023-06-12):

  1. Change in 7.6.2.8 [expr.new] paragraph 1 as follows:

    The new-expression attempts to create an object of the type-id (9.3.2 [dcl.name]) or new-type-id (9.3.2 [dcl.name]) to which it is applied. The type of that object is the allocated type. This type shall be a complete object type (6.8.1 [basic.types.general]), but not an abstract class type (11.7.4 [class.abstract]) or array thereof (6.7.2 [intro.object]).
  2. Change in 9.3.2 [dcl.name] paragraph 1 as follows:

    To specify type conversions explicitly, and as an argument of sizeof, alignof, new, or typeid, the name of a type shall be specified. This can be done with a type-id or new-type-id (7.6.2.8 [expr.new]), which is syntactically a declaration for a variable or function of that type that omits the name of the entity.



2758. What is "access and ambiguity control"?

Section: 7.6.2.9  [expr.delete]     Status: DRWP     Submitter: CWG     Date: 2023-06-12

[Accepted as a DR at the November, 2023 meeting.]

Subclause 7.6.2.9 [expr.delete] paragraph 12 specifies:

Access and ambiguity control are done for both the deallocation function and the destructor (11.4.7 [class.dtor], 11.4.11 [class.free]).

It is unclear what that means. In particular, ambiguity checking is part of overload resolution, and access checking requires a point of reference.

Proposed resolution (approved by CWG 2023-08-25):

  1. Change in 7.6.2.9 [expr.delete] paragraph 6 as follows:

    If the value of the operand of the delete-expression is not a null pointer value and the selected deallocation function (see below) is not a destroying operator delete, evaluating the delete-expression will invoke invokes the destructor (if any) for the object or the elements of the array being deleted. The destructor shall be accessible from the point where the delete-expression appears. In the case of an array, the elements will be are destroyed in order of decreasing address (that is, in reverse order of the completion of their constructor; see 11.9.3 [class.base.init]).
  2. Change in 7.6.2.9 [expr.delete] paragraph 10

    If more than one deallocation function is found, the The deallocation function to be called is selected as follows:
    • ...

    Unless the deallocation function is selected at the point of definition of the dynamic type's virtual destructor, the selected deallocation function shall be accessible from the point where the delete-expression appears.

  3. Remove 7.6.2.9 [expr.delete] paragraph 12:

    Access and ambiguity control are done for both the deallocation function and the destructor (11.4.7 [class.dtor], 11.4.11 [class.free]).



2828. Ambiguous interpretation of C-style cast

Section: 7.6.3  [expr.cast]     Status: DRWP     Submitter: Jim X     Date: 2022-03-21

[Accepted as a DR at the March, 2024 meeting.]

(From editorial issue 5355.)

Consider:

   int*** ptr = 0;
   auto t = (int const*const*const*)ptr;

There is more than one way how this can be interpreted as a static_cast followed by a const_cast, namely:

  const_cast<int const * const * const * >(static_cast<int * * const * >(ptr));
  const_cast<int const * const * const * >(static_cast<int * const * const * >(ptr));

Subclause 7.6.3 [expr.cast] paragraph 4 makes such a program ill-formed:

... If a conversion can be interpreted in more than one way as a static_cast followed by a const_cast, the conversion is ill-formed.

Proposed resolution (approved by CWG 2024-03-20):

Change in 7.6.3 [expr.cast] paragraph 4 as follows:

... If a conversion can be interpreted in more than one of the ways listed above, the interpretation that appears first in the list is used, even if a cast resulting from that interpretation is ill-formed. If a conversion can be interpreted in more than one way as a static_cast followed by a const_cast is used and the conversion can be interpreted in more than one way as such, the conversion is ill-formed. [ Example 1 :
  struct A { };
  struct I1 : A { };
  struct I2 : A { };
  struct D : I1, I2 { };
  A* foo( D* p ) {
    return (A*)( p );  // ill-formed static_cast interpretation
  }

  int*** ptr = 0;
  auto t = (int const*const*const*)ptr;  // OK, const_cast interpretation

  struct S {
    operator const int*();
    operator volatile int*();
  };
  int *p = (int*)S();  // error: two possible interpretations using static_cast followed by const_cast
-- end example ]



2853. Pointer arithmetic with pointer to hypothetical element

Section: 7.6.6  [expr.add]     Status: DRWP     Submitter: Jim X     Date: 2024-02-03

[Accepted as a DR at the March, 2024 meeting.]

(From submission #495.)

The phrasing in 7.6.6 [expr.add] bullet 4.2 excludes pointer arithmetic on a pointer pointing to the hypothetical (past-the-end) array element.

Proposed resolution (approved by CWG 2024-02-16):

Change in 7.6.6 [expr.add] bullet 4.2 as follows:




2724. Clarify rounding for arithmetic right shift

Section: 7.6.7  [expr.shift]     Status: DRWP     Submitter: Jan Schultke     Date: 2023-04-07

[Accepted as a DR at the June, 2023 meeting.]

(From editorial issue 6225.)

Subclause 7.6.7 [expr.shift] paragraph 3 specifies:

The value of E1 >> E2 is E1/2E2, rounded down.

It is unclear whether "rounded down" means "towards zero" or "towards negative infinity".

Proposed resolution (approved by CWG 2023-05-12):

Change in 7.6.7 [expr.shift] paragraph 3 as follows:

The value of E1 >> E2 is E1/2E2, rounded down towards negative infinity.



2749. Treatment of "pointer to void" for relational comparisons

Section: 7.6.9  [expr.rel]     Status: DRWP     Submitter: lprv     Date: 2023-03-12     Liaison: SG22

[Accepted as a DR at the November, 2023 meeting.]

(From editorial issue 6173.)

Subclause 7.6.9 [expr.rel] paragraph 4 and paragraph 5 specify:

The result of comparing unequal pointers to objects [ Footnote: ] is defined in terms of a partial order consistent with the following rules: ...

[Note 1: A relational operator applied to unequal function pointers or to unequal pointers to void yields an unspecified result. -- end note]

Comparing pointers to objects that are stored in a variable of type "pointer to void" should be fine.

Proposed resolution (approved by CWG 2023-06-16):

Change in 7.6.9 [expr.rel] paragraph 4 and paragraph 5 as follows:

The result of comparing unequal pointers to objects [ Footnote: ... ] is defined in terms of a partial order consistent with the following rules: ...

[Note 1: A relational operator applied to unequal function pointers or to unequal pointers to void yields an unspecified result. A pointer value of type "pointer to cv void" can point to an object (6.8.4 [basic.compound]). -- end note]




2796. Function pointer conversions for relational operators

Section: 7.6.9  [expr.rel]     Status: DRWP     Submitter: Alisdair Meredith     Date: 2023-09-14

[Accepted as a DR at the November, 2023 meeting.]

Consider:

  void f() {}
  void g() noexcept {}

  void q() {
    bool b1 = f == g;     // OK
    bool b2 = f > g;      // error: different types
  }

For the equality operators, 7.6.10 [expr.eq] paragraph 3 specifies:

If at least one of the operands is a pointer, pointer conversions (7.3.12 [conv.ptr]), function pointer conversions (7.3.14 [conv.fctptr]), and qualification conversions (7.3.6 [conv.qual]) are performed on both operands to bring them to their composite pointer type (7.2.2 [expr.type]). Comparing pointers is defined as follows: ...

In contrast, the corresponding rule for relational operators in 7.6.9 [expr.rel] paragraph 3 specifies:

The usual arithmetic conversions (7.4 [expr.arith.conv]) are performed on operands of arithmetic or enumeration type. If both operands are pointers, pointer conversions (7.3.12 [conv.ptr]) and qualification conversions (7.3.6 [conv.qual]) are performed to bring them to their composite pointer type (7.2.2 [expr.type]). After conversions, the operands shall have the same type.

However, all major implementations accept the example.

Proposed resolution (approved by CWG 2023-10-06):

Change in 7.6.9 [expr.rel] paragraph 3 as follows:

The usual arithmetic conversions (7.4 [expr.arith.conv]) are performed on operands of arithmetic or enumeration type. If both operands are pointers, pointer conversions (7.3.12 [conv.ptr]), function pointer conversions (7.3.14 [conv.fctptr]), and qualification conversions (7.3.6 [conv.qual]) are performed to bring them to their composite pointer type (7.2.2 [expr.type]). After conversions, the operands shall have the same type.



2699. Inconsistency of throw-expression specification

Section: 7.6.18  [expr.throw]     Status: DRWP     Submitter: Krystian Stasiowski     Date: 2020-04-06

[Accepted as a DR at the June, 2023 meeting.]

Subclause 7.6.18 [expr.throw] paragraph 2 and 3 specify:

Evaluating a throw-expression with an operand throws an exception (14.2 [except.throw]); the type of the exception object is determined by removing any top-level cv-qualifier s from the static type of the operand and adjusting the type from “array of T” or function type T to “pointer to T”.

A throw-expression with no operand rethrows the currently handled exception (14.4 [except.handle]). The exception is reactivated with the existing exception object; no new exception object is created. The exception is no longer considered to be caught.

This means that throwing a value of type const char[3] would throw a char* rather than const char*, which is not intended.

Proposed resolution (approved by CWG 2023-03-03):

Change in 7.6.18 [expr.throw] paragraph 2 through 4 as follows:

Evaluating a A throw-expression with an operand throws an exception (14.2 [except.throw]); the. The array-to-pointer (7.3.3 [conv.array]) and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the operand. The type of the exception object is determined by removing any top-level cv-qualifiers from the static type of the (possibly converted) operand and adjusting the type from “array of T” or function type T to “pointer to T”.

A throw-expression with no operand rethrows the currently handled exception (14.4 [except.handle]). If no exception is presently being handled, the function std:: terminate is invoked (14.6.2 [except.terminate]). The Otherwise, the exception is reactivated with the existing exception object; no new exception object is created. The exception is no longer considered to be caught.

If no exception is presently being handled, evaluating a throw-expression with no operand calls std:: terminate() (14.6.2 [except.terminate]).




2711. Source for copy-initializing the exception object

Section: 7.6.18  [expr.throw]     Status: DRWP     Submitter: Brian Bi     Date: 2023-03-26

[Accepted as a DR at the June, 2023 meeting.]

Neither 14.2 [except.throw] nor 7.6.18 [expr.throw] specifiy the source for copy-initializing the exception object.

Proposed resolution (based on the resolution of issue 2699; approved by CWG 2023-06-12):

  1. Change in 7.6.18 [expr.throw] paragraph 2 as follows:

    Evaluating a throw-expression with an operand throws an exception (14.2 [except.throw]); the type of the exception object is determined by removing any top-level cv-qualifiers from the static type of the operand and adjusting the type from “array of T” or function type T to “pointer to T”. The exception object is copy-initialized (9.4.1 [dcl.init.general]) from the (possibly converted) operand.
  2. Change in 14.2 [except.throw] paragraph 3 as follows:

    Throwing an exception copy-initializes (9.4 [dcl.init], 11.4.5.3 [class.copy.ctor]) a temporary object, called the exception object. If the type of the exception object would be an incomplete type (6.8.1 [basic.types.general]), an abstract class type (11.7.4 [class.abstract]), or a pointer to an incomplete type other than cv void (6.8.4 [basic.compound]), the program is ill-formed.



2768. Assignment to enumeration variable with a braced-init-list

Section: 7.6.19  [expr.ass]     Status: DRWP     Submitter: Shafik Yaghmour     Date: 2023-07-06

[Accepted as a DR at the November, 2023 meeting.]

Consider:

   enum class E {E1};

   void f() {
     E e;
     e = E{0}; // #1
     e = {0};  // #2
   }

#1 first initializes a temporary of type E and then assigns that to e. For #2, 7.6.19 [expr.ass] bullet 8.1 specifies that #2 is equivalent to #1:

A braced-init-list may appear on the right-hand side of

However, there is no syntactic hint that #2 would invoke direct-initialization, and in fact gcc, icc, and MSVC reject #2, but clang accepts.

Proposed resolution (approved by CWG 2023-11-06):

Change in 7.6.19 [expr.ass] paragraph 8 as follows:

A braced-init-list B may appear on the right-hand side of



2552. Constant evaluation of non-defining variable declarations

Section: 7.7  [expr.const]     Status: DRWP     Submitter: Hubert Tong     Date: 2022-03-21

[Accepted as a DR at the June, 2023 meeting.]

Paper P2242 (Non-literal variables (and labels and gotos) in constexpr functions) added 7.7 [expr.const] bullet 5.2:

It seems that block-scope extern (i.e. non-defining) declarations are covered by the above bullet, but only definitions should be in view here.

Proposed resolution (approved by CWG 2023-06-15):

  1. Change in 7.7 [expr.const] bullet 5.2 as follows:




2710. Loops in constant expressions

Section: 7.7  [expr.const]     Status: DRWP     Submitter: Daniel Krügler     Date: 2023-03-23

[Accepted as a DR at the June, 2023 meeting.]

Iteration statements such as while and for loops are specified by equivalent code involving goto (8.6.2 [stmt.while] paragraph 2, 8.6.4 [stmt.for] paragraph 1, 8.6.5 [stmt.ranged] paragraph 1). The goto statement cannot be evaluated in constant expressions (7.7 [expr.const] bullet 5.30), thus while and for loops cannot be evaluated in constant expressions. Similar concerns arise for continue (8.7.3 [stmt.cont] paragraph 1).

However, that is neither intended nor existing practice.

Suggested resolution [SUPERSEDED]:

Change in 7.7 [expr.const] bullet 5.30 as follows:

An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following:

CWG 2023-03-30

Keep the rule non-normative and non-exhaustive.

Proposed resolution (approved by CWG 2023-04-28):

Change in 7.7 [expr.const] bullet 5.30 as follows:

An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following:



2750. construct_at without constructor call

Section: 7.7  [expr.const]     Status: DRWP     Submitter: CWG     Date: 2023-06-16

[Accepted as a DR at the June, 2023 meeting.]

Subclause 7.7 [expr.const] paragraph 6 talks about invoking the "underlying constructor", but there is no constructor when creating an aggregate or a scalar type.

Proposed resolution:

For the purposes of determining whether an expression E is a core constant expression, the evaluation of the body of a member function of std::allocator<T> as defined in 20.2.10.2 [allocator.members], where T is a literal type, is ignored. Similarly, the evaluation of the body of std::construct_at or std::ranges::construct_at is considered to include only the underlying constructor call initialization of the T object if the first argument (of type T*) points to storage allocated with std::allocator<T> or to an object whose lifetime began within the evaluation of E.



2755. Incorrect wording applied by P2738R1

Section: 7.7  [expr.const]     Status: DRWP     Submitter: Jens Maurer     Date: 2023-06-28

[Accepted as a DR at the November, 2023 meeting.]

P2738R1 (constexpr cast from void*: towards constexpr type-erasure) applied incorrect wording to 7.7 [expr.const] bullet 5.14:

The issue is that T is defined to be a pointer type, but the "similar to" phrasing uses it as the pointee type.

Proposed resolution (approved by CWG 2023-07-14):

Change in 7.7 [expr.const] bullet 5.14 as follows:




2760. Defaulted constructor that is an immediate function

Section: 7.7  [expr.const]     Status: DRWP     Submitter: Corentin Jabot     Date: 2023-07-08

[Accepted as a DR at the November, 2023 meeting.]

Consider:

  consteval int f(int);
  struct S {
   int x = f(0);
   S() = default;
  };

  int main() {
    S s;     // OK?
  }

Is S an immediate function?

The relevant specification is in 7.7 [expr.const] paragraph 18:

An immediate function is a function or constructor that is

Suggested resolution [SUPERSEDED]:

Change in 7.7 [expr.const] paragraph 18 as follows:

An immediate function is a function or constructor that is

Proposed resolution (approved by CWG 2023-08-25):

  1. Change in 7.7 [expr.const] paragraph 18 as follows:

    An immediate function is a function or constructor that is
    • declared with the consteval specifier, or
    • an immediate-escalating function F whose function body contains an immediate-escalating expression E such that E's innermost enclosing non-block scope is F's function parameter scope. [ Note: Default member initializers used to initialize a base or member subobject (11.9.3 [class.base.init]) are considered to be part of the function body (9.5.1 [dcl.fct.def.general]). -- end note ]
  2. Change in 9.5.1 [dcl.fct.def.general] paragraph 1 as follows:

    Any informal reference to the body of a function should be interpreted as a reference to the non-terminal function-body, including, for a constructor, default member initializers or default initialization used to initialize a base or member subobject in the absence of a mem-initializer-id (11.9.3 [class.base.init]).



2763. Ignorability of [[noreturn]] during constant evaluation

Section: 7.7  [expr.const]     Status: DRWP     Submitter: Jiang An     Date: 2023-07-10

[Accepted as a DR at the November, 2023 meeting.]

Subclause 9.12.11 [dcl.attr.noreturn] paragraph 2 specifies:

If a function f is called where f was previously declared with the noreturn attribute and f eventually returns, the behavior is undefined.

Undefineed behavior is, in general, detected during constant evaluation, thus requiring an implementation to actually support the noreturn attribute, such as in the following example:

  [[noreturn]] constexpr void f() {}
  constexpr int x = (f(), 0);

It might be desirable to treat the assume and noreturn attributes alike in that regard.

Proposed resolution (approved by CWG 2023-07-14) [SUPERSEDED]:

Split into a separate paragraph and change 7.7 [expr.const] paragraph 5 as follows:

It is unspecified whether E is a core constant expression if E satisfies the constraints of a core constant expression, but evaluation of E would evaluate

CWG 2023-07-14

As an alternative, all of 9.12 [dcl.attr] could be added to the "library undefined behavior" bullet. However, CWG felt that a case-by-case consideration is warranted, given that assumptions set precedent in requiring special treatment.

Possible resolution (reviewed by CWG 2023-08-25) [SUPERSEDED]:

Split into a separate paragraph and change 7.7 [expr.const] paragraph 5 as follows:

It is unspecified whether E is a core constant expression if E satisfies the constraints of a core constant expression, but evaluation of E would evaluate

Proposed resolution (approved by CWG 2023-11-09):

Split into a separate paragraph and change 7.7 [expr.const] paragraph 5 as follows:

It is unspecified whether E is a core constant expression if E satisfies the constraints of a core constant expression, but evaluation of E would evaluate



2798. Manifestly constant evaluation of the static_assert message

Section: 7.7  [expr.const]     Status: DRWP     Submitter: Jason Merrill     Date: 2023-09-12

[Accepted as a DR at the November, 2023 meeting.]

The message of a static_assert declaration is a conditional-expression and thus is not manifestly constant evaluated. Consider this example:

  struct X {
    std::string s;
    const char *p;
  };
  consteval X f() { return {.s = "some long string that requires a heap allocation", .p = "hello"}; }

  static_assert(cond, f().p);

The example is ill-formed, because the immediate invocation f() lets a pointer to the heap escape.

Proposed resolution (approved by CWG 2023-10-06):

  1. Change in 7.7 [expr.const] paragraph 19 as follows:

    [Note 11: Except for a static_assert-message, a A manifestly constant-evaluated expression is evaluated even in an unevaluated operand (7.2.3 [expr.context]). —end note]
  2. Change the grammar in 9.1 [dcl.pre] as follows:

    static_assert-message:
      unevaluated-string
      conditional-expression constant-expression
    
  3. Change in 9.1 [dcl.pre] bullet 11.2 as follows:

    • ...
    • if the static_assert-message is a conditional-expression constant-expression M, ...



2851. Allow floating-point conversions in converted constant expressions

Section: 7.7  [expr.const]     Status: DRWP     Submitter: Brian Bi     Date: 2023-11-03

[Accepted as a DR at the March, 2024 meeting.]

(From submission #456.)

With the adoption of P1907R1, non-type template parameters of floating-point types have been introduced. It is surprising that a double value cannot be passed as a template argument for a template parameter of type long double, because floating-point conversions are not allowed in converted constant expressions.

Proposed resolution (approved by CWG 2024-02-16):

Change in 7.7 [expr.const] paragraph 12 as follows:

A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only and where the reference binding (if any) binds directly.



2825. Range-based for statement using a braced-init-list

Section: 8.6.5  [stmt.ranged]     Status: DRWP     Submitter: Arthur O'Dwyer     Date: 2023-11-08     Liaison: EWG

[Accepted as a DR at the March, 2024 meeting.]

Consider:

  for (int i : { 1, 2, 3 })  // argument-dependent lookup for begin(std::initializer_list<int>)
    /* ... */;

This is undesirable; instead, a member function begin should be preferred.

Proposed resolution (approved by CWG 2023-11-09):

Change in 8.6.5 [stmt.ranged] bullet 1.3 as follows:

begin-expr and end-expr are determined as follows:

Additional notes (November, 2023)

Forwarded to EWG via paper issue 1694 for confirmation of the design direction.

EWG 2023-11-09

Approved by EWG.




2791. Unclear phrasing about "returning to the caller"

Section: 8.7.4  [stmt.return]     Status: DRWP     Submitter: Jan Schultke     Date: 2023-08-23

[Accepted as a DR at the November, 2023 meeting.]

In 8.7.4 [stmt.return] and 8.7.5 [stmt.return.coroutine], the standard uses the phrasing "returns to its caller" when specifying return or co_return. It would be better to talk about transfer of control, which is a term used elsewhere in the standard.

Proposed resolution (approved by CWG 2023-10-06):

  1. Change in 7.6.2.4 [expr.await] paragraph 1 as follows:

    The co_await expression is used to suspend evaluation of a coroutine (9.5.4 [dcl.fct.def.coroutine]) while awaiting completion of the computation represented by the operand expression. Suspending the evaluation of a coroutine transfers control to its caller or resumer.
  2. Change 8.7.4 [stmt.return] paragraph 1 as follows:

    A function returns control to its caller by the return statement.
  3. Change 8.7.5 [stmt.return.coroutine] paragraph 1 as follows:

    A coroutine returns to its caller or resumer (9.5.4 [dcl.fct.def.coroutine]) by the co_return statement or when suspended (7.6.2.4 [expr.await]). A co_return statement transfers control to the caller or resumer of a coroutine (9.5.4 [dcl.fct.def.coroutine]). A coroutine shall not enclose a return statement (8.7.4 [stmt.return]).
  4. Change in 9.5.4 [dcl.fct.def.coroutine] paragraph 10 as follows:

    If the allocation function returns nullptr, the coroutine returns transfers control to the caller of the coroutine and the return value is obtained by a call to T::get_return_object_on_allocation_failure(), where T is the promise type.



2556. Unusable promise::return_void

Section: 8.7.5  [stmt.return.coroutine]     Status: DRWP     Submitter: Davis Herring     Date: 2022-03-24

[Accepted as a DR at the November, 2023 meeting.]

Subclause 8.7.5 [stmt.return.coroutine] paragraph 3 specifies:

If p.return_void() is a valid expression, flowing off the end of a coroutine's function-body is equivalent to a co_return with no operand; otherwise flowing off the end of a coroutine's function-body results in undefined behavior.

However, 9.5.4 [dcl.fct.def.coroutine] paragraph 6 suggests:

If searches for the names return_void and return_value in the scope of the promise type each find any declarations, the program is ill-formed. [Note: If return_void is found, flowing off the end of a coroutine is equivalent to a co_return with no operand. Otherwise, flowing off the end of a coroutine results in undefined behavior (8.7.5 [stmt.return.coroutine]). —end note]

The difference is between the conditions "valid expression" and "found by name lookup". Effectively, it means that undefined behavior might result where the implementation could instead diagnose an ill-formed use of return_void (for example, because it is inaccessible, deleted, or the function call requires arguments).

Proposed resolution (approved by CWG 2023-06-17):

Change in 8.7.5 [stmt.return.coroutine] paragraph 3 as follows:

If p.return_void() is a valid expression a search for the name return_void in the scope of the promise type finds any declarations, flowing off the end of a coroutine's function-body is equivalent to a co_return with no operand; otherwise flowing off the end of a coroutine's function-body results in undefined behavior.



2531. Static data members redeclared as constexpr

Section: 9.2.6  [dcl.constexpr]     Status: DRWP     Submitter: Davis Herring     Date: 2022-02-16

[Accepted as a DR at the November, 2023 meeting.]

C++17 made constexpr static data members implicitly inline (9.2.6 [dcl.constexpr] paragraph 1):

A function or static data member declared with the constexpr or consteval specifier is implicitly an inline function or variable (9.2.8 [dcl.inline]).

However, that makes the following well-formed C++14 program ill-formed, no diagnostic required, per 9.2.8 [dcl.inline] paragraph 5:

If a function or variable with external or module linkage is declared inline in one definition domain, an inline declaration of it shall be reachable from the end of every definition domain in which it is declared; no diagnostic is required.
  // x.hh
  struct X {
    static const int x;
  };

  // TU 1
  #include "x.hh"
  constexpr int X::x{};

  // TU 2
  #include "x.hh"
  int main() { return !&X::x; }

Proposed resolution (reviewed by CWG 2023-02-07, approved by CWG 2023-11-07):

Change 9.2.6 [dcl.constexpr] paragraph 1 as follows:

A function or static data member declared with the constexpr or consteval specifier on its first declaration is implicitly an inline function or variable (9.2.8 [dcl.inline]).

Drafting note: Functions must be declared constexpr on every declaration if on any, so this isn't a change for them.




2634. Avoid circularity in specification of scope for friend class declarations

Section: 9.2.9.5  [dcl.type.elab]     Status: DRWP     Submitter: Jim X     Date: 2022-07-04

[Accepted as a DR at the March, 2024 meeting.]

Consider:

auto f(struct X* ptr) {
  struct D {
    private:
      int d;
      friend class X;      // #1
  };
  return D{};
}
X* b = 0;
struct X {
  void show() {
    auto t = f(0);
    t.d = 10;              // #2 error: ::X is not a friend of f::D
  }
};

The target scope for #2 is f's block scope, making ::X not a friend of f::D. Thus the access at #2 is ill-formed. Clang disagrees.

Subclause 9.2.9.5 [dcl.type.elab] paragraph 3 specifies:

... If E contains an identifier but no nested-name-specifier and (unqualified) lookup for the identifier finds nothing, E shall not be introduced by the enum keyword and declares the identifier as a class-name. The target scope of E is the nearest enclosing namespace or block scope.

If an elaborated-type-specifier appears with the friend specifier as an entire member-declaration, the member-declaration shall have one of the following forms:

friend class-key nested-name-specifieropt identifier ;
...
Any unqualified lookup for the identifier (in the first case) does not consider scopes that contain the target scope; no name is bound.

This specification is circular in that the target scope that limits unqualified lookup is defined only if the identifier is actually declared, but the identifier is declared only if lookup finds nothing.

Proposed resolution (approved by CWG 2023-11-11):

Change in 9.2.9.5 [dcl.type.elab] paragraph 4 as follows:

... Any unqualified lookup for the identifier (in the first case) does not consider scopes that contain the target nearest enclosing namespace or block scope; no name is bound. [ Note: ... ]



2476. placeholder-type-specifiers and function declarators

Section: 9.2.9.7.1  [dcl.spec.auto.general]     Status: DRWP     Submitter: Davis Herring     Date: 2021-01-29

[Accepted as a DR at the March, 2024 meeting.]

According to 9.2.9.7.1 [dcl.spec.auto.general] paragraph 3,

The placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such a declarator is valid. If the function declarator includes a trailing-return-type (9.3.4.6 [dcl.fct]), that trailing-return-type specifies the declared return type of the function. Otherwise, the function declarator shall declare a function.

This wording disallows a declaration like

   int f();
   auto (*fp)()=f;

The requirement to declare a function was introduced by the resolution of issue 1892.

Proposed resolution (April, 2021) [SUPERSEDED]:

Change 9.2.9.7.1 [dcl.spec.auto.general] paragraph 3 as follows:

The placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such a declarator is valid if the function declarator includes a trailing-return-type T (9.3.4.6 [dcl.fct]) or declares a function. If the function declarator includes a trailing-return-type (9.3.4.6 [dcl.fct]), that trailing-return-type specifies In the former case, T is the declared return type of the function. Otherwise, the function declarator shall declare a function. If the declared return type of the a function contains a placeholder type, the return type of the function is deduced from non-discarded return statements, if any, in the body of the function (8.5.2 [stmt.if]).

Additional notes (May, 2021):

It was observed that the proposed resolution above does not address the example in the issue, since fp neither has a trailing-return-type nor declares a function. Presumably another case in which a function declarator with a placeholder return type should be permitted is in the declaration of a variable in which the type is deduced from its initializer.

It was also noted in passing that the deduction in the example is only partial: the parameter-type-list is specified by the declarator and only the return type is deduced from the initializer. Although this example is supported by current implementations, there is implementation divergence in the support of another case in which only part of the variable's type is deduced:

    auto (&ar)[2] = L"a";  // Array bound declared, element type deduced

This issue is related to issue 1892, which prohibited cases like

    std::vector<auto(*)()> v;

The ultimate outcome of the two issues should be:

    int f();
    auto (*fp1)() = f;       // OK
    auto (*fp2)()->int = f;  // OK
    auto (*fp3)()->auto = f; // OK

    template<typename T> struct C { };
    C<auto(*)()> c1;         // Not OK
    C<auto(*)()->int> c2;    // OK
    C<auto(*)()->auto> c3;   // Not OK

Proposed resolution (January, 2023) [SUPERSEDED]:

  1. Change in 9.2.9.7.1 [dcl.spec.auto.general] paragraph 1 as follows:

    A placeholder-type-specifier designates a placeholder type that will be replaced later, typically by deduction from an initializer.
  2. Change and split 9.2.9.7.1 [dcl.spec.auto.general] paragraph 3 as follows:

    A placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such for a function declarator is valid that includes a trailing-return-type (9.3.4.6 [dcl.fct]). If the function declarator includes a trailing-return-type (9.3.4.6 [dcl.fct]), that trailing-return-type specifies the declared return type of the function.

    Otherwise, the A placeholder type can appear in the decl-specifier-seq or type-specifier-seq in the declared return type of a function declarator shall declare that declares a function. If the declared return type of the function contains a placeholder type, ; the return type of the function is deduced from non-discarded return statements, if any, in the body of the function (8.5.2 [stmt.if]).

  3. Change in 9.2.9.7.1 [dcl.spec.auto.general] paragraph 4 as follows:

    The type of a variable declared using a placeholder type is deduced from its initializer. This use is allowed in an initializing declaration (9.4 [dcl.init]) of a variable. The placeholder type shall appear as one of the decl-specifiers in the decl-specifier-seq and or as one of the type-specifiers in a trailing-return-type that specifies the type that replaces such a decl-specifier; the decl-specifier-seq shall be followed by one or more declarators, each of which shall be followed by a non-empty initializer. [ Example:
      ...
      auto f() -> int;                // OK, f returns int
      auto (*fp)() -> auto = f;       // OK
      ...
    
    -- end example ]
  4. Change in 9.3.4.6 [dcl.fct] paragraph 1 as follows:

    In a declaration T D where D has the form
    D1 ( parameter-declaration-clause ) cv-qualifier-seqopt
      ref-qualifieropt noexcept-specifieropt attribute-specifier-seqopt trailing-return-typeopt
    
    and the type of the contained declarator-id in the declaration T D1 is "derived-declarator-type-list T",:
    • If the trailing-return-type is present, T shall be the single type-specifier auto, and the declared return type of the function type is the type specified by the trailing-return-type.
    • Otherwise, the declared return type of the function type is T.
    theThe type of the declarator-id in D is "derived-declarator-type-list noexceptopt function of parameter-type-list cv-qualifier-seqopt ref-qualifieropt returning T U", where
    • the parameter-type-list is derived from the parameter-declaration-clause as described below,
    • U is the declared return type, and
    • the optional noexcept is present if and only if the exception specification (14.5 [except.spec]) is non-throwing.
    The optional attribute-specifier-seq appertains to the function type.
  5. Remove 9.3.4.6 [dcl.fct] paragraph 2:

    In a declaration T D where D has the form
    D1 ( parameter-declaration-clause ) cv-qualifier-seqopt
      ref-qualifieropt noexcept-specifieropt attribute-specifier-seqopt trailing-return-type
    
    and the type ... The optional attribute-specifier-seq appertains to the function type.
  6. Change in 11.4.8.3 [class.conv.fct] paragraph 1 as follows:

    A declaration whose declarator-id has an unqualified-id that is a conversion-function-id declares a conversion function; its declarator shall be a function declarator (9.3.4.6 [dcl.fct]) of the form
    ptr-declarator noptr-declarator ( parameter-declaration-clause ) cv-qualifier-seqopt
          ref-qualifier-seqopt noexcept-specifieropt attribute-specifier-seqopt parameters-and-qualifiers
    
    where the ptr-declarator noptr-declarator consists solely of an id-expression, an optional attribute-specifier-seq, and optional surrounding parentheses, and the id-expression has one of the following forms: ...
  7. Change in 11.4.8.3 [class.conv.fct] paragraph 2 as follows:

    A conversion function shall have no non-object parameters and shall be a non-static member function of a class or class template X; its declared return type is the conversion-type-id and it specifies a conversion from X to the type specified by the conversion-type-id interpreted as a type-id (9.3.2 [dcl.name]). A decl-specifier in the decl-specifier-seq of a conversion function (if any) shall not be a defining-type-specifier .
  8. Remove 11.4.8.3 [class.conv.fct] paragraph 3:

    The type of the conversion function is “noexceptopt function taking no parameter cv-qualifier-seq opt ref-qualifier opt returning conversion-type-id”.

CWG 2023-06

This does not address void f2(auto (*)() -> auto);

Proposed resolution (approved by CWG 2023-11-10):

  1. Change in 9.2.9.7.1 [dcl.spec.auto.general] paragraph 1 as follows:

    A placeholder-type-specifier designates a placeholder type that will be replaced later, typically by deduction from an initializer.
  2. Change 9.2.9.7.1 [dcl.spec.auto.general] paragraph 2 as follows:

    A The type of a parameter-declaration of a function declaration (9.3.4.6 [dcl.fct]), lambda-expression (7.5.6 [expr.prim.lambda]), or template-parameter (13.2 [temp.param]) can be declared using a placeholder-type-specifier of the form type-constraintopt auto can be used as a decl-specifier of the decl-specifier-seq of a parameter-declaration of a function declaration or lambda-expression and, if it is not the auto type-specifier introducing. The placeholder type shall appear as one of the decl-specifiers in the decl-specifier-seq or as one of the type-specifiers in a trailing-return-type , that specifies the type that replaces such a decl-specifier (see below); the placeholder type is a generic parameter type placeholder of the function declaration or, lambda-expression, or template-parameter, respectively.
  3. Change and split 9.2.9.7.1 [dcl.spec.auto.general] paragraph 3 as follows:

    A placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such for a function declarator is valid that includes a trailing-return-type (9.3.4.6 [dcl.fct]). If the function declarator includes a trailing-return-type (9.3.4.6 [dcl.fct]), that trailing-return-type specifies the declared return type of the function.

    Otherwise, the A placeholder type can appear in the decl-specifier-seq or type-specifier-seq in the declared return type of a function declarator shall declare that declares a function. If the declared return type of the function contains a placeholder type, ; the return type of the function is deduced from non-discarded return statements, if any, in the body of the function (8.5.2 [stmt.if]).

  4. Change in 9.2.9.7.1 [dcl.spec.auto.general] paragraph 4 as follows:

    The type of a variable declared using a placeholder type is deduced from its initializer. This use is allowed in an initializing declaration (9.4 [dcl.init]) of a variable. The placeholder type shall appear as one of the decl-specifiers in the decl-specifier-seq and or as one of the type-specifiers in a trailing-return-type that specifies the type that replaces such a decl-specifier; the decl-specifier-seq shall be followed by one or more declarators, each of which shall be followed by a non-empty initializer. [ Example:
      ...
      auto f() -> int;                // OK, f returns int
      auto (*fp)() -> auto = f;       // OK
      ...
    
    -- end example ]
  5. Change and split 9.2.9.7.1 [dcl.spec.auto.general] paragraph 5 as follows:

    A placeholder type can also be used in the type-specifier-seq in of the new-type-id or in the type-id of a new-expression (7.6.2.8 [expr.new]) and as a decl-specifier of the parameter-declaration's decl-specifier-seq in a template-parameter (13.2 [temp.param]). In such a type-id, the placeholder type shall appear as one of the type-specifiers in the type-specifier-seq or as one of the type-specifiers in a trailing-return-type that specifies the type that replaces such a type-specifier.

    The auto type-specifier can also be used as the simple-type-specifier in an explicit type conversion (functional notation) (7.6.1.4 [expr.type.conv]).

  6. Change in 9.3.4.6 [dcl.fct] paragraph 1 as follows:

    In a declaration T D where T may be empty and D has the form
    D1 ( parameter-declaration-clause ) cv-qualifier-seqopt
      ref-qualifieropt noexcept-specifieropt attribute-specifier-seqopt trailing-return-typeopt
    
    a derived-declarator-type-list is determined as follows: and
    • If the unqualified-id of the declarator-id is a conversion-function-id, the derived-declarator-type-list is empty.
    • Otherwise, the derived-declarator-type-list is as appears in the type "derived-declarator-type-list T" of the contained declarator-id in the declaration T D1 is "derived-declarator-type-list T".
    The declared return type U of the function type is determined as follows:
    • If the trailing-return-type is present, T shall be the single type-specifier auto, and U is the type specified by the trailing-return-type.
    • Otherwise, if the declaration declares a conversion function, see 11.4.8.3 [class.conv.fct].
    • Otherwise, U is T.
    theThe type of the declarator-id in D is "derived-declarator-type-list noexceptopt function of parameter-type-list cv-qualifier-seqopt ref-qualifieropt returning T U", where
    • the parameter-type-list is derived from the parameter-declaration-clause as described below and
    • the optional noexcept is present if and only if the exception specification (14.5 [except.spec]) is non-throwing.
    The optional attribute-specifier-seq appertains to the function type.
  7. Remove 9.3.4.6 [dcl.fct] paragraph 2:

    In a declaration T D where D has the form
    D1 ( parameter-declaration-clause ) cv-qualifier-seqopt
      ref-qualifieropt noexcept-specifieropt attribute-specifier-seqopt trailing-return-type
    
    and the type ... The optional attribute-specifier-seq appertains to the function type.
  8. Change in 11.4.8.3 [class.conv.fct] paragraph 1 as follows:

    A declaration whose declarator-id has an unqualified-id that is a conversion-function-id declares a conversion function; its declarator shall be a function declarator (9.3.4.6 [dcl.fct]) of the form
    ptr-declarator noptr-declarator ( parameter-declaration-clause ) cv-qualifier-seqopt
          ref-qualifier-seqopt noexcept-specifieropt attribute-specifier-seqopt parameters-and-qualifiers
    
    where the ptr-declarator noptr-declarator consists solely of an id-expression, an optional attribute-specifier-seq, and optional surrounding parentheses, and the id-expression has one of the following forms: ...
  9. Change in 11.4.8.3 [class.conv.fct] paragraph 2 as follows:

    A conversion function shall have no non-object parameters and shall be a non-static member function of a class or class template X; its declared return type is the conversion-type-id and it specifies a conversion from X to the type specified by the conversion-type-id interpreted as a type-id (9.3.2 [dcl.name]). A decl-specifier in the decl-specifier-seq of a conversion function (if any) shall not be a defining-type-specifier .
  10. Remove 11.4.8.3 [class.conv.fct] paragraph 3:

    The type of the conversion function is “noexceptopt function taking no parameter cv-qualifier-seq opt ref-qualifier opt returning conversion-type-id”.



2831. Non-templated function definitions and requires-clauses

Section: 9.3.1  [dcl.decl.general]     Status: DRWP     Submitter: Krystian Stasiowski     Date: 2020-04-13

[Accepted as a DR at the March, 2024 meeting.]

(From editorial issue 3936.)

Subclause 9.3.1 [dcl.decl.general] paragraph 4 specifies:

The optional requires-clause in an init-declarator or member-declarator shall be present only if the declarator declares a templated function (13.1 [temp.pre]). ...

This rule does not address function definitions, because those have neither an init-declarator nor a member-declarator.

Proposed resolution (approved by CWG 2024-03-20):

Change in 9.5.1 [dcl.fct.def.general] paragraph 1 as follows:

... The optional attribute-specifier-seq in a function-definition appertains to the function. A virt-specifier-seq can be part of a function-definition only if it is function-definition with a virt-specifier-seq shall be a member-declaration (11.4 [class.mem]). A function-definition with a requires-clause shall define a templated function.



453. References may only bind to “valid” objects

Section: 9.3.4.3  [dcl.ref]     Status: DRWP     Submitter: Gennaro Prota     Date: 18 Jan 2004

[Accepted as a DR at the March, 2024 meeting.]

9.3.4.3 [dcl.ref] paragraph 4 says:

A reference shall be initialized to refer to a valid object or function. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the "object" obtained by dereferencing a null pointer, which causes undefined behavior ...]

What is a "valid" object? In particular the expression "valid object" seems to exclude uninitialized objects, but the response to Core Issue 363 clearly says that's not the intent. This is an example (overloading construction on constness of *this) by John Potter, which I think is supposed to be legal C++ though it binds references to objects that are not initialized yet:

 struct Fun {
    int x, y;
    Fun (int x, Fun const&) : x(x), y(42) { }
    Fun (int x, Fun&) : x(x), y(0) { }
  };
  int main () {
    const Fun f1 (13, f1);
    Fun f2 (13, f2);
    cout << f1.y << " " << f2.y << "\n";
  }

Suggested resolution: Changing the final part of 9.3.4.3 [dcl.ref] paragraph 4 to:

A reference shall be initialized to refer to an object or function. From its point of declaration on (see 6.4.2 [basic.scope.pdecl]) its name is an lvalue which refers to that object or function. The reference may be initialized to refer to an uninitialized object but, in that case, it is usable in limited ways (6.7.3 [basic.life], paragraph 6) [Note: On the other hand, a declaration like this:
    int & ref = *(int*)0;
is ill-formed because ref will not refer to any object or function ]

I also think a "No diagnostic is required." would better be added (what about something like int& r = r; ?)

Proposed Resolution (October, 2004) [SUPERSEDED]:

(Note: the following wording depends on the proposed resolution for issue 232.)

Change 9.3.4.3 [dcl.ref] paragraph 4 as follows:

A reference shall be initialized to refer to a valid object or function. If an lvalue to which a reference is directly bound designates neither an existing object or function of an appropriate type (9.4.4 [dcl.init.ref]), nor a region of memory of suitable size and alignment to contain an object of the reference's type (6.7.2 [intro.object], 6.7.3 [basic.life], 6.8 [basic.types]), the behavior is undefined. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” empty lvalue obtained by dereferencing a null pointer, which causes undefined behavior. As does not designate an object or function. Also, as described in 11.4.10 [class.bit], a reference cannot be bound directly to a bit-field. ]

The name of a reference shall not be used in its own initializer. Any other use of a reference before it is initialized results in undefined behavior. [Example:

  int& f(int&);
  int& g();

  extern int& ir3;
  int* ip = 0;

  int& ir1 = *ip;     // undefined behavior: null pointer
  int& ir2 = f(ir3);  // undefined behavior: ir3 not yet initialized
  int& ir3 = g();
  int& ir4 = f(ir4);  // ill-formed: ir4 used in its own initializer
end example]

Rationale: The proposed wording goes beyond the specific concerns of the issue. It was noted that, while the current wording makes cases like int& r = r; ill-formed (because r in the initializer does not "refer to a valid object"), an inappropriate initialization can only be detected, if at all, at runtime and thus "undefined behavior" is a more appropriate treatment. Nevertheless, it was deemed desirable to continue to require a diagnostic for obvious compile-time cases.

It was also noted that the current Standard does not say anything about using a reference before it is initialized. It seemed reasonable to address both of these concerns in the same wording proposed to resolve this issue.

Notes from the April, 2005 meeting:

The CWG decided that whether to require an implementation to diagnose initialization of a reference to itself should be handled as a separate issue (504) and also suggested referring to “storage” instead of “memory” (because 6.7.2 [intro.object] defines an object as a “region of storage”).

Proposed Resolution (April, 2005) [SUPERSEDED]:

(Note: the following wording depends on the proposed resolution for issue 232.)

Change 9.3.4.3 [dcl.ref] paragraph 4 as follows:

A reference shall be initialized to refer to a valid object or function. If an lvalue to which a reference is directly bound designates neither an existing object or function of an appropriate type (9.4.4 [dcl.init.ref]), nor a region of storage of suitable size and alignment to contain an object of the reference's type (6.7.2 [intro.object], 6.7.3 [basic.life], 6.8 [basic.types]), the behavior is undefined. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” empty lvalue obtained by dereferencing a null pointer, which causes undefined behavior. As does not designate an object or function. Also, as described in 11.4.10 [class.bit], a reference cannot be bound directly to a bit-field. ]

Any use of a reference before it is initialized results in undefined behavior. [Example:

  int& f(int&);
  int& g();

  extern int& ir3;
  int* ip = 0;

  int& ir1 = *ip;     // undefined behavior: null pointer
  int& ir2 = f(ir3);  // undefined behavior: ir3 not yet initialized
  int& ir3 = g();
  int& ir4 = f(ir4);  // undefined behavior: ir4 used in its own initializer
end example]

Note (February, 2006):

The word “use” in the last paragraph of the proposed resolution was intended to refer to the description in 6.3 [basic.def.odr] paragraph 2. However, that section does not define what it means for a reference to be “used,” dealing only with objects and functions. Additional drafting is required to extend 6.3 [basic.def.odr] paragraph 2 to apply to references.

Additional note (May, 2008):

The proposed resolution for issue 570 adds wording to define “use” for references.

Note, January, 2012:

The resolution should also probably deal with the fact that the “one-past-the-end” address of an array does not designate a valid object (even if such a pointer might “point to” an object of the correct type, per 6.8.4 [basic.compound]) and thus is not suitable for the lvalue-to-rvalue conversion.

CWG 2023-11-06

We need a (possibly out-of-lifetime) object, not just a region of storage here. Empty lvalues do not exist. Otherwise, the direction is confirmed.

Proposed resolution (approved by CWG 2023-11-10) [SUPERSEDED]:

  1. Change in 7.2.1 [basic.lval] paragraph 11 as follows:

    An object of dynamic type Tobj is type-accessible through a glvalue of type Tref if Tref is similar (7.3.6 [conv.qual]) to:

    • Tobj,
    • a type that is the signed or unsigned type corresponding to Tobj, or
    • a char, unsigned char, or std:byte type.

    If a program attempts to access (3.1 [defns.access]) the stored value of an object through a glvalue whose type is not similar (7.3.6 [conv.qual]) to one of the following types through which it is not type-accessible, the behavior is undefined:.[ Footnote: ... ]
    • the dynamic type of the object,
    • a type that is the signed or unsigned type corresponding to the dynamic type of the object, or
    • a char, unsigned char, or std::byte type.
    If a program invokes a defaulted copy/move constructor or copy/move assignment operator for a union of type U with a glvalue argument that does not denote an object of type cv U within its lifetime, the behavior is undefined.
  2. Change in 7.6.1.3 [expr.call] paragraph 5 as follows:

    A type Tcall is call-compatible with a function type Tfunc if Tcall is the same type as Tfunc or if the type "pointer to Tfunc" can be converted to type "pointer to Tcall" via a function pointer conversion (7.3.14 [conv.fctptr]). Calling a function through an expression whose function type E is different from the function is not call-compatible with the type F of the called function's definition results in undefined behavior unless the type “pointer to F” can be converted to the type “pointer to E” via a function pointer conversion (7.3.14 [conv.fctptr]). [Note 4: The exception applies This requirement allows the case when the expression has the type of a potentially-throwing function, but the called function has a non-throwing exception specification, and the function types are otherwise the same. —end note]
  3. Change and split in 9.3.4.3 [dcl.ref] paragraph 5 as follows:

    There shall be no references to references, no arrays of references, and no pointers to references. The declaration of a reference shall contain an initializer (9.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 class definition, or is the declaration of a parameter or a return type (9.3.4.6 [dcl.fct]); see 6.2 [basic.def]. A reference shall be initialized to refer to a valid object or function.

    Attempting to bind a reference to a function where the initializer is a glvalue whose type is not call-compatible (7.6.1.3 [expr.call]) with the type of the function's definition results in undefined behavior. Attempting to bind a reference to an object where the initializer is a glvalue through which the object is not type-accessible (7.2.1 [basic.lval]) results in undefined behavior. [Note 2: In particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by indirection through a null pointer, which causes undefined behavior. The object designated by such a glvalue can be outside its lifetime (6.7.3 [basic.life]). Because a null pointer value or a pointer past the end of an object does not point to an object, a reference in a well-defined program cannot refer to such things; see 7.6.2.2 [expr.unary.op]. As described in 11.4.10 [class.bit], a reference cannot be bound directly to a bit-field. —end note] An odr-use (6.3 [basic.def.odr]) of a reference that does not happen after (6.9.2.2 [intro.races]) its initialization results in undefined behavior. [ Example:

    int &f(int&);
    int &g();
    extern int &ir3;
    int *ip = 0;
    int &ir1 = *ip;    // undefined behavior: null pointer
    int &ir2 = f(ir3); // undefined behavior: ir3 not yet initialized
    int &ir3 = g();
    int &ir4 = f(ir4); // undefined behavior: ir4 used in its own initializer
    
    -- end example ]

Additional notes (November, 2023):

An odr-use is a property of the program, not an evaluation that participates in the "happens before" relation.

Proposed resolution (approved by CWG 2024-03-20):

  1. Change in 7.2.1 [basic.lval] paragraph 11 as follows:

    An object of dynamic type Tobj is type-accessible through a glvalue of type Tref if Tref is similar (7.3.6 [conv.qual]) to:

    • Tobj,
    • a type that is the signed or unsigned type corresponding to Tobj, or
    • a char, unsigned char, or std:byte type.

    If a program attempts to access (3.1 [defns.access]) the stored value of an object through a glvalue whose type is not similar (7.3.6 [conv.qual]) to one of the following types through which it is not type-accessible, the behavior is undefined:.[ Footnote: ... ]
    • the dynamic type of the object,
    • a type that is the signed or unsigned type corresponding to the dynamic type of the object, or
    • a char, unsigned char, or std::byte type.
    If a program invokes a defaulted copy/move constructor or copy/move assignment operator for a union of type U with a glvalue argument that does not denote an object of type cv U within its lifetime, the behavior is undefined.
  2. Change in 7.6.1.3 [expr.call] paragraph 5 as follows:

    A type Tcall is call-compatible with a function type Tfunc if Tcall is the same type as Tfunc or if the type "pointer to Tfunc" can be converted to type "pointer to Tcall" via a function pointer conversion (7.3.14 [conv.fctptr]). Calling a function through an expression whose function type E is different from the function is not call-compatible with the type F of the called function's definition results in undefined behavior unless the type “pointer to F” can be converted to the type “pointer to E” via a function pointer conversion (7.3.14 [conv.fctptr]). [Note 4: The exception applies This requirement allows the case when the expression has the type of a potentially-throwing function, but the called function has a non-throwing exception specification, and the function types are otherwise the same. —end note]
  3. Change and split in 9.3.4.3 [dcl.ref] paragraph 5 as follows:

    There shall be no references to references, no arrays of references, and no pointers to references. The declaration of a reference shall contain an initializer (9.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 class definition, or is the declaration of a parameter or a return type (9.3.4.6 [dcl.fct]); see 6.2 [basic.def]. A reference shall be initialized to refer to a valid object or function.

    Attempting to bind a reference to a function where the converted initializer is a glvalue whose type is not call-compatible (7.6.1.3 [expr.call]) with the type of the function's definition results in undefined behavior. Attempting to bind a reference to an object where the converted initializer is a glvalue through which the object is not type-accessible (7.2.1 [basic.lval]) results in undefined behavior. [Note 2: In particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by indirection through a null pointer, which causes undefined behavior. The object designated by such a glvalue can be outside its lifetime (6.7.3 [basic.life]). Because a null pointer value or a pointer past the end of an object does not point to an object, a reference in a well-defined program cannot refer to such things; see 7.6.2.2 [expr.unary.op]. As described in 11.4.10 [class.bit], a reference cannot be bound directly to a bit-field. —end note] The behavior of an evaluation of a reference (7.5.5 [expr.prim.id], 7.6.1.5 [expr.ref]) that does not happen after (6.9.2.2 [intro.races]) the initialization of the reference is undefined. [ Example:

    int &f(int&);
    int &g();
    extern int &ir3;
    int *ip = 0;
    int &ir1 = *ip;    // undefined behavior: null pointer
    int &ir2 = f(ir3); // undefined behavior: ir3 not yet initialized
    int &ir3 = g();
    int &ir4 = f(ir4); // undefined behavior: ir4 used in its own initializer
    
    char x alignas(int);
    int &ir5 = *reinterpret_cast<int *>(&x);  // undefined behavior: initializer refers to char object
    
    -- end example ]




2550. Type "reference to cv void" outside of a declarator

Section: 9.3.4.3  [dcl.ref]     Status: DRWP     Submitter: Jens Maurer     Date: 2022-03-13

[Accepted as a DR at the June, 2023 meeting.]

9.3.4.3 [dcl.ref] paragraph 1 specifies:

A declarator that specifies the type “reference to cv void” is ill-formed.

A declarator does not contain the leading decl-specifier-seq of a declaration, so the following example is not covered by the prohibition:

  void f(void& x);

Proposed resolution (approved by CWG 2023-06-15):

Change in 9.3.4.3 [dcl.ref] paragraph 1 as follows:

A declarator that specifies Forming the type “reference to cv void” is ill-formed.



2846. Out-of-class definitions of explicit object member functions

Section: 9.3.4.6  [dcl.fct]     Status: DRWP     Submitter: Krystian Stasiowski     Date: 2024-01-28

[Accepted as a DR at the March, 2024 meeting.]

(From submission #493.)

Consider:

  struct A {
    void f(this A&);
  };
  void A::f(this A&) { }    // #1

This is accepted by all major implementations. However, 9.3.4.6 [dcl.fct] paragraph 6 specifies:

An explicit-object-parameter-declaration is a parameter-declaration with a this specifier. An explicit-object-parameter-declaration shall appear only as the first parameter-declaration of a parameter-declaration-list of either: A member-declarator with an explicit-object-parameter-declaration shall not include a ref-qualifier or a cv-qualifier-seq and shall not be declared static or virtual.

The function-definition at #1 is neither of the two allowed options.

Similar concerns arise for explicit instantiations and explicit specializations.

Proposed resolution (approved by CWG 2024-02-16):

Change in 9.3.4.6 [dcl.fct] paragraph 6 as follows:

An explicit-object-parameter-declaration is a parameter-declaration with a this specifier. An explicit-object-parameter-declaration shall appear only as the first parameter-declaration of a parameter-declaration-list of either one of: A member-declarator with an explicit-object-parameter-declaration shall not include a ref-qualifier or a cv-qualifier-seq and shall not be declared static or virtual.



2683. Default arguments for member functions of templated nested classes

Section: 9.3.4.7  [dcl.fct.default]     Status: DRWP     Submitter: Matthew House     Date: 2023-01-11

[Accepted as a DR at the June, 2023 meeting.]

Subclause 9.3.4.7 [dcl.fct.default] paragraph 6 specifies:

Except for member functions of class templates, the default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition; the program is ill-formed if a default constructor (11.4.5.2 [class.default.ctor]), copy or move constructor (11.4.5.3 [class.copy.ctor]), or copy or move assignment operator (11.4.6 [class.copy.assign]) is so declared. Default arguments for a member function of a class template shall be specified on the initial declaration of the member function within the class template.

That rule appears to allow adding default arguments for member functions of classes that are nested within class templates, for example:

  template<class> struct A { struct B { void c(int); }; };
  template<class T> void A<T>::B::c(int = 0) {}

MSVC accepts; gcc and clang reject.

Proposed resolution (approved by CWG 2023-03-03):

Change in 9.3.4.7 [dcl.fct.default] paragraph 6 as follows:

Except for member functions of class templates templated classes, the default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition; the program is ill-formed if a default constructor (11.4.5.2 [class.default.ctor]), copy or move constructor (11.4.5.3 [class.copy.ctor]), or copy or move assignment operator (11.4.6 [class.copy.assign]) is so declared. Default arguments for a member function of a templated class template shall be specified on the initial declaration of the member function within the templated class template.



2708. Parenthesized initialization of arrays

Section: 9.4.1  [dcl.init.general]     Status: DRWP     Submitter: Mike Miller     Date: 2023-03-14

[Accepted as a DR at the June, 2023 meeting.]

Consider:

  const int arr[2](1,2);

This is accepted by all major implementations, yet 9.4.1 [dcl.init.general] paragraph 13 prohibits it:

If the entity being initialized does not have class type, the expression-list in a parenthesized initializer shall be a single expression.

Presumably, this was an oversight when adding parenthesized aggregate initialization.

Proposed resolution (approved by CWG 2023-04-28):

Change in 9.4.1 [dcl.init.general] paragraph 13 as follows:

If the entity being initialized does not have class or array type, the expression-list in a parenthesized initializer shall be a single expression.



2820. Value-initialization and default constructors

Section: 9.4.1  [dcl.init.general]     Status: DRWP     Submitter: Shafik Yaghmour     Date: 2023-10-31

[Accepted as a DR at the March, 2024 meeting.]

Subclause 9.4.1 [dcl.init.general] paragraph 9 specifies:

To value-initialize an object of type T means:

The specification about checking the semantic constraints and invoking only non-trivial default constructors is overly convoluted. Omitting a call to a trivial constructor is an as-if optimization that should not be prescribed by the standard.

Proposed resolution (approved by CWG 2024-01-19):

Change in 9.4.1 [dcl.init.general] bullet 9.1.2 as follows:




2824. Copy-initialization of arrays

Section: 9.4.1  [dcl.init.general]     Status: DRWP     Submitter: Anoop Rana     Date: 2023-11-06

[Accepted as a DR at the March, 2024 meeting.]

Consider:

  std::string arr[] = "some string";

Prior to the application of paper P0960R3 (Allow initializing aggregates from a parenthesized list of values), this was ill-formed, but now it is well-formed. However, the specification talks about an expression-list as part of the initializer, which does not exist in this case.

Proposed resolution (approved by CWG 2023-11-07):

Change in 9.4.1 [dcl.init.general] bullet 16.5 as follows:

Otherwise, if the destination type is an array, the object is initialized as follows. The initializer shall be of the form ( expression-list ). Let x1 , . . . , xk be the elements of the expression-list. If the destination type is an array of unknown bound, it is defined as having k elements. Let n denote the array size after this potential adjustment. If k is greater than n, the program is ill-formed. Otherwise, ...



2149. Brace elision and array length deduction

Section: 9.4.2  [dcl.init.aggr]     Status: DRWP     Submitter: Vinny Romano     Date: 2015-06-25

[Accepted as a DR as paper P3106R1 at the March, 2024 meeting.]

According to 9.4.2 [dcl.init.aggr] paragraph 4,

An array of unknown size initialized with a brace-enclosed initializer-list containing n initializer-clauses, where n shall be greater than zero, is defined as having n elements (9.3.4.5 [dcl.array]).

However, the interaction of this with brace elision is not clear. For instance, in the example in paragraph 7,

  struct X { int i, j, k = 42; };
  X a[] = { 1, 2, 3, 4, 5, 6 };
  X b[2] = { { 1, 2, 3 }, { 4, 5, 6 } };

a and b are said to have the same value, even though there are six initializer-clauses in the initializer list in a's initializer and two in b's initializer.

Similarly, 13.10.3.2 [temp.deduct.call] paragraph 1 says,

in the P'[N] case, if N is a non-type template parameter, N is deduced from the length of the initializer list

Should that take into account the underlying type of the array? For example,

  template<int N> void f1(const X(&)[N]);
  f1({ 1, 2, 3, 4, 5, 6 }); // Is N deduced to 2 or 6?

  template<int N> void f2(const X(&)[N][2]);
  f2({ 1, 2, 3, 4, 5, 6 }); // Is N deduced to 1 or 6?

Additional notes (April, 2024)

The situation with arrays of unknown bound was clarified by P3106R1. The concern about template argument deduction was left untouched; the existing wording in 13.10.3.2 [temp.deduct.call] paragraph 1 seems to be clear, rendering the two examples shown above ill-formed, because deduction of P' against the integer arguments fails.




2657. Cv-qualification adjustment when binding reference to temporary

Section: 9.4.4  [dcl.init.ref]     Status: DRWP     Submitter: Brian Bi     Date: 2022-11-10

[Accepted as a DR at the March, 2024 meeting.]

Core issue 2481 was resolved by clarifying that the temporary object in p5.4.2 is cv-qualified if the reference being initialized is cv-qualified. However, this is not the right bullet point for the example given,

  constexpr const int &r = 42;

Such an initialization would actually use bullet 5.3.1 instead. (5.4.2 would be used if the initializer were, for example, 3.14.) We therefore need to make a similar clarification in bullet 5.3, and ideally using the same language.

Proposed resolution (approved by CWG 2024-03-20):

  1. Change in 9.4.4 [dcl.init.ref] bullet 5.3 as follows:

    Otherwise, if the initializer expression
    • is an rvalue (but not a bit-field) or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
    • has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an rvalue or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (see 12.2.2.7 [over.match.ref]),
    then the initializer expression in the first case and the converted expression in the second case is called the converted initializer. If the converted initializer is a prvalue, let its type be denoted by T4; the temporary materialization conversion (7.3.5 [conv.rval]) is applied, considering the type of the prvalue to be is adjusted to type “cv1 T4” (7.3.6 [conv.qual]) and the temporary materialization conversion (7.3.5 [conv.rval]) is applied. In any case, the reference binds to the resulting glvalue (or to an appropriate base class subobject).
  2. Append to the example in 9.4.4 [dcl.init.ref] bullet 5.3 as follows:

      B&& rrb = x;  // binds directly to the result of operator B
    
      constexpr int f() {
        const int &x = 42;
        const_cast<int &>(x) = 1;  // undefined behavior
        return x;
      }
      constexpr int z = f();   // error: not a constant expression
    
  3. Change the example in 9.4.4 [dcl.init.ref] bullet 5.4 as follows:

      const double& rcd2 = 2;   // rcd2 refers to temporary with type const double and value 2.0
    



2801. Reference binding with reference-related types

Section: 9.4.4  [dcl.init.ref]     Status: DRWP     Submitter: Brian Bi     Date: 2023-09-18

[Accepted as a DR at the November, 2023 meeting.]

Consider:

  int* p;
  const int*&& r = static_cast<int*&&>(p);

The intent of core issues 2018 and 2352 was to make this example ill-formed, because it surprisingly introduces a temporary.

Proposed resolution (approved by CWG 2023-10-20):

Change in 9.4.4 [dcl.init.ref] bullet 5.4 as follows:




2252. Enumeration list-initialization from the same type

Section: 9.4.5  [dcl.init.list]     Status: DRWP     Submitter: Richard Smith     Date: 2016-03-22

[Accepted as a DR at the November, 2023 meeting.]

According to 9.4.5 [dcl.init.list] bullet 3.8,

Otherwise, if T is an enumeration with a fixed underlying type (9.7.1 [dcl.enum]), the initializer-list has a single element v, and the initialization is direct-list-initialization, the object is initialized with the value T(v) (7.6.1.4 [expr.type.conv]); if a narrowing conversion is required to convert v to the underlying type of T , the program is ill-formed.

This could be read as requiring that there be a conversion from v to the underlying type of T, leaving the status of an example like the following unclear:

  enum class E {};
  struct X { operator E(); };
  E{X()}; // ok? 

Notes from the March, 2018 meeting:

CWG disagreed that the existing wording requires such a conversion, only that if such a conversion is possble, it must not narrow. A formulation along the lines of “if that initialization involves a narrowing conversion to the underlying type of T...” was suggested to clarify the intent. This will be handled editorially, and the issue will be left in "review" status until the change has been verified.

Additional notes (August, 2023)

Issue 2374 has meanwhile clarified that v is required to implicitly convert to the underlying type of the enumeration for 9.4.5 [dcl.init.list] bullet 3.8 to apply. Now, the logic falls through to 9.4.5 [dcl.init.list] bullet 3.9 for the above example, making it well-formed.

CWG 2023-09-15

Class types with conversions to scalar types were not in view when the wording in this bullet was conceived.

Proposed resolution (approved by CWG 2023-10-06):

Change in 9.4.5 [dcl.init.list] bullet 3.8 as follows:

Otherwise, if T is an enumeration with a fixed underlying type (9.7.1 [dcl.enum]) U, the initializer-list has a single element v of scalar type, v can be implicitly converted to U, and the initialization is direct-list-initialization, the object is initialized with the value T(v) (7.6.1.4 [expr.type.conv]); if a narrowing conversion is required to convert v to U, the program is ill-formed.



2638. Improve the example for initializing by initializer list

Section: 9.4.5  [dcl.init.list]     Status: DRWP     Submitter: Shafik Yaghmour     Date: 2022-10-26

[Accepted as a DR at the March, 2024 meeting.]

Issue 2137 amended the rules for initialization by initializer list, but neglected to add an example.

Proposed resolution (approved by CWG 2023-11-11):

Change the example in 9.4.5 [dcl.init.list] bullet 3.7 as follows:

struct S {
  S(std::initializer_list<double>); // #1
  S(std::initializer_list<int>);    // #2
  S(std::initializer_list<S>);      // #3
  S();                              // #3#4

  // ...
};
S s1 = { 1.0, 2.0, 3.0 };  // invoke #1
S s2 = { 1, 2, 3 };        // invoke #2
S s3{s2};                  // invoke #3 (not the copy constructor)
S s3s4 = { };              // invoke #3#4



2713. Initialization of reference-to-aggregate from designated initializer list

Section: 9.4.5  [dcl.init.list]     Status: DRWP     Submitter: Richard Smith     Date: 2023-04-06

[Accepted as a DR at the June, 2023 meeting.]

Aggregates can be initialized by a designated initializer list, but references to aggregates cannot, although list-initialization of such with a regular braced-init-list is fine.

Subclause 9.4.5 [dcl.init.list] paragraph 3 specifies:

List-initialization of an object or reference of type T is defined as follows:

Subclause 12.2.4.2.6 [over.ics.list] paragraph 2 specifies:

If the initializer list is a designated-initializer-list, a conversion is only possible if the parameter has an aggregate type that can be initialized from the initializer list according to the rules for aggregate initialization (9.4.2 [dcl.init.aggr]), in which case the implicit conversion sequence is a user-defined conversion sequence whose second standard conversion sequence is an identity conversion.

Proposed resolution (approved by CWG 2023-04-28):

  1. Change in 9.4.5 [dcl.init.list] bullet 3.1 as follows:

    • If the braced-init-list contains a designated-initializer-list and T is not a reference type, T shall be an aggregate class. ...
    • ...
  2. Change in 9.4.5 [dcl.init.list] bullet 3.9 as follows:

    Otherwise, if the initializer list is not a designated-initializer-list and has a single element of type E and ...
  3. Change in 9.4.5 [dcl.init.list] bullet 3.10 as follows:

    [ Example:
      ...
      const B& b2{a};  // error: cannot copy-list-initialize B temporary from A
      struct C { int x; };
      C&& c = { .x = 1 };  // OK
    
    -- end example ]
  4. Change in 12.2.4.2.6 [over.ics.list] paragraph 2 as follows:

    If the initializer list is a designated-initializer-list and the parameter is not a reference, a conversion is only possible if the parameter has an aggregate type that can be initialized from the initializer list according to the rules for aggregate initialization (9.4.2 [dcl.init.aggr]), in which case the implicit conversion sequence is a user-defined conversion sequence whose second standard conversion sequence is an identity conversion.



2830. Top-level cv-qualification should be ignored for list-initialization

Section: 9.4.5  [dcl.init.list]     Status: DRWP     Submitter: Krystian Stasiowski     Date: 2019-11-19

[Accepted as a DR at the March, 2024 meeting.]

(From editorial issue 3492.)

Subclause 9.4.5 [dcl.init.list] paragraph 3 specifies:

List-initialization of an object or reference of type T is defined as follows:

Top-level cv-qualifiers should be ignored when comparing T to other types, e.g. in 9.4.5 [dcl.init.list] bullet 3.2.

Proposed resolution (approved by CWG 2024-01-19):

Change in 9.4.5 [dcl.init.list] paragraph 3 as follows:

List-initialization of an object or reference of type cv T is defined as follows:



2547. Defaulted comparison operator function for non-classes

Section: 9.5.2  [dcl.fct.def.default]     Status: DRWP     Submitter: Jim X     Date: 2022-03-07

[Accepted as a DR at the March, 2024 meeting.]

(See editorial issue 5337.)

Subclause 9.5.2 [dcl.fct.def.default] paragraph 1 specifies:

A function definition whose function-body is of the form = default ; is called an explicitly-defaulted definition. A function that is explicitly defaulted shall

There seem to be no further restrictions on which comparison operator functions are allowed to be defaulted. For example,

  enum E { };
  bool operator==(E, E) = default;  // well-formed?

Subclause 11.10.1 [class.compare.default] paragraph 1 applies only to comparison operator functions "for some class":

A defaulted comparison operator function (12.4.3 [over.binary]) for some class C shall be a non-template function that is

Proposed resolution [SUPERSEDED]:

  1. Change in 9.5.2 [dcl.fct.def.default] paragraph 1 as follows:
    A function definition whose function-body is of the form = default ; is called an explicitly-defaulted definition. A function that is explicitly defaulted shall
  2. Change in 11.10.1 [class.compare.default] paragraph 1 as follows:
    A defaulted comparison operator function (12.4.3 [over.binary]) for some class C shall be a non-template function that is
    • a non-static const non-volatile member of some class C having one parameter of type const C& and either no ref-qualifier or the ref-qualifier &, or
    • a friend of some class C having either two parameters of type const C& or two parameters of type C.

    Such a comparison operator function is termed a comparison operator function for class C. A comparison operator function for class C that is defaulted on its first declaration ...

CWG 2023-12-01

A defaulted comparison function for an incomplete class later declared a friend for that class should be made ill-formed.

Proposed resolution (approved by CWG 2023-12-15):

  1. Change in 9.5.2 [dcl.fct.def.default] paragraph 1 as follows:
    A function definition whose function-body is of the form = default ; is called an explicitly-defaulted definition. A function that is explicitly defaulted shall
  2. Change in 11.10.1 [class.compare.default] paragraph 1 as follows:

    A defaulted comparison operator function (12.4.3 [over.binary]) for some class C shall be a non-template function that is
    • is a non-static member or friend of some class C,
    • is defined as defaulted in C or in a context where C is complete, and
    • either has two parameters of type const C& or two parameters of type C, where the implicit object parameter (if any) is considered to be the first parameter.
    Such a comparison operator function is termed a defaulted comparison operator function for class C. Name lookups in the implicit definition (9.5.2 [dcl.fct.def.default]) of a comparison operator function are performed from a context equivalent to its function-body. A definition of a comparison operator as defaulted that appears in a class shall be the first declaration of that function. [ Example:
      struct S;
      bool operator==(S, S) = default;  // error: S is not complete
      struct S {
        friend bool operator==(S, const S&) = default; // error: parameters of different types
      };
      enum E { };
      bool operator==(E, E) = default;  // error: not a member or friend of a class
    
    -- end example ]



2570. Clarify constexpr for defaulted functions

Section: 9.5.2  [dcl.fct.def.default]     Status: DRWP     Submitter: Gabriel dos Reis     Date: 2022-04-18

[Accepted as a DR at the November, 2023 meeting.]

After the application of P2448R2, 9.5.2 [dcl.fct.def.default] paragraph 3 reads:

A function explicitly defaulted on its first declaration is implicitly inline (9.2.8 [dcl.inline]), and is implicitly constexpr (9.2.6 [dcl.constexpr]) if it satisfies the requirements for a constexpr function.

It is unclear that no other such defaulted function is implicitly constexpr.

Proposed resolution (approved by CWG 2023-06-17):

A function explicitly defaulted on its first declaration is implicitly inline (9.2.8 [dcl.inline]), and is implicitly constexpr (9.2.6 [dcl.constexpr]) if it satisfies the requirements for a constexpr function. [Note: Other defaulted functions are not implicitly constexpr. -- end note ]



2809. An implicit definition does not redeclare a function

Section: 9.5.2  [dcl.fct.def.default]     Status: DRWP     Submitter: Brian Bi     Date: 2023-09-27

[Accepted as a DR at the March, 2024 meeting.]

Subclause 9.5.2 [dcl.fct.def.default] paragraph 5 specifies:

... A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is implicitly defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. A non-user-provided defaulted function (i.e., implicitly declared or explicitly defaulted in the class) that is not defined as deleted is implicitly defined when it is odr-used (6.3 [basic.def.odr]) or needed for constant evaluation (7.7 [expr.const]).

In the first case, there is a second point of declaration for the function, wherever the user wrote the definition. In contrast, there is no redeclaration for the second case, where the function is not user-provided. A note would clarify.

Proposed resolution (approved by CWG 2024-03-20):

Insert a paragraph break before the quoted section and change in 9.5.2 [dcl.fct.def.default] paragraph 5 as follows:

... A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is implicitly defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [Note 1: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. —end note] A non-user-provided defaulted function (i.e., implicitly declared or explicitly defaulted in the class) that is not defined as deleted is implicitly defined when it is odr-used (6.3 [basic.def.odr]) or needed for constant evaluation (7.7 [expr.const]). [Note 1: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. —end note] [ Note: The implicit definition of a non-user-provided defaulted function does not bind any names. -- end note ]



2754. Using *this in explicit object member functions that are coroutines

Section: 9.5.4  [dcl.fct.def.coroutine]     Status: DRWP     Submitter: Christof Meerwald     Date: 2023-06-23

[Accepted as a DR at the November, 2023 meeting.]

Subclause 9.5.4 [dcl.fct.def.coroutine] paragraph 4 specifies:

In the following, pi is an lvalue of type Pi , where p1 denotes the object parameter and pi+1 denotes the ith non-object function parameter for a non-static member function, and pi denotes the ith function parameter otherwise. For a non-static member function, q1 is an lvalue that denotes *this; any other qi is an lvalue that denotes the parameter copy corresponding to pi , as described below.

An explicit object member function is a non-static member function, but there is no this.

Proposed resolution (approved by CWG 2023-07-14):

Change in 9.5.4 [dcl.fct.def.coroutine] paragraph 4 as follows:

In the following, pi is an lvalue of type Pi , where p1 denotes the object parameter and pi+1 denotes the ith non-object function parameter for a non-static an implicit object member function, and pi denotes the ith function parameter otherwise. For a non-static an implicit object member function, q1 is an lvalue that denotes *this; any other qi is an lvalue that denotes the parameter copy corresponding to pi, as described below.



2663. Example for member redeclarations with using-declarations

Section: 9.9  [namespace.udecl]     Status: DRWP     Submitter: Shafik Yaghmour     Date: 2022-11-28

[Accepted as a DR at the June, 2023 meeting.]

Issue 36 was resolved by P1787R6, but no example was added.

Proposed resolution (approved by CWG 2023-06-13):

  1. Add an example to 9.9 [namespace.udecl] paragraph 8 as follows:

    [ Example:
    struct C {
      int i;
    };
    
    struct D1 : C { };
    struct D2 : C { };
    
    struct D3 : D1, D2 {
      using D1::i;   // OK, equivalent to using C::i
      using D1::i;   // error: duplicate
      using D2::i;   // error: duplicate, also names C::i
    };
    
    -- end example ]
  2. Change the example in 9.9 [namespace.udecl] paragraph 10 as follows:

      using B::x;
      using A::x;              // OK, hides struct B::x
      using A::x;              // OK, does not conflict with previous using A::x
      x = 99;                  // assigns to A::x
      struct x x1;             // x1 has class type B::x
    }
    

CWG 2023-01-06

There is implementation divergence in handling this example.

CWG 2023-02-07

P1787R6 clarified that the example added to 9.9 [namespace.udecl] paragraph 10 is accepted, even in the non-function case.




2733. Applying [[maybe_unused]] to a label

Section: 9.12.9  [dcl.attr.unused]     Status: DRWP     Submitter: Barry Revzin     Date: 2023-05-25     Liaison: EWG

[Accepted as a DR at the November, 2023 meeting.]

Subclause 9.12.9 [dcl.attr.unused] paragraph 2 specifies:

The attribute may be applied to the declaration of a class, a typedef-name, a variable (including a structured binding declaration), a non-static data member, a function, an enumeration, or an enumerator.

Absent from that list are labels, but both gcc and clang accept [[maybe_unused]] on a label, and behave accordingly.

Proposed resolution (approved by CWG 2023-07-14)

Change in 9.12.9 [dcl.attr.unused] as follows:

The attribute-token maybe_unused indicates that a name, label, or entity is possibly intentionally unused. No attribute-argument-clause shall be present.

The attribute may be applied to the declaration of a class, a typedef-name, a variable (including a structured binding declaration), a non-static data member, a function, an enumeration, or an enumerator, or to an identifier label (8.2 [stmt.label]).

A name or entity declared without the maybe_unused attribute can later be redeclared with the attribute and vice versa. An entity is considered marked after the first declaration that marks it.

Recommended practice: For an entity marked maybe_unused, implementations should not emit a warning that the entity or its structured bindings (if any) are used or unused. For a structured binding declaration not marked maybe_unused, implementations should not emit such a warning unless all of its structured bindings are unused. For a label to which maybe_unused is applied, implementations should not emit a warning that the label is used or unused.

[Example 1:
  [[maybe_unused]] void f([[maybe_unused]] bool thing1,
                          [[maybe_unused]] bool thing2) {
    [[maybe_unused]] bool b = thing1 && thing2;
    assert(b);
#ifdef NDEBUG
    goto x;
#endif
    [[maybe_unused]] x:
  }
Implementations should not warn that b or x is unused, whether or not NDEBUG is defined. — end example]

CWG 2023-07-14

CWG has reviewed and approved the proposed resolution. However, this is a new (albeit small) feature, thus forwarding to EWG via paper issue 1585 for approval.

EWG 2023-11-07

Accept the proposed resolution, forward to CWG for inclusion in C++26.




2732. Can importable headers react to preprocessor state from point of import?

Section: 10.3  [module.import]     Status: DRWP     Submitter: Xu Chuanqi     Date: 2023-05-25

[Accepted as a DR at the June, 2023 meeting.]

Subclause 10.3 [module.import] paragraph 5 specifies:

A module-import-declaration that specifies a header-name H imports a synthesized header unit, which is a translation unit formed by applying phases 1 to 7 of translation (5.2 [lex.phases]) to the source file or header nominated by H, which shall not contain a module-declaration. [Note 2: All declarations within a header unit are implicitly exported (10.2 [module.interface]), and are attached to the global module (10.1 [module.unit]). —end note]

It is unclear whether the contents of header units can vary depending on the set of defined macros at the point where the import (or #include) appears.

Proposed resolution (approved by CWG 2023-06-13):

Change in 10.3 [module.import] paragraph 5 as follows:

[Note 2: A header unit is a separate translation unit with an independent set of defined macros. All declarations within a header unit are implicitly exported (10.2 [module.interface]), and are attached to the global module (10.1 [module.unit]). —end note]



2783. Handling of deduction guides in global-module-fragment

Section: 10.4  [module.global.frag]     Status: DRWP     Submitter: Daniela Engert     Date: 2023-08-21

[Accepted as a DR at the November, 2023 meeting.]

Consider:

  // header "S.h"
  
  template<class T>
  struct S {
    S(const T*);
  };
  template<class T>
  S(T*) -> S<T>

  // translation unit
  module;
  #include "S.h"

  export module M;
  export using ::S;

Obviously, the using-declaration referring to the class template S is exported by M, but what about the deduction guide of S?

Proposed resolution (approved by CWG 2023-08-25) [SUPERSEDED]:

Add a new bullet after 10.4 [module.global.frag] bullet 3.5.7 as follows:

Proposed resolution (approved by CWG 2023-10-06):

Add a new bullet after 10.4 [module.global.frag] bullet 3.5.7 as follows:




2637. Injected-class-name as a simple-template-id

Section: 11.1  [class.pre]     Status: DRWP     Submitter: Shafik Yaghmour     Date: 2022-10-26

[Accepted as a DR at the March, 2024 meeting.]

Issue 2237 sought to disallow simple-template-ids as constructor names, by referring to the injected-class-name. However, 11.1 [class.pre] paragraph 2 specifies:

The class-name is also bound in the scope of the class (template) itself; this is known as the injected-class-name.

The grammar non-terminal class-name includes the option of a simple-template-id (for declaring a partial specialization).

Proposed resolution (approved by CWG 2023-11-11):

Change in 11.1 [class.pre] paragraph 2 as follows:

The component name of the class-name is also bound in the scope of the class (template) itself; this is known as the injected-class-name. ...



2661. Missing disambiguation rule for pure-specifier vs. brace-or-equal-initializer

Section: 11.4.1  [class.mem.general]     Status: DRWP     Submitter: Richard Smith     Date: 2022-10-07

[Accepted as a DR at the March, 2024 meeting.]

Subclause 11.4.1 [class.mem.general] has this grammar:

    member-declarator:
        declarator virt-specifier-seq[opt] pure-specifier[opt]
        declarator brace-or-equal-initializer[opt]

    pure-specifier:
        = 0

The primary issue is that foo = 0 matches both member-declarator productions. Secondarily, a declarator by itself is also ambiguous.

Code such as virtual FunctionType f = 0; can be valid, so disambiguation on the syntactic form of the declarator is not possible.

Proposed resolution (approved by CWG 2024-02-16):

Change and add before 11.4.1 [class.mem.general] paragraph 1 as follows:

    member-declarator:
        declarator virt-specifier-seqopt pure-specifieropt
        declarator brace-or-equal-initializeropt
In the absence of a virt-specifier-seq, the token sequence = 0 is treated as a pure-specifier if the type of the declarator-id (9.3.4.1 [dcl.meaning.general]) is a function type, and is otherwise treated as a brace-or-equal-initializer. [ Note: If the member declaration acquires a function type through template instantiation, the program is ill-formed; see 13.9.1 [temp.spec.general]. --end note ]



2759. [[no_unique_address] and common initial sequence

Section: 11.4.1  [class.mem.general]     Status: DRWP     Submitter: Richard Smith     Date: 2020-11-10

[Accepted as a DR at the November, 2023 meeting.]

The interaction of [[no_unique_address]] and the definition of common initial sequence is still problematic. Subclause 11.4.1 [class.mem.general] bullet 23.3 specifies that corresponding members in a common initial sequence are not allowed to differ with respect to the presence or absence of a [[no_unique_address]] attribute. However, the Itanium ABI will not allocate two successive data members of the same empty class type at the same address, causing non-conforming behavior for the following example:

  struct A {};
  struct B {};

  struct C {
   [[no_unique_address]] A a;
   [[no_unique_address]] B b;
  };

  struct D {
   [[no_unique_address]] A a1;
   [[no_unique_address]] A a2;
  };

  static_assert(offsetof(C, b) == offsetof(D, a2));

See Itanium ABI issue 108.

Since "common initial sequence" and "layout compatible" are concepts mostly used for C compatibility, but [[no_unique_address]] does not exist in C, it seems reasonable to terminate a common initial sequence at the first data member that is declared [[no_unique_address]].

Another concern is the behavior of std::is_layout_compatible on implementations that ignore [[no_unique_address]]. On such an implementation, the following example would be considered layout-compatible, although it actually is not:

  struct E {};

  struct A {
    E e;
    int i;
  };

  struct B {
    [[no_unique_address]] E e;
    int i;
  };

  static_assert(
    std::is_layout_compatible_v<A, B>
  );

Alternative possible resolution [SUPERSEDED]:

Change in 11.4.1 [class.mem.general] paragraph 23 as follows:

The common initial sequence of two standard-layout struct (11.2 [class.prop]) types is the longest sequence of non-static data members and bit-fields in declaration order, starting with the first such entity in each of the structs, such that

Proposed resolution (approved by CWG 2023-08-25):

Change in 11.4.1 [class.mem.general] paragraph 23 as follows:

The common initial sequence of two standard-layout struct (11.2 [class.prop]) types is the longest sequence of non-static data members and bit-fields in declaration order, starting with the first such entity in each of the structs, such that



2771. Transformation for unqualified-ids in address operator

Section: 11.4.3  [class.mfct.non.static]     Status: DRWP     Submitter: Jim X     Date: 2023-07-16

[Accepted as a DR at the March, 2024 meeting.]

Consider:

  struct A{
    int a;
    void show(){
      int* r = &a; // #1 
    }
  };

According to 11.4.3 [class.mfct.non.static] paragraph 2, the transformation to class member access does not happen for the id-expression a, because it is the unparenthesized operand of &:

When an id-expression (7.5.5 [expr.prim.id]) that is neither part of a class member access syntax (7.6.1.5 [expr.ref]) nor the un-parenthesized operand of the unary & operator (7.6.2.2 [expr.unary.op]) is used where the current class is X (7.5.3 [expr.prim.this]), if name lookup (6.5 [basic.lookup]) resolves the name in the id-expression to a non-static non-type member of some class C, and if either the id-expression is potentially evaluated or C is X or a base class of X, the id-expression is transformed into a class member access expression (7.6.1.5 [expr.ref]) using (*this) as the postfix-expression to the left of the . operator. [Note 1: If C is not X or a base class of X, the class member access expression is ill-formed. —end note] This transformation does not apply in the template definition context (13.8.3.2 [temp.dep.type]).

Proposed resolution (approved by CWG 2024-03-20):

This resolution moves the transformation to 7.5.5.1 [expr.prim.id.general], where the similar transformation for anonymous unions is already described.

  1. Change in 6.3 [basic.def.odr] paragraph 7 as follows:

    *this is odr-used if this appears as a potentially-evaluated expression (including as the result of the any implicit transformation in the body of a non-static member function to a class member access expression (11.4.3 [class.mfct.non.static] 7.5.5.1 [expr.prim.id.general])).
  2. Add a new paragraph before 7.5.5.1 [expr.prim.id.general] paragraph 2:

    If an id-expression E denotes a non-static non-type member of some class C at a point where the current class (7.5.3 [expr.prim.this]) is X and

    • E is potentially evaluated or C is X or a base class of X, and
    • E is not the id-expression of a class member access expression (7.6.1.5 [expr.ref]), and
    • if E is a qualified-id, E is not the un-parenthesized operand of the unary & operator (7.6.2.2 [expr.unary.op]),
    the id-expression is transformed into a class member access expression using (*this) as the object expression. [Note 1: If C is not X or a base class of X, the class member access expression is ill-formed. Also, if the id-expression occurs within a static or explicit object member function, the class member access is ill-formed. —end note] This transformation does not apply in the template definition context (13.8.3.2 [temp.dep.type]).

    If an id-expression E denotes a member M of an anonymous union (11.5.2 [class.union.anon]) U:

    • If U is a non-static data member, E refers to M as a member of the lookup context of the terminal name of E (after any implicit transformation to a class member access expression (11.4.3 [class.mfct.non.static])).
    • ...

  3. Change in 7.5.5.1 [expr.prim.id.general] paragraph 3 as follows:

    An id-expression that denotes a non-static data member or implicit object member function of a class can only be used:
    • as part of a class member access (7.6.1.5 [expr.ref]after any implicit transformation (see above)) in which the object expression refers to the member's class [ Footnote: This also applies when the object expression is an implicit (*this) (11.4.3 [class.mfct.non.static]).] or a class derived from that class, or
    • ...
  4. Change in 7.5.5.2 [expr.prim.id.unqual] paragraph 1 as follows:

    ... [ Note: ... Within the definition of a non-static member function, an identifier that names a non-static member is transformed to a class member access expression (11.4.3 [class.mfct.non.static]).end note]
  5. Remove 11.4.3 [class.mfct.non.static] paragraph 2, including the example:

    When an id-expression (7.5.5 [expr.prim.id]) that is neither part of a class member access syntax (7.6.1.5 [expr.ref]) nor the un-parenthesized operand of the unary & operator (7.6.2.2 [expr.unary.op]) is used where the current class is X (7.5.3 [expr.prim.this]), if name lookup (6.5 [basic.lookup]) resolves the name in the id-expression to a non-static non-type member of some class C, and if either the id-expression is potentially evaluated or C is X or a base class of X, the id-expression is transformed into a class member access expression (7.6.1.5 [expr.ref]) using (*this) as the postfix-expression to the left of the . operator. [Note 1: If C is not X or a base class of X, the class member access expression is ill-formed. —end note] This transformation does not apply in the template definition context (13.8.3.2 [temp.dep.type]). [ Example: ... ]
  6. Change in 13.8.3.2 [temp.dep.type] paragraph 6 as follows:

    [ Example: ...
      template int C<B>::g();    // OK, transformation to class member access syntax
                                 // does not occur in the template definition context; see 11.4.3 [class.mfct.non.static] 7.5.5.1 [expr.prim.id.general]
    
    -- end example ]



2595. "More constrained" for eligible special member functions

Section: 11.4.4  [special]     Status: DRWP     Submitter: Barry Revzin     Date: 2022-06-08

[Accepted as a DR at the November, 2023 meeting.]

Consider:

  #include <type_traits>

  template<typename T>
  concept Int = std::is_same_v<T, int>;

  template<typename T>
  concept Float = std::is_same_v<T, float>;

  template<typename T>
  struct Foo {
    Foo() requires Int<T> = default; // #1
    Foo() requires Int<T> || Float<T> = default; // #2
  };

Per the wording, #1 is not eligible for Foo<float>, because the constraints are not satisfied. But #2 also is not eligible, because #1 is more constrained than #2. The intent is that #2 is eligible.

Proposed resolution (approved by CWG 2023-06-17):

Change in 11.4.4 [special] paragraph 6 as follows:

An eligible special member function is a special member function for which:



1353. Array and variant members and deleted special member functions

Section: 11.4.5  [class.ctor]     Status: DRWP     Submitter: Sean Hunt     Date: 2011-08-16

[Accepted as a DR at the June, 2023 meeting.]

The specification of when a defaulted special member function is to be defined as deleted sometimes overlooks variant and array members.

Proposed resolution (approved by CWG 2023-02-07):

  1. Change in 11.4.5.2 [class.default.ctor] paragraph 2 as follows:

    A defaulted default constructor for class X is defined as deleted if:
    • X is a union that has a variant member with a non-trivial default constructor and no variant member of X has a default member initializer,
    • X is a non-union class that has a variant member M with a non-trivial default constructor and no variant member of the anonymous union containing M has a default member initializer,
    • any non-static data member with no default member initializer (11.4 [class.mem]) is of reference type,
    • any non-variant non-static data member of const-qualified type (or possibly multi-dimensional array thereof) with no brace-or-equal-initializer is not const-default-constructible (9.4 [dcl.init]),
    • X is a union and all of its variant members are of const-qualified type (or possibly multi-dimensional array thereof),
    • X is a non-union class and all members of any anonymous union member are of const-qualified type (or possibly multi-dimensional array thereof),
    • any potentially constructed subobject, except for a non-static data member with a brace-or-equal-initializer or a variant member of a union where another non-static data member has a brace-or-equal-initializer, has class type M (or possibly multi-dimensional array thereof) and either M has no default constructor or overload resolution (12.2 [over.match]) as applied to find M's corresponding constructor results in an ambiguity or in a function that is deleted or inaccessible from the defaulted default constructor either does not result in a usable candidate (12.2.1 [over.match.general]) or, in the case of a variant member, selects a non-trivial function, or
    • any potentially constructed subobject has a type with class type M (or possibly multi-dimensional array thereof) and M has a destructor that is deleted or inaccessible from the defaulted default constructor.
  2. Change in 11.4.5.3 [class.copy.ctor] paragraph 10 as follows:

    ... A defaulted copy/move constructor for a class X is defined as deleted (9.5.3 [dcl.fct.def.delete]) if X has:
    • a potentially constructed subobject of type M (or possibly multi-dimensional array thereof) that cannot be copied/moved because for which overload resolution (12.2 [over.match]), as applied to find M's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor either does not result in a usable candidate (12.2.1 [over.match.general]) or, in the case of a variant member, selects a non-trivial function,
    • a variant member whose corresponding constructor as selected by overload resolution is non-trivial,
    • any potentially constructed subobject of a type with class type M (or possibly multi-dimensional array thereof) where M has a destructor that is deleted or inaccessible from the defaulted constructor, or,
    • for the copy constructor, a non-static data member of rvalue reference type.
  3. Change in 11.4.6 [class.copy.assign] paragraph 7 as follows:

    A defaulted copy/move assignment operator for class X is defined as deleted if X has:
    • a variant member with a non-trivial corresponding assignment operator and X is a union-like class, or
    • a non-static data member of const non-class type (or possibly multi-dimensional array thereof), or
    • a non-static data member of reference type, or
    • a direct non-static data member of class type M (or possibly multi-dimensional array thereof) or a direct base class M that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to find M's corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator either does not result in a usable candidate (12.2.1 [over.match.general]) or, in the case of a variant member, selects a non-trivial function.
  4. Change in 11.4.7 [class.dtor] paragraph 7 as follows:

    A defaulted destructor for a class X is defined as deleted if:
    • X is a union-like class that has a variant member with a non-trivial destructor,
    • any potentially constructed subobject has class type M (or possibly multi-dimensional array thereof) and M has a deleted destructor that is deleted or a destructor that is inaccessible from the defaulted destructor or, in the case of a variant member, is non-trivial,
    • or, for a virtual destructor, lookup of the non-array deallocation function results in an ambiguity or in a function that is deleted or inaccessible from the defaulted destructor.



2761. Implicitly invoking the deleted destructor of an anonymous union member

Section: 11.4.7  [class.dtor]     Status: DRWP     Submitter: Corentin Jabot     Date: 2023-07-11

[Accepted as a DR at the November, 2023 meeting.]

Consider:

  struct S{
    ~S() {}
  };

  struct A {
    union {
      S arr_;
    };
    ~A(); // user-provided!
  };

  auto foo() {
    return A{S()};
  }

Does the destructor of A attempt to destroy the (unnamed) data member that is the anonymous union? The latter has a deleted destructor per 11.4.7 [class.dtor]. For the default constructor, 11.9.3 [class.base.init] paragraph 9.2 prevents the corresponding construction.

Proposed resolution (approved by CWG 2023-08-25):

  1. Change in 9.4.2 [dcl.init.aggr] paragraph 9 as follows:

    The destructor for each element of class type other than an anonymous union member is potentially invoked (11.4.7 [class.dtor]) from the context where the aggregate initialization occurs
  2. Change in 11.4.7 [class.dtor] paragraph 13 as follows:

    After executing the body of the destructor and destroying any objects with automatic storage duration allocated within the body, a destructor for class X calls the destructors for X's direct non-variant non-static data members other than anonymous unions, the destructors for X's non-virtual direct base classes and, if X is the most derived class (11.9.3 [class.base.init]), its destructor calls the destructors for X's virtual base classes. All destructors are called as if they were referenced with a qualified name, that is, ignoring any possible virtual overriding destructors in more derived classes. Bases and members are destroyed in the reverse order of the completion of their constructor (see 11.9.3 [class.base.init]).
  3. Change in 14.3 [except.ctor] paragraph 3 as follows:

    A subobject is known to be initialized if it is not an anonymous union member and its initialization is specified
    • in 11.9.3 [class.base.init] for initialization by constructor,
    • in 11.4.5.3 [class.copy.ctor] for initialization by defaulted copy/move constructor,
    • in 11.9.4 [class.inhctor.init] for initialization by inherited constructor,
    • in 9.4.2 [dcl.init.aggr] for aggregate initialization,
    • in 7.5.6.3 [expr.prim.lambda.capture] for the initialization of the closure object when evaluating a lambda-expression,
    • in 9.4.1 [dcl.init.general] for default-initialization, value-initialization, or direct-initialization of an array.



2807. Destructors declared consteval

Section: 11.4.7  [class.dtor]     Status: DRWP     Submitter: Corentin Jabot     Date: 2023-09-07

[Accepted as a DR at the November, 2023 meeting.]

There is a conflict between 9.2.6 [dcl.constexpr] paragraph 2

A destructor, an allocation function, or a deallocation function shall not be declared with the consteval specifier.

and 11.4.7 [class.dtor] paragraph 1

Each decl-specifier of the decl-specifier-seq of a prospective destructor declaration (if any) shall be friend, inline, virtual, constexpr, or consteval.

Proposed resolution (approved by CWG 2023-10-20):

Change in 11.4.7 [class.dtor] paragraph 1 as follows:

Each decl-specifier of the decl-specifier-seq of a prospective destructor declaration (if any) shall be friend, inline, virtual, or constexpr, or consteval.



2716. Rule about self-or-base conversion is normatively redundant

Section: 11.4.8.3  [class.conv.fct]     Status: DRWP     Submitter: Brian Bi     Date: 2023-04-11

[Accepted as a DR at the June, 2023 meeting.]

The rule in 11.4.8.3 [class.conv.fct] paragraph 4 is normatively redundant explanation:

A conversion function is never used to convert a (possibly cv-qualified) object to the (possibly cv-qualified) same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to it), or to cv void. [ Footnote: These conversions are considered as standard conversions for the purposes of overload resolution (12.2.4.2 [over.best.ics], 12.2.4.2.5 [over.ics.ref]) and therefore initialization (9.4 [dcl.init]) and explicit casts (7.6.1.9 [expr.static.cast]). A conversion to void does not invoke any conversion function (7.6.1.9 [expr.static.cast]). Even though never directly called to perform a conversion, such conversion functions can be declared and can potentially be reached through a call to a virtual conversion function in a base class. -- end footnote ]

Proposed resolution (approved by CWG 2023-04-28):

Change in 11.4.8.3 [class.conv.fct] paragraph 4 as follows:

[ Note: A conversion function is never used to convert invoked for implicit or explicit conversions of an a (possibly cv-qualified) object to the (possibly cv-qualified) same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to it), or to cv void. [ Footnote: These conversions are considered as standard conversions for the purposes of overload resolution (12.2.4.2 [over.best.ics], 12.2.4.2.5 [over.ics.ref]) and therefore initialization (9.4 [dcl.init]) and explicit casts (7.6.1.9 [expr.static.cast]). A conversion to void does not invoke any conversion function (7.6.1.9 [expr.static.cast]). Even though never directly called to perform a conversion, such conversion functions can be declared and can potentially be reached through a call to a virtual conversion function in a base class. -- end footnote ] -- end note ]

[ Example: ... ]




2591. Implicit change of active union member for anonymous union in union

Section: 11.5.1  [class.union.general]     Status: DRWP     Submitter: Richard Smith     Date: 2022-05-29

[Accepted as a DR at the November, 2023 meeting.]

Subclause 11.5.1 [class.union.general] paragraph 6 describes how union member subobjects are implicitly created by certain assignment operations that assign to union members. However, this description does not appear to properly handle the case of an anonymous union appearing within a union:

  union A {
    int x;
    union {
     int y;
    };
  };
  void f() {
    A a = {.x = 1};
    a.y = 2;
  }

Here, the expectation is that the assignment to a.y starts the lifetime of the anonymous union member subobject within A and also the int member subobject of the anonymous union member subobject. But the algorithm for computing S(a.y) determines that it is {a.y} and does not include the anonymous union member subobject.

Proposed resolution (approved by CWG 2023-06-17):

Change in 11.5.1 [class.union.general] paragraph 6 as follows:

In an assignment expression of the form E1 = E2 that uses either the built-in assignment operator (7.6.19 [expr.ass]) or a trivial assignment operator (11.4.6 [class.copy.assign]), for each element X of S(E1) and each anonymous union member X (11.5.2 [class.union.anon]) that is a member of a union and has such an element as an immediate subobject (recursively), if modification of X would have undefined behavior under 6.7.3 [basic.life], an object of the type of X is implicitly created in the nominated storage; no initialization is performed and the beginning of its lifetime is sequenced after the value computation of the left and right operands and before the assignment.

Editing note: Adding this rule into the definition of S would be more logical, but S(E) is a set of subexpressions of E and there is no form of expression that names an anonymous union member. Redefining S(E) to be a set of objects might be a better option.




2504. Inheriting constructors from virtual base classes

Section: 11.9.4  [class.inhctor.init]     Status: DRWP     Submitter: Hubert Tong     Date: 2021-11-03

[Accepted as a DR at the November, 2023 meeting.]

According to 11.9.4 [class.inhctor.init] paragraph 1,

When a constructor for type B is invoked to initialize an object of a different type D (that is, when the constructor was inherited (9.9 [namespace.udecl])), initialization proceeds as if a defaulted default constructor were used to initialize the D object and each base class subobject from which the constructor was inherited, except that the B subobject is initialized by the invocation of the inherited constructor. The complete initialization is considered to be a single function call; in particular, the initialization of the inherited constructor's parameters is sequenced before the initialization of any part of the Dobject.

First, this assumes that the base class constructor will be invoked from the derived class constructor, which will not be true if the base is virtual and initialized by a more-derived constructor.

If the call to the virtual base constructor is omitted, the last sentence is unclear whether the initialization of the base class constructor's parameters by the inheriting constructor occurs or not. There is implementation divergence in the initialization of V's parameter in the following example:

  struct NonTriv {
    NonTriv(int);
    ~NonTriv();
  };
  struct V { V() = default; V(NonTriv); };
  struct Q { Q(); };
  struct A : virtual V, Q {
    using V::V;
    A() : A(42) { }
  };
  struct B : A { };
  void foo() { B b; }

CWG telecon 2022-09-23:

Inheriting constructors from a virtual base class ought to be ill-formed. Inform EWG accordingly.

Possible resolution [SUPERSEDED]:

  1. Change in 9.9 [namespace.udecl] paragraph 3 as follows:

    ... If a using-declarator names a constructor, its nested-name-specifier shall name a direct non-virtual base class of the current class. If the immediate (class) scope is associated with a class template, it shall derive from the specified base class or have at least one dependent base class.
  2. Change the example in 11.9.4 [class.inhctor.init] paragraph 1 as follows:

    D2 f(1.0);  // error: B1 has a deleted no default constructor
    
    struct W { W(int); };
    struct X : virtual W { using W::W; X() = delete; };
    struct Y : X { using X::X; };
    struct Z : Y, virtual W { using Y::Y; };
    Z z(0);  // OK, initialization of Y does not invoke default constructor of X
    
  3. Change the example in 11.9.4 [class.inhctor.init] paragraph 2 as follows:

    struct V1 : virtual B { using B::B; };
    struct V2 : virtual B { using B::B; };
    
    struct D2 : V1, V2 {
      using V1::V1;
      using V2::V2;
    };
    D1 d1(0);  // error: ambiguous
    D2 d2(0);  // OK, initializes virtual B base class, which initializes the A base class
               // then initializes the V1 and V2 base classes as if by a defaulted default constructor
    

CWG telecon 2022-10-07:

Given that there are examples that discuss inheriting constructors from virtual base classes and given the existing normative wording, making it clear that NonTriv is not constructed, CWG felt that the implementation divergence is best addressed by amending the examples.

Possible resolution [SUPERSEDED]:

Add another example before 11.9.4 [class.inhctor.init] paragraph 2 as follows:

[ Example:

struct NonTriv {
  NonTriv(int);
  ~NonTriv();
};
struct V { V() = default; V(NonTriv); };
struct Q { Q(); };
struct A : virtual V, Q {
  using V::V;
  A() : A(42) { }    // #1, A(42) is equivalent to V(42)
};
struct B : A { };
void foo() { B b; }

In this example, the V subobject of b is constructed using the defaulted default constructor. The mem-initializer naming the constructor inherited from V at #1 is not evaluated and thus no object of type NonTriv is constructed. -- end example ]

If the constructor was inherited from multiple base class subobjects of type B, the program is ill-formed.

Proposed resolution (approved by CWG 2023-11-06):

  1. Change in 11.9.4 [class.inhctor.init] paragraph 1 as follows:

    When a constructor for type B is invoked to initialize an object of a different type D (that is, when the constructor was inherited (9.9 [namespace.udecl])), initialization proceeds as if a defaulted default constructor were used to initialize the D object and each base class subobject from which the constructor was inherited, except that the B subobject is initialized by the invocation of the inherited constructor if the base class subobject were to be initialized as part of the D object (11.9.3 [class.base.init]). The invocation of the inherited constructor, including the evaluation of any arguments, is omitted if the B subobject is not to be initialized as part of the D object. The complete initialization is considered to be a single function call; in particular, unless omitted, the initialization of the inherited constructor's parameters is sequenced before the initialization of any part of the Dobject.
  2. Add another example before 11.9.4 [class.inhctor.init] paragraph 2 as follows:

    [ Example:

    struct V { V() = default; V(int); };
    struct Q { Q(); };
    struct A : virtual V, Q {
      using V::V;
      A() = delete;
    };
    int bar() { return 42; }
    struct B : A {
      B() : A(bar()) {}  // ok
    };
    struct C : B {};
    void foo() { C c; } // bar is not invoked, because the V subobject is not initialized as part of B
    

    -- end example ]

CWG telecon 2022-10-21:

This is an ABI break for implementations when transitioning to the C++17 model for inheriting constructors.




2568. Access checking during synthesis of defaulted comparison operator

Section: 11.10.1  [class.compare.default]     Status: DRWP     Submitter: Nicolai Josuttis     Date: 2022-04-11

[Accepted as a DR at the March, 2024 meeting.]

Consider:

  struct Base {
  protected:
    bool operator==(const Base& other) const = default;
  };

  struct Child : Base {
    int i;
    bool operator==(const Child& other) const = default;
  };

Per 11.10.1 [class.compare.default] paragraph 6,

Let xi be an lvalue denoting the i-th element in the expanded list of subobjects for an object x (of length n), where xi is formed by a sequence of derived-to-base conversions (12.2.4.2 [over.best.ics]), class member access expressions (7.6.1.5 [expr.ref]), and array subscript expressions (7.6.1.2 [expr.sub]) applied to x.

The derived-to-base conversion for this loses the context of access to the protected Base::operator==, violating 11.8.5 [class.protected] paragraph 1. The example is rejected by implementations, but ought to work.

For this related example, there is implementation divergence:

  struct B {
  protected:
    constexpr operator int() const { return 0; }
  };
  struct D : B {
    constexpr bool operator==(const D&) const = default;
  };
  template<typename T> constexpr auto comparable(T t) -> decltype(t == t) { return t == t; }
  constexpr bool comparable(...) { return false; }
  static_assert(comparable(D{}));

Is D::operator== deleted, because its defaulted definition violates the protected access rules? Is D::operator== not deleted, but synthesis fails on use because of the proctected access rules? Is the synthesis not in the immediate context, making the expression comparable(D{}) ill-formed?

CWG 2023-06-17

There is no implementation divergence; the first example is intended to be well-formed.

Proposed resolution (approved by CWG 2023-12-01):

Change in 11.10.1 [class.compare.default] paragraph 1 as follows:

... Name lookups and access checks in the implicit definition (9.5.2 [dcl.fct.def.default]) of a comparison operator function are performed from a context equivalent to its function-body . A definition of a comparison operator as defaulted that appears in a class shall be the first declaration of that function.



2546. Defaulted secondary comparison operators defined as deleted

Section: 11.10.4  [class.compare.secondary]     Status: DRWP     Submitter: Jim X     Date: 2022-03-07

[Accepted as a DR at the March, 2024 meeting.]

(See also editorial issues 5335 and 5336.)

Consider the example in 11.10.4 [class.compare.secondary] paragraph 3:

  struct HasNoLessThan { };
  struct C {
    friend HasNoLessThan operator<=>(const C&, const C&);
    bool operator<(const C&) const = default;  // OK, function is deleted
  };

While the comment may reflect the intent, it does not follow from the wording. 11.10.4 [class.compare.secondary] paragraph 2 specifies:

The operator function with parameters x and y is defined as deleted if

Otherwise, the operator function yields x @ y. The defaulted operator function is not considered as a candidate in the overload resolution for the @ operator.

Overload resolution applied to x < y results in a usable candidate operator<=> (12.2.1 [over.match.general]) and that candidate is a rewritten candidate (12.2.2.3 [over.match.oper] bullet 3.4), thus operator< in the above example is not deleted. However, its definition is ill-formed, because the rewrite (x <=> y) < 0 is ill-formed (12.2.2.3 [over.match.oper] paragraph 8).

There is implementation divergence.

Subclause 11.10.3 [class.spaceship] paragraph 1 seems to prefer an ill-formed program for similar synthesized situations:

[Note 1: A synthesized three-way comparison is ill-formed if overload resolution finds usable candidates that do not otherwise meet the requirements implied by the defined expression. —end note]

Proposed resolution (approved by CWG 2024-03-20):

Change in 11.10.4 [class.compare.secondary] paragraph 2 as follows:
The operator function with parameters x and y is defined as deleted if

In any of the two overload resolutions above, the defaulted operator function is not considered as a candidate for the @ operator. Otherwise, the operator function yields x @ y. The defaulted operator function is not considered as a candidate in the overload resolution for the @ operator.




2762. Type of implicit object parameter

Section: 12.2.2.1  [over.match.funcs.general]     Status: DRWP     Submitter: Jim X     Date: 2023-07-11

[Accepted as a DR at the November, 2023 meeting.]

Subclause 12.2.2.1 [over.match.funcs.general] paragraph 4 specifies:

For implicit object member functions, the type of the implicit object parameter is where X is the class of which the function is a member and cv is the cv-qualification on the member function declaration.

Since a member of some class C is also a member of any class derived from C, this specification is unclear.

Proposed resolution (approved by CWG 2023-08-25):

Change in 12.2.2.1 [over.match.funcs.general] paragraph 4 as follows:

For implicit object member functions, the type of the implicit object parameter is where X is the class of which the function is a direct member and cv is the cv-qualification on the member function declaration.



2712. Simplify restrictions on built-in assignment operator candidates

Section: 12.2.2.3  [over.match.oper]     Status: DRWP     Submitter: Brian Bi     Date: 2023-03-24

[Accepted as a DR at the June, 2023 meeting.]

Subclause 12.2.2.3 [over.match.oper] paragraph 5 specifies:

For the built-in assignment operators, conversions of the left operand are restricted as follows:

The first bullet is redundant, because standard conversion sequences cannot bind "vq T&" (the type of the first parameter of a built-in assignment operator) to a temporary.

Proposed resolution (approved by CWG 2023-03-30):

Change in 12.2.2.3 [over.match.oper] paragraph 5 as follows:

For the first parameter of the built-in assignment operators, only standard conversion sequences (12.2.4.2.2 [over.ics.scs]) are considered. conversions of the left operand are restricted as follows:



2856. Copy-list-initialization with explicit default constructors

Section: 12.2.2.8  [over.match.list]     Status: DRWP     Submitter: Anoop Rana     Date: 2024-01-09

[Accepted as a DR at the March, 2024 meeting.]

(From submission #486.)

Consider:

  struct A {
     explicit A(int = 10);
     A() = default;   // converting constructor (11.4.8.2 [class.conv.ctor] paragraph 1)
  };

  A a = {};       // #1, copy-initialization
  
  int f(A);
  int x = f({});  // #2

  A b;            // #3

#1 and #2 are accepted by MSVC and EDG, but considered ambiguous by gcc and clang. #3 is rejected as ambiguous by all major implementations.

#1 is copy-list-initialization (9.4.5 [dcl.init.list] paragraph 1), and A has a default constructor, thus a is value-initialized (9.4.5 [dcl.init.list] bullet 3.4). The default constructors are user-provided, thus a is default-initialized (9.4.1 [dcl.init.general] bullet 8.1, 9.4.1 [dcl.init.general] bullet 7.1). Overload resolution then chooses a constructor according to 12.2.2.4 [over.match.ctor] paragraph 1:

... For copy-initialization (including default initialization in the context of copy-initialization), the candidate functions are all the converting constructors (11.4.8.2 [class.conv.ctor]) of that class. ...

Thus, the explicit constructor is not a candidate; #1 chooses the converting constructor.

In contrast, #2 uses the special rules for forming a list-initialization sequence (12.2.4.2.6 [over.ics.list] paragraph 7), which perform overload resolution according to 12.2.2.8 [over.match.list] paragraph 1, which considers all constructors for overload resolution (and makes the program ill-formed if an explicit constructor is chosen). For the example, overload resolution is ambiguous.

#3 performs default-initialization, and overload resolution then chooses a constructor according to 12.2.2.4 [over.match.ctor] paragraph 1:

... For direct-initialization or default-initialization that is not in the context of copy-initialization, the candidate functions are all the constructors of the class of the object being initialized. ...

In this case, the overload resolution is ambiguous.

Proposed resolution (approved by CWG 2024-02-16):

Change in 12.2.2.4 [over.match.ctor] paragraph 1 as follows:

When objects of class type are direct-initialized (9.4 [dcl.init]), copy-initialized from an expression of the same or a derived class type (9.4 [dcl.init]), or default-initialized (9.4 [dcl.init]), overload resolution selects the constructor. For direct-initialization or default-initialization that is not in the context of copy-initialization (including default-initialization in the context of copy-list-initialization), the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization (including default initialization in the context of copy-initialization) Otherwise, the candidate functions are all the converting constructors (11.4.8.2 [class.conv.ctor]) of that class. The argument list is the expression-list or assignment-expression of the initializer. For default-initialization in the context of copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.

CWG 2024-02-16

The fact that #2 considers all constructors was discussed (and established) in the C++11 timeframe when brace-initialization was first introduced. #1 should be consistent with that, even though the = is usually a clear sign that explicit constructors are not considered.




2628. Implicit deduction guides should propagate constraints

Section: 12.2.2.9  [over.match.class.deduct]     Status: DRWP     Submitter: Roy Jacobson     Date: 2022-09-11

[Accepted as a DR at the November, 2023 meeting.]

Consider:

template<class T> concept True = true;

template<class T> struct X {
  template<class U> requires True<T> X(T, U(&)[3]);
};
template<typename T, typename U> X(T, U(&)[3]) -> X<T>;
int arr3[3];
X z(3, arr3);     // #1

According to 12.2.2.9 [over.match.class.deduct] bullet 1.1, the requires-clause of the constructor is not propagated to the function template synthesized for the implicit deduction guide. Thus, instead of favoring the more-constrained implicit deduction guide per 12.2.4.1 [over.match.best.general] bullet 2.6, the user-declared deduction-guide is preferred per 12.2.4.1 [over.match.best.general] bullet 2.11.

Proposed resolution (approved by CWG 2023-10-20):

Change in 12.2.2.9 [over.match.class.deduct] bullet 1.1 as follows:




2714. Implicit deduction guides omit properties from the parameter-declaration-clause of a constructor

Section: 12.2.2.9  [over.match.class.deduct]     Status: DRWP     Submitter: Richard Smith     Date: 2017-02-13

[Accepted as a DR at the March, 2024 meeting.]

Subclause 12.2.2.9 [over.match.class.deduct] bullet 1.1.2 specifies:

However, this does not consider default arguments or variadic constructors.

Proposed resolution (approved by CWG 2023-04-28):

(This also resolves issue 2628.)

Change in 12.2.2.9 [over.match.class.deduct] paragraph 1 as follows:




2789. Overload resolution with implicit and explicit object member functions

Section: 12.2.4.1  [over.match.best.general]     Status: DRWP     Submitter: Corentin Jabot     Date: 2023-08-08

[Accepted as a DR at the November, 2023 meeting.]

Consider:

  template <typename T = int>
  struct S {
    constexpr void f();                      // #1
    constexpr void f(this S&) requires true; // #2
  };

  void test() {
    S<> s;
    s.f();                 // #3
  }

With the current rules, the call at #3 is ambiguous, even though #2 is more constrainted.

Proposed resolution (approved by CWG 2023-11-07):

Change in 12.2.4.1 [over.match.best.general] bullet 2.6 as follows:




2803. Overload resolution for reference binding of similar types

Section: 12.2.4.2.5  [over.ics.ref]     Status: DRWP     Submitter: Brian Bi     Date: 2023-06-14

[Accepted as a DR at the March, 2024 meeting.]

Consider:

  int foo(int*& r);       // #1
  int foo(const int* const& r); // #2

  int *p;
  int x = foo(p);

Both #1 and #2 perform direct reference binding; no qualification conversions are involved. Despite the lack of a rule, implementations prefer #1 over #2.

Proposed resolution (approved by CWG 2023-11-10):

  1. Change in 12.2.4.2.5 [over.ics.ref] paragraph 1 as follows:

    When a parameter of reference type "reference to cv T" binds directly (9.4.4 [dcl.init.ref]) to an argument expression, the implicit conversion sequence is the identity conversion, unless:
    • If the argument expression has a type that is a derived class of the parameter type, in which case the implicit conversion sequence is a derived-to-base conversion (12.2.4.2 [over.best.ics]).
    • Otherwise, if T is a function type, or if the type of the argument is possibly cv-qualified T, or if T is an array type of unknown bound with element type U and the argument has an array type of known bound whose element type is possibly cv-qualified U, the implicit conversion sequence is the identity conversion. [ Note: When T is a function type, the type of the argument may differ only by the presence of noexcept. -- end note]
    • Otherwise, the implicit conversion sequence is a qualification conversion.

    [Example 1: ... —end example]

    If the parameter binds directly to the result of applying a conversion function to the argument expression, the implicit conversion sequence is a user-defined conversion sequence (12.2.4.2.3 [over.ics.user]) whose second standard conversion sequence is either an identity conversion or, if the conversion function returns an entity of a type that is a derived class of the parameter type, a derived-to-base conversion determined by the above rules.
  2. Change in 12.2.4.3 [over.ics.rank] bullet 3.2.5 as follows:

    • S1 and S2 differ only in their qualification conversion (7.3.6 [conv.qual]) and yield similar types T1 and T2, respectively (where a standard conversion sequence that is a reference binding is considered to yield the cv-unqualified referenced type), where T1 can be converted to T2 by a qualification conversion and T2 are not the same type, and const T2 is reference-compatible with T1 (9.4.4 [dcl.init.ref]). [Example 5:
        int f(const volatile int *);
        int f(const int *);
        int i;
        int j = f(&i);  // calls f(const int*)
        int g(const int*);
        int g(const volatile int* const&);
        int* p;
        int k = g(p);          // calls g(const int*)
      
      -- end example] or, if not that,
  3. Change in 12.2.4.3 [over.ics.rank] bullet 3.2.6 as follows:

    • S1 and S2 include reference bindings bind "reference to T1" and "reference to T2", respectively (9.4.4 [dcl.init.ref]), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers where T1 and T2 are not the same type, and T2 is reference-compatible with T1. [Example 6: ...
         int h1(int (&)[]);
         int h1(int (&)[1]);
         int h2(void (&)());
         int h2(void (&)() noexcept);
         void g2() {
           int a[1];
           h1(a);            // calls h1(int (&)[1])
           extern void f2() noexcept;
           h2(f2);            // calls h2(void (&)() noexcept)
         }
      
      -- end example ]



1038. Overload resolution of &x.static_func

Section: 12.3  [over.over]     Status: DRWP     Submitter: Mike Miller     Date: 2010-03-02

[Accepted as a DR at the November, 2023 meeting.]

The Standard is not clear whether the following example is well-formed or not:

    struct S {
        static void f(int);
        static void f(double);
    };
    S s;
    void (*pf)(int) = &s.f;

According to 7.6.1.5 [expr.ref] bullet 4.3, you do function overload resolution to determine whether x.f is a static or non-static member function. 7.6.2.2 [expr.unary.op] paragraph 6 says that you can only take the address of an overloaded function in a context that determines the overload to be chosen, and the initialization of a function pointer is such a context (12.3 [over.over] paragraph 1) . The problem is that 12.3 [over.over] is phrased in terms of “an overloaded function name,” and this is a member access expression, not a name.

There is variability among implementations as to whether this example is accepted; some accept it as written, some only if the & is omitted, and some reject it in both forms.

Additional note (October, 2010):

A related question concerns an example like

    struct S {
        static void g(int*) {}
        static void g(long) {}
    } s;

    void foo() {
        (&s.g)(0L);
    }

Because the address occurs in a call context and not in one of the contexts mentioned in 12.3 [over.over] paragraph 1, the call expression in foo is presumably ill-formed. Contrast this with the similar example

    void g1(int*) {}
    void g1(long) {}

    void foo1() {
        (&g1)(0L);
    }

This call presumably is well-formed because 12.2.2.2 [over.match.call] applies to “the address of a set of overloaded functions.” (This was clearer in the wording prior to the resolution of issue 704: “...in this context using &F behaves the same as using the name F by itself.”) It's not clear that there's any reason to treat these two cases differently.

This question also bears on the original question of this issue, since the original wording of 12.2.2.2 [over.match.call] also described the case of an ordinary member function call like s.g(0L) as involving the “name” of the function, even though the postfix-expression is a member access expression and not a “name.” Perhaps the reference to “name” in 12.3 [over.over] should be similarly understood as applying to member access expressions?

Additional notes (February, 2023)

This appears to be resolved, in part by P1787R6 (accepted November, 2020).

CWG 2023-06-12

The clarifications in P1787R6 did not address the core of this issue, so it is kept open. In order to avoid confusion, a wording change to clarify the treatment (regardless of direction) seems advisable. CWG felt that the first and second examples should be treated consistently, and expressed a mild preferences towards making those ill-formed. It was noted that the reference to id-expression in 12.3 [over.over] can be understood to refer to the id-expression of a class member access.

This issue is resolved by issue 2725.




2777. Type of id-expression denoting a template parameter object

Section: 13.2  [temp.param]     Status: DRWP     Submitter: Jim X     Date: 2023-07-26

[Accepted as a DR at the March, 2024 meeting.]

The type of a template parameter object is specified to be const T in 13.2 [temp.param] paragraph 8:

An id-expression naming a non-type template-parameter of class type T denotes a static storage duration object of type const T, known as a template parameter object, whose value is that of the corresponding template argument after it has been converted to the type of the template-parameter. ...

However, it is unclear what the type of an id-expression is that refers to such an object. There is implementation divergence in the treatment of the following example:

  struct A {};

  template<auto a, auto x>  // also consider A a and const auto x
  int f() {
    decltype(a) b;          // also consider decltype((a))
    A& rb = b;
    decltype(x) y;
    int& ry = y;
  }

  int x = f<A{}, 42>();

Note that non-type template parameters are handled specially for decltype, as specified in 9.2.9.6 [dcl.type.decltype] paragraph 1:

For an expression E, the type denoted by decltype(E) is defined as follows:

Proposed resolution (approved by CWG 2024-03-01):

Change in 7.5.5.2 [expr.prim.id.unqual] paragraph 3 as follows:

... [Note 4: If the entity is a template parameter object for a template parameter of type T (13.2 [temp.param]), the type of the expression is const T. end note] In all other cases, the type of the expression is the type of the entity.



2450. braced-init-list as a template-argument

Section: 13.3  [temp.names]     Status: DRWP     Submitter: Marek Polacek     Date: 2019-01-07

[ Resolved by paper P2308R1 (Template parameter initialization), adopted in November, 2023. ]

Since non-type template parameters can now have class types, it would seem to make sense to allow a braced-init-list as a template-argument, but the grammar does not permit it.

See also issues 2049 and 2459.

Possible resolution:

The resolution also addresses issue 2049.

  1. Change in 7.3.1 [conv.general] paragraph 3 as follows:

    An expression or braced-init-list E can be implicitly converted to a type T if and only if the declaration T t = E; is well-formed, for some invented temporary variable t (9.4 [dcl.init]).
  2. Change in 7.7 [expr.const] paragraph 12 as follows:

    A converted constant expression of type T is an expression or braced-init-list, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only (12.1 [over.pre])
    • user-defined conversions,
    • ...
  3. Change in 13.3 [temp.names] paragraph 1 as follows:

    template-argument:
      constant-expression
      type-id
      id-expression
      braced-init-list
    
  4. Change in 13.4.2 [temp.arg.type] paragraph 4 as follows:

      template<auto n> struct B { /* ... */ };
      B<5> b1;        // OK, template parameter type is int
      B<'a'> b2;      // OK, template parameter type is char
      B<2.5> b3;      // OK, template parameter type is double
      B<void(0)> b4;  // error: template parameter type cannot be void
      template<int i> struct C { /* ... */ };
      C<{ 42 }> c1; // OK
    



2049. List initializer in non-type template default argument

Section: 13.4.3  [temp.arg.nontype]     Status: DRWP     Submitter: Ville Voutilainen     Date: 2014-11-20

[ Resolved by paper P2308R1 (Template parameter initialization), adopted in November, 2023. ]

According to 13.4.3 [temp.arg.nontype] paragraph 1,

A template-argument for a non-type template-parameter shall be a converted constant expression (7.7 [expr.const]) of the type of the template-parameter.

This does not permit an example like:

  template <int* x = {}> struct X {};

which seems inconsistent.

See also issues 2450 and 2459.




2459. Template parameter initialization

Section: 13.4.3  [temp.arg.nontype]     Status: DRWP     Submitter: Davis Herring     Date: 2020-09-21

[ Resolved by paper P2308R1 (Template parameter initialization), adopted in November, 2023. ]

The initialization of template parameters is severely underspecified. The only descriptions in the existing wording that apply are that the argument is “[converted] to the type of the template-parameter” (13.6 [temp.type] bullet 1.3) and, in 13.4.3 [temp.arg.nontype] paragraph 2,

A template-argument for a non-type template-parameter shall be a converted constant expression (7.7 [expr.const]) of the type of the template-parameter.

This omission is particularly important for template parameters of class type with lvalue template parameter objects whose addresses can be examined during construction. See also issue 2450.

Suggested resolution:

To avoid address-based paradoxes, template arguments for a template parameter of class type C that are not of that type or a derived type are converted to C to produce an exemplar. No restrictions are imposed on the conversion from a template argument to a constructor parameter, since explicit and list-initialization may already be used to limit conversions in a similar fashion. Template arguments that are of such a type are used directly as the exemplar (potentially after a materialization conversion); the effect is as if the template parameter were of type const C& (except that temporaries are allowed). (In the latter case, we must impose some restrictions on glvalue template parameters to interpret them.) Each exemplar is used to copy-initialize the template parameter object to which it is (to be) template-argument-equivalent; the initialization is required to produce a template-argument-equivalent value. The multiple initializations of the template parameter object are (required to be) all equivalent and produce no side effects, so it is unobservable which happen.




2697. Deduction guides using abbreviated function syntax

Section: 13.7.2.3  [temp.deduct.guide]     Status: DRWP     Submitter: CWG     Date: 2023-02-11     Liaison: EWG

[Accepted as a DR at the June, 2023 meeting.]

It is unclear whether deduction guides can be expressed using abbreviated function syntax. Subclause 13.7.2.3 [temp.deduct.guide] paragraph 3 refers to the restrictions of a function's parameter-declaration-clause:

The same restrictions apply to the parameter-declaration-clause of a deduction guide as in a function declaration (9.3.4.6 [dcl.fct]). ...

However, that subclause is silent on the meaning of abbreviated function syntax when used for deduction guides. Furthermore, 9.3.4.6 [dcl.fct] paragraph 22 explicitly restricts the definition to function templates, which deduction guides are not:

An abbreviated function template is a function declaration that has one or more generic parameter type placeholders (9.2.9.7 [dcl.spec.auto]). ...

Arguably, the lack of template parameter names in abbreviated function syntax makes it less suitable to specifiy deduction guides.

CWG 2023-02-11

CWG solicits input from EWG whether abbreviated function syntax is intended to be used for deduction guides. See cplusplus/papers#1465.

EWG 2023-05-11

CWG should clarify that abbreviated function syntax should not be permitted in deduction guides.

Proposed resolution (approved by CWG 2023-05-12):

Change in 13.7.2.3 [temp.deduct.guide] paragraph 3 as follows:

The same restrictions apply to the parameter-declaration-clause of a deduction guide as in a function declaration (9.3.4.6 [dcl.fct]), except that a generic parameter type placeholder (9.2.9.7 [dcl.spec.auto]) shall not appear in the parameter-declaration-clause of a deduction guide. The simple-template-id shall name a class template specialization. The template-name shall be the same identifier as the template-name of the simple-template-id. A deduction-guide shall inhabit the scope to which the corresponding class template belongs and, for a member class template, have the same access. Two deduction guide declarations for the same class template shall not have equivalent parameter-declaration-clauses if either is reachable from the other.



2707. Deduction guides cannot have a trailing requires-clause

Section: 13.7.2.3  [temp.deduct.guide]     Status: DRWP     Submitter: Richard Smith     Date: 2020-02-26

[Accepted as a DR at the March, 2024 meeting.]

The grammar for deduction-guide does not, but should, allow a trailing requires-clause:

deduction-guide:
   explicit-specifieropt template-name ( parameter-declaration-clause ) -> simple-template-id ;

Proposed resolution (approved by CWG 2023-11-11):

Change the grammar in 13.7.2.3 [temp.deduct.guide] paragraph 1 as follows:

deduction-guide:
   explicit-specifieropt template-name ( parameter-declaration-clause ) requires-clauseopt -> simple-template-id ;



2717. Pack expansion for alignment-specifier

Section: 13.7.4  [temp.variadic]     Status: DRWP     Submitter: Jim X     Date: 2023-04-11

[Accepted as a DR at the June, 2023 meeting.]

Subclause 13.7.4 [temp.variadic] paragraph 11 specifies:

The instantiation of any other pack expansion produces a list of elements E1, E2, ... , EN.

Consider this example:

  template<class ...T>
  struct Align{
   alignas(T...) unsigned char buffer[128];
  };
  Align<int, short> a;

The pack expansion of the alignment-specifier yields alignas(int), alignas(short), an ill-formed list per the grammar in 9.12.1 [dcl.attr.grammar].

Proposed resolution (approved by CWG 2023-04-28):

Insert a new paragraph after 13.7.4 [temp.variadic] paragraph 9 as follows:

The instantiation of a sizeof... expression (7.6.2.5 [expr.sizeof]) produces an integral constant with value N.

The instantiation of an alignment-specifier with an ellipsis produces E1 E2 ... EN.




2720. Template validity rules for templated entities and alias templates

Section: 13.8.1  [temp.res.general]     Status: DRWP     Submitter: Richard Smith     Date: 2023-03-29

[Accepted as a DR at the June, 2023 meeting.]

Subclause 13.8.1 [temp.res.general] paragraph 6 specifies rules to determine when a template is valid, but the specification is in terms of "instantiation". However, some kinds of templates (namely alias templates) are not instantiated.

Further, the rule discusses only templates, but should apply to any templated entity.

Proposed resolution (approved by CWG 2023-05-12):

Change in 13.8.1 [temp.res.general] paragraph 6 as follows:

The validity of a template templated entity may be checked prior to any instantiation. [Note 3: Knowing which names are type names allows the syntax of every template to be checked in this way. —end note]

The program is ill-formed, no diagnostic required, if:




2746. Checking of default template arguments

Section: 13.8.1  [temp.res.general]     Status: DRWP     Submitter: Shafik Yaghmour     Date: 2022-12-13

[Accepted as a DR at the March, 2024 meeting.]

Consider:

  static int x = 1;
  template<auto y = x> void f() {}

Is the definition of f well-formed? Since x is not a constant expression, any use of the default template argument is ill-formed, but for example f<5>() does not actually use it.

Are implementations allowed or required to reject this situation, even if the template is never instantiated? If the default template argument is dependent, checking may need to be deferred to instantiations in any case.

Proposed resolution (approved by CWG 2024-03-01):

Change in 13.8.1 [temp.res.general] paragraph 6 as follows:

The validity of a template may be checked prior to any instantiation. [Note : ... —end note]

The program is ill-formed, no diagnostic required, if:




2806. Make a type-requirement a type-only context

Section: 13.8.1  [temp.res.general]     Status: DRWP     Submitter: Barry Revzin     Date: 2023-10-10

[Accepted as a DR at the November, 2023 meeting.]

Consider:

  template <typename T>
  concept C = requires {
    typename T::type<void>;   // template required?
  };

There is implementation divergence: gcc accepts, clang and MSVC reject.

A type-requirement ought to be a type-only context.

Proposed resolution (approved by CWG 2023-10-20):

  1. Change in 7.5.8.3 [expr.prim.req.type] paragraph 1 as follows:

    A type-requirement asserts the validity of a type. The component names of a type-requirement are those of its nested-name-specifier (if any) and type-name. [Note 1: The enclosing requires-expression will evaluate to false if substitution of template arguments fails. —end note]
  2. Change in 13.8.1 [temp.res.general] paragraph 4 as follows:

    A qualified or unqualified name is said to be in a type-only context if it is the terminal name of
    • a typename-specifier, type-requirement, nested-name-specifier, elaborated-type-specifier, class-or-decltype, or
    • ...



2810. Requiring the absence of diagnostics for templates

Section: 13.8.1  [temp.res.general]     Status: DRWP     Submitter: Andrey Erokhin     Date: 2022-01-23

[Accepted as a DR at the March, 2024 meeting.]

Subclause 13.8.1 [temp.res.general] paragraph 6 specifies:

The program is ill-formed, no diagnostic required, if: ... Otherwise, no diagnostic shall be issued for a template for which a valid specialization can be generated.

The "Otherwise..." part is misleading; it could be interpreted to mean that warnings must be suppressed in templates.

Proposed resolution (approved by CWG 2023-12-01):

Change in 13.8.1 [temp.res.general] paragraph 6 as follows:

Otherwise, no diagnostic shall be issued for a template for which a valid specialization can be generated.



2600. Type dependency of placeholder types

Section: 13.8.3.3  [temp.dep.expr]     Status: DRWP     Submitter: Hubert Tong     Date: 2022-06-18

[Accepted as a DR at the November, 2023 meeting.]

Subclause 13.8.3.2 [temp.dep.type] paragraph 7 has a list of types considered to be dependent. This list covers placeholder types only insofar as it has an entry about decltype(expression). Subclause 13.8.3.3 [temp.dep.expr] paragraph 3 has a list of expression forms not considered dependent unless specific types named by the expressions are dependent. This list includes forms where placeholder types are allowed. For example, the wording does not say that the new-expression at #1 (below) is dependent, but it ought to be:

  template <typename T> struct A { A(bool, T); };

  void g(...);

  template <typename T>
  auto f(T t) { return g(new A(t, 0)); }  // #1

  int g(A<int> *);
  int h() { return f<void *>(nullptr); }

Some implementation even treats an obviously non-dependent case as dependent:

  template <typename T, typename U> struct A { A(T, U); };

  void g(...); // #1

  template <typename T>
  auto f() { return g(new A(0, 0)); } // #1 or #2?

  int g(A<int, int> *); // #2
  void h() { return f<void *>(); }

A similar example that is non-dependent:

  template <typename T, typename U = T> struct A { A(T, U); };

  void g(...);

  template <typename T>
  auto f() { return g(new A(0, 0)); }

  int g(A<int> *);
  void h() { return f<void *>(); }

And another non-dependent one:

  template <typename T, typename U = T> struct A { A(T); };

  void g(...);

  template <typename T>
  auto f() { return g(new A(0)); }

  int g(A<int> *);
  void h() { return f<void *>(); }

And here is an example that is dependent:

  template<class T>
  struct S {
   template<class U = T> struct A { A(int); };

   auto f() { return new A(0); } // dependent return type
  };

Proposed resolution (November, 2022) [SUPERSEDED]:

  1. Change in 7.6.2.8 [expr.new] paragraph 2 as follows:

    If a placeholder type (9.2.9.7 [dcl.spec.auto]) or a placeholder for a deduced class type (9.2.9.8 [dcl.type.class.deduct]) appears in the type-specifier-seq of a new-type-id or type-id of a new-expression, the allocated type is deduced as follows: Let init be the new-initializer , if any, and T be the new-type-id or type-id of the new-expression, then the allocated type is the type deduced for the variable x in the invented declaration (9.2.9.7 [dcl.spec.auto]):
    T x init ;
    
  2. Insert new paragraphs before 13.8.3.2 [temp.dep.type] paragraph 7 and change the latter as follows:

    An initializer is dependent if any constituent expression (6.9.1 [intro.execution]) of the initializer is type-dependent. A placeholder type (9.2.9.7.1 [dcl.spec.auto.general]) is dependent if it designates a type deduced from a dependent initializer.

    A placeholder for a deduced class type (9.2.9.8 [dcl.type.class.deduct]) is dependent if

    • it has a dependent initializer or
    • any default template-argument of the primary class template named by the placeholder is dependent when considered in the scope enclosing the primary class template.

    A type is dependent if it is

    • ...
    • a function type whose exception specification is value-dependent,
    • denoted by a dependent placeholder type,
    • denoted by a dependent placeholder for a deduced class type,
    • ...

Proposed resolution (approved by CWG 2023-06-12):

  1. Change in 7.6.2.8 [expr.new] paragraph 2 as follows:

    If a placeholder type (9.2.9.7 [dcl.spec.auto]) or a placeholder for a deduced class type (9.2.9.8 [dcl.type.class.deduct]) appears in the type-specifier-seq of a new-type-id or type-id of a new-expression, the allocated type is deduced as follows: Let init be the new-initializer , if any, and T be the new-type-id or type-id of the new-expression, then the allocated type is the type deduced for the variable x in the invented declaration (9.2.9.7 [dcl.spec.auto]):
    T x init ;
    
  2. Insert new paragraphs before 13.8.3.2 [temp.dep.type] paragraph 7 and change the latter as follows:

    An initializer is dependent if any constituent expression (6.9.1 [intro.execution]) of the initializer is type-dependent. A placeholder type (9.2.9.7.1 [dcl.spec.auto.general]) is dependent if it designates a type deduced from a dependent initializer.

    A placeholder for a deduced class type (9.2.9.8 [dcl.type.class.deduct]) is dependent if

    • it has a dependent initializer, or
    • it refers to an alias template that is a member of the current instantiation and whose defining-type-id is dependent after class template argument deduction (12.2.2.9 [over.match.class.deduct]) and substitution (13.7.8 [temp.alias]).

    [ Example:

      template<class T, class V>
      struct S { S(T); };
    
      template<class U>
      struct A {
        template<class T> using X = S<T, U>;
        template<class T> using Y = S<T, int>;
        void f() {
          new X(1);    // dependent
          new Y(1);    // not dependent
        }
      };
    

    -- end example ]

    A type is dependent if it is

    • ...
    • a function type whose exception specification is value-dependent,
    • denoted by a dependent placeholder type,
    • denoted by a dependent placeholder for a deduced class type,
    • ...



2785. Type-dependence of requires-expression

Section: 13.8.3.3  [temp.dep.expr]     Status: DRWP     Submitter: CWG     Date: 2023-07-17

[Accepted as a DR at the November, 2023 meeting.]

(Split off from issue 2774.)

Subclause 13.8.3.3 [temp.dep.expr] is lacking specifiation about the type-dependence of requires-expressions.

Proposed resolution (approved by CWG 2023-08-25):

Change in 13.8.3.3 [temp.dep.expr] paragraph 4 as follows:

Expressions of the following forms are never type-dependent (because the type of the expression cannot be dependent):
  ...
  noexcept ( expression )
  requires-expression



2848. Omitting an empty template argument list for explicit instantiation

Section: 13.9.3  [temp.explicit]     Status: DRWP     Submitter: Anoop Rana     Date: 2024-01-15

[Accepted as a DR at the March, 2024 meeting.]

(From submission #489.)

Subclause 13.9.3 [temp.explicit] paragraph 8 specifies:

A trailing template-argument can be left unspecified in an explicit instantiation of a function template specialization or of a member function template specialization provided it can be deduced (13.10.3.7 [temp.deduct.decl]). If all template arguments can be deduced, the empty template argument list <> may be omitted. [Example 3:
  template<class T> class Array { /* ... */ };
  template<class T> void sort(Array<T>& v) { /* ... */ }

  // instantiate sort(Array<int>&) -- template-argument deduced
  template void sort<>(Array<int>&);
end example]

This paragraph is redundant with a more general provision on explicitly specifying template arguments in 13.10.2 [temp.arg.explicit] paragraph 4:

Trailing template arguments that can be deduced (13.10.3 [temp.deduct]) or obtained from default template-arguments may be omitted from the list of explicit template-arguments. [Note 1: A trailing template parameter pack (13.7.4 [temp.variadic]) not otherwise deduced will be deduced as an empty sequence of template arguments. —end note] If all of the template arguments can be deduced or obtained from default template-arguments, they may all be omitted; in this case, the empty template argument list <> itself may also be omitted. ...

A similar duplication is in 13.9.4 [temp.expl.spec] paragraph 10.

Proposed resolution (approved by CWG 2024-02-16):

  1. Change the example in 13.9.3 [temp.explicit] paragraph 4 as follows:

      ...
      template<class T> void sort(Array<T>& v) { /* ... */ }
      template void sort(Array<char>&);  // argument is deduced here (13.10.2 [temp.arg.explicit])
      ...
    
  2. Remove 13.9.3 [temp.explicit] paragraph 8:

    A trailing template-argument can be left unspecified in an explicit instantiation of a function template specialization or of a member function template specialization provided it can be deduced (13.10.3.7 [temp.deduct.decl]). If all template arguments can be deduced, the empty template argument list <> may be omitted. [Example 3:
      template<class T> class Array { /* ... */ };
      template<class T> void sort(Array<T>& v) { /* ... */ }
    
      // instantiate sort(Array<int>&) -- template-argument deduced
      template void sort<>(Array<int>&);
    
    end example]
  3. Change the example in 13.9.4 [temp.expl.spec] paragraph 1 as follows:

    ... [ Example:
      template<class T> class stream;
      template<> class stream<char> { /* ... */ }; // #1
    
      template<class T> class Array { /* ... */ };
      template<class T> void sort(Array<T>& v) { /* ... */ }
    
      template<> void sort<int>(Array<int>&);   // #2
      template<> void sort<char*>(Array<char*>&);   // #3 template argument is deduced (13.10.2 [temp.arg.explicit])
      ...
    
    Given these declarations, stream<char> #1 will be used as the definition of streams of chars; other streams will be handled by class template specializations instantiated from the class template. Similarly, #2 will be used as the sort function for arguments of type Array<int> and #3 sort<char*> will be used as the sort function for arguments of type Array<char*>; other Array types will be sorted by functions generated from the function template. —end example]
  4. Remove 13.9.4 [temp.expl.spec] paragraph 10:

    A trailing template-argument can be left unspecified in the template-id naming an explicit function template specialization provided it can be deduced (13.10.3.7 [temp.deduct.decl]). [Example 6:
      template<class T> class Array { /* ... */ };
      template<class T> void sort(Array<T>& v);
    
      // explicit specialization for sort(Array<int>&)
      // with deduced template-argument of type int
      template<> void sort(Array<int>&);
    
    end example]



2054. Missing description of class SFINAE

Section: 13.10.3  [temp.deduct]     Status: DRWP     Submitter: Ville Voutilainen     Date: 2014-12-07

[Accepted as a DR at the November, 2023 meeting.]

Presumably something like the following should be well-formed, where a deduction failure in a partial specialization is handled as a SFINAE case as it is with function templates and not a hard error:

  template <class T, class U> struct X   {
    typedef char member;
  };

  template<class T> struct X<T,
   typename enable_if<(sizeof(T)>sizeof(
     float)), float>::type>
  {
    typedef long long member;
  };

  int main() {
    cout << sizeof(X<double, float>::member);
  }

However, this does not appear to be described anywhere in the Standard.

Additional notes (January, 2023)

The section on SFINAE (13.10.3.1 [temp.deduct.general] paragraph 8) is not specific to function templates, and 13.7.6.2 [temp.spec.partial.match] paragraph 2 hands off the "matching" determination for partial specializations to 13.10.3 [temp.deduct] in general. However, the definition of deduction substitution loci in 13.10.3.1 [temp.deduct.general] paragraph 7 does not account for the template argument list of a partial specialization.

Proposed resolution (approved by CWG 2023-11-08):

Change in 13.10.3.1 [temp.deduct.general] paragraph 7 as follows:

The deduction substitution loci are The substitution occurs in all types and expressions that are used in the deduction substitution loci. ...



2672. Lambda body SFINAE is still required, contrary to intent and note

Section: 13.10.3.1  [temp.deduct.general]     Status: DRWP     Submitter: Richard Smith     Date: 2022-09-30

[Accepted as a DR at the November, 2023 meeting.]

Subclause 13.10.3.1 [temp.deduct.general] paragraph 9 specifies:

A lambda-expression appearing in a function type or a template parameter is not considered part of the immediate context for the purposes of template argument deduction. [Note 7: The intent is to avoid requiring implementations to deal with substitution failure involving arbitrary statements. ... -- end note ]

However, the intent of the note is not satisfied by the normative rule, because a lambda-expression appearing in a requires-expression has the same concerns as one in a function signature.

Suggested resolution: Change the rule to say that substitution into the body of a lambda is never in the immediate context of substitution into the lambda-expression and move the rule somewhere more general.

Possible resolution (reviewed by CWG 2023-08-25) [SUPERSEDED]:

  1. Change in 13.5.2.3 [temp.constr.atomic] paragraph 3 as follows:

    To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression in the immediate context of the atomic constraint (13.10.3.1 [temp.deduct.general]), the constraint is not satisfied. Otherwise, the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is performed if necessary, and E shall be a constant expression of type bool. The constraint is satisfied if and only if evaluation of E results in true.
  2. Change in 13.10.3.1 [temp.deduct.general] paragraph 9 as follows:

    A lambda-expression appearing in a function type or a template parameter Substituting into the body of a lambda-expression is not considered part of never in the immediate context for the purposes of template argument deduction of substitution into the lambda-expression. [Note 7: The intent is to avoid requiring implementations to deal with substitution failure involving arbitrary statements.

Proposed resolution (approved by CWG 2023-11-09):

  1. Change in 7.5.8.1 [expr.prim.req.general] paragraph 5 as follows:

    The substitution of template arguments into a requires-expression may can result in the formation of invalid types or expressions in the immediate context of its requirements (13.10.3.1 [temp.deduct.general]) or the violation of the semantic constraints of those requirements. In such cases, the requires-expression evaluates to false; it does not cause the program to be ill-formed. The substitution and semantic constraint checking proceeds in lexical order and stops when a condition that determines the result of the requires-expression is encountered. If substitution (if any) and semantic constraint checking succeed, the requires-expression evaluates to true.
  2. Change in 13.5.2.3 [temp.constr.atomic] paragraph 3 as follows:

    To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression in the immediate context of the atomic constraint (13.10.3.1 [temp.deduct.general]), the constraint is not satisfied. Otherwise, the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is performed if necessary, and E shall be a constant expression of type bool. The constraint is satisfied if and only if evaluation of E results in true.
  3. Change in 13.10.3.1 [temp.deduct.general] paragraph 9 as follows:

    A lambda-expression appearing in a function type or a template parameter is not considered part of the immediate context for the purposes of template argument deduction. When substituting into a lambda-expression, substitution into its body is not in the immediate context. [Note 7: The intent is to avoid requiring implementations to deal with substitution failure involving arbitrary statements.



2775. Unclear argument type for copy of exception object

Section: 14.2  [except.throw]     Status: DRWP     Submitter: Jiang An     Date: 2023-05-31

[Accepted as a DR at the March, 2024 meeting.]

Subclause 14.2 [except.throw] paragraph 5 specifies:

When the thrown object is a class object, the constructor selected for the copy-initialization as well as the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible, even if the copy/move operation is elided (11.9.6 [class.copy.elision]). The destructor is potentially invoked (11.4.7 [class.dtor]).

This provision is for capturing a copy constructor for implementations not using reference-counted std::exception_ptrs, but that ought to be described separately from the "thrown object", which can be interpreted as the operand of the throw-expression.

Proposed resolution (approved by CWG 2023-09-15) [SUPERSEDED]:

Change in 14.2 [except.throw] paragraph 5 as follows:

When the thrown object is a class object, the constructor selected for the copy-initialization as well as the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible, even if the copy/move operation is elided (11.9.6 [class.copy.elision]). Let T denote the type of the exception object. Copy-initialization of an object of type T from an lvalue of type const T in a context unrelated to any class shall be well-formed. If T is a class type, The the destructor of T is potentially invoked (11.4.7 [class.dtor]).

CWG 2023-11-09

The drafting should also consider odr-use of the constructor potentially invoked for the copy-initialization.

Proposed resolution (approved by CWG 2023-11-10) [SUPERSEDED]:

  1. Change in 6.3 [basic.def.odr] paragraph 9 as follows:

    An assignment operator function in a class is odr-used by an implicitly-defined copy assignment or move assignment function for another class as specified in 11.4.6 [class.copy.assign]. A constructor for a class is odr-used as specified in 9.4 [dcl.init] and when selected for the potential copy-initialization as specified in 14.2 [except.throw]. A destructor for a class is odr-used if it is potentially invoked (11.4.7 [class.dtor]).
  2. Change in 14.2 [except.throw] paragraph 5 as follows:

    When the thrown object is a class object, the constructor selected for the copy-initialization as well as the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible, even if the copy/move operation is elided (11.9.6 [class.copy.elision]). Let T denote the type of the exception object. Copy-initialization of an object of type T from an lvalue of type const T in a context unrelated to T shall be well-formed. If T is a class type, The the destructor of T is potentially invoked (11.4.7 [class.dtor]).

Proposed resolution (approved by CWG 2024-03-20):

  1. Change in 14.2 [except.throw] paragraph 5 as follows:

    When the thrown object is a class object, the constructor selected for the copy-initialization as well as the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible, even if the copy/move operation is elided (11.9.6 [class.copy.elision]). Let T denote the type of the exception object. Copy-initialization of an object of type T from an lvalue of type const T in a context unrelated to T shall be well-formed. If T is a class type, the selected constructor is odr-used (6.3 [basic.def.odr]) and The the destructor of T is potentially invoked (11.4.7 [class.dtor]).



2854. Storage duration of exception objects

Section: 14.2  [except.throw]     Status: DRWP     Submitter: Jiang An     Date: 2024-01-22

[Accepted as a DR at the March, 2024 meeting.]

(From submission #492.)

According to 14.2 [except.throw] paragraph 3, the exception object is a temporary object. But none of the possible storage durations of temporary objects matches the behavior of exception objects.

Proposed resolution (approved by CWG 2024-02-16):

  1. Change in 6.7.7 [class.temporary] paragraph 1 as follows:

    Temporary objects are created
    • when a prvalue is converted to an xvalue (7.3.5 [conv.rval]), and
    • when needed by the implementation to pass or return an object of trivially copyable type (see below), and.
    • when throwing an exception (14.2 [except.throw]). [Note 1: The lifetime of exception objects is described in 14.2 [except.throw]. —end note]
    ...
  2. Change in 14.2 [except.throw] paragraph 3 as follows:

    Throwing an exception initializes a temporary an object with dynamic storage duration, called the exception object. If the type of the exception object would be an incomplete type (6.8.1 [basic.types.general]), an abstract class type (11.7.4 [class.abstract]), or a pointer to an incomplete type other than cv void (6.8.4 [basic.compound]) the program is ill-formed.



2772. Missing Annex C entry for linkage effects of linkage-specification

Section: C.6.4  [diff.cpp03.dcl.dcl]     Status: DRWP     Submitter: Hubert Tong     Date: 2023-07-15

[Accepted as a DR at the November, 2023 meeting.]

With C++11, anonymous namespaces changed from external linkage (with a unique namespace name) to internal linkage. That implies that extern "C", which affects names with external linkage only, no longer has an effect inside anonymous namespaces.

However, a corresponding Annex C entry is missing.

Proposed resolution (approved by CWG 2023-09-15):

Add a new paragraph in C.6.4 [diff.cpp03.dcl.dcl] as follows:

Affected subclause: 9.11 [dcl.link]
Change: Names declared in an anonymous namespace changed from external linkage to internal linkage; language linkage applies to names with external linkage only.
Rationale: Alignment with user expectations.
Effect on original feature: Valid C++ 2003 code may violate the one-definition rule (6.3 [basic.def.odr]) in this revision of C++. For example:
  namespace { extern "C" { extern int x; } }  // #1, previously external linkage and C language linkage, now internal linkage and C++ language linkage
  namespace A { extern "C" int x = 42; }      // #2, external linkage and C language linkage
  int main(void) { return x; }
This code is valid in C++ 2003, but #2 is not a definition for #1 in this revision of C++, violating the one-definition rule.





Issues with "WP" Status




Issues with "CD1" Status


663. Valid Cyrillic identifier characters

Section: _N2691_.E  [extendid]     Status: CD1     Submitter: Steve Clamage     Date: 30 November 2007

[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).




122. template-ids as unqualified-ids

Section: _N4567_.5.1.1  [expr.prim.general]     Status: CD1     Submitter: Mike Miller     Date: 3 June 1999

[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.




125. Ambiguity in friend declaration syntax

Section: _N4567_.5.1.1  [expr.prim.general]     Status: CD1     Submitter: Martin von Loewis     Date: 7 June 1999

[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::B
init-declarator = ::C()
simple-declaration = friend A::B ::C();
or
simple-type-specifier = A
init-declarator = ::B::C()
simple-declaration = friend A ::B::C();
Since it is a friend declaration, the init-declarator may be qualified, and start with a global scope.

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,

  1. 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 ::.
  2. 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]

  3. 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.




466. cv-qualifiers on pseudo-destructor type

Section: _N4778_.7.6.1.4  [expr.pseudo]     Status: CD1     Submitter: Mark Mitchell     Date: 18 Mar 2004

[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.

See also issues 305 and 399.

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 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.



141. Non-member function templates in member access expressions

Section: _N4868_.6.5.6  [basic.lookup.classref]     Status: CD1     Submitter: fvali     Date: 31 July 1999

[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 class or function template...



305. Name lookup in destructor call

Section: _N4868_.6.5.6  [basic.lookup.classref]     Status: CD1     Submitter: Mark Mitchell     Date: 19 May 2001

[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):

  1. 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...
  2. 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. and If 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 up in the context of the entire postfix-expression and in the scope of class C. The type-name shall refer to a class-name. If type-name is found in both contexts, the name shall refer to the same class type. If the type of the object expression is of scalar type, the type-name is looked up in the scope of the complete postfix-expression. 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.]




381. Incorrect example of base class member lookup

Section: _N4868_.6.5.6  [basic.lookup.classref]     Status: CD1     Submitter: Steve Adamczyk     Date: 8 Nov 2002

[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.




414. Multiple types found on destructor lookup

Section: _N4868_.6.5.6  [basic.lookup.classref]     Status: CD1     Submitter: John Spicer     Date: 1 May 2003

[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.




452. Wording nit on description of this

Section: _N4868_.11.4.3.2  [class.this]     Status: CD1     Submitter: Gennaro Prota     Date: 8 Jan 2004

[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):

  1. Change 9.5 [dcl.fct.def] paragraph 1 as indicated:

  2. Function definitions have the form

    An informal reference to the body of a function should be interpreted as a reference to the nonterminal function-body.

  3. Change the definition of function-try-block in Clause 14 [except] paragraph 1:

  4. Change 6.4.7 [basic.scope.class] paragraph 1, point 1, as indicated:

  5. The potential scope of a name declared in a class consists not only of the declarative region following the name's point of declaration, but also of all function bodies, bodies and default arguments, and constructor ctor-initializers in that class (including such things in nested classes).
  6. Change 6.4.7 [basic.scope.class] paragraph 1, point 5, as indicated:

  7. 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, 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:...
  8. Change footnote 32 in 6.5.3 [basic.lookup.unqual] paragraph 8 as indicated:

  9. That is, an unqualified name that occurs, for instance, in a type or default argument expression in the parameter-declaration-clause, parameter-declaration-clause or in the function body, or in an expression of a mem-initializer in a constructor definition.
  10. Change _N4567_.5.1.1 [expr.prim.general] paragraph 3 as indicated:

  11. ...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])...
  12. Change 11.4 [class.mem] paragraph 2 as indicated:

  13. ...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)...
  14. Change 11.4 [class.mem] paragraph 9 as indicated:

  15. 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]), or or 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]).
  16. Change the note in 11.4.2 [class.mfct] paragraph 5 as indicated:

  17. [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]), or or 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]
  18. Change 11.4.3 [class.mfct.non.static] paragraph 1 as indicated:

  19. ...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.

  20. Change 11.4.3 [class.mfct.non.static] paragraph 3 as indicated:

  21. When an id-expression (_N4567_.5.1.1 [expr.prim.general]) that is not part of a class member access syntax (7.6.1.5 [expr.ref]) and not used to form a pointer to member (7.6.2.2 [expr.unary.op]) is used in the body of a non-static member function of class X or used in the mem-initializer for a constructor of class X, if name lookup (6.5.3 [basic.lookup.unqual]) resolves the name in the id-expression to a non-static non-type member 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...
  22. Change 11.4.5 [class.ctor] paragraph 7 as indicated:

  23. ...The implicitly-defined default constructor performs the set of initializations of the class that would be performed by a user-written default constructor for that class with an empty mem-initializer-list no ctor-initializer (11.9.3 [class.base.init]) and an empty function body compound-statement...
  24. Change 11.9.3 [class.base.init] paragraph 4 as indicated:

  25. ...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.
  26. Change the last bullet of 11.9.3 [class.base.init] paragraph 5 as indicated:

  27. Change Clause 14 [except] paragraph 4 as indicated:

  28. A function-try-block associates a handler-seq with the ctor-initializer, if present, and the function-body compound-statement. An exception thrown during the execution of the initializer expressions in the ctor-initializer or during the execution of the function-body compound-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)
        {
            // constructor function body statements
        }
        catch (...)
        {
            // handles exceptions thrown from the ctor-initializer
            // and from the constructor function body statements
        }
    

    end example]

  29. Change 14.3 [except.ctor] paragraph 2 as indicated:

  30. 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 the compound-statement, compound-statement or ctor-initializer, or function-body following the try keyword was most recently entered by the thread of control and not yet exited.



387. Errors in example in 14.6.5

Section: _N4868_.13.8.6  [temp.inject]     Status: CD1     Submitter: Aleksey Gurtovoy     Date: 27 Oct 2002

[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

  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
  }
by
  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.




357. Definition of signature should include name

Section: Clause 3  [intro.defs]     Status: CD1     Submitter: Steve Clamage     Date: 26 May 2002

[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):

  1. Replace Clause 3 [intro.defs] “signature” with the following:

  2. 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]
  3. Delete paragraph 3 and replace the first sentence of 13.7.7.2 [temp.over.link] as follows:

  4. 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 list is defined in Clause 3 [intro.defs]. The names of the template parameters are significant...

(See also issue 537.)




537. Definition of “signature”

Section: Clause 3  [intro.defs]     Status: CD1     Submitter: Daveed Vandevoorde     Date: 12 October 2005

[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.




362. Order of initialization in instantiation units

Section: 5.2  [lex.phases]     Status: CD1     Submitter: Mark Mitchell     Date: 2 July 2002

[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 specialized Explicit specializations and definitions of class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) 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.



558. Excluded characters in universal character names

Section: 5.3  [lex.charset]     Status: CD1     Submitter: Daveed Vandevoorde     Date: 8 February 2006

[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.




505. Conditionally-supported behavior for unknown character escapes

Section: 5.13.3  [lex.ccon]     Status: CD1     Submitter: Mike Miller     Date: 14 Apr 2005

[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.



309. Linkage of entities whose names are not simply identifiers, in introduction

Section: 6.1  [basic.pre]     Status: CD1     Submitter: Mike Miller     Date: 17 Sep 2001

[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.




485. What is a “name”?

Section: 6.1  [basic.pre]     Status: CD1     Submitter: Gabriel Dos Reis     Date: 9 Nov 2004

[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

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):

  1. Change 6.1 [basic.pre] paragraphs 3-8 as follows:

    1. 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.

    2. A name is a use of an identifier identifier (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.

    3. 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]).

    4. A variable is introduced by the declaration of an object. The variable's name denotes the object.

    5. 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]).

    6. Two names are the same if

      • they are identifiers identifiers composed of the same character sequence; or

      • they are the names of overloaded operator functions operator-function-ids formed with the same operator; or

      • they are the names of user-defined conversion functions conversion-function-ids formed with the same type., or

      • they are template-ids that refer to the same class or function (13.6 [temp.type]).

    7. An identifier A 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 the identifier name specified in each translation unit.

  2. 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 the identifier declarator-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.)




261. When is a deallocation function "used?"

Section: 6.3  [basic.def.odr]     Status: CD1     Submitter: Mike Miller     Date: 7 Nov 2000

[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.]




289. Incomplete list of contexts requiring a complete type

Section: 6.3  [basic.def.odr]     Status: CD1     Submitter: Mike Miller     Date: 25 May 2001

[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:




433. Do elaborated type specifiers in templates inject into enclosing namespace scope?

Section: 6.4.2  [basic.scope.pdecl]     Status: CD1     Submitter: Daveed Vandevoorde     Date: 2 September 2003

[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 form
   class-key identifier
if the elaborated-type-specifier is used in the decl-specifier-seq or parameter-declaration-clause of a function defined in namespace scope, the identifier is declared as a class-name in the namespace that contains the declaration; otherwise, except as a friend declaration, the identifier is declared in the smallest non-class, non-function-prototype scope that contains the declaration. [Note: These rules also apply within templates.] [Note: ...]



432. Is injected class name visible in base class specifier list?

Section: 6.4.7  [basic.scope.class]     Status: CD1     Submitter: Daveed Vandevoorde     Date: 29 August 2003

[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:

  1. Should the injected class name be available in the base class specifiers?
    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.
  2. Do you agree the wording should be clarified whatever the answer to the first question?
    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 declaration declarator, 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).




39. Conflicting ambiguity rules

Section: 6.5.2  [class.member.lookup]     Status: CD1     Submitter: Neal M Gafter     Date: 20 Aug 1998

[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):

  1. 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.
  2. 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):

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.)




306. Ambiguity by class name injection

Section: 6.5.2  [class.member.lookup]     Status: CD1     Submitter: Clark Nelson     Date: 19 Jul 2001

[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".




139. Error in friend lookup example

Section: 6.5.3  [basic.lookup.unqual]     Status: CD1     Submitter: Mike Miller     Date: 14 Jul 1999

[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):

  1. 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)
                }
            };
        }
    
  2. Delete the sentence immediately following the example:

    The expression f(a) is a cast-expression equivalent to int(a).



514. Is the initializer for a namespace member in the scope of the namespace?

Section: 6.5.3  [basic.lookup.unqual]     Status: CD1     Submitter: Mike Miller     Date: 24 Mar 2005

[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]




143. Friends and Koenig lookup

Section: 6.5.4  [basic.lookup.argdep]     Status: CD1     Submitter: Mike Miller     Date: 21 Jul 1999

[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:

  1. 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.
  2. 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 namespaces and classes are 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 namespaces and classes associated with the argument types.

(See also issues 95, 136, 138, 139, 165, 166, and 218.)




218. Specification of Koenig lookup

Section: 6.5.4  [basic.lookup.argdep]     Status: CD1     Submitter: Hyman Rosen     Date: 28 Mar 2000

[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:

  1. The name in the function call expression is looked up like any other unqualified name.
  2. If the ordinary unqualified lookup finds nothing or finds the declaration of a (non-member) function, function template, or overload set, argument-dependent lookup is done and any functions found in associated namespaces are added to the result of the ordinary lookup.

This approach is not reflected in the current wording of the Standard. Instead, the following appears to be the status quo:

  1. Lookup of an unqualified name used as the postfix-expression in the function call syntax always performs Koenig lookup (6.5.3 [basic.lookup.unqual] paragraph 3).
  2. Unless ordinary lookup finds a class member function, the result of Koenig lookup always includes the declarations found in associated namespaces (6.5.4 [basic.lookup.argdep] paragraph 2), regardless of whether ordinary lookup finds a declaration and, if so, what kind of entity is found.
  3. The declarations from associated namespaces are not limited to functions and template functions by anything in 6.5.4 [basic.lookup.argdep]. However, if Koenig lookup results in more than one declaration and at least one of the declarations is a non-function, the program is ill-formed (9.8.4 [namespace.udir], paragraph 4; although this restriction is in the description of the using-directive, the wording applies to any lookup that spans namespaces).

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:

  1. 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.

  2. 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.

  3. 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.

  4. 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):

  1. 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.

  2. 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.




403. Reference to a type as a template-id

Section: 6.5.4  [basic.lookup.argdep]     Status: CD1     Submitter: John Spicer     Date: 18 Sep 2003

[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 ...




557. Does argument-dependent lookup cause template instantiation?

Section: 6.5.4  [basic.lookup.argdep]     Status: CD1     Submitter: Mike Miller     Date: 8 February 2006

[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:

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:

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):

  1. Change bullet 2 of 6.5.4 [basic.lookup.argdep] paragraph 2 as indicated:

  2. Delete bullet 8 of 6.5.4 [basic.lookup.argdep] paragraph 2:




298. T::x when T is cv-qualified

Section: 6.5.5.2  [class.qual]     Status: CD1     Submitter: Steve Adamczyk     Date: 7 Jul 2001

[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 to be have that class type (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 used following the class-key in an elaborated-type-specifier (9.2.9.5 [dcl.type.elab]), or in the class-head of a class declaration (Clause 11 [class]), or is used as the identifier in the declarator for a constructor or destructor declaration (11.4.5 [class.ctor], 11.4.7 [class.dtor]), to identify the subject of an elaborated-type-specifier (9.2.9.5 [dcl.type.elab]), class declaration (Clause 11 [class]), constructor declaration (11.4.5 [class.ctor]), or destructor declaration (11.4.7 [class.dtor]), the program is ill-formed. ] [Example: ...

9.2.9.5 [dcl.type.elab] paragraph 2:

This is the only remaining (normative) statement that a typedef-name can't be used in an elaborated-type-specifier. The reference to template type-parameter is deleted by the resolution of issue 283.

... If the identifier resolves to a typedef-name or 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.

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 used in 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, a A typedef-name that names a class shall not be used as the identifier class-name in the declarator declarator-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, a A typedef-name that names a class shall not be used as the identifier class-name following the ~ in the declarator for a destructor declaration.



318. struct A::A should not name the constructor of A

Section: 6.5.5.2  [class.qual]     Status: CD1     Submitter: John Spicer     Date: 18 Oct 2001

[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



400. Using-declarations and the "struct hack"

Section: 6.5.5.3  [namespace.qual]     Status: CD1     Submitter: Mark Mitchell     Date: 22 Jan 2003

[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]).



245. Name lookup in elaborated-type-specifiers

Section: 6.5.6  [basic.lookup.elab]     Status: CD1     Submitter: Jack Rouse     Date: 14 Sep 2000

[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]:

  1. 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.

  2. The second sentence quoted above seems to suggest that the name found will not be used if it is not a class name. typedef-name names are ill-formed due to the sentence preceding the quote. If lookup finds, for instance, an enum-name then a new declaration will be created. This differs from C, and from the enum case, and can have surprising effects:

        struct S {
           enum E {
               one = 1
           };
           class E* p;     // declares a global class E?
        };
    

    Was this really the intent? If this is the case then some more work is needed on 6.5.6 [basic.lookup.elab]. Note that the section does not make finding a type template formal ill-formed, as is done in 9.2.9.5 [dcl.type.elab]. I don't see anything that makes a type template formal name a class-name. So the example in 9.2.9.5 [dcl.type.elab] of friend class T; where T is a template type formal would no longer be ill-formed with this interpretation because it would declare a new class T.

(See also issue 254.)

Notes from the 4/02 meeting:

This will be consolidated with the changes for issue 254. See also issue 298.

Proposed resolution (October 2002):

As given in N1376=02-0034. Note that the inserts and strikeouts in that document do not display correctly in all browsers; <del> --> <strike> and <ins> --> <b>, and the similar changes for the closing delimiters, seem to do the trick.




254. Definitional problems with elaborated-type-specifiers

Section: 6.5.6  [basic.lookup.elab]     Status: CD1     Submitter: Clark Nelson     Date: 26 Oct 2000

[Voted into WP at April 2003 meeting.]

  1. 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.

  2. In both 6.5.6 [basic.lookup.elab] and 9.2.9.5 [dcl.type.elab], the text asserts that an elaborated-type-specifier that refers to a typedef-name is ill-formed. However, it is permissible for the form of elaborated-type-specifier that begins with typename to refer to a typedef-name.

    This problem is the result of adding the typename form to the elaborated-type-name grammar without changing the verbiage correspondingly. It could be fixed either by updating the verbiage or by moving the typename syntax into its own production and referring to both nonterminals when needed.

(See also issue 180. If this issue is resolved in favor of a separate nonterminal in the grammar for the typename forms, the wording in that issue's resolution must be changed accordingly.)

Notes from 04/01 meeting:

The consensus was in favor of moving the typename forms out of the elaborated-type-specifier grammar.

Notes from the 4/02 meeting:

This will be consolidated with the changes for issue 245.

Proposed resolution (October 2002):

As given in N1376=02-0034.




216. Linkage of nameless class-scope enumeration types

Section: 6.6  [basic.link]     Status: CD1     Submitter: Daveed Vandevoorde     Date: 13 Mar 2000

[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 of
That prohibits for example:
    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.



319. Use of names without linkage in declaring entities with linkage

Section: 6.6  [basic.link]     Status: CD1     Submitter: Clark Nelson     Date: 29 Oct 2001

[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.




389. Unnamed types in entities with linkage

Section: 6.6  [basic.link]     Status: CD1     Submitter: Daveed Vandevoorde     Date: 31 Oct 2002

[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 if A type without linkage shall not be used as the type of a variable or function with linkage, unless the variable or function has extern "C" linkage (9.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.]

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:

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.




474. Block-scope extern declarations in namespace members

Section: 6.6  [basic.link]     Status: CD1     Submitter: Daveed Vandevoorde     Date: 23 Jul 2004

[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]




513. Non-class “most-derived” objects

Section: 6.7.2  [intro.object]     Status: CD1     Submitter: Marc Schoolderman     Date: 20 Mar 2005

[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.



119. Object lifetime and aggregate initialization

Section: 6.7.3  [basic.life]     Status: CD1     Submitter: Jack Rouse     Date: 20 May 1999

[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:
    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]).]



274. Cv-qualification and char-alias access to out-of-lifetime objects

Section: 6.7.3  [basic.life]     Status: CD1     Submitter: Mike Miller     Date: 14 Mar 2001

[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:

to read:




404. Unclear reference to construction with non-trivial constructor

Section: 6.7.3  [basic.life]     Status: CD1     Submitter: Mike Miller     Date: 8 Apr 2003

[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.




594. Coordinating issues 119 and 404 with delegating constructors

Section: 6.7.3  [basic.life]     Status: CD1     Submitter: Tom Plum     Date: 30 August 2006

[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 T begins when:

The lifetime of an object of type T ends when...




521. Requirements for exceptions thrown by allocation functions

Section: 6.7.5.5.2  [basic.stc.dynamic.allocation]     Status: CD1     Submitter: Alisdair Meredith     Date: 22 May 2005

[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 shall only indicate failure only by throwing an exception of class std::bad_alloc (17.6.4.1 [bad.alloc]) or a class derived from std::bad_alloc a type that would match a handler (14.4 [except.handle]) of type std::bad_alloc (17.6.4.1 [bad.alloc]).



220. All deallocation functions should be required not to throw

Section: 6.7.5.5.3  [basic.stc.dynamic.deallocation]     Status: CD1     Submitter: Herb Sutter     Date: 31 Mar 2000

[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):

  1. Replacement deallocation functions are already required not to throw an exception (cf 16.4.5.8 [res.on.functions] paragraph 2, as applied to 17.6.3.2 [new.delete.single] paragraph 12 and 17.6.3.3 [new.delete.array] paragraph 11).
  2. Section 16.4.5.6 [replacement.functions] is describing the signatures of the functions to be replaced; exception specfications are not part of the signature.
  3. There does not appear to be any pressing need to require that class member deallocation functions not throw.

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...



348. delete and user-written deallocation functions

Section: 6.7.5.5.3  [basic.stc.dynamic.deallocation]     Status: CD1     Submitter: Ruslan Abdikeev     Date: 1 April 2002

[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:

  1. Make it clear that delete-expr will not call deallocation function if null pointer is given (in 7.6.2.9 [expr.delete]).
  2. Specify what user deallocation function shall do when null is given (either 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]).

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):

  1. 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, if the value of the operand of delete is the null pointer the operation has no effect may be a null pointer value. If it is not a null pointer value, in In the first alternative (delete object), the value of the operand of delete shall be a pointer to a non-array object or a pointer to a sub-object (6.7.2 [intro.object]) representing a base class of such an object (11.7 [class.derived])...
  2. 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]

  3. Change 7.6.2.9 [expr.delete] paragraphs 6-7 as follows:

    The If 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]).

    The If 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]

  4. Change 6.7.5.5.3 [basic.stc.dynamic.deallocation] paragraph 3 as indicated:

    The value of the first argument supplied to one of the a deallocation functions provided in the standard library may be a null pointer value; if so, and if the deallocation function is one supplied in the standard library, the call to the deallocation function has 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.]




649. Optionally ill-formed extended alignment requests

Section: 6.7.6  [basic.align]     Status: CD1     Submitter: Mike Miller     Date: 12 Aug 2007

[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,

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, the implementation may reject the request as program is ill-formed. The implementation may also silently ignore the requested alignment. [Note: aAdditionally, a request for runtime allocation of dynamic memory storage for which the requested alignment cannot be honored may shall be treated as an allocation failure. end note]



86. Lifetime of temporaries in query expressions

Section: 6.7.7  [class.temporary]     Status: CD1     Submitter: Steve Adamczyk     Date: Jan 1999

[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.




124. Lifetime of temporaries in default initialization of class arrays

Section: 6.7.7  [class.temporary]     Status: CD1     Submitter: Jack Rouse     Date: 3 June 1999

[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.




199. Order of destruction of temporaries

Section: 6.7.7  [class.temporary]     Status: CD1     Submitter: Alan Nash     Date: 27 Jan 2000

[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.




201. Order of destruction of temporaries in initializers

Section: 6.7.7  [class.temporary]     Status: CD1     Submitter: Alan Nash     Date: 31 Jan 2000

[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.]

  1. 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.
  2. 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.



320. Question on copy constructor elision example

Section: 6.7.7  [class.temporary]     Status: CD1     Submitter: Steve Clamage     Date: 2 Nov 2001

[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 for either the argument a or the result of f(a) to avoid undesired aliasing of a the result of f(a), which is then assigned to a.



392. Use of full expression lvalue before temporary destruction

Section: 6.7.7  [class.temporary]     Status: CD1     Submitter: Stephen Clamage     Date: 21 Nov 2002

[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

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 is but again the resulting construct might be a function call upon a constructor function with one assignment-expression as an argument; again, the function call is a full-expression. ] [Example:

  struct S {
      S(int i): I(i) { }
      int& v() { return I; }
    private:
      int I;
  };

  S s1(1);           // full-expression is call of S::S(int)
  S s2 = 2;          // full-expression is call of S::S(int)

  void f() {
      if (S(3).v())  // full-expression includes lvalue-to-rvalue and
                     // int to bool conversions, performed before
                     // temporary is deleted at end of full-expression
      { }
  }

end example]



443. Wording nit in description of lifetime of temporaries

Section: 6.7.7  [class.temporary]     Status: CD1     Submitter: Matthias Hofmann     Date: 2 Dec 2003

[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 object to of a subobject of to which the temporary reference is bound persists for the lifetime of the reference except as specified below.



464. Wording nit on lifetime of temporaries to which references are bound

Section: 6.7.7  [class.temporary]     Status: CD1     Submitter: Allan Odgaard     Date: 21 Feb 2004

[Voted into WP at April, 2006 meeting.]

Section 6.7.7 [class.temporary] paragraph 5 ends with this "rule":

[...] if obj2 is an object with static or automatic storage duration created after the temporary is created, the temporary shall be destroyed after obj2 is destroyed.

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 object with static or automatic storage duration created 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 object with static or automatic storage duration created after the temporary is created with the same storage duration as the temporary, the temporary shall be destroyed after obj2 is destroyed...



644. Should a trivial class type be a literal type?

Section: 6.8  [basic.types]     Status: CD1     Submitter: Alisdair Meredith     Date: 8 Aug 2007

[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:



637. Sequencing rules and example disagree

Section: 6.9.1  [intro.execution]     Status: CD1     Submitter: Ofer Porat     Date: 2 June 2007

[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:

  1. 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).

  2. The LHS (i) is an lvalue, so its value computation involves computing the address of i.

  3. 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 = ++i i++ + 1;        // the behavior is undefined
    i = i + 1;              // the value of i is incremented

This resolution also resolves issue 222.




639. What makes side effects “different” from one another?

Section: 6.9.1  [intro.execution]     Status: CD1     Submitter: James Widman     Date: 26 July 2007

[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 different another 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...




270. Order of initialization of static data members of class templates

Section: 6.9.3.2  [basic.start.static]     Status: CD1     Submitter: Jonathan H. Lundquist     Date: 9 Feb 2001

[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 like
    template <class T>
    class List {
       ...  static List<T>* sentinel; ...
    };

    template <class T>
    List<T>* List<T>::sentinel( new List<T> ); // static member definition

The 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
[ 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.




441. Ordering of static reference initialization

Section: 6.9.3.2  [basic.start.static]     Status: CD1     Submitter: Mike Miller     Date: 1 Dec 2003

[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 are Zero-initialization and initialization with a constant expression are collectively called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed 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.



688. Constexpr constructors and static initialization

Section: 6.9.3.2  [basic.start.static]     Status: CD1     Submitter: Peter Dimov     Date: 26 March, 2008

[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):

  1. Change 6.9.3.2 [basic.start.static] paragraph 1 as follows:

  2. ...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]); this If 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...
  3. Change 8.8 [stmt.dcl] paragraph 4 as follows:

  4. ...A local object of trivial or literal type (6.8 [basic.types]) with static storage duration initialized with constant-expressions is initialized Constant initialization (6.9.3.2 [basic.start.static]) of a local entity with static storage duration is performed before its block is first entered...
  5. Change 9.2.6 [dcl.constexpr] paragraph 7 as follows:

  6. 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])...
  7. Replace 9.4.2 [dcl.init.aggr] paragraph 14 as follows:

  8. 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.)




28. 'exit', 'signal' and static object destruction

Section: 6.9.3.3  [basic.start.dynamic]     Status: CD1     Submitter: Martin J. O'Riordan     Date: 19 Oct 1997

[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]).



222. Sequence points and lvalue-returning operators

Section: Clause 7  [expr]     Status: CD1     Submitter: Andrew Koenig     Date: 20 Dec 1999

[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.

Note that this suggestion is different from asking that there be a sequence point after evaluation of an assignment. In particular, I am not suggesting that an order constraint be imposed on any side effects other than the assignment itself.

Francis Glassborow:

My understanding is that for a single variable:

  1. Multiple read accesses without a write are OK
  2. A single read access followed by a single write (of a value dependant on the read, so that the read MUST happen first) is OK
  3. A write followed by an actual read is undefined behaviour
  4. Multiple writes have undefined behaviour

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:

  1. if the result of an assignment is used as an rvalue, its value is that of the variable after assignment. The actual store takes place before the next sequence point, but may be before the value is used. This is consistent with C usage.
  2. if the result of an assignment is used as an lvalue to store another value, then the new value will be stored in the variable before the next sequence point. It is unspecified whether the first assigned value is stored intermediately.
  3. if the result of an assignment is used as an lvalue to take an address, that address is given (it doesn't change). The actual store of the new value takes place before the next sequence point.

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.




351. Sequence point error: unspecified or undefined?

Section: Clause 7  [expr]     Status: CD1     Submitter: Andrew Koenig     Date: 23 April 2002

[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]



451. Expressions with invalid results and ill-formedness

Section: Clause 7  [expr]     Status: CD1     Submitter: Gennaro Prota     Date: 19 Jan 2004

[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 expression is a constant expression appears where an integral constant expression is required (7.7 [expr.const]), in which case the program is ill-formed.



158. Aliasing and qualification conversions

Section: 7.2.1  [basic.lval]     Status: CD1     Submitter: Mike Stump     Date: 20 Aug 1999

[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:"




519. Null pointer preservation in void* conversions

Section: 7.3.12  [conv.ptr]     Status: CD1     Submitter: comp.std.c++     Date: 19 May 2005

[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:

  1. Add the following sentence to 7.3.12 [conv.ptr] paragraph 2:

  2. The null pointer value is converted to the null pointer value of the destination type.
  3. Add the following sentence to 7.6.1.9 [expr.static.cast] paragraph 10:

  4. The null pointer value (7.3.12 [conv.ptr]) is converted to the null pointer value of the destination type.

Proposed resolution (October, 2005):

  1. Add the indicated words to 7.3.12 [conv.ptr] paragraph 2:

  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.
  3. Add the indicated words to 7.6.1.9 [expr.static.cast] paragraph 11:

  4. 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...



654. Conversions to and from nullptr_t

Section: 7.3.12  [conv.ptr]     Status: CD1     Submitter: Jason Merrill     Date: 7 October 2007

[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.




480. Is a base of a virtual base also virtual?

Section: 7.3.13  [conv.mem]     Status: CD1     Submitter: Mark Mitchell     Date: 18 Oct 2004

[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):

  1. Change 7.3.13 [conv.mem] paragraph 2 as indicated:

  2. ...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...
  3. Change 7.6.1.9 [expr.static.cast] paragraph 2 as indicated:

  4. ...and B is not neither a virtual base class of D nor a base class of a virtual base class of D...
  5. Change 7.6.1.9 [expr.static.cast] paragraph 9 as indicated:

  6. ...and B is not neither a virtual base class of D nor a base class of a virtual base class of D...



113. Visibility of called function

Section: 7.6.1.3  [expr.call]     Status: CD1     Submitter: Christophe de Dinechin     Date: 5 May 1999

[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.)




118. Calls via pointers to virtual member functions

Section: 7.6.1.3  [expr.call]     Status: CD1     Submitter: Martin O'Riordan     Date: 17 May 1999

[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:
        (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. ]
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.

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):

  1. 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 expression If the selected function is non-virtual, or if the id-expression in the class member access expression is a qualified-id, that function is called. Otherwise, its final overrider (11.7.3 [class.virtual]) in the dynamic type of the object expression is called. ...
  2. 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.



506. Conditionally-supported behavior for non-POD objects passed to ellipsis

Section: 7.6.1.3  [expr.call]     Status: CD1     Submitter: Mike Miller     Date: 14 Apr 2005

[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.



634. Conditionally-supported behavior for non-POD objects passed to ellipsis redux

Section: 7.6.1.3  [expr.call]     Status: CD1     Submitter: Howard Hinnant     Date: 6 Jun 2007

[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:

...Passing an a potentially-evaluated argument of non-trivial class type (Clause 11 [class]) with no corresponding parameter is conditionally-supported, with implementation-defined semantics...



421. Is rvalue.field an rvalue?

Section: 7.6.1.5  [expr.ref]     Status: CD1     Submitter: Gabriel Dos Reis     Date: 15 June 2003

[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.



492. typeid constness inconsistent with example

Section: 7.6.1.8  [expr.typeid]     Status: CD1     Submitter: Ron Natalie     Date: 15 Dec 2004

[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.



54. Static_cast from private base to derived class

Section: 7.6.1.9  [expr.static.cast]     Status: CD1     Submitter: Steve Adamczyk     Date: 13 Oct 1998

[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:

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.
}
--- end example]

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 by can be performed using the cast notation of explicit type conversion. The same semantic restrictions and behaviors apply.

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:

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:



427. static_cast ambiguity: conversion versus cast to derived

Section: 7.6.1.9  [expr.static.cast]     Status: CD1     Submitter: Mark Mitchell     Date: 5 July 2003

[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:

  1. D is derived from B, and b is an lvalue, so a cast to D& is OK.
  2. const D& t = b is valid, using the constructor for D. [Ed. note: actually, this should be parenthesized initialization.]

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.




439. Guarantees on casting pointer back to cv-qualified version of original type

Section: 7.6.1.9  [expr.static.cast]     Status: CD1     Submitter: Mark Mitchell     Date: 30 Oct 2003

[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:
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.]

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.




671. Explicit conversion from a scoped enumeration type to integral type

Section: 7.6.1.9  [expr.static.cast]     Status: CD1     Submitter: Daveed Vandevoorde     Date: 22 December 2007

[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.



195. Converting between function and object pointers

Section: 7.6.1.10  [expr.reinterpret.cast]     Status: CD1     Submitter: Steve Clamage     Date: 12 Jan 2000

[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:


  1. Early C++ supported this conversion and it was deliberately removed during the standardization process.
  2. The existence of an appropriate integral type is irrelevant to whether the conversion is "safe." The condition should be on whether information is lost in the conversion or not.
  3. There are numerous ways to address the problem at an implementation level rather than changing the language. For example, the compiler could recognize the specific case of dlsym and omit the diagnostic, or the C++ binding to dlsym could be changed (using templates, for instance) to circumvent the violation.
  4. The conversion is, in fact, not supported by C; the dlsym function is simply relying on non-mandated characteristics of C implementations, and we would be going beyond the requirements of C compatibility in requiring (some) implementations to support the conversion.
  5. This issue is in fact not a defect (omitted or self-contradictory requirements) in the current Standard; the proposed change would actually be an extension and should not be considered until the full review of the IS.
  6. dlsym appears not to be used very widely, and the declaration in the header file is not problematic, only calls to it. Since C code generally requires some porting to be valid C++ anyway, this should be considered one of those items that requires porting.

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.)

  1. 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]).

  2. Add the indicated words to 4.1 [intro.compliance] paragraph 2, bullet 2:

  3. 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.
  4. Change 9.10 [dcl.asm] paragraph 1 as indicated:

    The meaning of an An asm declaration evokes conditionally-supported behavior. If supported, its meaning is implementation-defined.
  5. 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. ]
  6. 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++, the behavior is implementation-defined result is conditionally-supported behavior, with implementation-defined semantics.



463. reinterpret_cast<T*>(0)

Section: 7.6.1.10  [expr.reinterpret.cast]     Status: CD1     Submitter: Gennaro Prota     Date: 14 Feb 2004

[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):

  1. 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.
  2. 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]



324. Can "&" be applied to assignment to bit-field?

Section: 7.6.2.2  [expr.unary.op]     Status: CD1     Submitter: Alasdair Grant     Date: 27 Nov 2001

[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) ...




659. Alignment of function types

Section: 7.6.2.6  [expr.alignof]     Status: CD1     Submitter: Alisdair Meredith     Date: 7 November 2007

[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.



256. Overflow in size calculations

Section: 7.6.2.8  [expr.new]     Status: CD1     Submitter: James Kanze     Date: 15 Oct 2000

[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.




299. Conversion on array bound expression in new

Section: 7.6.2.8  [expr.new]     Status: CD1     Submitter: Mark Mitchell     Date: 19 Jul 2001

[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 shall have be of integral type, or enumeration 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 a non-negative value. [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 shall have integral or enumeration type (6.8.2 [basic.fundamental]) with a non-negative value be 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: ...



429. Matching deallocation function chosen based on syntax or signature?

Section: 7.6.2.8  [expr.new]     Status: CD1     Submitter: John Wilkinson     Date: 18 July 2003

[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:
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 
--- end example]



624. Overflow in calculating size of allocation

Section: 7.6.2.8  [expr.new]     Status: CD1     Submitter: Jens Maurer     Date: 8 March 2007

[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, if When the value of the that expression in a direct-new-declarator is 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.]




288. Misuse of "static type" in describing pointers

Section: 7.6.2.9  [expr.delete]     Status: CD1     Submitter: James Kuyper     Date: 19 May 2001

[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 that
if 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):

  1. Change 7.6.2.9 [expr.delete] paragraph 3 as follows:

  2. In the first alternative (delete object), if the static type of the operand object to be deleted is different from its dynamic type, the static type shall be a base class of the operand's dynamic 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.
  3. Change the footnote in 11.4.11 [class.free] paragraph 4 as follows:

  4. 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 the delete-expression's operand object to be deleted be the same as its dynamic type.
  5. Change the footnote in 11.4.11 [class.free] paragraph 5 as follows:

  6. If the static type in the delete-expression of 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).]




353. Is deallocation routine called if destructor throws exception in delete?

Section: 7.6.2.9  [expr.delete]     Status: CD1     Submitter: Duane Smith     Date: 30 April 2002

[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. ]



442. Incorrect use of null pointer constant in description of delete operator

Section: 7.6.2.9  [expr.delete]     Status: CD1     Submitter: Matthias Hofmann     Date: 2 Dec 2003

[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 not the a null pointer constant, 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.




520. Old-style casts between incomplete class types

Section: 7.6.3  [expr.cast]     Status: CD1     Submitter: comp.std.c++     Date: 19 May 2005

[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:

  1. casting from void pointer to incomplete type

  2.     struct A;
        struct B;
    
        void *v;
        A *a = (A*)v; // allowed to choose reinterpret_cast
    
  3. variant application of static or reinterpret casting

  4.     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
    
  5. ability to somehow choose static_cast

  6. 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]



497. Missing required initialization in example

Section: 7.6.4  [expr.mptr.oper]     Status: CD1     Submitter: Giovanni Bajo     Date: 03 Jan 2005

[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
    }



614. Results of integer / and %

Section: 7.6.5  [expr.mul]     Status: CD1     Submitter: Gabriel Dos Reis     Date: 15 January 2007

[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.]




661. Semantics of arithmetic comparisons

Section: 7.6.9  [expr.rel]     Status: CD1     Submitter: Daveed Vandevoorde     Date: 27 November 2007

[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):

  1. Append the following paragraph to the end of 7.6.9 [expr.rel]:

  2. 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.
  3. Append the following paragraph to the end of 7.6.10 [expr.eq]:

  4. Each of the operators shall yield true if the specified relation is true and false if it is false.



446. Does an lvalue-to-rvalue conversion on the "?" operator produce a temporary?

Section: 7.6.16  [expr.cond]     Status: CD1     Submitter: John Potter     Date: 31 Dec 2003

[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):

  1. 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 T2 that 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.
  2. 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.
  3. 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.]




339. Overload resolution in operand of sizeof in constant expression

Section: 7.7  [expr.const]     Status: CD1     Submitter: Steve Adamczyk     Date: 11 Mar 2002

[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:

  1. errors that occur while processing some entity external to the expression, e.g., an instantiation of a template or the generation of the definition of an implicitly-declared copy constructor
  2. errors due to implementation limits
  3. errors due to access violations (this is a judgment call, but the philosophy of access has always been that it doesn't affect visibility)

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.)




366. String literal allowed in integral constant expression?

Section: 7.7  [expr.const]     Status: CD1     Submitter: Martin v. Loewis     Date: 29 July 2002

[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]), ...




367. throw operator allowed in constant expression?

Section: 7.7  [expr.const]     Status: CD1     Submitter: Martin v. Loewis     Date: 29 July 2002

[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-call function call (including new-expressions and delete-expressions), or comma operators, or throw-expressions shall not be used.

Note: this sentence is also changed by the resolution of issue 530.




457. Wording nit on use of const variables in constant expressions

Section: 7.7  [expr.const]     Status: CD1     Submitter: Mark Mitchell     Date: 03 Feb 2004

[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.



530. Nontype template arguments in constant expressions

Section: 7.7  [expr.const]     Status: CD1     Submitter: Mark Mitchell     Date: 21 August 2005

[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.




684. Constant expressions involving the address of an automatic variable

Section: 7.7  [expr.const]     Status: CD1     Submitter: Jens Maurer     Date: 13 March, 2008

[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:

Proposed resolution (June, 2008):

  1. Change 6.9.3.2 [basic.start.static] paragraph 1 as follows:

  2. 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:
    • 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.

    Together, zero-initialization and constant initialization...
  3. Add the following in 7.7 [expr.const] paragraph 2:

(Note: the change to 6.9.3.2 [basic.start.static] paragraph 1 needs to be reconciled with the conflicting change in issue 688.)




276. Order of destruction of parameters and temporaries

Section: 8.7  [stmt.jump]     Status: CD1     Submitter: James Kanze     Date: 28 Mar 2001

[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...



378. Wording that says temporaries are declared

Section: 8.7  [stmt.jump]     Status: CD1     Submitter: Gennaro Prota     Date: 07 September 2002

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.




281. inline specifier in friend declarations

Section: 9.2.3  [dcl.fct.spec]     Status: CD1     Submitter: John Spicer     Date: 24 Apr 2001

[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.



317. Can a function be declared inline after it has been called?

Section: 9.2.3  [dcl.fct.spec]     Status: CD1     Submitter: Steve Clamage     Date: 14 Oct 2001

[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...



396. Misleading note regarding use of auto for disambiguation

Section: 9.2.3  [dcl.fct.spec]     Status: CD1     Submitter: Herb Sutter     Date: 3 Jan 2003

[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]




397. Same address for string literals from default arguments in inline functions?

Section: 9.2.3  [dcl.fct.spec]     Status: CD1     Submitter: Mark Mitchell     Date: 13 Jan 2003

[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]



477. Can virtual appear in a friend declaration?

Section: 9.2.3  [dcl.fct.spec]     Status: CD1     Submitter: Daveed Vandevoorde     Date: 23 Sep 2004

[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 only be used only in declarations the initial declaration of a non-static class member functions that appear within a member-specification of a class definition function; see 11.7.3 [class.virtual].

The explicit specifier shall be used only in declarations the declaration of constructors a constructor within a its class definition; see 11.4.8.2 [class.conv.ctor].




424. Wording problem with issue 56 resolution on redeclaring typedefs in class scope

Section: 9.2.4  [dcl.typedef]     Status: CD1     Submitter: Daveed Vandevoorde     Date: 25 June 2003

[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
  };
]



647. Non-constexpr instances of constexpr constructor templates

Section: 9.2.6  [dcl.constexpr]     Status: CD1     Submitter: Mike Miller     Date: 12 Aug 2007

[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.]

  1. Change 9.2.6 [dcl.constexpr] paragraph 2 as follows:

  2. A constexpr specifier used in a function declaration the 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).
  3. Change 9.2.6 [dcl.constexpr] paragraph 3 as follows:

  4. The definition of a constexpr function shall satisfy the following constraints:

    [Example:...

  5. Change 9.2.6 [dcl.constexpr] paragraph 4 as follows:

  6. The definition of a constexpr constructor shall satisfy the following constraints:

    A trivial copy constructor is also a constexpr constructor. [Example: ...

  7. Change 9.2.6 [dcl.constexpr] paragraph 5 as follows:

  8. 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 ignored and the specialization is not a constexpr function.
  9. Change 9.2.6 [dcl.constexpr] paragraph 6 as follows:

  10. A constexpr specifier used in for a non-static member function definition that is not a constructor declares that member function to be const (11.4.3 [class.mfct.non.static]). [Note: ...



648. Constant expressions in constexpr initializers

Section: 9.2.6  [dcl.constexpr]     Status: CD1     Submitter: Mike Miller     Date: 12 Aug 2007

[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 be initialized, 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...



283. Template type-parameters are not syntactically type-names

Section: 9.2.9.3  [dcl.type.simple]     Status: CD1     Submitter: Clark Nelson     Date: 01 May 2001

[Voted into WP at April 2003 meeting.]

Although 13.2 [temp.param] paragraph 3 contains an assertion that

A type-parameter defines its identifier to be a type-name (if declared with class or typename)

the grammar in 9.2.9.3 [dcl.type.simple] paragraph 1 says that a type-name is either a class-name, an enum-name, or a typedef-name. The identifier in a template type-parameter is none of those. One possibility might be to equate the identifier with a typedef-name instead of directly with a type-name, which would have the advantage of not requiring parallel treatment of the two in situations where they are treated the same (e.g., in elaborated-type-specifiers, see issue 245). See also issue 215.

Proposed resolution (Clark Nelson, March 2002):

In 13.2 [temp.param] paragraph 3, change "A type-parameter defines its identifier to be a type-name" to "A type-parameter defines its identifier to be a typedef-name"

In 9.2.9.5 [dcl.type.elab] paragraph 2, change "If the identifier resolves to a typedef-name or a template type-parameter" to "If the identifier resolves to a typedef-name".

This has been consolidated with the edits for some other issues. See N1376=02-0034.




516. Use of signed in bit-field declarations

Section: 9.2.9.3  [dcl.type.simple]     Status: CD1     Submitter: comp.std.c++     Date: 25 Apr 2005

[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 whether bit-fields and objects 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 objects and bit-fields to be signed; it is redundant with other integral types in other contexts. end note]



651. Problems in decltype specification and examples

Section: 9.2.9.3  [dcl.type.simple]     Status: CD1     Submitter: Daveed Vandevoorde     Date: 16 Aug 2007

[Voted into the WP at the September, 2008 meeting.]

The second bullet of 9.2.9.3 [dcl.type.simple] paragraph 4 reads,

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:

  1. The declaration of struct A should end with a semicolon.

  2. 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:

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]




629. auto parsing ambiguity

Section: 9.2.9.7  [dcl.spec.auto]     Status: CD1     Submitter: Daveed Vandevoorde     Date: 14 March 2007

[Voted into the WP at the February, 2008 meeting as paper J16/08-0056 = WG21 N2546.]

We've found an interesting parsing ambiguity with the new meaning of auto. Consider:

    typedef int T;
    void f() {
        auto T = 42;  // Valid or not?
    }

The question here is whether T should be a type specifier or a storage class? 9.2.9.7 [dcl.spec.auto] paragraph 1 says,

The auto type-specifier has two meanings depending on the context of its use. In a decl-specifier-seq that contains at least one type-specifier (in addition to auto) that is not a cv-qualifier, the auto type-specifier specifies that the object named in the declaration has automatic storage duration.

In this case, T is a type-specifier, so the declaration is ill-formed: there is no declarator-id. Many, however, would like to see auto work “just like int,” i.e., forcing T to be redeclared in the inner scope. Concerns cited included hijacking of the name in templates and inline function bodies over the course of time if a program revision introduces a type with that name in the surrounding context. Although it was pointed out that enclosing the name in parentheses in the inner declaration would prevent any such problems, this was viewed as unacceptably ugly.

Notes from the April, 2007 meeting:

The CWG wanted to avoid a rule like, “if auto can be a type-specifier, it is” (similar to the existing “if it can be a declaration, it is” rule) because of the lookahead and backtracking difficulties such an approach would pose for certain kinds of parsing techniques. It was noted that the difficult lookahead cases all involve parentheses, which would not be a problem if only the “=” form of initializer were permitted in auto declarations; only very limited lookahead is required in that case. It was also pointed out that the “if it can be a type-specifier, it is” approach results in a quiet change of meaning for cases like

    typedef int T;
    int n = 3;
    void f() {
        auto T(n);
    }

This currently declares n to be an int variable in the inner scope but would, under the full lookahead approach, declare T to be a variable, quitely changing uses of n inside f() to refer to the outer variable.

The consensus of the CWG was to pursue the change to require the “=” form of initializer for auto.

Notes from the July, 2007 meeting:

See paper J16/07-0197 = WG21 N2337. There was no consensus among the CWG for either of the approaches recommended in the paper; additional input and direction is required.




686. Type declarations/definitions in type-specifier-seqs and type-ids

Section: 9.3.2  [dcl.name]     Status: CD1     Submitter: Jens Maurer     Date: 21 March, 2008

[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:

  1. 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.

  2. 7.5.6 [expr.prim.lambda], 9.1 [dcl.pre], and 9.3.4.6 [dcl.fct] (late-specified return types) do not contain any restriction at all.

  3. 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):

  1. Delete the indicated words from 7.6.1.7 [expr.dynamic.cast] paragraph 1:

  2. ...Types shall not be defined in a dynamic_cast....
  3. Delete the indicated words from 7.6.1.8 [expr.typeid] paragraph 4:

  4. ...Types shall not be defined in the type-id....
  5. Delete the indicated words from 7.6.1.9 [expr.static.cast] paragraph 1:

  6. ...Types shall not be defined in a static_cast....
  7. Delete the indicated words from 7.6.1.10 [expr.reinterpret.cast] paragraph 1:

  8. ...Types shall not be defined in a reinterpret_cast....
  9. Delete the indicated words from 7.6.1.11 [expr.const.cast] paragraph 1:

  10. ...Types shall not be defined in a const_cast....
  11. Delete paragraph 5 of 7.6.2.5 [expr.sizeof]:

  12. Types shall not be defined in a sizeof expression.
  13. Delete paragraph 5 of 7.6.2.8 [expr.new]:

  14. The type-specifier-seq shall not contain class declarations, or enumeration declarations.
  15. Delete paragraph 4 of 7.6.2.6 [expr.alignof]:

  16. A type shall not be defined in an alignof expression.
  17. Delete paragraph 3 of 7.6.3 [expr.cast]:

  18. Types shall not be defined in casts.
  19. Delete the indicated words from 8.5 [stmt.select] paragraph 2:

  20. ...The type-specifier-seq shall not contain typedef and shall not declare a new class or enumeration....
  21. Add the indicated words to 9.2.9 [dcl.type] paragraph 3:

  22. 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]).
  23. Delete the indicated words from 11.4.8.3 [class.conv.fct] paragraph 1:

  24. ...Classes, enumerations, and typedef-names shall not be declared in the type-specifier-seq....
  25. Delete the indicated words from 14.4 [except.handle] paragraph 1:

  26. ...Types shall not be defined in an exception-declaration.
  27. Delete paragraph 6 of 14.5 [except.spec]:

  28. Types shall not be defined in exception-specifications.

[Drafting note: no changes are required to 7.5.6 [expr.prim.lambda], 9.2.4 [dcl.typedef], 9.12.2 [dcl.align], 9.7.1 [dcl.enum], 9.3.4.6 [dcl.fct], 13.2 [temp.param], or 13.3 [temp.names].]




160. Missing std:: qualification

Section: 9.3.3  [dcl.ambig.res]     Status: CD1     Submitter: Al Stevens     Date: 23 Aug 1999

[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 function
void 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 function
void 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 function
void 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 function
bool 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".




112. Array types and cv-qualifiers

Section: 9.3.4.5  [dcl.array]     Status: CD1     Submitter: Steve Clamage     Date: 4 May 1999

[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:
    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] . ]
The Note appears to contradict the sentence that precedes it.

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].



140. Agreement of parameter declarations

Section: 9.3.4.6  [dcl.fct]     Status: CD1     Submitter: Steve Clamage     Date: 15 Jul 1999

[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):

  1. In Clause 3 [intro.defs] “signature,” change "the types of its parameters" to "its parameter-type-list (9.3.4.6 [dcl.fct])".

  2. 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."

  3. 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."

  4. 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.

  5. 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.

  6. In 9.3.4.6 [dcl.fct] paragraph 4, change "the parameter type list" to "the parameter-type-list."

  7. In the second bullet of _N4868_.12.2 [over.load] paragraph 2, change all occurrences of "parameter types" to "parameter-type-list."

  8. In 12.2 [over.match] paragraph 1, change "the types of the parameters" to "the parameter-type-list."

  9. 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.




262. Default arguments and ellipsis

Section: 9.3.4.6  [dcl.fct]     Status: CD1     Submitter: Jamie Schmeiser     Date: 13 Nov 2000

[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



295. cv-qualifiers on function types

Section: 9.3.4.6  [dcl.fct]     Status: CD1     Submitter: Nathan Sidwell     Date: 29 Jun 2001

[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:
  typedef void F();
  struct S {
    const F f;          // ill-formed
  };
-- end example]
by
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.




681. Restrictions on declarators with late-specified return types

Section: 9.3.4.6  [dcl.fct]     Status: CD1     Submitter: Mike Miller     Date: 10 March, 2008

[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

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;  // Disallowed
Suggested resolution:
  1. Delete the words “and the derived-declarator-type-list shall be empty” from 9.3.4.6 [dcl.fct] paragraph 2.

  2. Add a new paragraph following 9.3 [dcl.decl] paragraph 4:

  3. 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):

  1. Change the grammar in 9.3 [dcl.decl] paragraph 4 as follows:

  2. Change the grammar in 9.3.2 [dcl.name] paragraph 1 as follows:

  3. Change 9.3.4.6 [dcl.fct] paragraph 2 as follows:

  4. ... T shall be the single type-specifier auto and the derived-declarator-type-list shall be empty. Then the type...
  5. 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:

  6. 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 a direct-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 a direct-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.




136. Default arguments and friend declarations

Section: 9.3.4.7  [dcl.fct.default]     Status: CD1     Submitter: Daveed Vandevoorde     Date: 9 July 1999

[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);             // #3
Does 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:

  1. Is the friend declaration in the scope of class C or in the surrounding namespace scope?

    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.

  2. Should default arguments be permitted in friend function declarations, and what effect should they have?

    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.

  3. What restrictions should be placed on default argument usage with friend declarations?

    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.

(See also issues 21, 95, 138, 139, 143, 165, and 166.)

Notes from 10/99 meeting:

Four possible outcomes were identified:

  1. If a friend declaration declares a default parameter, allow no other declarations of that function in the translation unit.
  2. Same as preceding, but only allow the friend declaration if it is also a definition.
  3. Disallow default arguments in friend declarations.
  4. Treat the default arguments in each friend declaration as a distinct set, causing an error if the call would be ambiguous.

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"):

  1. 0/14/5
  2. 8/13/5
  3. 11/7/14
  4. 7/10/9

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.



5. CV-qualifiers and type conversions

Section: 9.4  [dcl.init]     Status: CD1     Submitter: Josee Lajoie     Date: unknown

[Moved to DR at 4/01 meeting.]

The description of copy-initialization in 9.4 [dcl.init] paragraph 14 says:

Should "destination type" in this last bullet refer to "cv-unqualified destination type" to make it clear that the destination type excludes any cv-qualifiers? This would make it clearer that the following example is well-formed:
     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.



78. Section 8.5 paragraph 9 should state it only applies to non-static objects

Section: 9.4  [dcl.init]     Status: CD1     Submitter: Judy Ward     Date: 15 Dec 1998

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...



177. Lvalues vs rvalues in copy-initialization

Section: 9.4  [dcl.init]     Status: CD1     Submitter: Steve Adamczyk     Date: 25 October 1999

[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):

  1. 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.
  2. 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.)




277. Zero-initialization of pointers

Section: 9.4  [dcl.init]     Status: CD1     Submitter: Andrew Sawyer     Date: 5 Apr 2001

[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.]



302. Value-initialization and generation of default constructor

Section: 9.4  [dcl.init]     Status: CD1     Submitter: Steve Adamczyk     Date: 23 Jul 2001

[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:

  1. Zero the object, then call the default generated constructor. This is not valid unless the standard is changed to say that the default constructor might be generated for value-initialization cases like the above (that is, it's implementation-dependent whether the constructor definition is generated). The zeroing operation can of course be optimized, if necessary, to hit only the pieces of the object that would otherwise be left uninitialized. An alternative would be to require generation of the constructor for value-initialization cases, even if the implementation technique doesn't call the constructor at that point. It's pretty likely that the constructor is going to have to be generated at some point in the program anyway.
  2. Make a new value-initialization "constructor," whose body looks a lot like the usual generated constructor, but which also zeroes other members. No errors would be generated while generating this modified constructor, because it generates code that does full initialization. (Actually, it wouldn't guarantee initialization of reference members, and that might be an argument for generating the constructor, in order to get that error.) This is standard-conforming, but it destroys object-code compatibility.
  3. Variation on (1): Zero first, and generate the object code for the default constructor when it's needed for value-initialization cases, but don't issue any errors at that time. Issue the errors only if it turns out the constructor is "really" referenced. Aside from the essential shadiness of this approach, I fear that something in the generation of the constructor will cause a template instantiation which will be an abservable side effect.

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]).



509. Dead code in the specification of default initialization

Section: 9.4  [dcl.init]     Status: CD1     Submitter: Mike Miller     Date: 18 Mar 2005

[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:

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.”




543. Value initialization and default constructors

Section: 9.4  [dcl.init]     Status: CD1     Submitter: Mike Miller     Date: 27 October 2005

[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 initialization as 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:

  1. 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).

  2. 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.

  3. 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:

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.




430. Ordering of expression evaluation in initializer list

Section: 9.4.2  [dcl.init.aggr]     Status: CD1     Submitter: Nathan Sidwell     Date: 23 July 2003

[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.




491. Initializers for empty-class aggregrate members

Section: 9.4.2  [dcl.init.aggr]     Status: CD1     Submitter: Nathan Sidwell     Date: 15 Dec 2004

[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.




632. Brace-enclosed initializer for scalar member of aggregate

Section: 9.4.2  [dcl.init.aggr]     Status: CD1     Submitter: Greg Comeau     Date: 3 May 2007

[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.




291. Overload resolution needed when binding reference to class rvalue

Section: 9.4.4  [dcl.init.ref]     Status: CD1     Submitter: Andrei Iltchenko     Date: 15 Jun 2001

[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 1