C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Allow automatically deduced return type for explicitly defaulted move- and copy-assignment operators

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Thu, 13 Jul 2023 12:30:39 -0400
On Thu, Jul 13, 2023 at 6:04 AM Matthew Taylor via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> Per [dcl.fct.def.default], a program is ill-formed if the return type of
> an explicitly defaulted assignment operator does not match that of the
> implicitly declared operator, and per [class.copy.assign], the return type
> [...] must be of type X& [...]
> I propose allowing auto& (and only auto& as an explicit reference) in
> place of X& for the return type, where returning auto& will be equivalent
> to returning X&. The motivation here is consistency. As far as I can see,
> explicitly defaulted assignment operators are about the only member
> functions you are likely to write which explicitly cannot deduce their
> return type. This seems stylistically inconsistent [...]
>

I strongly agree. However, I would go a little further in a couple of
directions.

(1) Observe that there are multiple possible parameter signatures for a
defaulted `operator=`: namely at least these.
    X& operator=(X&) = default;
    X& operator=(X&&) = default;
    X& operator=(const X&) = default;
    X& operator=(const X&&) = default;
But not these:
    const X& operator=(const X&) const = default; // Ill-formed!
    const X& operator=(const X&) = default; // Ill-formed!
    X operator=(const X&) = default; // Ill-formed!
    X& operator=(X) = default; // Ill-formed!
    void operator=(const X&) = default; // Ill-formed!
    X& operator=(this X&, const X&) = default; // Ill-formed!
I think it's reasonable to say that the latter are all "out of scope" for
this proposal, but I'd say so explicitly. (IOW, this proposal isn't adding
any new *signatures*; it's just regularizing the *syntax* by which today's
valid signatures can be expressed.)

(2) I think the return type should be deduced as usual from the "invisible
return statement" in the invisible body of the defaulted function; and then
*after* that deduction, the compiler should reject any return type that
isn't exactly `X&`. I would not tie the well-formedness of the declaration
to the *exact syntax* `auto&`. In particular, I see no reason to prohibit
    auto& operator=(const X&) = default; // should deduce X& and be OK
    auto&& operator=(const X&) = default; // should deduce X& and be OK
(contra what you said)
    decltype(auto) operator=(const X&) = default; // should deduce X& and
be OK (contra what you said)
But I think it's reasonable to still prohibit
    const auto& operator=(const X&) = default; // should deduce const X&
and thus be ill-formed
    auto operator=(const X&) = default; // should deduce X and thus be
ill-formed
    auto* operator=(const X&) = default; // should fail deduction (X is
not a pointer type) and thus be ill-formed

(3) The other big place this comes up is `operator==`. This is annoying to
the working programmer because it takes the natural cut-and-paste (`auto`
in both places) and turns it into needless busywork. (In the admittedly
unusual case that one needs *both* operators: e.g. when spaceship can't be
defaulted but equality can.)
    auto operator<=>(const X&) const = default; // OK
    auto operator==(const X&) const = default; // Ill-formed, must type
`bool` instead
I would apply the same logic to `operator==` as we did above to `operator=`:
    auto operator==(const X&) const = default; // should deduce bool and
be OK
    decltype(auto) operator==(const X&) const = default; // should deduce
bool and be OK
    friend auto operator==(const X&, const X&) = default; // should deduce
bool and be OK
    const auto& operator==(const X&) const = default; // should deduce
const bool& and thus be ill-formed

(4) Likewise, the other five comparison operators.
    auto operator!=(const X&) const = default; // ill-formed, should be OK
    auto operator<(const X&) const = default; // ill-formed, should be OK
    auto operator>(const X&) const = default; // ill-formed, should be OK
    auto operator<=(const X&) const = default; // ill-formed, should be OK
    auto operator>=(const X&) const = default; // ill-formed, should be OK

Have you looked into proposed wording for all this? Is it just replacing
one sentence with another?

This proposal would make writing Rule-of-Five classes a little easier (e.g.
in library unit tests where we typically have lots of Rule-of-Five classes
with VeryLongAndSimilarNames
<https://github.com/Quuxplusone/llvm-project/blob/main/clang/test/SemaCXX/type-traits.cpp#L97-L104>),
and it would remove annoying busywork from how people typically define
`operator==` (by cut-and-paste).

I'd be happy to coauthor; let me know.

Cheers,
Arthur

Received on 2023-07-13 16:30:54