Document number:  P4271R0
Date:  2026-06-12
Project:  Programming Language C++
Reference:  ISO/IEC 14882:2024
Reply to:  Jens Maurer
 jens.maurer@gmx.net


Core Language Working Group "ready" Issues for the June, 2026 meeting


References in this document reflect the section and paragraph numbering of document WG21 N5046.


2228. Ambiguity resolution for cast to function type

Section: 9.3.3  [dcl.ambig.res]     Status: ready     Submitter: Richard Smith     Date: 2016-02-02

Consider:

  int x = (int()) + 5; 

This is ill-formed, because 9.3.3 [dcl.ambig.res] paragraph 2 specifies:

An ambiguity can arise from the similarity between a function-style cast and a type-id. The resolution is that any construct that could possibly be a type-id in its syntactic context shall be considered a type-id.

and thus int() is interpreted as a type-id instead of as a function-style cast, so this is an ill-formed cast to a function type.

This seems to be the wrong disambiguation for all cases where there is a choice between a C-style cast and a parenthesized expression: in all those cases, the C-style cast interpretation results in a cast to a function type, which is always ill-formed.

Further, there is implementation divergence in the handling of this example:

  struct T { int operator++(int); T operator[](int); };
  int a = (T()[3])++; // not a cast

EWG 2022-11-11

This is tracked in github issue cplusplus/papers#1376.

Additional notes (May, 2025)

Also consider this example:

  (S())[]->A<int>;              // OK, constructor call
  (S())[]->A<int> {return {};}; // error: C-style cast of lambda

