Search code examples
c++c++17move-semanticsconst-correctness

Should I make my local variables const or movable?


My default behaviour for any objects in local scopes is to make it const. E.g.:

auto const cake = bake_cake(arguments);

I try to have as little non-functional code as I can as this increases readability (and offers some optimisation opportunities for the compiler). So it is logical to also reflect this in the type system.

However, with move semantics, this creates the problem: what if my cake is hard or impossible to copy and I want to pass it out after I'm done with it? E.g.:

if (tastes_fine(cake)) {
  return serve_dish(cake);
}

As I understand copy elision rules it's not guaranteed that the cake copy will be elided (but I'm not sure on this).

So, I'd have to move cake out:

return serve_dish(std::move(cake)); // this will not work as intended

But that std::move will do nothing useful, as it (correctly) will not cast Cake const& to Cake&&. Even though the lifetime of the object is very near its end. We cannot steal resources from something we promised not to change. But this will weaken const-correctness.

So, how can I have my cake and eat it too?

(i.e. how can I have const-correctness and also benefit from move semantics.)


Solution

  • I believe it's not possible to move from a const object, at least with a standard move constructor and non-mutable members. However, it is possible to have a const automatic local object and apply copy elision (namely NRVO) for it. In your case, you can rewrite your original function as follows:

    Cake helper(arguments)
    {
       const auto cake = bake_cake(arguments);
       ...  // original code with const cake
       return cake;  // NRVO 
    }
    

    Then, in your original function, you can just call:

    return serve_dish(helper(arguments));
    

    Since the object returned by helper is already a non-const rvalue, it may be moved-from (which may be, again, elided, if applicable).

    Here is a live-demo that demonstrates this approach. Note that there are no copy/move constructors called in the generated assembly.