1. Changelog
-
R4 (pre-Búzios):
-
Add two examples for clarity.
-
Make
invariably ill-formed, rather than "ill-formed if non-deleted, well-formed if deleted." This harmonizes the wordings added to [class.copy.assign] and [over.inc.default]. Adjust § 3.2 "Defaulted as deleted" and § 3.7 Impact on existing code to match.auto * operator ++ ( int ) = default ; -
Make
deduceauto operator <=> ( const X & ) const = default ; if it is defaulted-as-deleted, rather than remaining undeduced. This has the benefit of makingstrong_ordering invariably ill-formed. In R3 it was ill-formed only if non-deleted.auto * operator <=> ( const X & ) const = default ;
-
-
R3 (pre-Búzios):
-
Wording changes after CWG review. Add the Annex C example.
-
Change paper title to be more descriptive of the full scope of the paper.
-
Update divergence table in § 3.6 Existing corner cases; list compiler versions tested.
-
-
R2 (pre-Hagenberg):
-
Forward to CWG (see § 5 Straw poll results).
-
Fix one mistake in a drafting note ("deleted" to "ill-formed").
-
-
R1:
-
Add discussion of
, and of triviality, in § 3.5 Other return types are still forbidden.void operator = ( X && ) = default
-
2. Motivation
Current C++ permits to appear only on certain signatures, with certain return types.
The current wording prohibits the use of placeholder types such as to express these return
types, with the single exception of C++20’s . This leads to redundant repetition,
such as in this real code from libc++'s test suite:
| Today | After P2952 |
|---|---|
|
|
The comparison operators are inconsistent among themselves:
can deduce , but the others cannot deduce .
| Today | After P2952 |
|---|---|
|
|
The status quo is inconsistent between non-defaulted and defaulted functions,
making it unnecessarily tedious to upgrade to :
| Today | After P2952 |
|---|---|
|
|
|
|
|
|
The ill-formedness of these declarations comes from overly restrictive wording in the standard,
such as [class.eq]/1
specifically requiring that a defaulted equality
operator must have a declared return type of , instead of simply specifying that its return type
must be .
We believe each of the examples above has an intuitively clear meaning: the placeholder return type
correctly matches the type which the defaulted body will actually return. We propose to loosen the
current restrictions and permit these declarations to be well-formed.
This proposal does not seek to change the set of valid return types for these functions.
We propose a purely syntactic change to expand the range of allowed declaration syntax, not semantics.
(But we do one drive-by clarification which we believe matches EWG’s original intent:
if an empty class’s defaulted returns a non-comparison-category type,
it should be defaulted as deleted.)
3. Proposal
We propose that a defaulted function declaration with a placeholder return type should have its type deduced ([dcl.spec.auto.general]) as if from a fictional return statement that returns:
-
a prvalue of type
, in the case ofbool ,operator == ,operator != ,operator < ,operator > , oroperator <= operator >= -
a prvalue of type
, in the case ofQ ; whereoperator <=> is the common comparison type of R0, R1,... Rn-1Q -
an lvalue of type
, in the case ofC for a class typeoperator = C -
an rvalue of type
, in the case ofC oroperator ++ for a class typeoperator -- C
Then, the deduced return type is compared to the return type(s) permitted by the standard. If the types match, the declaration is well-formed. Otherwise it’s ill-formed.
struct MyClass { auto & operator = ( const MyClass & ) = default ; // Proposed OK: deduces MyClass& decltype ( auto ) operator = ( const MyClass & ) = default ; // Proposed OK: deduces MyClass& auto && operator = ( const MyClass & ) = default ; // Proposed OK: deduces MyClass& const auto & operator = ( const MyClass & ) = default ; // Still ill-formed: deduced const MyClass& is not MyClass& auto operator = ( const MyClass & ) = default ; // Still ill-formed: deduced MyClass is not MyClass& auto * operator = ( const MyClass & ) = default ; // Still ill-formed: deduction fails void operator = ( const MyClass & ) = default ; // Still ill-formed: void is not MyClass& };
For , our proposal gives the following behavior:
struct MyClass { auto operator == ( const MyClass & ) const = default ; // Proposed OK: deduces bool decltype ( auto ) operator == ( const MyClass & ) const = default ; // Proposed OK: deduces bool auto && operator == ( const MyClass & ) const = default ; // Still ill-formed: deduced bool&& is not bool auto & operator == ( const MyClass & ) const = default ; // Still ill-formed: deduction fails };
3.1. "Return type" versus "declared return type"
Today, vendors unanimously reject .
But we can’t find any wording in [class.copy.assign]
or [dcl.fct.def.default] that directly justifies
this behavior. It seems that vendors are interpreting e.g. [dcl.fct.def.default]/2.5’s
"[if] the return type of F1 differs from the return type of F2"
to mean "the declared return type of F1," even though newer sections such as
[class.compare] consistently distinguish
the "declared return type" from the (actual) return type.
We tentatively propose to leave [dcl.fct.def.default] alone, and simply add an example that indicates the (new) intent of the (existing) wording: that it should now be interpreted as talking about the assignment operator’s actual return type, not its declared (placeholder) return type.
3.2. "Defaulted as deleted"
The current wording for comparison operators is crafted so that the following is well-formed.
Its is defaulted as deleted (so that operator is unusable), but the instantiation
of class itself is OK. We need to preserve this in our rewriting.
(Godbolt.)
template < class T > struct Container { T t ; auto operator <=> ( const Container & ) const = default ; }; Container < std :: mutex > cm ; // OK, <=> is deleted struct Weird { int operator <=> ( Weird ) const ; }; Container < Weird > cw ; // OK, <=> is deleted because Weird's operator<=> // returns a non-comparison-category type
Here, [CWG3207] is relevant. is deleted because
is not comparable. We propose that the return type of the deleted should be
. This means the return type of is still a comparison category
type, so that is not ill-formed.
Until R3, we considered the following case to be similar:
But now this use of template parametertemplate < class R > struct C { int i ; R operator <=> ( const C & ) const = default ; }; static_assert ( std :: three_way_comparable < C < std :: strong_ordering >> ); static_assert ( ! std :: three_way_comparable < C < int >> ); // Today: OK, C<int>'s operator<=> is deleted // Proposed: ill-formed, a defaulted operator<=> can't have return type int
R as the return type of operator <=> , without forcing R
to be a comparison category type at all, strikes us as contrived and thus not necessary to support.
We aim particularly to make this simplest case ill-formed, instead of deleted:
struct C { int operator <=> ( const C & ) const = default ; // Today: OK, defined as deleted // Proposed: ill-formed, a defaulted operator<=> can't have return type int };
3.3. "Deducing this " and CWG2586
[CWG2586] (adopted for C++23) permits defaulted functions to have explicit object parameters.
This constrained the wording we could choose for : we couldn’t say “the return type is deduced
as if from ” because there might not be a .
There’s a quirk with rvalue-ref-qualified assignment operators — not move assignment, but assignment where the destination object is explicitly rvalue-ref-qualified.
-
deduces an lvalue referenceauto && operator = ( const B & ) && { return * this ; } -
deduces an rvalue referenceauto && operator = ( this B && self , const B & ) { return self ; }
Nonetheless, a defaulted assignment operator always returns an lvalue reference ([class.copy.assign]/6, [dcl.fct.def.default]/2.5), regardless of whether it’s declared using explicit object syntax.
struct A { A & operator = ( const A & ) && = default ; // OK today A && operator = ( const A & ) && = default ; // Ill-formed, return type isn't A& decltype ( auto ) operator = ( const A & ) && { return * this ; } // OK, deduces A& decltype ( auto ) operator = ( const A & ) && = default ; // Ill-formed since P2953; would have deduced A& }; struct B { B & operator = ( this B && self , const B & ) { return self ; } // Error, self can't bind to B& B && operator = ( this B && self , const B & ) { return self ; } // OK decltype ( auto ) operator = ( this B && self , const B & ) { return self ; } // OK, deduces B&& B & operator = ( this B & self , const B & ) = default ; // OK B & operator = ( this B && self , const B & ) = default ; // OK B && operator = ( this B && self , const B & ) = default ; // Ill-formed, return type isn't B& decltype ( auto ) operator = ( this B && self , const B & ) = default ; // Proposed OK, deduces B& };
Defaulted rvalue-ref-qualified assignment operators are weird; C++29 has adopted our [P2953] which forbids them entirely. Either way, P2952 doesn’t need to treat them specially. Defaulted assignment operators invariably return lvalue references, so we invariably deduce as-if-from an lvalue reference, full stop.
3.4. Burden on specifying new defaultable operators
We propose to leave [dcl.fct.def.default] alone and reinterpret its term "return type" to mean the actual return type, not the declared return type. This will, by default, permit the programmer to use placeholder return types on their defaulted operators. So there is a new burden on the specification of the defaultable operator, to specify exactly how return type deduction works for the implicitly defined operator.
operator ++ ( int ) defaultable in
the same way as a secondary comparison operator, by
adding wording to [over.inc].
P2952 needs to modify this wording (see § 6.5 [over.inc.default] below)
to specify exactly how return type deduction works for defaulted increment and decrement operators.
3.5. Other return types are still forbidden
Notice that returning any type other than from a defaulted remains ill-formed.
For example, we don’t propose to accept:
Any paper that did propose to permit defaultingstruct X { void operator = ( X && ) = default ; // still ill-formed };
void operator = would have to consider very carefully
whether to call that operator "trivial." Today,
is_trivially_move_assignable_v < T > is true only when
decltype ( declval < T &> () = declval < T > ()) is T & . It’s just barely conceivable that existing
user code might rely on that fact, and break when presented with a trivial assignment
operator that returned void instead. And certainly the return-by-copy X operator = ( X && ) = default
shouldn’t be considered "trivial" for non-trivially-copy-constructible X .
But none of this is a problem for this proposal P2952, because we propose to continue rejecting
a defaulted that returns any type but . We merely propose to allow the programmer
to spell that return type using a placeholder such as .
3.6. Existing corner cases
There is vendor divergence in some corner cases. Here is a table of the divergences we found, plus our opinion as to the conforming behavior, and our proposed behavior. The compilers tested are GCC 16, Clang 23, MSVS 18.6, and EDG 6.9.
| URL | Code | GCC | Clang | MSVC | EDG | Correct |
|---|---|---|---|---|---|---|
| link |
| ✗ | ✗ | ✗ | ✓ | ✗ |
| link |
| ✓ | ✗ | ✓ | ✓ | ✓? |
| link |
| ✓ | ✗ | ✗ | ✗ | Today: ✗ Proposed: ✓ |
| link |
| ✗ | ✗ | ✓ | ✓ | Today: ✗ Proposed: ✗ |
| link |
| ✓ | ✗ | ✓ | ✓ | Today: ✗ Proposed: ✓ |
| link |
| unmet | ✗ | ✓ | ✓ | Today: ✗ Proposed: unmet |
| link |
| deleted | unusable | ✗ | deleted | Today: deleted? Proposed: ✗ |
| link |
| ✓ | ✓ | ✗ | deleted | Today: ✓ Proposed: ✗ |
| link |
| deleted | unusable | ✗ | deleted | Today: ? Proposed: ✗ |
| link |
| ✓ | ✓ | ✗ | deleted | Today: ✓ Proposed: ✗ |
| link |
| deleted | ✓ | ✗ | deleted | Today: ✓ Proposed: ✗ |
| link |
| ICE | ✓ | ✓ | deleted | deleted |
| link |
| ✓ | ✓ | ✓ | deleted | Today: ✓ Proposed: ✗ |
| link |
| ✓ | ✓ | noexcept | incon- sistent | ✓ |
| link |
| ✓ | ✓ | ✓ | ✓ | ✓ |
| link |
| deleted | deleted | ✗ | deleted | deleted |
| link |
| deleted | deleted | ✓ | deleted | deleted |
3.7. Impact on existing code
There should be little effect on existing code, since this proposal mainly allows syntax that was ill-formed before. As shown in § 3.6 Existing corner cases, we do propose to change some arcane and contrived examples, e.g.
struct U { U ( std :: strong_ordering ); }; struct C { U operator <=> ( const C & ) const = default ; // Today: Well-formed, non-deleted (GCC and Clang are correct) // Tomorrow: Ill-formed (MSVC becomes correct) }; struct D { int i ; int operator <=> ( const D & ) const = default ; // Today: Well-formed, deleted (Clang and EDG are correct) // Tomorrow: Ill-formed (MSVC becomes correct) };
4. Implementation experience
None yet.
5. Straw poll results
Arthur O’Dwyer presented P2952R1 in the EWG telecon of 2025-01-08. The following straw polls were taken. The first was interpreted as "no consensus"; the second was interpreted as consensus (pending electronic polling).
| SF | F | N | A | SA | |
|---|---|---|---|---|---|
| EWG prefers this paper contains the change in P2953
(banning explicitly defaulted operator= with rvalue ref-qualifier). [Chair: This means EWG wants to see this paper again.] | 2 | 4 | 9 | 1 | 0 |
| Forward P2952R1 to CWG for inclusion in C++26, pending online polling. | 3 | 10 | 4 | 1 | 0 |
The electronic poll was taken as follows:
| SF | F | N | A | SA | |
|---|---|---|---|---|---|
| Forward P2952R1 to CWG for C++26 (pending confirmation during in-person meeting). | 3 | 6 | 1 | 1 | 0 |
This paper was presented again to EWG in Hagenberg. The following poll was taken:
| SF | F | N | A | SA | |
|---|---|---|---|---|---|
| P2952R2 auto& operator=(X&&) = default: forward to CWG for C++26. | 5 | 25 | 13 | 5 | 4 |
6. Proposed wording
6.1. [class.eq]
DRAFTING NOTE:
The phrase "equality operator function" ([over.binary])
means or . But is not covered by [class.eq]; it’s covered by [class.compare.secondary] below.
The new example is modeled on [dcl.spec.auto.general] Example 3.
Modify [class.eq] as follows:
1․ A defaulted
equalityoperator function ([over.binary]) shall have== a declaredthe return type. If its declared return type contains a placeholder type, its return type is deduced as if frombool .return true; 2․ A defaulted
operator function for a class== is defined as deleted unless, for eachC i in the expanded list of subobjects for an objectx of typex ,C ix i is usable ([class.compare.default]).== x 3․ The return value of a defaulted
operator function with parameters== andx is determined by comparing corresponding elementsy i andx i in the expanded lists of subobjects fory andx (in increasing index order) until the first index i wherey ix == iy yields a result value which,whencontextually converted to, yieldsbool false. The return value isfalseif such an index exists andtrueotherwise.4․ [Example 1:
— end example]struct D { int i ; friend bool operator == ( const D & x , const D & y ) = default ; // OK, returns x.i == y.i }; struct E { struct N {} n ; auto * operator == ( const E & ) const = default ; // error: cannot deduce auto* from true };
6.2. [class.spaceship]
DRAFTING NOTE:
There are only three "comparison category types" in C++, and is implicitly convertible
to all three of them. The status quo already effectively forbids to return a non-comparison-category type,
since either is deduced as a common comparison type (which is a comparison category type by definition), or else
a synthesized three-way comparison of type must exist (which means must be a comparison category type),
or else the sequence i must be empty (in which case there are no restrictions on except
that it be constructible from ). We strengthen the wording to directly mandate that the
return type be a comparison category type, even in the empty case.
DRAFTING NOTE:
The wording below replaces the Tentatively Ready fix for [CWG3207]. That fix had proposed that a defaulted-as-deleted
should remain undeduced. We propose instead that it should deduce . This means
that the placeholder return type of a defaulted-as-deleted function is deduced, just as the
placeholder return type of a defaulted-as-deleted , , , , or secondary comparison operator is deduced.
In short: is never well-formed, even if it’s defaulted-as-deleted.
Modify [class.spaceship] as follows:
[...]
2․ Let
be the declared return type of a defaulted three-way comparison operator function, and letR i be the elements of the expanded list of subobjects for an objectx of typex .C
— (2.1)IfR iscontains a placeholder type , then let cviauto i be the type of the expressionR ix i.<=> x The operator function is defined as deleted if that expression is not usable or ifIf that expression is usable andi is not a comparison category type ([cmp.categories.pre]) for any i.R i is a comparison category type for all i, letR be the common comparison type (see below) ofQ 0,R 1, ...,R n−1. Otherwise, letR beQ . The return type is deduced as if fromstd :: strong_ordering return Q ( std :: strong_ordering :: equal ); the common comparison type (see below) of.0,R 1, ...,R n-1R x․
x․ [Example:— (2.2) Otherwise,If the synthesized three-way comparison of typeshall not contain a placeholder type.R between any objectsR i andx i is not defined, the operator function is defined as deleted.x — end example]struct E { struct N { auto operator == ( const N & ) const = default ; } n ; auto * operator <=> ( const E & ) const = default ; // OK: defined as deleted, and the return type is not deduced }; bool b1 = ( E () <=> E ()); // ill-formed: operator<=> is deleted bool b2 = ( E () == E ()); // OK: operator== is implicitly declared x․ A defaulted three-way comparison operator function that is not defined as deleted shall have a return type that is a comparison category type ([cmp.categories.pre]).
3․ The return value of type
of the defaulted three-way comparison operator function with parametersR andx y of the same typeis determined by comparing corresponding elementsi andx i in the expanded lists of subobjects fory andx (in increasing index order) until the first index i where the synthesized three-way comparison of typey betweenR i andx i yields a result valuey i wherev iv , contextually converted to!= 0 , yieldsbool true. The return value isa copy ofi if such an index exists andv otherwise.static_cast < R > ( std :: strong_ordering :: equal ) 4․ The common comparison type
of a possibly-empty list of n comparison category typesU 0,T 1, ...,T n-1 is defined as follows: [...]T
6.3. [class.compare.secondary]
Modify [class.compare.secondary] as follows:
1․ A secondary comparison operator
@is a relational operator ([expr.rel]) or theoperator.!= A defaulted operator function ([over.binary]) for a secondary comparison operator@shall have a declared return type.bool x․ A defaulted secondary comparison operator function shall have the return type
. If its declared return type contains a placeholder type, its return type is deduced as if frombool .return true; 2․
TheA defaulted secondary comparison operator function with parametersandx is defined as deleted ify — (2.1) a first overload resolution ([over.match]), as applied to
,x @y — (2.1.1) does not result in a usable candidate, or
— (2.1.2) the selected candidate is not a rewritten candidate, or
— (2.2) a second overload resolution for the expression resulting from the interpretation of
using the selected rewritten candidate ([over.match.oper]) does not result in a usable candidate (for example, that expression might bex @y ), or( x <=> y ) @0 — (2.3)
cannot be implicitly converted tox @y .bool In any of the two overload resolutions above, the defaulted operator function is not considered as a candidate for the
@operator. Otherwise, the operator function yields.x @y 3․ [Example 1:
— end example]struct HasNoLessThan { }; struct C { friend HasNoLessThan operator <=> ( const C & , const C & ); bool operator < ( const C & ) const = default ; // OK, function is deleted };
6.4. [class.copy.assign]
DRAFTING NOTE:
[class.copy.assign]/6
already clearly states that "The implicitly-declared copy/move assignment operator for class has the return type ."
But we need this new wording to ensure that an explicitly-defaulted copy/move assignment operator
will deduce that same type. (If it deduces a different type, then the explicitly-defaulted operator
becomes ill-formed, as in example below.)
DRAFTING NOTE:
We deduce from "an lvalue reference to "
to ensure that deduces , not .
Modify [class.copy.assign] as follows:
14․ The implicitly-defined copy/move assignment operator for a class returns the object for which the assignment operator is invoked, that is, the object assigned to.
15․ If the declared return type of a defaulted copy/move assignment operator for a class
contains a placeholder type, the return type is deduced as if fromX , wherereturn r ; is an lvalue reference tor .X 16․ [Example:
—end example]struct A { decltype ( auto ) operator = ( A && ) = default ; // OK, return type is A& }; struct B { auto operator = ( B && ) = default ; // error: return type is B, which violates [dcl.fct.def.default] };
6.5. [over.inc.default]
DRAFTING NOTE:
We deduce from "a prvalue expression of type "
(rather than merely "an lvalue reference to " or "a move-eligible rvalue reference to ")
to ensure that deduces a non-reference type.
Such code is contrived, but should do the Right Thing rather than the Wrong Thing if we can help it.
Modify [over.inc.default] as follows:
1․ A defaulted postfix increment or decrement operator function for a type
shall be a non-template function thatC — (1.1) has a first parameter of type “reference to
” or a first parameter of type “reference to volatileC ”, where the implicit object parameter (if any) is considered to be the first parameter,C — (1.2) is defined as defaulted in
or in a context whereC is complete, andC — (1.3) has
a declaredthe return typeof.C [Note 1: A postfix increment or decrement operator always has a parameter of type int ([over.inc.general]). — end note]
Name lookups and access checks in the implicit definition ([dcl.fct.def.default]) of a defaulted postfix increment or decrement operator function are performed from a context equivalent to its function-body. A definition of a postfix increment or decrement operator as defaulted that appears in a class shall be the first declaration of that function.
[Example 1:
— end example]struct S ; S operator ++ ( S & , int ) = default ; // error: S is not complete x․ If the declared return type of a defaulted postfix increment or decrement operator for a class
contains a placeholder type, the return type is deduced as if fromC , wherereturn r ; is a prvalue expression of typer .C 2․ A defaulted postfix increment or decrement operator function for a type
is defined as deleted if [...]C
6.6. Annex C, [diff.cpp26]
Add a clause to Annex C, [diff.cpp26.class], as follows:
Affected clause: [class.spaceship]
Change: The defaulted three-way comparison operator of an empty class type must return a comparison category type.
Rationale: Consistency with the existing restriction on defaulted three-way comparison operators of non-empty class types.
Effect on original feature: A valid C++26 program that contains a defaulted three-way comparison operator with a return type that is not a comparison category type is ill-formed.
Example:struct U { U ( std :: strong_ordering ); }; struct C { U operator <=> ( const C & ) const = default ; // ill-formed; previously well-formed };