Without the suggested simple rule that (T()) is never a conversion to a function type (which is always semantically ill-formed), syntactic disambiguation would require analysis whether the thing after the arrow is a type (i.e. a trailing return type) or an expression (for operator->).

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

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

    unary-expression :
      postfix-expression
      unary-operator cast-expression
      ++ cast-expression
      -- cast-expression
      await-expression
      sizeof unary-expression
      sizeof ( nofun-type-id )
      sizeof ... ( identifier )
      alignof ( nofun-type-id )
      noexcept-expression
      new-expression
      delete-expression
    
  2. Change in 7.6.2.5 [expr.sizeof] paragraph 1 as follows:

    The operand is either an expression, which is an unevaluated operand (7.2.3 [expr.context]), or a parenthesized nofun-type-id.
  3. Change in 7.6.2.6 [expr.alignof] paragraph 1 as follows:

    The operand shall be a nofun-type-id representing a complete object type, or an array thereof, or a reference to one of those types.
  4. Replace type-id with nofun-type-id throughout 7.6.2.8 [expr.new], for example in paragraph 1:

      new-expression :
        ::opt new new-placementopt new-type-id new-initializeropt
        ::opt new new-placementopt ( nofun-type-id ) new-initializeropt
    
  5. Change in 7.6.3 [expr.cast] paragraph 2 as follows:

      cast-expression :
        unary-expression
        ( nofun-type-id ) cast-expression
    
  6. Change in 9.2.9.7.1 [dcl.spec.auto.general] paragraph 6 as follows:

    A placeholder type can also be used in the type-specifier-seq of the new-type-id or in the nofun-type-id of a new-expression (7.6.2.8 [expr.new]). In such a nofun-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 .
  7. Change in 9.2.9.7.2 [dcl.type.auto.deduct] paragraph 2 as follows:

    A placeholder for a deduced class type can also be used in the type-specifier-seq in the new-type-id or nofun-type-id of a new-expression (7.6.2.8 [expr.new]), as the simple-type-specifier in an explicit type conversion (functional notation) (7.6.1.4 [expr.type.conv]), or as the type-specifier in the parameter-declaration of a template-parameter (13.2 [temp.param]). A placeholder for a deduced class type shall not appear in any other context.
  8. Change in 9.3.1 [dcl.decl.general] paragraph 6 as follows, to fix auto (*p)() -> int(X()); (now a function pointer initialized by X()):

      trailing-return-type :
        -> nofun-type-id
    
  9. 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 A type can be named with a type-id, nofun-type-id, or new-type-id (7.6.2.8 [expr.new]), each of which is syntactically a declaration for a variable or (only for a type-id) function of that type that omits the name of the entity.
      nofun-type-id:
        type-specifier-seq nofun-declaratoropt
    
      nofun-declarator:
        ptr-nofun-declarator
        noptr-nofun-declarator parameters-and-qualifiers trailing-return-type
    
      ptr-nofun-declarator:
        noptr-nofun-declarator
        ptr-operator ptr-nofun-declaratoropt
    
      noptr-nofun-declarator:
        noptr-nofun-declarator parameters-and-qualifiers
        noptr-nofun-declaratoropt [ constant-expressionopt ] attribute-specifier-seqopt
        ( ptr-nofun-declarator )
    

    It is possible to identify uniquely the location in the abstract-declarator or nofun-declarator where the identifier would appear if the construction were a declarator in a declaration. The named type is then the same as the type of the hypothetical identifier.

  10. Change in 9.3.3 [dcl.ambig.res] paragraph 2 as follows:

    An ambiguity can arise from the similarity between a function-style cast and a type-id. The resolution is that any construct that could possibly be a type-id in its syntactic context shall be considered a type-id. [ Note: No such ambiguity can arise between an expression and a nofun-type-id. -- end note ] However, a construct that can syntactically be a type-id whose outermost abstract-declarator would match the grammar of an abstract-declarator with a trailing-return-type is considered a type-id only if it starts with auto. [Example 2 :
      template <class T> struct X {};
      template <int N> struct Y {};
      X<int()> a;   // type-id
      X<int(1)> b;  // expression (ill-formed)
      Y<int()> c;   // type-id (ill-formed)
      Y<int(1)> d;  // expression
      void foo(signed char a) {
        sizeof(int());    // type-id (ill-formed) expression
        sizeof(int(a));   // expression
        sizeof(int(unsigned(a)));    // type-id (ill-formed) expression
        (int())+1;              // type-id (ill-formed) expression
        (int(a))+1;             // expression
        (int(unsigned(a)))+1;   // type-id (ill-formed) expression
      }
      typedef struct BB { int C[2]; } *B, C;
      void g() {
      sizeof(B()->C[1]);     // OK, sizeof(expression)
      sizeof(auto()->C[1]);  // error: sizeof of a function returning an array ill-formed expression
    }
    
    -- end example ]
  11. Change in 9.13.1 [dcl.attr.grammar] paragraph 1 as follows:

    alignment-specifier :
      alignas ( nofun-type-id ...opt )
      alignas ( constant-expression ...opt )
    
  12. Change in 9.13.2 [dcl.align] paragraph 3 as follows:

    An alignment-specifier of the form alignas( nofun-type-id ) has the same effect as alignas(alignof( nofun-type-id )) (7.6.2.6 [expr.alignof]).
  13. Do not change 13.8.1 [temp.res.general] paragraph 4.

  14. Change in 13.8.3.3 [temp.dep.expr] paragraph 3 as follows:

    Expressions of the following forms are type-dependent only if the type specified by the type-id, nofun-type-id, simple-type-specifier, typename-specifier, or new-type-id is dependent, even if any subexpression is type-dependent:
      simple-type-specifier ( expression-listopt )
      simple-type-specifier braced-init-list
      typename-specifier ( expression-listopt )
      typename-specifier braced-init-list
      ::opt new new-placementopt new-type-id new-initializeropt
      ::opt new new-placementopt ( nofun-type-id ) new-initializeropt
      dynamic_cast < type-id > ( expression )
      static_cast < type-id > ( expression )
      const_cast < type-id > ( expression )
      reinterpret_cast < type-id > ( expression )
      ( nofun-type-id ) cast-expression
    
  15. Change in 13.8.3.3 [temp.dep.expr] paragraph 4 as follows:

      literal
      sizeof unary-expression
      sizeof ( nofun-type-id )
      sizeof ... ( identifier )
      alignof ( nofun-type-id )
      typeid ( expression )
      typeid ( type-id )
      ::opt delete cast-expression
      ::opt delete [ ] cast-expression
      throw assignment-expressionopt
      noexcept ( expression )
      requires-expression
    
  16. Change in 13.8.3.4 [temp.dep.constexpr] paragraph 2 as follows:

    Expressions of the following form are value-dependent if the unary-expression or expression is type-dependent or the type-id or nofun-type-id is dependent:
      sizeof unary-expression
      sizeof ( nofun-type-id )
      typeid ( expression )
      typeid ( type-id )
      alignof ( nofun-type-id )
    
  17. Change in 13.8.3.4 [temp.dep.constexpr] paragraph 3 as follows:

    Expressions of the following form are value-dependent if either the type-id, nofun-type-id, simple-type-specifier, or typename-specifier is dependent or the expression or cast-expression is value-dependent or any expression in the expression-list is value-dependent or any assignment-expression in the braced-init-list is value-dependent:
      simple-type-specifier ( expression-listopt )
      typename-specifier ( expression-listopt )
      simple-type-specifier braced-init-list
      typename-specifier braced-init-list
      static_cast < type-id > ( expression )
      const_cast < type-id > ( expression )
      reinterpret_cast < type-id > ( expression )
      dynamic_cast < type-id > ( expression )
      ( nofun-type-id ) cast-expression
    

EWG 2025-06-16

EWG agrees with the direction represented by the wording for the May, 2025 notes, above.




2684. thread_local dynamic initialization

Section: 6.10.3.3  [basic.start.dynamic]     Status: ready     Submitter: Jason Merrill     Date: 2023-01-06

Subclause 6.10.3.3 [basic.start.dynamic] paragraph 7 specifies:

