1. Changelog
-
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 lvalue 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
Similarly for dependent return types:
template < 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 >> ); // OK, C<int>'s operator<=> is deleted
Therefore we can’t just say " shall have a return type which is a comparison category type";
we must say that if the return type is not a comparison category type then the operator is defaulted as deleted.
3.3. "Deducing this " and CWG2586
[CWG2586] (adopted for C++23) permits defaulted functions to have explicit object parameters.
This constrains the wordings we can choose for : we can’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 | Today: ✓ Proposed: deleted |
| link |
| ✓ | ✓ | ✗ | deleted | deleted |
| link |
| ICE | ✓ | ✓ | deleted | deleted |
| link |
| ✓ | ✓ | ✓ | deleted | Today: ✓ Proposed: deleted |
| link |
| ✓ | ✓ | noexcept | incon- sistent | ✓ |
| link |
| ✓ | ✓ | ✓ | ✓ | ✓ |
| link |
| deleted | deleted | ✗ | deleted | deleted |
| link |
| deleted | deleted | ✓ | deleted | deleted |
| link |
| ✓ | ✓ | ✓ | ✓ | ✗ only since [P2953] |
| link |
| ✗ | ✗ | ✗ | ✗ | ✗ |
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 very arcane examples, e.g.
struct C { const std :: strong_ordering & operator <=> ( const C & ) const = default ; // Today: Well-formed, non-deleted // Tomorrow: Well-formed, deleted };
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.
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 };
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 "new" wording below incorporates the Tentatively Ready fix for [CWG3207], which tries to ensure that the placeholder return type of a deleted function is never deduced. Our proposed wording in [over.inc.default] also matches CWG3207’s intent. But our proposed wording in [class.eq] and [class.compare.secondary] and [class.copy.assign] do not match CWG3207’s intent. Should CWG3207 therefore remain open? For an alternative wording that almost works, see our R2.
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) If
R iscontains a placeholder type , then let cviauto R i be the type of the expressionS ix i. The operator function is defined as deleted if that expression is not usable or if<=> x R i is not a comparison category type ([cmp.categories.pre]) for any i.S TheOtherwise, the return type is deduced as if fromwherereturn Q ( std :: strong_ordering :: equal ); is the common comparison type (see below) ofQ R 0,S R 1, ...,S R n-1.S — (2.2) Otherwise,
x․ A defaulted three-way comparison operator function that is not defined as deleted shall have a return type which is a comparison category type ([cmp.categories.pre]).if the synthesized three-way comparison of typeshall not contain a placeholder type. IfR between any objectsR i andx i is not defined, the operator function is defined as deleted.x 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.)
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, its 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]
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 [...]
3․ The implicit definition of a defaulted postfix increment or decrement operator function
that is not defined as deleted for a typeF is equivalent toC for a postfix increment operator function, orC tmp ( c ); ++ c ; return tmp ; for a postfix decrement operator function, whereC tmp ( c ); -- c ; return tmp ; is a variable defined for exposition only, andtmp is an lvalue that denotesc if* this is an implicit object member function, or the first parameter ofF otherwise.F x. If the declared return type of a defaulted postfix increment or decrement operator function that is not defined as deleted contains a placeholder type, its return type is deduced as if from
.return tmp ; 4․ [Example: [...]
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 };