17
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.

7
  • Without looking into the Std, I'd say that both copy ctor AND move ctor should be invoked. Are you sure that only the copy ctor is invoked? Commented Apr 18, 2013 at 17:54
  • The temporary created for the return statement can only be elided "when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type" [class.copy]/31. This is not the case here, so in FuncUsingConst, the object a should be copied, then after the call the move ctor invoked. Commented Apr 18, 2013 at 18:01
  • I do not want the copy ctor to be invoked at all, and I am not discussing copy elision. I "simply" want to see move semantics for the temporary that is the return value of FuncUsingConst and that is passed on to the STest move ctor. Commented Apr 18, 2013 at 20:23
  • 1
    I'm not entirely sure if I interpret the Standard correctly here: It seems to me that the type used in the return statement (type of a) has to be the same as the function return type, including cv-qualifiers in order to elide the copy. If this is correct, your compiler should complain that the copy from a to a non-const temporary STest is not allowed. The return type of FuncUsingConst would always be a non-const STest. Commented Apr 18, 2013 at 22:06
  • 2
    Not sure if I'm interpreting your question correctly, but you seem to be asking why the compiler tries to initialize s2 from the result of FuncUsingConst via the copy constructor instead of the move constructor? You complain about the implementation affecting the interface of the function this way? But that's not what is happening. The compiler tries to initialize the result of FuncUsingConst from the const local variable and fails doing that. It has nothing at all to do with the outside. In fact, this code should fail to compile even if you never call FuncUsingConst. Commented Apr 19, 2013 at 12:18

2 Answers 2

16

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)); } 
Sign up to request clarification or add additional context in comments.

6 Comments

How about this? const auto&& obj = STest(n);
@balki: That would work only with a const_cast, which is a possibility though. Thank you for the idea, I will expand in my answer
@andy-prowl, see EDIT in original post for a detailed reply.
@vschoech: Your move constructor accepting an STest const&& does nothing: how do you think you would implement that constructor, if all you have is a reference to const? You can't alter the state of that object. It is const. Unfortunately, at the moment I do not have time to answer your question more extensively than I already did. Also, SO is not a forum where you address posts at people. If you don't like the answer, do not accept it. If you feel like you need to ask further questions about this answer, post a new question.
@vschoech: Btw, the reason why I am suggesting to post a new question is that you will have a much greater chance to get a reply than by editing an already-answered question.
|
2

A program is ill-formed if the copy/move constructor [...] for an object is implicitly odr-used and the special member function is not accessible

-- n3485 C++ draft standard [class.copy]/30

I suspect your problem is with MSVC 2012, and not with C++11.

This code, even without calling it, is not legal C++11:

struct STest { STest(STest const&) = delete 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; } 

because there is no legal way to turn a into a return value. While the return can be elided, eliding the return value does not remove the requirement that the copy constructor exist.

If MSVC2012 is allowing FuncUsingConst to compile, it is doing so in violation of the C++11 standard.

10 Comments

I already have stated something similar in a comment to the OP, could you help me interpret what the Standard says about this? The temporary created for the return statement can only be elided "when the expression is the name of a non-volatile automatic object [...] with the same cv-unqualified type as the function return type" [class.copy]/31. I'm not sure what "cv-_un_qualified" means in this context (either same qualification or qualifiers stripped).
@DyP I'd read that as saying "you can ignore cv qualifications when eliding that copy", but copy elision does not remove the requirement that the copy construction to be elided exists to be elided. I may be wrong: I'm trying to decipher the standardese around that right now. I think [class.copy]/30 covers that requirement. "A program is ill-formed if the copy/move constructor [...] for an object is implicitly odr-used and the special member function is not accessible"
m( you're absolutely right. [class.temporary]/1 says it quite clearly: "Even when the creation of the temporary object is unevaluated (Clause 5) or otherwise avoided (12.8), all the semantic restrictions shall be respected as if the temporary object had been created and later destroyed." Therefore it doesn't matter whether the copy is elided or not; it's not allowed as the copy ctor is deleted (therefore inaccessible).
But does that mean if the copy ctor is available, you can elide the copy (RVA)? That is, you can move an object declared as const by using STest res = FuncUsingConst(42);?
@dyp it is not moved, it never existed. What you thought was a const instance was a const view of a non const object. The object that existed was the return value... which is non const!
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.