Search code examples
c++templatesautoc++17

Why does auto return type lose move semantics in this example?


I was watching the video where Nicolai says that auto loses move semantics for this example:

template<typename Callable, typename... Args>
auto call(Callable&& op, Args&&... args) {
return std::invoke(std::forward<Callable>(op), std::forward<Args>(args)...);
}

I was wondering:

  1. why is this the case?

  2. does guaranteed RVO kick in in this example? If so what is the point of worrying about move?


Solution

  • I think Nicolai could have just phrased it a bit better.

    When you return by auto, your function returns a value (the type of which will be deduced). If std::invoke returns a pure rvalue or an xvalue, then of course the result of call will be constructed accordingly (by a move, if possible). We don't "lose move semantics" in that sense.

    But when we return by value, that value object needs to be created. It can be created by a move, and under certain circumstances (which aren't present here) it can be elided, but it must be created. Guaranteed copy elision doesn't allow us to cop out of creating this object.

    That can be quite wasteful if std::invoke gives us back an xvalue (an rvalue reference to something). Why should we construct an object to return it?

    That's why a slide or two later he says we should return by decltype(auto). The deduction rules will then preserve the value cateogry of the the call to std::invoke:

    1. If it returns a value, we are no worse off. Our own call will return a value (which it may create by moving the return value of std::invoke).

    2. If std::invoke returns an lvalue(X&) or an xvalue(X&&), that will be returned as is, without creating another object by a copy or move.