Understanding of the implementation of std::forward since C++11 - c++

(constexpr and noexcept are left out, since they seem irrelevant for the purpose of understanding how std::forward behaves.)
Based on my understanding of Scott Meyers' "Effective Modern C++",
a sample implementation of std::move in C++14 is the following
template<typename T>
decltype(auto) move(T&& param) {
return static_cast<remove_reference_t<T>&&>(param);
}
Given the explanation of what a forwarding (or "universal") reference is, this implementation, I think, is pretty clear to me:
the parameter param is of type T&&, i.e. an rvalue reference or lvalue reference (to whichever the type of the argument is), depending on whether the argument is an rvalue or lvalue in the caller; in other words param can bind both to rvalues and lvalues (i.e. anything); this is intended, since move should cast anything to rvalue.
decltype(auto) is just the concise way to express the return type based on the actual return statement.
the returned object is the same object param, casted to an rvalue reference (&&) to whatever the type T is, once its deduced referenceness is stripped off (the deduction is done on T&&, not on ⋯<T>&&).
In short, my understanding of the use of forwarding/universal references in the implementation of move is the following:
the forwarding/universal reference T&& is used for the parameter since it is intended to bind to anything;
the return type is an rvalue reference, since move is intended to turn anything to rvalue.
It'd be nice to know if my understanding is right so far.
On the other hand, a sample implementation of std::forward in C++14 is the following
template<typename T>
T&& forward(remove_reference_t<T>& param) {
return static_cast<T&&>(param);
}
My understanding is the following:
T&&, the return type, must be a forwarding/universal reference, since we want forward to return either by rvalue reference or by lvalue reference, hence type deduction takes place on the return type here (unlike what happens for move, where type deduction takes place on the parameter side) is an rvalue reference to whichever template type argument is passed to forward;
since T encodes the lvalue/rvalue-ness of the actual argument which binds the callers' parameter that is passed as argument to forward, T itself can result to be actual_type& or actual_type, hence T&& can be either an lvalue reference or rvalue reference.
The type of param is an lvalue reference to whatever the type T is, once its deduced referenceness is stripped off. Actually in std::forward type deduction is disabled on purpose, requiring that the template type argument be passed explicitly.
My doubts are the following.
The two instances of forward (two for each type on which is it called, actually) only differ for the return type (rvalue reference when an rvalue is passed, lvalue reference when an lvalue is passed), since in both cases param is of type lvalue reference to non-const reference-less T. Isn't the return type something which does not count in overload resolution? (Maybe I've used "overload" improperly, here.)
Since the type of param is non-const lvalue reference to reference-less T, and since an lvalue reference must be to-const in order to bind to an rvalue, how can param bind to an rvalue?
As a side question:
can decltype(auto) be used for the return type, as it is done for move?

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, then T is deduced as a non-const lvalue reference type. T&& is the same as T, and forward<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, then T is deduced as a const lvalue reference type. T&& is the same as T, and forward<T>(arg) is a const lvalue expression. Therefore, f is called with a const lvalue expression.
If g is called with an rvalue, then T is deduced as a non-reference type. T&& is an rvalue reference type, and forward<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.

Related

confused with the fowarding reference

