Search code examples
c++11visual-studio-2013rvalue-referencelvalue

MSVC++ 2013 seems to allow assignments to temporary objects, effectively treating them as lvalues


I've come across this "feature" in MSVC++ and I'm now not sure if it's a bug or my understanding of lvalues/rvalues in C++ is just plain wrong.

I've added some seriously dumbed-down code to illustrate, but basically the issue is that MSVC++ 2013 (both base and NOV 2013 CTP compilers) allows assignment to temporary objects, which should really be rvalues and hence disallow any assignment attempts at compile time.

#include <iostream>

struct account {
    int value;

    explicit account(int v) : value{ v } { 
        std::cout << "account ctor: " << value << std::endl; 
    }
    account(const account & acc) : value{ acc.value } { 
        std::cout << "account copy ctor" << std::endl; 
    }
    account(account && acc) : value{ acc.value } { 
        std::cout << "account move ctor" << std::endl; 
    }
    account & operator=(const account & acc) {
        value = acc.value;
        std::cout << "account copy assign" << std::endl;
        return *this;
    }
    account & operator=(account && acc) {
        value = acc.value;
        std::cout << "account move assign" << std::endl;
        return *this;
    }   
};

int get_int() { return 42; }

account get_account() {
    return account(123);
}

int main() {
    //get_int() = 5;    // this predictably fails to compile
                        // with '=' : left operand must be l-value

    // everything below succeeds
    get_account() = account(42);    // console trace for this 
                                    // account ctor: 42
                                    // account ctor: 123
                                    // account move assign

    account(123) = account(42);     // console trace same as above

    account acc(0);                 // account ctor: 0
    account(42) = acc;              // account ctor: 42
                                    // account copy assign

    get_account() = acc;            // console trace same as above
}

Surely get_account() = acc; or account(42) = acc; is not C++ Standard's prescribed behaviour?! Both get_account() & account(42) should result in rvalues, which by definition do not allow assignments.

Incidentally, overloading member functions based on lvalue/rvalue qualifiers

...
void memberFn() const &;
void memberFn() &&;
...

which is supported in NOV 2013 CTP is not working properly or at all. I assume this is a result of failing to recognise rvalues, so that this is always an lvalue.

PS Unfortunately, I do not have an opportunity to test this with other compilers.


Solution

  • According to my understanding, this is perfectly valid C++11.

    Only built-in assignment to prvalues is prohibited.

    From [5, expr]:

    Note: Operators can be overloaded, that is, given meaning when applied to expressions of class type (Clause 9) or enumeration type (7.2). Uses of overloaded operators are transformed into function calls as described in 13.5. Overloaded operators obey the rules for syntax specified in Clause 5, but the requirements of operand type, value category, and evaluation order are replaced by the rules for function call.

    So the requirements on

    get_account() = account(42);
    

    are the same as on any other member-function call

    get_account().foo_bar(account(42));
    

    which makes sense since it is just a nicer syntax for

    get_account().operator=(account(42));
    

    The section 3.10 on Lvalues and rvalues makes this as clear well [basic.lval]:

    For example, the built-in assignment operators expect that the left operand is an lvalue and that the right operand is a prvalue and yield an lvalue as the result. User-defined operators are functions, and the categories of values they expect and yield are determined by their parameter and return types.