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.

Community
  • 1
  • 1
vschoech
  • 325
  • 1
  • 8
  • 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? – dyp Apr 18 '13 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. – dyp Apr 18 '13 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. – vschoech Apr 18 '13 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`. – dyp Apr 18 '13 at 22:06
  • @DyP, "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`." <- That kind of behavior would make sense to me and would resolve my issue. – vschoech Apr 19 '13 at 06:37
  • 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. – Sebastian Redl Apr 19 '13 at 12:18
  • @sebastian-redl, in fact the code fails to compile even without being called. Thank you for taking the time and explaining once again in different words what DyP probably wanted to convey all along. It took me a while but it all makes sense now. If you would put your explanation into an answer rather than a comment, I'd happily accept that answer. – vschoech Apr 19 '13 at 13:03

2 Answers2

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));
}
Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • 2
    How about this? `const auto&& obj = STest(n);` – balki Apr 18 '13 at 18:43
  • @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 Apr 18 '13 at 18:47
  • @andy-prowl, see EDIT in original post for a detailed reply. – vschoech Apr 18 '13 at 20:18
  • 2
    @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. – Andy Prowl Apr 18 '13 at 20:27
  • @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. – Andy Prowl Apr 18 '13 at 20:28
  • @andy-prowl, of course you are right wrt `STest const&&`. Also thank you for the education wrt SO. I removed the explicit reference to your reply from the original post. – vschoech Apr 18 '13 at 21:28
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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 2
    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 Apr 19 '13 at 20:04
  • 2
    @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" – Yakk - Adam Nevraumont Apr 19 '13 at 20:22
  • 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). – dyp Apr 19 '13 at 21:29
  • 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 Apr 19 '13 at 21:32
  • @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`! – Yakk - Adam Nevraumont Apr 20 '13 at 00:29
  • Not sure I understand what you say. Had to look up the difference between RVO and NRVO, though. What I meant is RVO but not NRVO (not likely, but allowed); that is, if the copy ctor was _not_ deleted, and the compiler _did_ elide the copy of `a` into a temporary to return, but the compiler _did not_ elide the creation of object `a` (not a temporary); then as I understand it, `a` has been _declared_ const, therefore must not be modified [dcl.type.cv]/4 and cannot be moved (as a return value, `STest myA = FuncUsingConst();`), even though the return type of `FuncUsingConst` does allow moving. – dyp Apr 20 '13 at 12:01
  • @Yakk "If MSVC2012 is allowing `FuncUsingConst` to compile, it is doing so in violation of the C++11 standard." <- You are right, it does not allow to compile the code even without `FuncUsingConst` being called. I was prematurely interpreting what I was seeing. Your explanation (as the explanation by sebastian-redl in a comment to the OP) is exactly right. Thank you for taking the time! – vschoech Apr 22 '13 at 06:47
  • @DyP basically, what I'm trying to say is that if we have an elided copy constructor, then we have one object with two names. In this case, it is both the (unnamed) non-`const` return value, and the `const` named local variable. Both are constructed at the same point. While it isn't legal to `move` from the `const` local variable, it is legal to move from the (unnamed) `const` return value, and it is legal for them to both be the same value. I'd say that the local variable didn't exist, but if you have a function that returns `const` from a non-`const` local, that wouldn't be true. – Yakk - Adam Nevraumont Apr 22 '13 at 13:09
  • "it is legal to move from the (unnamed) const return value" typo? **non**-const return value? If I understand you correctly, you're referring to NRVO: eliding the creation of an object with automatic storage duration ("local variable") inside the function in favor of directly manipulating/constructing into the target object `target = FuncWithoutConst();`. I was referring to RVO: eliding the copy/move from the "local variable" into the object that is returned. Those are [_two distinct copy operations_](http://en.wikipedia.org/wiki/Return_value_optimization). – dyp Apr 22 '13 at 14:07
  • @DyP Yes, the non-`const` return value. I find "NRVO" and "RVO" names to be less than clarifying. So I'll use an example instead. I was referring to `Foo func() { Foo const a; return a; }` eliding the copy from `a` to the return value of `func`. Which means both `a` and the unnamed return value of `func` are the same object. If you do `Foo x = func();`, and that copy is also elided, then all 3 of `a`, `x` and the unnamed return value of `func` are the same object. As `a` that object is `const`, as `x` or the unnamed return value it is not. – Yakk - Adam Nevraumont Apr 22 '13 at 14:13