In the c++ std type_traits file below the first overloaded function, the comment says:
forward an lvalue as either an lvalue or an rvalue
However the return value is just an rvalue reference, I wonder how it could be either an lavlue or an rvalue? Does it mean the returned value is a universal reference? If so what decides how to deduce it to an lvalue reference or an rvalue reference?
Also the second overload returns exactly the same thing, why does it say in the comment only forwarding as a rvalue reference without lvalue reference?
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(
remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
return static_cast<_Ty&&>(_Arg);
}
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
return static_cast<_Ty&&>(_Arg);
}
If _Ty is either a rvalue reference or a non-reference, then _Ty&& is (by reference collapsing rules) a rvalue reference. Hence the function call expression will be a xvalue (a kind of rvalue).
If _Ty is however an lvalue reference to type T, i.e. _Ty = T&, then the reference collapsing rules imply that also _Ty&& = T & && = T&. So then the function call will be an lvalue expression.
This is an implementation of std::forward. The template argument for _Ty is not intended to (and can't be) deduced. Instead it must be given explicitly.
Normally std::forward should only be used in the form std::forward<U>(u); where u is a forwarding reference parameter of the form U&& u in a function template with U a template parameter.
Under these conditions, if a rvalue was passed for u, U will be deduced to a non-reference and if an lvalue was passed U will be deduced to an lvalue reference.
Then std::forward<U>(u) will pass either a non-reference or an lvalue reference type as template argument for _Ty to std::forward accordingly and by the rule above, std::forward<U>(u) will have the same value category (rvalue or lvalue) as the argument to the forwarding reference u had. (However, it maps both prvalues and xvalues to xvalues.)
In the second overload the comment doesn't mention forwarding as lvalue, because the static_assert will trigger if the user tried to use it that way. It should not be allowed to call std::forward with a lvalue reference template argument while the argument is a rvalue. That would not match the intended usage I discussed above.

Are there any realistic use cases for `decltype(auto)` variables?

Both from my personal experience and from consulting answers to questions like What are some uses of decltype(auto)? I can find plenty of valuable use cases for decltype(auto) as a function return type placeholder.
However, I am seriously struggling to think of any valid (i.e. useful, realistic, valuable) use case for decltype(auto) variables. The only possibility that comes to mind is to store the result of a function returning decltype(auto) for later propagation, but auto&& could be used there as well and it would be simpler.
I've even searched throughout all my projects and experiments, and the 391 occurrences of decltype(auto) are all return type placeholders.
So, are there any realistic use cases for decltype(auto) variables? Or it this feature only useful when used as a return type placeholder?
How do you define "realistic"?
I am looking for a use case that provides value (i.e. it's not just an example to show how the feature works) where decltype(auto) is the perfect choice, compared to alternatives such as auto&& or to not declaring a variable at all.
The problem domain doesn't matter, it could be some obscure metaprogramming corner case or arcane functional programming construct. However, the example would need to make me go "Hey, that's clever/beautiful!" and using any other feature to achieve the same effect would require more boilerplate or have some sort of drawback.
Essentially, the case for variables is the same for functions. The idea is that we store the result of an function invocation with a decltype(auto) variable:
decltype(auto) result = /* function invocation */;
Then, result is
a non-reference type if the result is a prvalue,
a (possibly cv-qualified) lvalue reference type if the result is a lvalue, or
an rvalue reference type if the result is an xvalue.
Now we need a new version of forward to differentiate between the prvalue case and the xvalue case: (the name forward is avoided to prevent ADL problems)
template <typename T>
T my_forward(std::remove_reference_t<T>& arg)
{
return std::forward<T>(arg);
}
And then use
my_forward<decltype(result)>(result)
Unlike std::forward, this function is used to forward decltype(auto) variables. Therefore, it does not unconditionally return a reference type, and it is supposed to be called with decltype(variable), which can be T, T&, or T&&, so that it can differentiate between lvalues, xvalues, and prvalues. Thus, if result is
a non-reference type, then the second overload is called with a non-reference T, and a non-reference type is returned, resulting in a prvalue;
an lvalue reference type, then the first overload is called with a T&, and T& is returned, resulting in an lvalue;
an rvalue reference type, then the second overload is called with a T&&, and T&& is returned, resulting in an xvalue.
Here's an example. Consider that you want to wrap std::invoke and print something to the log: (the example is for illustration only)
template <typename F, typename... Args>
decltype(auto) my_invoke(F&& f, Args&&... args)
{
decltype(auto) result = std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
my_log("invoke", result); // for illustration only
return my_forward<decltype(result)>(result);
}
Now, if the invocation expression is
a prvalue, then result is a non-reference type, and the function returns a non-reference type;
a non-const lvalue, then result is a non-const lvalue reference, and the function returns a non-const lvalue reference type;
a const lvalue, then result is a const lvalue reference, and the function returns a const lvalue reference type;
an xvalue, then result is an rvalue reference type, and the function returns an rvalue reference type.
Given the following functions:
int f();
int& g();
const int& h();
int&& i();
the following assertions hold:
static_assert(std::is_same_v<decltype(my_invoke(f)), int>);
static_assert(std::is_same_v<decltype(my_invoke(g)), int&>);
static_assert(std::is_same_v<decltype(my_invoke(h)), const int&>);
static_assert(std::is_same_v<decltype(my_invoke(i)), int&&>);
(live demo, move only test case)
If auto&& is used instead, the code will have some trouble differentiating between prvalues and xvalues.
Probably not a very deep answer, but basically decltype(auto) was proposed to be used for return type deduction, to be able to deduce references when the return type is actually a reference (contrary to plain auto that will never deduce the reference, or auto&& that will always do it).
The fact that it can also be used for variable declaration not necessarily means that there should be better-than-other scenarios. Indeed, using decltype(auto) in variable declaration will just complicate the code reading, given that, for a variable declaration, is has exactly the same meaning. On the other hand, the auto&& form allows you to declare a constant variable, while decltype(auto) doesn't.

Using std::forward with a non-forwarding, plain old reference

Asking for a friend:
Why does std::forward in the following code cast the parameter c to an rvalue ?
template <typename T>
void f (T& c) {
using value_type = typename std::remove_reference_t<T>;
std::vector<value_type> v;
// debugger reveals: push_back( T&& value ) is called here
v.push_back(std::forward<T>(c));
}
Note that c is not a universal/forwarding reference here. I am aware of the fact that this function would most probably be of more use if it actually was, but curious all the same.
To understand this situation, you have to understand why forwarding references work. Given a definition like
template <typename T>
void foo(T&& t) {}
when you write something like
some_type some_object;
foo(some_object);
template deduction deduces T to be some_type&. Now the parameter t has the type some_type& &&. Since you can't have references to references, reference collapsing rules are applied and some_type& && is collapsed to some_type&.
If, instead, you write something like
some_type some_object;
foo(std::move(some_object));
template deduction deduces T to be some_type. Now the parameter t has the type some_type&&. That's a perfectly valid type, so no reference collapsing is done.
Now we get to std::forward. All std::forward<U> does is cast its parameter to U&&. If U is some_type, as in the second case above, the parameter is cast to some_type&&. It remains an rvalue-reference. If U is some_type&, as in the first case above, reference collapsing is performed again, and some_type& && becomes some_type&. So std::forward returns an lvalue-reference.
So the ultimate answer to your original question is that the return type of std::forward only depends on the type passed as std::forward's template parameter. Since T in your case will always be deduced as a non-reference type, std::forward will always return an rvalue-reference.
std::forward<T>(c) is equivalent to static_cast<T&&>(c).
If T&& is a forwarding reference then this allows an lvalue to be forwarded as an lvalue because T will be deduced as an lvalue reference type and T&& will be the same lvalue reference type by the reference-collapsing rules. In your situation, T&& is not a forwarding reference, so this doesn't work.
Well, the definition of std::forward<T>(x) is to cast x to type T&&. If you pass a non-reference as argument you’ll get an rvalue reference T&& back. Since your T cannot be a reference type (you cannot have a reference to a reference), it must be a non-reference type.

How does std::forward receive the correct argument?

Consider:
void g(int&);
void g(int&&);
template<class T>
void f(T&& x)
{
g(std::forward<T>(x));
}
int main()
{
f(10);
}
Since the id-expression x is an lvalue, and std::forward has overloads for lvalues and rvalues, why doesn't the call bind to the overload of std::forward that takes an lvalue?
template<class T>
constexpr T&& forward(std::remove_reference_t<T>& t) noexcept;
It does bind to the overload of std::forward taking an lvalue:
template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;
It binds with T == int. This function is specified to return:
static_cast<T&&>(t)
Because the T in f deduced to int. So this overload casts the lvalue int to xvalue with:
static_cast<int&&>(t)
Thus calling the g(int&&) overload.
In summary, the lvalue overload of std::forward may cast its argument to either lvalue or rvalue, depending upon the type of T that it is called with.
The rvalue overload of std::forward can only cast to rvalue. If you try to call that overload and cast to lvalue, the program is ill-formed (a compile-time error is required).
So overload 1:
template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;
catches lvalues.
Overload 2:
template <class T> constexpr T&& forward(remove_reference_t<T>&& t) noexcept;
catches rvalues (which is xvalues and prvalues).
Overload 1 can cast its lvalue argument to lvalue or xvalue (the latter which will be interpreted as an rvalue for overload resolution purposes).
Overload 2 can can cast its rvalue argument only to an xvalue (which will be interpreted as an rvalue for overload resolution purposes).
Overload 2 is for the case labeled "B. Should forward an rvalue as an rvalue" in N2951. In a nutshell this case enables:
std::forward<T>(u.get());
where you are unsure if u.get() returns an lvalue or rvalue, but either way if T is not an lvalue reference type, you want to move the returned value. But you don't use std::move because if T is an lvalue reference type, you don't want to move from the return.
I know this sounds a bit contrived. However N2951 went to significant trouble to set up motivating use cases for how std::forward should behave with all combinations of the explicitly supplied template parameter, and the implicitly supplied expression category of the ordinary parameter.
It isn't an easy read, but the rationale for each combination of template and ordinary parameters to std::forward is in N2951. At the time this was controversial on the committee, and not an easy sell.
The final form of std::forward is not exactly what N2951 proposed. However it does pass all six tests presented in N2951.
why doesn't the call bind to the overload of std::forward that takes an lvalue?
It does exactly that, but std::forward does not deduce its template argument, you tell it what type it is, and that's where the magic occurs. You're passing a prvalue to f() so f() deduces [T = int]. It then calls the lvalue overload of forward, and due to reference collapsing both the return type and static_cast<T&&> that happens within forward will be of type int&&, thus calling the void g(int&&) overload.
If you were to pass an lvalue to f()
int x = 0;
f(x);
f() deduces [T = int&], again the same lvalue overload of forward gets called, but this time the return type and static_cast<T&&> are both int&, again because of the reference collapsing rules. This will then call the void g(int&) overload instead.
Live demo
Howard already has a good answer for your question about why the rvalue overload of forward is required, but I'll add a contrived example that shows the two versions in action.
The basic idea behind the two overloads is that the rvalue overload will be invoked in cases where the result of the expression you pass to forward yields an rvalue (prvalue or xvalue).
Say you have a type foo that has a ref-qualified pair of get() member function overloads. The one with the && qualifier returns an int while the other returns int&.
struct foo
{
int i = 42;
int get() && { return i; }
int& get() & { return i; }
};
And say f() invokes the get() member function on whatever was passed to it, and forwards that on to g()
template<class T>
auto f(T&& t)
{
std::cout << __PRETTY_FUNCTION__ << '\n';
g(forward<decltype(forward<T>(t).get())>(forward<T>(t).get()));
}
foo foo1;
f(foo1); // calls lvalue overload of forward for both calls to forward
f(std::move(foo1)); // calls lvalue overload of forward for forward<T>(t)
// but calls rvalue overload for outer call to forward
Live demo
How does std::forward receive the correct argument?
For perfect forwarding,as your code:
template<class T>
void f(T&& x)
{
g(std::forward<T>(x));
}
Note this:std::forward requires both a function argument and a template type argument.
The template parameter T will encode whether the argument passed to param was an lvalue or an rvalue and then forward use it.
In this case,not matter x refer what ,x itself is an lvalue,choice overload 1 ,and x is forwarding(universal) reference parameters.Rules as follow:
if f the argument's expr is lvalue ,Both x and T will lvalue reference
type
if f the argument's expr is rvalue ,T will be non-reference type.
For example use gcc source code :
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); } //overload 1
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}// overload 2
if pass function f lvalue of type is string or string&,for
forward function _Tp will be string&,__t will be string & &
is equal to string&,_Tp&& will be string& && is equal to
string&.(reference collapsing)
if pass function f rvalue of type is string or string&&,for
forward function _Tp will be string,__t will be
string&,_Tp&& will be string&&.
What the function do?
if rvalue to rvalue(Overload 2) or lvalue to lvalue(Overload 1) ,nothing to do,just return.
If you're not careful or something else, lead rvalue to lvalue, gives you a compilation error by static_assert.(Overload 2)
If you do lvalue to rvalue(Overload 1),it is same with std::move,but not convenience.
So just as Scott Meyers say:
Given that both std::move and std::forward boil down to casts, the
only difference being that std::move always casts, while
std::forward only sometimes does, you might ask whether we can
dispense with std::move and just use std::forward everywhere. From a
purely technical perspective, the answer is yes: std::forward can do
it all. std::move isn’t necessary. Of course, neither function is
really necessary, because we could write casts everywhere, but I
hope we agree that that would be, well, yucky.
What is the rvalue overload for?
I'm not sure, it should be used where you really need it.Such as Howard Hinnant says:
Overload 2 is for the case labeled "B. Should forward an rvalue as an
rvalue" in N2951. In a nutshell this case enables:
std::forward<T>(u.get());
where you are unsure if u.get() returns an lvalue or rvalue, but
either way if T is not an lvalue reference type, you want to move the
returned value. But you don't use std::move because if T is an lvalue
reference type, you don't want to move from the return.
In a word,it allow us use T's type to decide whether move or not.

