According to the (cppreference) there is a need to use std::forward()
in order to perfectly pass outcome of a function passed to another call without storing it explicitly elsewhere.
[...] For example, if a wrapper does not just forward its argument, but calls a member function on the argument, and forwards its result:
// transforming wrapper
template<class T>
void wrapper(T&& arg)
{
foo(forward<decltype(forward<T>(arg).get())>(forward<T>(arg).get()));
}
where the type of arg may be
struct Arg
{
int i = 1;
int get() && { return i; } // call to this overload is rvalue
int& get() & { return i; } // call to this overload is lvalue
};
Attempting to forward an rvalue as an lvalue, such as by instantiating the form (2) with lvalue reference type T, is a compile-time error. [...]
Isn't is the case that calling a function with a parameter that is outcome of another call perfectly passes the outcome with original value type and category? Consider following snippet that is close to the original example:
struct Inner{};
struct Arg
{
Inner inner{};
Inner&& get() && { return move(inner); } // call to this overload is rvalue
Inner& get() & { return inner; } // call to this overload is lvalue
const Inner& get() const & { return inner; }; // call to this overload is also lvalue
};
void foo(Inner&&) { std::cout << "&&" << std::endl; }
void foo(Inner&) { std::cout << "&" << std::endl; }
void foo(const Inner&) { std::cout << "const&" << std::endl; }
template<class T>
void wrapper(T&& arg)
{
// NOTE: choose either the former or the latter version!
// with extra forward
foo(forward<decltype(forward<T>(arg).get())>(forward<T>(arg).get()));
// without extra forward
foo(forward<T>(arg).get());
}
int main()
{
// prvalue
wrapper(Arg{});
// xvalue
Arg arg;
wrapper(move(arg));
// lvalue
Arg arg2;
wrapper(arg2);
// const lvalue
const Arg arg3;
wrapper(arg3);
return 0;
}
In both cases the output is exactly the same:
&&
&&
&
const&
You are right. The second forward
does absolutely nothing, except force a return-by-value function that returns a prvalue to create a temporary (usually unwanted).
If decltype(forward<T>(arg).get())
is an lvalue reference, forward<T>(arg).get()
is an lvalue already and the outer forward does nothing.
If decltype(forward<T>(arg).get())
is an rvalue reference, forward<T>(arg).get()
is an xvalue already and the outer forward does nothing.
If decltype(forward<T>(arg).get())
is not a reference, forward<T>(arg).get()
is a prvalue and the outer forward forces a temporary to be materialized (resulting in an xvalue). *In C++11/14 this would turn a prvalue into an xvalue, inhibiting an optional RVO
forward<decltype(expr)>(expr)
should only be used if expr
is the name of a variable. Thats the only time that decltype(expr)
can have different ref-qualifiers than the value category of the expression. If expr
is not a name, it is better to just write (expr)
.