On Mon, Oct 17, 2022 at 12:52 PM Edward Catmur via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Mon, 17 Oct 2022 at 17:31, Lénárd Szolnoki via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On 17 October 2022 12:38:32 BST, "Peter Sommerlad (C++) via Std-Proposals" <std-proposals@lists.isocpp.org> wrote:
>
>struct X{};
>auto & dangle = (X{} = X{});

The main issue here seems to be to return a reference from the assignment at all. And if `rvalue = something` returns a reference, it should better be an rvalue reference. If the object is expiring, then it is still expiring after you assign to it.

But we write the copy assignment operator as X& X::X(X&), and the move assignment operator as [X&] X::X(X&&); the implicit object parameter is not ref-qualified, so it can accept any value category, but for historical reasons it always returns lvalue. And this is what the automatically-generated assignment operators do as well.

Are you proposing to double the number of required/supported assignment operator overloads?

I think the first-order proposal here is for user-defined types to stop returning a reference from operator= at all. Programmers should just be able to return `void` from operator= wherever possible. Unfortunately that can't easily be supported for historical reasons:

    struct X {
        void operator=(const X&) = default;  // Proposal: Make this compile
    };

    struct RuleOfZero {};
    RuleOfZero z;
    z = z = z;  // Caveat: But this C/C++98 code must continue to compile successfully

And C++20 Ranges made the situation exponentially worse by requiring all user-defined iterator types to proliferate the "reference-returning assignment" antipattern. So now such a proposal would also need to change the library side of things, too:

    struct It {
      using value_type = int;
      using difference_type = int;
      It();
      It(const It&);
      void operator=(const It&); // N.B.: void, not It&; this line breaks the static_assert below
      int operator*() const;
      It& operator++();
      void operator++(int); // N.B.: this line is actually already OK in C++20
    };
    static_assert(std::input_iterator<It>); // Proposal: make this static_assert compile

    template<std::input_iterator It>
    It user_defined_algorithm(It& first) {
      return first = It();  // Proposal: designate this C++20 code as undesirable, and actively break it
    }

I think these changes would all be great, but I think the relevant ship sailed in C++98 and then pretty much fell off the edge of the earth in C++20.

–Arthur