Is it meaningless to declare the return type of a function as T&&?

As we have known, in most common cases, T&& means "this is a temporary object". However, if one wants to return a temporary object from a function, he/she can declare the function as follows:
template<class T>
T f()
{
T t;
......
return t;
}
or (Note: Not Correct)
template<class T>
T&& f()
{
T t;
......
return t;
}
But I think the latter is overdid, because the former is enough and backward compatible.
Yet, I also find the std::forward()'s return type is declared as T&&, so I'm sure my understanding about this is incomplete.
My real question is: When and where should we declare the return type of a function as T&&?
In your example, the T&& is wrong, it's a dangling reference.
But std::forward doesn't return an rvalue reference to a local variable in its own definition, it returns an rvalue reference to its by-rvalue-reference argument (or an lvalue reference to a by-lvalue-reference argument).
You should return an rvalue reference only if you want the caller of your function to be able to move from whatever that reference refers to.
Normally that will only be if the purpose of the function is to provide move access to some significant object (perhaps which already exists). So that includes std::move (which allows you to move from an lvalue), and similarly you might write an accessor function specifically designed for users to move from a data member of some object, or an element of some container. If the object itself isn't significant, only the value, then you can return by value.
As grizzly says, sometimes due to reference collapsing you can take advantage of tricks which mean that you type T&& in your code, but when T is already an lvalue-reference type T&& is the same lvalue reference type. std::forward uses that trick. That is to say, because of reference collapsing T&& doesn't mean "rvalue reference to T", it means "T if T is a reference type, otherwise rvalue reference to T".
T&& doesn't necessarily mean that the result is an rvalue. When used with a template parameter && denotes a universal reference, which can either be an rvalue or an lvalue reference. More specifically: If T is an lvalue reference type foo&, T&& is actually an lvalue refere to foo, otherwise it denotes an rvalue reference.
This can be used to write functions, which take any kind of argument:
template<typename T> void foo(T&&);
bar a;
const bar b;
foo(a);//T and T&& will both be bar&
foo(b);//T and T&& will both be const bar&
foo(bar());//T will be bar, T&& will be bar&&
With this in mind std::forward is called with a T& and casts it to T&&, where T is explicitly stated. So if the original function parameter was an lvalue reference, it will return one, otherwise it will return an rvalue reference, enabling perfect forwarding.
As for when to use it as a return type: Rarely, though it might be useful to avoid copies when arguments are passed through, for example:
template<typename T> T&& foo(T&& bar) {/*some ops*/ return std::forward<T>(bar);}
The answer depends on whether your function is a template function or not. In your question it was, but let's first take a look at if it isn't:
Non-Template
T&& is not meaningless as a return type. The reason is that it, in fact, does not mean "temporary object". It means "rvalue reference". The difference is subtle, but can be highlighted with a class of functions for which this return type is relevant. Namely, what if we want to return a reference, to an rvalue, but the object it refers to is not a local object of our function?
T&& return_rvalue(/*some data*/)
{
T&& t = Get_a_reference();
// Do something fascinating.
return static_cast<T&&>(t);
}
One very special case of this pattern is the function std::move, which takes any reference and returns it a corresponding rvalue reference. Naturally, in real code, you should of course use std::move rather than performing the cast directly, since this more clearly shows your intention.
Template
If T is a template parameter, T&& is what Scott Meyers refers to as a Universal Reference. This means the type of T&& will be figured out using reference collapsing... In short, it means T&& is an rvalue if T i not a reference type, and an lvalue reference if it is..