'The implementation of std::forward
I'm reading Overview of the New C++ (C++11/14) (PDF only), at Slide 288 it gives an implementation of std::forward
:
template<typename T> // For lvalues (T is T&),
T&& std::forward(T&& param) // take/return lvalue refs.
{ // For rvalues (T is T),
return static_cast<T&&>(param); // take/return rvalue refs.
}
And then gives another implemention in text:
The usual
std::forward
implementation is:
template<typename T>
struct identity {
typedef T type;
};
template<typename T>
T&& forward(typename identity<T>::type&& param)
{
return static_cast<identity<T>::type&&>(param);
}
What is the difference? Why is latter the usual implementation?
Solution 1:[1]
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;
Solution 2:[2]
The implementation in libc++ uses std::remove_reference
and two overloads. Here is the source (after removing some macros):
template <class T>
inline T&& forward(typename std::remove_reference<T>::type& t) noexcept
{
return static_cast<T&&>(t);
}
template <class T>
inline T&& forward(typename std::remove_reference<T>::type&& t) noexcept
{
static_assert(!std::is_lvalue_reference<T>::value,
"Can not forward an rvalue as an lvalue.");
return static_cast<T&&>(t);
}
but note that in C++14, std::forward
is constexpr
.
Solution 3:[3]
The first case as Sebastian Redl said will always give you an lvalue reference. The reason is that an rvalue reference in the parameter would be passed as an lvalue reference, and the parameter T&&
type is a universal reference rather than an rvalue reference.
Actually if the first case is correct, we don't even need forward
any more. Here is an experiment to demonstrate how universal reference parameters are passed
template <typename T, typename U>
void g(T&& t, U&& u)
{
std::cout << "t is lvalue ref: "
<< std::is_lvalue_reference<decltype(t)>::value << std::endl; // 1
std::cout << "t is rvalue ref: "
<< std::is_rvalue_reference<decltype(t)>::value << std::endl; // 0
std::cout << "u is lvalue ref: "
<< std::is_lvalue_reference<decltype(u)>::value << std::endl; // 1
std::cout << "u is rvalue ref: "
<< std::is_rvalue_reference<decltype(u)>::value << std::endl; // 0
}
template <typename T, typename U>
void f(T&& t, U&& u)
{
std::cout << "t is lvalue ref: "
<< std::is_lvalue_reference<decltype(t)>::value << std::endl; // 1
std::cout << "t is rvalue ref: "
<< std::is_rvalue_reference<decltype(t)>::value << std::endl; // 0
std::cout << "u is lvalue ref: "
<< std::is_lvalue_reference<decltype(u)>::value << std::endl; // 0
std::cout << "u is rvalue ref: "
<< std::is_rvalue_reference<decltype(u)>::value << std::endl; // 1
g(t, u);
}
int main()
{
std::unique_ptr<int> t;
f(t, std::unique_ptr<int>());
return 0;
}
The program turns out that both t
and u
passed from f
to g
is lvalue references, despite that u
is an rvalue reference in f
. So in the first case the parameter of forward
just doesn't have a chance to be an rvalue reference.
The identity
is used to change the parameter type from universal reference to an rvalue reference (as mentioned by Redl, it's more precise to use std::remove_reference
). However this change makes the template type deduction not possible any longer, so that the type parameter for forward
is mandatory, as a result we shall write forward<T>(t)
.
But the second case in your question is not correct either, as also mentioned by Redl, the correct approach is an overload whose parameter is an lvalue reference.
The most straightforward implementation I can find is this
template <typename T>
T&& forward(typename identity<T>::type& param)
{
return static_cast<T&&>(param);
}
It works for universal references, for example
template <typename T, typename U>
void f(T&& t, U&& u)
{
::forward<T>(t);
::forward<U>(u);
}
std::unique_ptr<int> t;
f(t, std::unique_ptr<int>());
// deduction in f:
// T = unique_ptr&, decltype(t) = unique_ptr&
// U = unique_ptr, decltype(u) = unique_ptr&& (but treated as an lvalue reference)
// specialization of forward:
// forward<T> = forward<unique_ptr&>, param type = unique_ptr&
// return type = unique_ptr&
// forward<U> = forward<unique_ptr>, param type = unique_ptr&
// return type = unique_ptr&&
Solution 4:[4]
Replies
What is the difference?
In practice there is no difference, the expected result in the execution is the same, given that only the way of writing is being more verbose
You are just typedefining the T and using the type defined through the typename, meaning literally no difference.
Why is latter the usual implementation?
I didn't follow this update so I can't say if they used this code officially in their repository, it's probably someone else's implementation and not the official one, whatever the reason is this:
They chose to do it this way, even though both implementations are invalid for the real scenario.
Note that both have the same result and do not diverge at all in the execution, that is, in the real scenario, diverging between them would not cause any difference or run/compilation error or anything like that
Code review
Missed add "typename" for compilation to succeed
template<typename T>
struct identity {
typedef T type;
};
template<typename T>
T&& forward(typename identity<T>::type&& param)
{
return static_cast<**typename** identity<T>::type&&>(param);
}
Explanations
Why is invalid
Considering the below code, the argument T is "int" containing the return type is "int&&", together expecting a function parameter of type rvalue (int&&) but an lvalue int& was passed generating a compilation error#include <iostream>
template<typename T>
T&& forward(T&& param)
{
return static_cast<T&&>(param);
}
int main() {
int value = 5;
forward<int>(value);
return 1;
}
Real scenery
The line 25, redir(5), causes a compilation error, and this would be a real scenario.The error is because the template T referring to the void redir is of type int and when calling forward< int >(param) it is passing param which is an lvalue variable that was explained in the section Why is invalid
#include <iostream>
template<typename T>
T&& forward(T&& param)
{
return static_cast<T&&>(param);
}
void print(int &&value){
std::cout << "rvalue: " << value << std::endl;
}
void print(int &value){
std::cout << "lvalue: " << value << std::endl;
}
template <class T>
void redir(T &¶m){
print(forward<T>(param));
}
int main() {
int value = 5;
redir(value);
**redir(5);**
return 0;
}
Solution
Currently the code has been updated and the problem has been fixed, you can check it at: https://en.cppreference.com/w/cpp/utility/forward.The code would be similar to this:
#include <iostream>
template< class T >
T&& forward( std::remove_reference_t<T> &¶m)
{
return static_cast<T&&>(param);
}
template< class T >
T&& forward( std::remove_reference_t<T> ¶m)
{
return static_cast<T&&>(param);
}
void print(int &&value){
std::cout << "rvalue: " << value << std::endl;
}
void print(int &value){
std::cout << "lvalue: " << value << std::endl;
}
template <class T>
void redir(T &¶m){
print(forward<T>(param));
}
int main() {
int value = 5;
redir(value);
redir(5);
return 0;
}
std::remove_reference_t is optional, even without using it the result will be the same.
They decided to use remove_reference_t due to good practices, that is, they are reinforcing that the first function expects int& and the second int&&.
Why std::remove_reference is irrelevant
Although it does not change the expected result in code execution, it is important due to good programming practices, it reinforces the expected result so to speak.Well, but the result explanation doesn't change is due to the following conversion rules:
TR R
T& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T& && -> T& // rvalue reference to cv TR -> TR (lvalue reference to T)
T&& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T&& && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)
As we can, the only way to obtain an rvalue (T&&) is through the conversion T&& + T&&, or T&& only.
Why is it necessary to use 2 functions (std::forward) and not just static_cast in one function
<int&&> int&& && forward(int&& &¶m) will result int&& forward(int&& param)
<int&> int& && forward(int& &¶m) will result int& forward(int& param)
<int> int && forward(int &¶m) will result int&& forward(int&& param)
Note that the second function: T&& forward( std::remove_reference_t ¶m) fills in just what was missing
<int> int && forward(int ¶m) will result int&& forward(int& param)
For this reason you need to declare 2 std::forward functions.
Solution 5:[5]
According to the rvalue reference proposal, a named rvalue is no different from an lvalue, except for decltype
. That is any named parameter of a function cannot be implicitly casted or used to initialize another rvalue reference; it only copies to lvalue references; but static_cast
can explicitly cast the valueness of the reference. So when you pass a named (in contrast to unnamed temporary) object/variable to a function, it can only be captured by lvlaues. To cast back a function argument to rvalue a static_cast
is necessary, but it is too verbose about valueness of its output type. Any function such as std::forward
has no way of deducing the decltype
of its named operand. So the type must be verbosly passed as a template type argument; Correct deduction of return type of forward
relies on its type argument. And the two overloads of this special function are necessary: the lvalue overload captures named rvalues and the rvalue overload captures unnamed lvalues who cannot directly bind to lvalue references. The deduction of underlying type of the input reference is prevented by using template type alises and stressed by nesting them in a trait type; either std::type_identity
or std::remove_reference
or else. This keeps programmers away from the pitfall of unintentionally passing wrong valueness while using std::forward
. I have yet to see a trick who can achieve the functionality of std::forward
, while omitting its verbose type parameter. It is a fine piece of art.
Regards, FM.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Walter |
Solution 3 | |
Solution 4 | Dharman |
Solution 5 | Red.Wave |