Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why would const-ness of a local variable inhibit move semantics for the returned value?

struct STest : public boost::noncopyable {
    STest(STest && test) : m_n( std::move(test.m_n) ) {}
    explicit STest(int n) : m_n(n) {}
    int m_n;
};

STest FuncUsingConst(int n) {
    STest const a(n);
    return a;
}

STest FuncWithoutConst(int n) {
    STest a(n);
    return a;
}

void Caller() {
    // 1. compiles just fine and uses move ctor
    STest s1( FuncWithoutConst(17) );

    // 2. does not compile (cannot use move ctor, tries to use copy ctor)
    STest s2( FuncUsingConst(17) );
}

The above example illustrates how in C++11, as implemented in Microsoft Visual C++ 2012, the internal details of a function can modify its return type. Up until today, it was my understanding that the declaration of the return type is all a programmer needs to know to understand how the return value will be treated, e.g., when passed as a parameter to a subsequent function call. Not so.

I like making local variables const where appropriate. It helps me clean up my train of thought and clearly structure an algorithm. But beware of returning a variable that was declared const! Even though the variable will no longer be accessed (a return statement was executed, after all), and even though the variable that was declared const has long gone out of scope (evaluation of the parameter expression is complete), it cannot be moved and thus will be copied (or fail to compile if copying is not possible).

This question is related to another question, Move semantics & returning const values. The difference is that in the latter, the function is declared to return a const value. In my example, FuncUsingConst is declared to return a volatile temporary. Yet, the implementational details of the function body affect the type of the return value, and determine whether or not the returned value can be used as a parameter to other functions.

Is this behavior intended by the standard?
How can this be regarded useful?

Bonus question: How can the compiler know the difference at compile time, given that the call and the implementation may be in different translation units?


EDIT: An attempt to rephrase the question.

How is it possible that there is more to the result of a function than the declared return type? How does it even seem acceptable at all that the function declaration is not sufficient to determine the behavior of the function's returned value? To me that seems to be a case of FUBAR and I'm just not sure whether to blame the standard or Microsoft's implementation thereof.

As the implementer of the called function, I cannot be expected to even know all callers, let alone monitor every little change in the calling code. On the other hand, as the implementer of the calling function, I cannot rely on the called function to not return a variable that happens to be declared const within the scope of the function implementation.

A function declaration is a contract. What is it worth now? We are not talking about a semantically equivalent compiler optimization here, like copy elision, which is nice to have but does not change the meaning of code. Whether or not the copy ctor is called does change the meaning of code (and can even break the code to a degree that it cannot be compiled, as illustrated above). To appreciate the awkwardness of what I am discussing here, consider the "bonus question" above.

like image 681
vschoech Avatar asked Apr 18 '13 17:04

vschoech


1 Answers

I like making local variables const where appropriate. It helps me clean up my train of thought and clearly structure an algorithm.

That is indeed a good practice. Use const wherever you can. Here, however, you cannot (if you expect your const object to be moved from).

The fact that you declare a const object inside your function is a promise that your object's state won't ever be altered as long as the object is alive - in other words, never before its destructor is invoked. Not even immediately before its destructor is invoked. As long as it is alive, the state of a const object shall not change.

However, here you are somehow expecting this object to be moved from right before it gets destroyed by falling out of scope, and moving is altering state. You cannot move from a const object - not even if you are not going to use that object anymore.

What you can do, however, is to create a non-const object and access it in your function only through a reference to const bound to that object:

STest FuncUsingConst(int n) {
    STest object_not_to_be_touched_if_not_through_reference(n);
    STest const& a = object_not_to_be_touched_if_not_through_reference;

    // Now work only with a

    return object_not_to_be_touched_if_not_through_reference;
}

With a bit of discipline, you can easily enforce the semantics that the function should not modify that object after its creation - except for being allowed to move from it when returning.

UPDATE:

As suggested by balki in the comments, another possibility would be to bind a constant reference to a non-const temporary object (whose lifetime would be prolonged as per § 12.2/5), and perform a const_cast when returning it:

STest FuncUsingConst(int n) {
    STest const& a = STest();

    // Now work only with a

    return const_cast<STest&&>(std::move(a));
}
like image 195
Andy Prowl Avatar answered Mar 23 '23 16:03

Andy Prowl