Search code examples
c++assignment-operator

Differences in Assigning to Temporaries for User-Defined vs Built-in Types in C++


I have been delving into the intricacies of C++ recently, and there's a specific behavior that has puzzled me: the rules and behaviors around temporaries, especially regarding taking their address and assigning to them.

Assignment to Temporaries: Furthermore, I observed that I can assign to non-const temporaries of UDTs:

Rational a(1, 2), b(3, 4), c(5, 6);
(a * b) = c;  // This line compiles

However, attempting to assign to the result of a function returning by value (which is an r-value) with built-in types leads to a compile error:

int f(int x, int y) {
    return x;
}

int main() {
    f(10, 5) = 12;  // This line won’t compile
}

My Questions: Assignment to Temporaries: How is it possible to assign to a non-const temporary of a UDT? Is this behavior consistent, and should it be used or avoided in practice? I've searched through various resources and haven't found a satisfactory explanation for these behaviors. Could someone please shed some light on these issues?

Thank you in advance!


Solution

  • The sole reason is that the built-in operators have certain requirements when it comes to the value category of the operand.

    The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand; [...]

    - [expr.ass] p1

    The operand of the unary & operator shall be an lvalue of some type T.

    - [expr.unary.op] p3

    Fr example, &4 isn't valid because 4 is a prvalue.

    Operator overloads can bypass these rules because &some_rational simply translates into a function call. If Rational has an overloaded & operator, you can do &Rational{} which would take the address of a prvalue. If Rational has an overloaded = operator (all class types do, actually), you can do Rational{} = ... which assigns to a prvalue. These expression aren't possible for the built-in & and = operator.

    Note that you can disable this behavior using ref-qualifiers:

    struct Rational {
        /* ... */
    
        // the & ref-qualifier means that the = operator only works with lvalues
        Rational& operator=(const Rational& other) &;
    };
    

    Since const& can bind to temporary objects, it is not possible to achieve the same for a member function with a const qualifier. The closest you could get is using C++23's explicit object parameters:

    template <std::same_as<const Rational> Self>
    address_type operator&(this Self auto& r);