1. Changelog
-
R1:
-
Propose an "ambitious" overhaul as well as the "conservative" surgery.
-
-
R0:
-
Initial revision.
-
2. Motivation and proposal
Currently, [dcl.fct.def.default]/2.5 permits an explicitly defaulted special member function to differ from the implicit one by adding ref-qualifiers, but not cv-qualifiers.
For example, the signature
is forbidden
because it is additionally const-qualified, and also because its return type differs
from the implicitly-defaulted
. This might be considered unfortunate, because that’s
a reasonable signature for a const-assignable proxy-reference type.
But programmers aren’t clamoring for that signature to be supported, so we do not propose to support it here.
Our concern is that the unrealistic signature
is permitted! This has three minor drawbacks:
-
The possibility of these unrealistic signatures makes C++ harder to understand. Before writing [P2952], Arthur didn’t know such signatures were possible.
-
The wording to permit these signatures is at least a tiny bit more complicated than if they weren’t permitted.
-
The quirky interaction with [CWG2586] and [P2952] discussed in the next subsection.
To eliminate all three drawbacks, we propose that a defaulted copy/move assignment operator should not be allowed to add to its implicit signature an rvalue ref-qualifier.
struct C { C & operator = ( const C & ) && = default ; // Today: Well-formed // Tomorrow: Deleted }; struct D { D & operator = ( this D && self , const C & ) = default ; // Today: Well-formed // Tomorrow: Deleted };
2.1. Interaction with P2952
[CWG2586] (adopted for C++23) permits
to have an explicit object parameter.
[P2952] proposes that
should (also) be allowed to have a placeholder return type.
If P2952 is adopted without P2953, then we’ll have:
struct C { auto && operator = ( this C && self , const C & ) { return self ; } // Today: OK, deduces C&& // After P2952: Still OK, still deduces C&& // Proposed: Still OK, still deduces C&& auto && operator = ( this C && self , const C & ) = default ; // Today: Ill-formed, return type involves placeholder // After P2952: OK, deduces C& // Proposed (conservative): Deleted, object parameter is not C& // Proposed (ambitious): Ill-formed, object parameter is not C& };
The first, non-defaulted, operator "does the natural thing" by returning its left-hand operand,
and deduces
. The second operator also "does the natural thing" by being defaulted; but
after P2952 it will deduce
. (For rationale, see [P2952] §3.3 "Deducing
and CWG2586.")
The two "natural" implementations deduce different types! This looks inconsistent.
If we adopt P2953 alongside P2952, then the second
will go back to being unusable,
which reduces the perception of inconsistency.
Today | P2952 | |
---|---|---|
Today | /ill-formed
| /
|
P2953 | /ill-formed
| /deleted
|
2.2. "Deleted" versus "ill-formed"
(See also [P2952] §3.2 "Defaulted as deleted".)
[dcl.fct.def.default]/2.6 goes out of its way to make many explicitly defaulted constructors, assignment operators, and comparison operators "defaulted as deleted," rather than ill-formed. This was done by [P0641] (resolving [CWG1331]), in order to support class templates with "canonically spelled" defaulted declarations:
struct A { // Permitted by (2.4) A ( A & ) = default ; A & operator = ( A & ) = default ; }; template < class T > struct C { T t_ ; explicit C (); // Permitted, but defaulted-as-deleted, by (2.6), since P0641 C ( const C & ) = default ; C & operator = ( const C & ) = default ; }; C < A > ca ; // OK
There is similar wording in [class.spaceship] and [class.eq]. We don’t want to interfere with
these use-cases; that is, we want to continue permitting programmers to write things like the above
.
2.3. 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.
URL | Code | Clang | GCC | MSVC | EDG | Correct | Proposed (conservative) | Proposed (ambitious) |
---|---|---|---|---|---|---|---|---|
link |
| ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
link |
| deleted | ✗ | ✗ | deleted | deleted | deleted | ✗ |
link |
| deleted | ✗ | ✗ | deleted | deleted | deleted | ✗ |
link |
| ✓ | ✓ | ✓ | ✓ | ✓ | deleted | ✗ |
link |
| ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
link |
| ✓ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ |
link |
| deleted | deleted | deleted | inconsistent | deleted | deleted | deleted |
3. Implementation experience
Arthur has implemented § 4 Proposed wording (conservative) in his fork of Clang, and used it to compile both LLVM/Clang/libc++ and another large C++17 codebase. Naturally, it caused no problems except in the relevant parts of Clang’s own test suite.
There is no implementation experience for § 5 Proposed wording (ambitious).
4. Proposed wording (conservative)
4.1. [dcl.fct.def.default]
Note: The only defaultable special member functions are default constructors, copy/move constructors, copy/move assignment operators, and destructors. Of these, only the assignment operators can ever be cvref-qualified at all.
Modify [dcl.fct.def.default] as follows:
1․ A function definition whose function-body is of the formis called an explicitly-defaulted definition. A function that is explicitly defaulted shall
= default ;
(1.1) be a special member function or a comparison operator function ([over.binary]), and
(1.2) not have default arguments.
2․ An explicitly defaulted special member function
1 is allowed to differ from the corresponding special member function
F 2 that would have been implicitly declared, as follows:
F
(2.1) if
2 is an assignment operator,
F 1 and
F 2 may have differing ref-qualifiers
F 1 may have an lvalue ref-qualifier;
F (2.2) if
2
F has an implicit object parameter of type “reference to C”is an assignment operator with an implicit object parameter of type,
C & 1 may
F be an explicit object member function whosehave an explicit object parameterisof type(possibly different) “reference to C”, in which case the type of
C & 1 would differ from the type of
F 2 in that the type of
F 1 has an additional parameter;
F (2.3)
1 and
F 2 may have differing exception specifications; and
F (2.4) if
2 has a non-object parameter of type
F , the corresponding non-object parameter of
const C & 1 may be of type
F .
C & If the type of
1 differs from the type of
F 2 in a way other than as allowed by the preceding rules, then:
F
(2.5) if
1
F 2 is an assignment operator, and the return type of
F 1 differs from the return type of
F 2 or
F 1’s non-object parameter type is not a reference, the program is ill-formed;
F (2.6) otherwise, if
1 is explicitly defaulted on its first declaration, it is defined as deleted;
F (2.7) otherwise, the program is ill-formed.
[...]
5. Proposed wording (ambitious)
Note: The intent of this "ambitious" wording is to lock down the signatures of defaultable member functions as much as possible, and make errors as eager as possible, except in the cases covered by § 2.2 "Deleted" versus "ill-formed" (which we want to keep working, i.e., "defaulted as deleted").
5.1. [dcl.fct.def.default]
Modify [dcl.fct.def.default] as follows:
1․ A function definition whose function-body is of the formis called an explicitly-defaulted definition. A function that is explicitly defaulted shall
= default ;
(1.1) be a special member function or a comparison operator function ([over.binary]), and
(1.2) not have default arguments.
2․ An explicitly defaulted special member function
1 is allowed to differ from the corresponding special member function
F 2 that would have been implicitly declared, as follows:
F
(2.1) if
2 is an assignment operator,
F 1 and
F 2 may have differing ref-qualifiers
F 1 may have an lvalue ref-qualifier;
F (2.2) if
2
F has an implicit object parameter of type “reference to C”is an assignment operator with an implicit object parameter of type,
C & 1 may
F be an explicit object member function whosehave an explicit object parameterisof type(possibly different) “reference to C”, in which case the type of
C & 1 would differ from the type of
F 2 in that the type of
F 1 has an additional parameter; and
F (2.3)
1 and
F 2 may have differing exception specifications;
F and(2.4) if
2 has a non-object parameter of type
F , the corresponding non-object parameter of
const C & 1 may be of type
F ; and
C & - (2.5) if
2 has a non-object parameter of type
F , the corresponding non-object parameter of
C & 1 may be of type
F .
const C & If the type of
1 differs from the type of
F 2 in a way other than as allowed by the preceding rules, then
F :
(2.5) if1 is an assignment operator, and the return type of
F 1 differs from the return type of
F 2 or
F 1’s non-object parameter type is not a reference, the program is ill-formed;
F (2.6) otherwise, if1 is explicitly defaulted on its first declaration, it is defined as deleted;
F (2.7) otherwise,the program is ill-formed.[...]
5.2. [class.copy.assign]
Note: If we do the wording patch above, then I think nothing in [class.copy.assign] needs
to change. But much of the wording above is concerned specifically with copy/move assignment operators,
so it might be nice to move that wording out of [dcl.fct.def.default] and into [class.copy.assign].
Also note that right now a difference in
-ness is handled explicitly by [dcl.fct.def.default]
for special member functions but only by omission-and-note in [class.compare] for comparison operators.
Modify [class.copy.assign] as follows:
TODO FIXME BUG HACK
6. Proposed straw polls
The next revision of this paper (if any) will be guided by the outcomes of these three straw polls.
SF | F | N | A | SA | |
---|---|---|---|---|---|
EWG would like to forbid rvalue-ref-qualified assignment operators (by any means, not necessarily by this proposed wording). | – | — | — | — | — |
EWG would like to pursue § 5 Proposed wording (ambitious) (regardless of the next poll). | – | — | — | — | — |
Advance § 4 Proposed wording (conservative) to CWG (regardless of the previous poll). | – | — | — | — | — |