It is implementation-defined whether the dynamic initialization of a non-block non-inline variable with thread storage duration is sequenced before the first statement of the initial function of a thread or is deferred. If it is deferred, the initialization associated with the entity for thread t is sequenced before the first non-initialization odr-use by t of any non-inline variable with thread storage duration defined in the same translation unit as the variable to be initialized. ...

How does the rule quoted above affect variables declared constinit? For example:

  extern thread_local constinit int x;

Clang and gcc do not emit suitable wrapper code to allow for x to trigger deferred initialization of other thread-local variables, which is non-conforming.

Proposed resolution (approved by CWG 2026-05-29):

Change in 6.10.3.3 [basic.start.dynamic] paragraph 7 as follows:

It is implementation-defined whether the dynamic initialization of a non-block non-inline variable with thread storage duration is sequenced before the first statement of the initial function of a thread or is deferred. If it is deferred, the initialization associated with the entity for thread t is sequenced before the first non-initialization odr-use by t of any non-inline variable with thread storage duration and dynamic initialization defined in the same translation unit as the variable to be initialized. ...



2756. Completion of initialization by delegating constructor

Section: 11.9  [class.init]     Status: ready     Submitter: Brian Bi     Date: 2023-06-20

Subclause 6.8.4 [basic.life] paragraph 1 specifies:

... The lifetime of an object of type T begins when: except that ...

It is unclear whether initialization is considered complete when the (ultimate) target constructor completes, or when the outermost delegating constructor completes. Subclause 14.3 [except.ctor] paragraph 4 suggests it is the former:

If the compound-statement of the function-body of a delegating constructor for an object exits via an exception, the object's destructor is invoked. ...

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

  1. Split and change 11.9.3 [class.base.init] paragraph 9 as follows:

    [Note 3: An abstract class ... -- end note ] An attempt to initialize more than one non-static data member of a union renders the program ill-formed. [Note 4: After the call to a constructor for class X ... -- end note ] [Example 6: ... -- end example ]

    An attempt to initialize more than one non-static data member of a union renders the program ill-formed.

    An object's initialization is considered complete when a non-delegating constructor for that object returns. [Note: Therefore, an object's lifetime can begin (6.8.4 [basic.life]) before all delegating constructors have completed. -- end note]

  2. Change in 6.8.4 [basic.life] bullet 1.2 as follows:

    ... The lifetime of an object of type T begins when:
    • storage with the proper alignment and size for type T is obtained, and
    • its initialization (if any) is complete (including vacuous initialization) (9.5 [dcl.init], 11.9.3 [class.base.init]),
    except that ...

CWG 2023-10-20

Utterances about "during construction or destruction" in 11.9.5 [class.cdtor] need to be adjusted.

Proposed resolution (approved by CWG 2026-05-19):

  1. Split and change 11.9.3 [class.base.init] paragraph 9 as follows:

    [Note 3: An abstract class ... -- end note ] An attempt to initialize more than one non-static data member of a union renders the program ill-formed. [Note 4: After the call to a constructor for class X ... -- end note ] [Example 6: ... -- end example ]

    An attempt to initialize more than one non-static data member of a union renders the program ill-formed.

    An object's initialization is considered complete when a non-delegating constructor for that object returns. [Note: Therefore, an object's lifetime can begin (6.8.4 [basic.life]) before all delegating constructors have completed. -- end note]

  2. Change in 6.8.4 [basic.life] bullet 1.2 as follows:

    ... The lifetime of an object of type T begins when:
    • storage with the proper alignment and size for type T is obtained, and
    • its initialization (if any) is complete (including vacuous initialization) (9.5 [dcl.init], 11.9.3 [class.base.init]),
    except that ...
  3. Change in 11.9.5 [class.cdtor] paragraph 2 as follows:

    During the construction initialization of an object, if the value of the object or any of its subobjects is accessed through a glvalue that is not obtained, directly or indirectly, from the constructor's this pointer, the value of the object or subobject thus obtained is unspecified.

CWG 2026-05-19

For e.g. a virtual base class VB that inherits constructors, the vtable will not be in its final state while executing inheriting constructors of VB, therefore 11.9.5 [class.cdtor] paragraph 4 through 6 are correct as written.




2974. Non-deduced context for qualified-id naming a template

Section: 13.10.3.6  [temp.deduct.type]     Status: ready     Submitter: Hubert Tong     Date: 2024-12-27

