The implementation of std::forward
The problem with the first is that you can write std::forward(x)
, which doesn't do what you want, since it always produces lvalue references.
The argument in the second case is a non-deduced context, preventing automatic deduction of the template argument. This forces you to write std::forward<T>(x)
, which is the right thing to do.
Also, the argument type for the second overload should be typename identity<T>::type&
because the input to idiomatic use of std::forward
is always an lvalue.
Edit: The standard actually mandates a signature equivalent to this one (which, incidentally, is exactly what libc++ has):
template <class T> T&& forward(typename remove_reference<T>::type& t) noexcept;
template <class T> T&& forward(typename remove_reference<T>::type&& t) noexcept;
Understanding of the implementation of std::forward since C++11
forward
is essentially a machinery to conserve the value category in perfect forwarding.
Consider a simple function that attempts to call the f
function transparently, respecting value category.
template <class T>
decltype(auto) g(T&& arg)
{
return f(arg);
}
Here, the problem is that the expression arg
is always an lvalue regardless of whether arg
is of rvalue reference type. This is where forward
comes in handy:
template <class T>
decltype(auto) g(T&& arg)
{
return f(forward<T>(arg));
}
Consider a reference implementation of std::forward
:
template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept
{
return static_cast<T&&>(t);
}
template <class T>
constexpr T&& forward(remove_reference_t<T>&& t) noexcept
{
static_assert(!std::is_lvalue_reference_v<T>);
return static_cast<T&&>(t);
}
(You can use decltype(auto)
here, because the deduced type will always be T&&
.)
In all the following cases, the first overload is called because the expression arg
denotes a variable and hence is an lvalue:
If
g
is called with a non-const lvalue, thenT
is deduced as a non-const lvalue reference type.T&&
is the same asT
, andforward<T>(arg)
is a non-const lvalue expression. Therefore,f
is called with a non-const lvalue expression.If
g
is called with a const lvalue, thenT
is deduced as a const lvalue reference type.T&&
is the same asT
, andforward<T>(arg)
is a const lvalue expression. Therefore,f
is called with a const lvalue expression.If
g
is called with an rvalue, thenT
is deduced as a non-reference type.T&&
is an rvalue reference type, andforward<T>(arg)
is an rvalue expression. Therefore,f
is called with an rvalue expression.
In all cases, the value category is respected.
The second overload is not used in normal perfect forwarding. See What is the purpose of std::forward()'s rvalue reference overload? for its usage.
std forward implementation and reference collapsing
Does it have to do with disallowing deducing types maybe?
Yes, typename std::remove_reference<T>::type
introduces a non-deduced context. It prevents the user from mistakenly writing...
std::forward(something)
...and forces him/her to provide an explicit template argument:
std::forward<T>(something)
Difference between std::forward implementation
Try hsing it like std forward in a real context. Yours does not work;
void test(std::vector<int>&&){}
template<class T>
void foo(T&&t){
test(my_forward<T>(t));
}
foo( std::vector<int>{} );
The above does not compile. It does with std::forward
.
Your forward does nothing useful other than block reference lifetime extension. Meanwhile, std::forward
is a conditional std::move
.
Everything with a name is an lvalue, but forward moves rvalue references with names.
Rvalue references with names are lvalues.
Is it true that std::forward and std::move do not generate code?
Whether something "generates code" or not depends on the compiler and its settings. As the other answer shows, you can expect some extra code to be generated if the optimizations are disabled.
std::move
and std::forward
merely return a reference to the parameter, which doens't require any actions at runtime (the change in value category happens at compile-time), and if optimizations are enabled, any half-decent compiler will generate no code for them.
If you want no extra code to be generated even in debug builds, use a static_cast<T &&>
instead of those functions.
How does std::apply forward parameters without explicit std::forward?
You do not need to std::forward
each element because std::get
is overloaded for rvalue-reference and lvalue-reference of tuple.
std::forward<Tuple>(t)
will give you either a lvalue (Tuple &
) or an rvalue (Tuple &&
), and depending on what you get, std::get
will give you a T &
(lvalue) or a T &&
(rvalue). See the various overload of std::get
.
A bit of details about std::tuple
and std::get
-
As mentioned by StoryTeller, every member of a tuple is an lvalue, whether it has been constructed from an rvalue or a lvalue is of no relevance here:
double a{0.0};
auto t1 = std::make_tuple(int(), a);
auto t2 = std::make_tuple(int(), double());
The question is - Is the tuple an rvalue? If yes, you can move its member, if no, you have to do a copy, but std::get
already take care of that by returning member with corresponding category.
decltype(auto) a1 = std::get<0>(t1);
decltype(auto) a2 = std::get<0>(std::move(t1));
static_assert(std::is_same<decltype(a1), int&>{}, "");
static_assert(std::is_same<decltype(a2), int&&>{}, "");
Back to a concrete example with std::forward
:
template <typename Tuple>
void f(Tuple &&tuple) { // tuple is a forwarding reference
decltype(auto) a = std::get<0>(std::forward<Tuple>(tuple));
}
f(std::make_tuple(int())); // Call f<std::tuple<int>>(std::tuple<int>&&);
std::tuple<int> t1;
f(t1); // Call f<std::tuple<int>&>(std::tuple<int>&);
In the first call of f
, the type of a
will be int&&
because tuple
will be forwarded as a std::tuple<int>&&
, while in the second case its type will be int&
because tuple
will be forwarded as a std::tuple<int>&
.
Reason for using std::forward before indexing operator in Effective Modern C++ example
It depends on how the Container is implemented. If it has two reference-qualified operator[]
for lvalue and rvalue like
T& operator[] (std::size_t) &; // used when called on lvalue
T operator[] (std::size_t) &&; // used when called on rvalue
Then
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
authenticateUser();
return std::forward<Container>(c)[i]; // call the 1st overload when lvalue passed; return type is T&
// call the 2nd overload when rvalue passed; return type is T
}
It might cause trouble without forwarding reference.
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
authenticateUser();
return c[i]; // always call the 1st overload; return type is T&
}
Then
const T& rt = authAndAccess(Container{1, 5, 9}, 0);
// dangerous; rt is dangling
BTW this doesn't work for std::vector
because it doesn't have reference-qualified operator[]
overloads.
Related Topics
How to Export Templated Classes from a Dll Without Explicit Specification
How to Get the Position and Draw Rectangle Using Opencv
Equivalent C++ to Python Generator Pattern
How to Read Entire Stream into a Std::String
Replacing Winmain() with Main() Function in Win32 Programs
Scale and Rotation Template Matching
Converting Float Values from Big Endian to Little Endian
What Happens If I Read a Map's Value Where the Key Does Not Exist
Fatal Error: "No Target Architecture" in Visual Studio
How to Tell If the C Function Atoi Failed or If It Was a String of Zeros
Conditional Operator Used in Cout Statement
At^Sysinfo and a C++ Terminal Program
C++ - Decimal to Binary Converting
Has the New C++11 Member Initialization Feature at Declaration Made Initialization Lists Obsolete
Best Method for Storing This Pointer for Use in Wndproc
Function in C++ Returns by Value or by Reference
Convert String to Mathematical Evaluation
Difference in Behavior While Using Dynamic_Cast with Reference and Pointers