| Document number: | P4271R0 |
| Date: | 2026-06-12 |
| Project: | Programming Language C++ |
| Reference: | ISO/IEC 14882:2024 |
| Reply to: | Jens Maurer |
| jens.maurer@gmx.net |
References in this document reflect the section and paragraph numbering of document WG21 N5046.
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):
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
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.
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.
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
Change in 7.6.3 [expr.cast] paragraph 2 as follows:
cast-expression :
unary-expression
( nofun-type-id ) cast-expression
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 .
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.
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
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 doneA 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.
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 ]
Change in 9.13.1 [dcl.attr.grammar] paragraph 1 as follows:
alignment-specifier : alignas ( nofun-type-id ...opt ) alignas ( constant-expression ...opt )
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]).
Do not change 13.8.1 [temp.res.general] paragraph 4.
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
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
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 )
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.
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. ...
Subclause 6.8.4 [basic.life] paragraph 1 specifies:
... The lifetime of an object of type T begins when:except that ...
- 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]),
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]:
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]
Change in 6.8.4 [basic.life] bullet 1.2 as follows:
... The lifetime of an object of type T begins when:except that ...
- 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]),
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):
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]
Change in 6.8.4 [basic.life] bullet 1.2 as follows:
... The lifetime of an object of type T begins when:except that ...
- 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]),
Change in 11.9.5 [class.cdtor] paragraph 2 as follows:
During theconstructioninitialization 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.
(From submission #660.)
Subclause 13.10.3.6 [temp.deduct.type] bullet 5.1 specifies as a non-deduced context:
- The nested-name-specifier of a type that was specified using a qualified-id.
- ...
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:
- The nested-name-specifier of a type or template that was specified using a qualified-id.
- ...
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]
(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, twoTwo such pack-index-specifiers (9.2.9.4 [dcl.type.pack.index]) refer to the same type only if
- their typedef-names refer to the same template parameter pack and
- if neither of their constant-expressions is value-dependent, then they have the same value, otherwise 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.
(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.
(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.
(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 ]
(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):
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].
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].
(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:
- ...
- Otherwise, if the id-expression denotes an overload set S,
overload resolution for the expression &S with no target shall select a unique function (12.3 [over.over])the expression &id-expression shall be well-formed when considered as an unevaluated operand; R representsthatthe function selected by overload resolution (12.3 [over.over]).
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:
- ...
- Otherwise, if the id-expression denotes an overload set S,
overload resolution for the expression &S with no target shall select a unique function (12.3 [over.over])the expression &id-expression shall be well-formed when considered as an unevaluated operand, except that the function F selected as described in 12.3 [over.over] may be deleted (9.6.3 [dcl.fct.def.delete]); R representsthat functionF .
(From submission #856.)
Consider:
typedef int x = 0;
There is no prohibition against this.
Proposed resolution (approved by CWG 2026-05-08):
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-clauseinof an init-declarator or member-declarator shall not be presentonly ifunless the declarator declares a templated function (13.1 [temp.pre]). ...
The optional function-contract-specifier-seq (9.4.1 [dcl.contract.func])inof an init-declarator shall not be presentonly ifunless the declarator declares a function.
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.
(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:
- No two signed integer types
other than char and signed char (if char is signed)have the same rank, even if they have the same representation.- ...
- The rank of char equals the rank of signed char and unsigned char.
- The rank of bool is less than the rank of all
standardother integer types.- The ranks of
char8_t, char16_t, char32_t, and wchar_tthe character types equal the ranks of their corresponding underlying types (6.9.2 [basic.fundamental]).- ...
(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.
(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:
- ...
- Otherwise, the next preprocessing token is the longest sequence of characters that could constitute a preprocessing token, even if that would cause further lexical analysis to fail, except that
- a string-literal token is never formed when a header-name token can be formed, and
- a header-name (5.6 [lex.header]) is only formed
A preprocessing token is considered to be immediately after another preprocessing token if the preprocessing tokens are on the same logical source line and there are no intervening preprocessing tokens.
- immediately after the include
,or embed, or importpreprocessing token in a #include (15.3 [cpp.include]),or #embed (15.4 [cpp.embed]), or import (15.6 [cpp.import])directive, respectively, or- immediately after an import preprocessing token that is at the start of a logical source line, or
- immediately after a preprocessing token sequence of __has_include or __has_embed immediately followed by ( in a #if, #elif, or #embed directive (15.2 [cpp.cond], 15.4 [cpp.embed]).
(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-dependenttype voidisare equivalent to an empty parameter list. Except forthisthese specialcasecases, 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-dependentwhose type is the same as (13.7.7.2 [temp.over.link]) void isequivalent tointerpreted 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]). ...
(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 performedinstead 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 argumentas if each element of the initializer list were the argument for a separate parameter having type P'. ...
(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 eachexpressioninitializer-clause in the expression-list and any default argument. ...
(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:
- both operands have arithmetic type; or
- both operands are pointers to
cv-qualified or cv-unqualified versions of the samesimilar (7.3.6 [conv.qual])completely-definedcomplete objecttypetypes; or- the left operand is a pointer to a completely-defined object type and the right operand has integral type.
(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 thatIf
- are found by a search
offor N in any associated namespace, or- are declared as a friend (11.8.4 [class.friend]) of any class with a reachable definition in the set of associated entities, or
- are exported, are attached to a named module M (10.2 [module.interface]), do not appear in the translation unit containing the point of the lookup, and have the same innermost enclosing non-inline namespace scope as a declaration of an associated entity attached to M (6.7 [basic.link]).
the lookup is for aN 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.
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]
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:
- ...
- Otherwise, if lookup finds a namespace alias (9.9.3 [namespace.alias]), R represents
thatan unspecified namespace alias among the lookup results.- ...
- Otherwise, if lookup finds a type alias A, R represents the underlying entity of A if A was introduced by the declaration of a template parameter; otherwise, R represents
Aan unspecified type alias among the lookup results.[ Example:namespace A { using T = int; } namespace B { using T = int; } using namespace A; using namespace B; auto r = ^^T; // OK, represents either A::T or B::T-- end example ]- ...
Proposed resolution (approved by CWG 2026-06-10):
Change in 7.6.2.10 [expr.reflect] paragraph 5 as follows:
- ...
- Otherwise, if lookup finds a namespace alias (9.9.3 [namespace.alias]), all declarations found by name lookup shall have the same target scope, and R represents
thatthe namespace alias.- ...
- Otherwise, if lookup finds a type alias
A,:
- If any declaration found by name lookup is of a template parameter T, R represents the underlying entity of T.
A if A was introduced by the declaration of a template parameter; otherwise,- Otherwise, all declarations found by name lookup shall have the same target scope, and R represents
Athe type alias.[ Example:namespace A { using T = int; } namespace B { using T = int; } using namespace A; using namespace B; auto r = ^^T; // error: lookup finds type aliases with different target scopes-- end example ]- ...
CWG 2026-05-19
Seeking EWG's approval via paper issue #2795.
EWG 2026-06-08
EWG approved the proposed resolution.