(From submission #660.)

Subclause 13.10.3.6 [temp.deduct.type] bullet 5.1 specifies as a non-deduced context:

This does not cover templates named using a qualified-id:

  template <template <typename> class>
  struct B;

  struct C {
    template <typename>
    struct Nested;
  };

  template <typename T>
  void f(T *, B<T::template Nested> *);

  void g(C *cp) {
    f(cp, 0);           // should be OK
  }

Proposed resolution (approved by CWG 2026-05-29):

Change in 13.10.3.6 [temp.deduct.type] bullet 5.1 as follows:




3023. Default arguments in list-initialization

Section: 9.5.5  [dcl.init.list]     Status: ready     Submitter: Brian Bi     Date: 2025-04-24

Consider:

  #include <iostream>
  struct I {
    I(int x) { std::cout << x; }
  };
  struct S {
    S(I, I = 2) {}
  };
  int main() {
    S(1, 2); std::cout << '\n';  // unspecified order; prints 12 or 21
    S{1, 2}; std::cout << '\n';  // prints 12 (9.5.5 [dcl.init.list] paragraph 4)
    S{1};                        // ???
  }

Since no initializer-clause is present for the second argument, 9.5.5 [dcl.init.list] paragraph 4 does not, but should, prescribe lexical ordering of the argument evaluations.

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

Change in 9.5.5 [dcl.init.list] paragraph 4 as follows:

Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (13.7.4 [temp.variadic]), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list. If the initializer-clauses of the initializer-list are interpreted as arguments of a constructor call (12.2.2.8 [over.match.list]), any default arguments used are evaluated in the order as if an initializer-clause were present for the corresponding parameter.

[Note 4: This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of the initializer-list are interpreted as arguments of a constructor call, even though ordinarily there are no sequencing constraints on the arguments of a call. [ Example:

  #include <iostream>
  struct I {
    I(int x) { std::cout << x; }
  };
  struct S {
    S(I, I = 2) {}
  };
  int main() {
    S(1, 2); std::cout << '\n';  // unspecified order; prints 12 or 21
    S{1, 2}; std::cout << '\n';  // prints 12
    S{1};                        // prints 12
  }
-- end example ]end note]




3027. Equivalence of pack-index-specifiers

Section: 13.6  [temp.type]     Status: ready     Submitter: Corentin Jabot     Date: 2025-04-14

(From submission #700.)

Subclause 13.6 [temp.type] paragraph 5 and paragraph 6 specify:

For a type template parameter pack T, T...[constant-expression] denotes a unique dependent type.

If the constant-expression of a pack-index-specifier is value-dependent, two such pack-index-specifiers refer to the same type only if their constant-expressions are equivalent (13.7.7.2 [temp.over.link]). Otherwise, two such pack-index-specifier s refer to the same type only if their indexes have the same value.

That seems to imply that pack-index-specifiers referring to different template parameter packs could be equivalent.

Proposed resolution (approved by CWG 2026-05-19):

Change and merge in 13.6 [temp.type] paragraph 5 and paragraph 6 as follows:

For a type template parameter pack T, T...[constant-expression] denotes a unique dependent type. If the constant-expression of a pack-index-specifier is value-dependent, two Two such pack-index-specifiers (9.2.9.4 [dcl.type.pack.index]) refer to the same type only if Otherwise, two such pack-index-specifier s refer to the same type only if their indexes have the same value.



3046. Enumerations as part of the common initial sequence

Section: 9.8.1  [dcl.enum]     Status: ready     Submitter: Benjamin Sch.     Date: 2025-07-28

(From submission #732.)

Consider:

  #include <iostream>
  #include <type_traits>

  enum E { E0 } e;
  enum F { F0, F1, F2, F3 };
  static_assert(std::is_same_v<std::underlying_type_t<E>, std::underlying_type_t<F>>);  // assume this passes

  struct A { E e; };
  struct B { F f; };
  union U {
    A a;
    B b;
  } u;

  bool test() {
    return u.a.e == 2;
  }

  auto ptest = test;

  int main() {
    u.b.f = F2;
    std::cout << ptest();
  }

Both u.a and u.b are part of the common initial sequence, allowing u.a.e to read the value of u.a.f, even though E cannot represent all values of F.

Proposed resolution (approved by CWG 2026-05-29):

Change in 9.8.1 [dcl.enum] paragraph 10 as follows:

Two enumeration types are layout-compatible enumerations if they have the same underlying type and the same values.



3146. Usual arithmetic conversions for enumerations are different in C

Section: C.7.4  [diff.expr]     Status: ready     Submitter: Hubert Tong     Date: 2025-12-09

(From submission #829.)

C23 converts values of an enumeration type to the underlying type of the enumeration type (which might be different from int), but enumerators always have type int. In contrast, in C++ enumerators have the type of their enumeration and integral promotions are performed, which are independent of the implementation's choice of underlying type.

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

Add a new entry in C.7.4 [diff.expr] as follows:

Affected subclause: 7.4 [expr.arith.conv]
Change: The usual arithmetic conversions differ, between C and C++, in the treatment of enumeration types with no fixed underlying type. In C++, values of such types are not specified as converting to the underlying type of the enumeration as part of the usual arithmetic conversions. Instead, integral promotions (7.3.7 [conv.prom]) based on the values of the enumeration (9.8.1 [dcl.enum]) are applied.
Rationale: Avoids the difference that can arise in C when replacing an enumeration constant in an expression with a cast of the same enumeration constant to the enumeration type. Allows C++ to treat an enumerator with behavior like that of int even while giving the enumerator the type of its enumeration.
Effect on original feature: Changes to semantics of well-defined feature. Some C expressions that have a dependence upon the implementation-defined underlying type of affected enumeration types will yield different results (as they would if a different underlying type is chosen by the C implementation).
[Example:
  typedef enum { E0 } E;         // the underlying type of E can be unsigned int
  static_assert(((E)E0 - 1 < 0) == (E0 - 1 < 0), "Must pass for C++");
The static_assert may fail in C. -- end example]
Difficulty of converting: Programs must add explicit casts to the underlying type.
How widely used: Rare.



3159. Instantiation of variables with incomplete array types

Section: 13.9.2  [temp.inst]     Status: ready     Submitter: Richard Smith     Date: 2025-04-01

(From submission #843.)

When a variable has an incomplete array type and a braceed initializer, the complete array type can be determined by instantiating a definition of the variable; see 13.9.2 [temp.inst] paragraph 4 and 13.9.2 [temp.inst] paragraph 7. The level of tracking differs between implementations.

  // Case 1: Clang rejects because the type of `*p` changes
  // between redeclarations; Clang seems to be incorrect:
  // The array bound affects the semantics of the program.
  template<typename T> struct X {
    static inline int arr[] = {1, 2, 3};
  };

  // Clang does not instantiate a definition here.
  extern decltype(X<int>::arr) *p;
  // Clang does instantiate a definition here.
  int n = sizeof(X<int>::arr);
  decltype(X<int>::arr) *p;

  // Case 2: GCC, EDG, MSVC reject because they instantiate a definition
  // of `X<int>::arr` even though it's not odr-used and doesn't appear to
  // affect the semantics of the program.
  template<typename T> struct X {
    static inline int arr[] = {1, 2, T::error};
  };

  // The array bound here doesn't matter; array-to-pointer decay doesn't
  // care about it.
  decltype(+X<int>::arr) r;

  // Case 3: GCC and clang accept; MSVC rejects because it instantiates a definition
  // of X<int>::arr even though it's not odr-used and its type is complete.
  // EDG rejects because it instantiates the definition of arr while instantiating X<int>.
  template<typename T> struct X {
    static inline int arr[3] = {1, 2, T::error};
  };

  // No definition needed, thus the initializer is not instantiated.
  decltype(+X<int>::arr) r;

Proposed resolution (approved by CWG 2026-05-08):

Add a new paragraph after 13.9.2 [temp.inst] paragraph 8 as follows:

The existence of a definition of a variable or function is considered to affect the semantics of the program if the variable or function is needed for constant evaluation by an expression (7.7 [expr.const]), even if constant evaluation of the expression is not required or if constant expression evaluation does not use the definition. [ Example: ... ]

Similarly, the existence of a definition of a variable or function is considered to affect the semantics of the program if the instantiation is necessary to determine the type of an expression in the program, even if the variable or function is not odr-used (6.3 [basic.def.odr]). [ Example:

  template<typename T> struct X {
    static inline int arr[] = {1, 2, T::error};
  };
  decltype(+X<int>::arr) r;  // error: definition of X<int>::arr is instantiated to complete its type

  template<typename T> struct X2 {
    static inline int arr[3] = {1, 2, T::error};
  };
  decltype(+X2<int>::arr) r2;  // OK, type of arr is complete and arr is not odr-used

-- end example ]




3164. Location of specification of __STDCPP_FLOAT16_T__ macro

Section: 15.12  [cpp.predefined]     Status: ready     Submitter: Jay Ghiron     Date: 2026-02-15

(From submission #851.)

The macros for the extended floating-point types are presented in a paragraph where unconditionally-defined macros are specified. However, those macros are conditionally defined and their presentation should be moved to the appropriate paragraph.

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

  1. Move the following macro definitions from 15.12 [cpp.predefined] paragraph 1 as follows:

    The following macro names shall be defined by the implementation:
    __STDCPP_FLOAT16_T__
    Defined as the integer literal 1 if and only if the implementation supports the ISO/IEC 60559 floating-point interchange format binary16 as an extended floating-point type (6.9.3 [basic.extended.fp]).
    __STDCPP_FLOAT32_T__
    Defined as the integer literal 1 if and only if the implementation supports the ISO/IEC 60559 floating-point interchange format binary32 as an extended floating-point type.
    __STDCPP_FLOAT64_T__
    Defined as the integer literal 1 if and only if the implementation supports the ISO/IEC 60559 floating-point interchange format binary64 as an extended floating-point type.
    __STDCPP_FLOAT128_T__
    Defined as the integer literal 1 if and only if the implementation supports the ISO/IEC 60559 floating-point interchange format binary128 as an extended floating-point type.
    __STDCPP_BFLOAT16_T__
    Defined as the integer literal 1 if and only if the implementation supports an extended floating-point type with the properties of the typedef-name std::bfloat16_t as described in 6.9.3 [basic.extended.fp].
  2. Add the macro definitions to 15.12 [cpp.predefined] paragraph 2 with the indicated changes as follows:

    The following macro names are conditionally defined by the implementation:
    __STDCPP_FLOAT16_T__
    Defined, and has the value integer literal 1, if and only if the implementation supports the ISO/IEC 60559 floating-point interchange format binary16 as an extended floating-point type (6.9.3 [basic.extended.fp]).
    __STDCPP_FLOAT32_T__
    Defined, and has the value integer literal 1, if and only if the implementation supports the ISO/IEC 60559 floating-point interchange format binary32 as an extended floating-point type.
    __STDCPP_FLOAT64_T__
    Defined , and has the value integer literal 1, if and only if the implementation supports the ISO/IEC 60559 floating-point interchange format binary64 as an extended floating-point type.
    __STDCPP_FLOAT128_T__
    Defined, and has the value integer literal 1, if and only if the implementation supports the ISO/IEC 60559 floating-point interchange format binary128 as an extended floating-point type.
    __STDCPP_BFLOAT16_T__
    Defined, and has the value integer literal 1, if and only if the implementation supports an extended floating-point type with the properties of the typedef-name std::bfloat16_t as described in 6.9.3 [basic.extended.fp].



3166. Protected access rule for a pointer-to-member from a reflect-expression

Section: 7.6.2.10  [expr.reflect]     Status: ready     Submitter: Brian Bi     Date: 2026-02-17

(From submission #852.)

Consider:

  class A {
  protected:
    void f();
  };

  struct B : A {
    static constexpr auto r = ^^A::f;
  };

Do the restrictions in 11.8.5 [class.protected] on the use of expression &A::f apply to the reflect-expression?

Suggested resolution [SUPERSEDED]:

Change in 7.6.2.10 [expr.reflect] bullet 7.2 as follows:

A reflect-expression R of the form ^^id-expression represents an entity determined as follows:

CWG 2026-05-08

Reflection supports deleted functions, but the phrasing above does not.

Proposed resolution (approved by CWG 2026-05-19):

Change in 7.6.2.10 [expr.reflect] bullet 7.2 as follows:

A reflect-expression R of the form ^^id-expression represents an entity determined as follows:



3167. Initializing typedefs

Section: 9.5  [dcl.init]     Status: ready     Submitter: Bee     Date: 2026-02-27

(From submission #856.)

Consider:

  typedef int x = 0;

There is no prohibition against this.

Proposed resolution (approved by CWG 2026-05-08):

  1. Add before 9.3.1 [dcl.decl.general] paragraph 4 and change paragraphs 4 and 5 as follows:

    The initializer of an init-declarator shall not be present unless the declarator declares a variable and the host scope (6.4.1 [basic.scope.scope]) of the declaration is the same as its target scope.

    The optional requires-clause in of an init-declarator or member-declarator shall not be present only if unless the declarator declares a templated function (13.1 [temp.pre]). ...
    The optional function-contract-specifier-seq (9.4.1 [dcl.contract.func]) in of an init-declarator shall not be present only if unless the declarator declares a function.
  2. Remove 9.5.1 [dcl.init.general] paragraph 5 as follows:

    A declaration D of a variable with linkage shall not have an initializer if D inhabits a block scope.



3168. Issues with integer conversion ranks

Section: 6.9.6  [conv.rank]     Status: ready     Submitter: Jay Ghiron     Date: 2026-03-01

(From submission #856.)

First, the rule about the conversion rank of signed integer types wrongly implies that char, if it is signed, would be a signed integer type.

Second, the rule about the conversion rank of bool allows it have a greater rank than extended integer types.

See also WG14 paper N3747.

Proposed resolution (approved by CWG 2026-05-08):

Change in 6.9.6 [conv.rank] paragraph 1 as follows:

Every integer type has an integer conversion rank defined as follows:



3176. Sequencing default arguments during constant evaluation

Section: 6.10.1  [intro.execution]     Status: ready     Submitter: Lénárd Szolnoki     Date: 2026-03-09

(From submission #859.)

According to 6.10.1 [intro.execution] paragraph 16, lexical order is used for sequencing during constant evalution. It is unclear how that applies to default arguments used in a function call.

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

Change in 6.10.1 [intro.execution] paragraph 16 as follows:

During the evaluation of an expression as a core constant expression (7.7 [expr.const]), evaluations of operands of individual operators and of subexpressions of individual expressions that are otherwise either unsequenced or indeterminately sequenced are evaluated in lexical order. For a function call (7.6.1.3 [expr.call]), any default arguments used are evaluated in the order as if an argument were present for the corresponding parameter.



3177. Formation of a header-name preprocessing token

Section: 5.5  [lex.pptoken]     Status: ready     Submitter: Richard Smith     Date: 2026-04-08

(From submission #881.)

The rules inn 5.5 [lex.pptoken] bullet 5.4.2.1 and 15.1 [cpp.pre] bullet 2.2 form a circular definition when specifying the formation of a header-name preprocessing token.

Proposed resolution (approved by CWG 2026-04-17):

Change in 5.5 [lex.pptoken] bullet 5.4 as follows:




3179. More edge cases for a void function parameter

Section: 9.3.4.6  [dcl.fct]     Status: ready     Submitter: Matheus Izvekov     Date: 2026-04-21

(From submission #889.)

Consider:

  template<class T>
  struct S {
    using U = decltype((void)(T*)0);
    void f(U);   // #1
  };
  template<class T>
  void S<T>::f() {}  // redeclaration of #1?

Also consider:

  template<class T>
  struct S {
    void f(std::void_t<T*>);  // #1
  };
  S<int> x;  // #1 is a zero-parameter function or has an ill-formed parameter of type void

The current phrasing in 9.3.4.6 [dcl.fct] paragraph 2 is the result of core issues 577 and 2915. The current rule, enabling typedefs for void, is primarily motivated by C compatibility.

Possible resolution (2026-04-22) [SUPERSEDED]:

Change in 9.3.4.6 [dcl.fct] paragraph 3 as follows and split into two paragraphs:

The parameter-declaration-clause determines the arguments that can be specified, and their processing, when the function is called. [Note 1: The parameter-declaration-clause is used to convert the arguments specified on the function call; see 7.6.1.3 [expr.call]. —end note] If the parameter-declaration-clause is empty, the function takes no arguments. A parameter list (void) and, for a non-templated function, a parameter list consisting of a single unnamed non-object parameter of non-dependent type void is are equivalent to an empty parameter list. Except for this these special case cases, a parameter shall not have type cv void. A parameter with volatile-qualified type is deprecated; see D.4 [depr.volatile.type].

If the parameter-declaration-clause terminates with an ellipsis or a function parameter pack (13.7.4 [temp.variadic]), the number of arguments shall be equal to or greater than the number of parameters that do not have a default argument and are not function parameter packs. Where syntactically correct and where “...” is not part of an abstract-declarator , “...” is synonymous with “, ...”. A parameter-declaration-clause of the form parameter-declaration-list ... is deprecated (D.5 [depr.ellipsis.comma]). ...

CWG 2026-04-28

Despite the fact that "equivalent type" is not well-defined (see issue 2584), the most appropriate approach is to use "equivalent type". Both examples above are ill-formed.

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

Change in 9.3.4.6 [dcl.fct] paragraph 3 as follows and split into two paragraphs:

The parameter-declaration-clause determines the arguments that can be specified, and their processing, when the function is called. [Note 1: The parameter-declaration-clause is used to convert the arguments specified on the function call; see 7.6.1.3 [expr.call]. —end note] If the parameter-declaration-clause is empty, the function takes no arguments. A parameter list consisting of a single unnamed non-object parameter of non-dependent whose type is the same as (13.7.7.2 [temp.over.link]) void is equivalent to interpreted as an empty parameter list. Except for this special case, a parameter shall not have type cv void. A parameter with volatile-qualified type is deprecated; see D.4 [depr.volatile.type].

[ Example:
  template<class T>
  struct S {
    void f(std::void_t<T*>);  // #1
  };
  S<int> x;  // error: #1 has a parameter of type void

  template<class T>
  struct S2 {
    void f(std::void_t<int*>);  // #2
  };
  S2<int> y;  // OK, #2 is a zero-parameter function
-- end example ]

If the parameter-declaration-clause terminates with an ellipsis or a function parameter pack (13.7.4 [temp.variadic]), the number of arguments shall be equal to or greater than the number of parameters that do not have a default argument and are not function parameter packs. Where syntactically correct and where “...” is not part of an abstract-declarator , “...” is synonymous with “, ...”. A parameter-declaration-clause of the form parameter-declaration-list ... is deprecated (D.5 [depr.ellipsis.comma]). ...




3182. Notional template parameters introduced for an array parameter

Section: 13.10.3.2  [temp.deduct.call]     Status: ready     Submitter: Brian Bi     Date: 2026-04-09

(From submission #883.)

Subclause 13.10.3.2 [temp.deduct.call] paragraph 1 states:

... If removing references and cv-qualifiers from P gives std::initializer_list<P'> or P'[N] for some P' and N and the argument is a non-empty initializer list (9.5.5 [dcl.init.list]), then deduction is performed instead for each element of the initializer list independently, taking P' as separate function template parameter types P'i and the ith initializer element as the corresponding argument. ...

There is no requirement that the deduction results for all such independent deductions agree, or be non-deduced contexts.

Proposed resolution (approved by CWG 2026-05-19):

Change in 13.10.3.2 [temp.deduct.call] paragraph 1 as follows:

... If removing references and cv-qualifiers from P gives std::initializer_list<P'> or P'[N] for some P' and N and the argument is a non-empty initializer list (9.5.5 [dcl.init.list]), then deduction is instead performed instead for each element of the initializer list independently, taking P' as separate function template parameter types P'i and the ith initializer element as the corresponding argument as if each element of the initializer list were the argument for a separate parameter having type P'. ...



3183. No expressions in an expression-list

Section: 7.6.1.3  [expr.call]     Status: ready     Submitter: Vlad Serebrennikov     Date: 2026-04-13

(From submission #886.)

Consider 7.6.1.3 [expr.call] paragraph 7:

The postfix-expression is sequenced before each expression in the expression-list and any default argument. ...

However, an expression-list is an initializer-list and consists of initializer-clause.

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

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

The postfix-expression is sequenced before each expression initializer-clause in the expression-list and any default argument. ...



3184. Pointer arithmetic with similar types

Section: 7.6.6  [expr.add]     Status: ready     Submitter: Jay Ghiron     Date: 2026-04-26

(From submission #892.)

Consider:

char**p;
int diff = p - (const char * const *)p;

This is ill-formed per 7.6.6 [expr.add] paragraph 2, but ought to be allowed, in harmony with 7.6.9 [expr.rel] paragraph 3, 7.6.10 [expr.eq] paragraph 3, and 7.6.16 [expr.cond] paragraph 4.

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

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

For subtraction, one of the following shall hold:



3186. Argument-dependent lookup is for a name

Section: 6.5.4  [basic.lookup.argdep]     Status: ready     Submitter: CWG     Date: 2026-04-25

(From submission #891.)

Consider:

  namespace N {
    struct C {
      friend void f(C);
      friend void g(C);
    };
  }

  void h() {
    f(N::C{});
  }

According to 6.5.4 [basic.lookup.argdep] paragraph 4, argument-dependent lookup finds functions without considering their names (such as g in the example above). This is misguided.

Proposed resolution (approved by CWG 2026-05-19):

Change in 6.5.4 [basic.lookup.argdep] paragraph 4 as follows:

The associated namespaces for a call are the innermost enclosing non-inline namespaces for its associated entities as well as every element of the inline namespace set (9.9.2 [namespace.def]) of those namespaces. Argument-dependent lookup for a name N finds all declarations of functions and function templates named N that If the lookup is for a N is a dependent name (13.8.3 [temp.dep], 13.8.4.2 [temp.dep.candidate]), the above lookup is also performed from each point in the instantiation context (10.6 [module.context]) of the lookup, additionally ignoring any declaration that appears in another translation unit, is attached to the global module, and is either discarded (10.4 [module.global.frag]) or has internal linkage.



3188. Behavior change for class template argument deduction

Section: C.2  [diff.cpp20]     Status: ready     Submitter: Christof Meerwald     Date: 2026-04-30

Consider:

   template<typename T>
   struct B {
     B(T);
   };

   template<typename T>
   struct D : B<T *> {
     D(T);
     using B<T *>::B;
     T m = "a";   // #1
   };

   D d("");       // #1 is OK with C++20 (T deduced as const char*), but ill-formed with C++23 (T deduced as const char)

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

Add a new subclause in C.2 [diff.cpp20] as follows:

C.2.5+ Clause 12: overloading [diff.cpp20.over]

Affected subclause: 12.2.2.9 [over.match.class.deduct]
Change: Deducing class template arguments from inherited constructors.
Rationale: Making class template argument deduction consistent with construction.
Effect on original feature: Valid ISO C++ 2020 code may become ill-formed or change meaning.
[Example 1 :
   template<typename T>
   struct B {
     B(T);
   };

   template<typename T>
   struct D : B<T *> {
     D(T);
     using B<T *>::B;
     T m = "a";   // #1
   };

   D d("");       // ill-formed at #1 (T deduced as const char); previously well-formed (T deduced as const char*)
-- end example]



3190. Ambiguous lookup for type aliases in reflection

Section: 7.6.2.10  [expr.reflect]     Status: ready     Submitter: Richard Smith     Date: 2026-05-06

Consider:

  namespace A {
    using T = int;
  }
  namespace B {
    using T = int;
  }
  using namespace A;
  using namespace B;
  T t;                // #1, OK
  auto r = ^^T;       // #2, ???

While 6.1 [basic.pre] paragraph 8 clarifies that #1 is well-formed by considering the underlying type, the treatment of #2 is unclear.

Possible resolution (option 2) [SUPERSEDED]:

Change in 7.6.2.10 [expr.reflect] paragraph 5 as follows:

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

Change in 7.6.2.10 [expr.reflect] paragraph 5 as follows:

CWG 2026-05-19

Seeking EWG's approval via paper issue #2795.

EWG 2026-06-08

EWG approved the proposed resolution.