#include <iostream>
#include <vector>
#include <type_traits>
#include <utility>
using namespace std;
template <typename Func, typename... Args>
void proxy(Func f, Args&&... args) {
f(std::forward<Args>(args)...);
}
void real_func(vector<int> v) {
cout << "size: " << v.size() << endl;
}
void multicast_func(vector<int> v) {
proxy(real_func, std::forward<vector<int>>(v));
proxy(real_func, std::forward<vector<int>>(v));
}
int main()
{
vector<int> ints = {1, 2, 3};
multicast_func(ints);
return 0;
}
and the output is:
size: 3
size: 0
why isn't it 3, 3? at what point does this lvalue become an rvalue and gets moved-from?
std::forward is intended to be used with universal references.
The parameter of multicast_func is not an universal reference, so std::forward makes no sense:
void multicast_func(vector<int> v) {
proxy(real_func, std::forward<vector<int>>(v));
proxy(real_func, std::forward<vector<int>>(v));
}
In this case, it effectively acts like std::move (because the template parameter is not an (lvalue) reference).
The prototype for std::forward called in your code is:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
When called with non-reference type, it effectively makes an rvalue reference out of the argument, which than is moved from. std::vector is guaranteed to be empty after being moved from it, so size becomes 0.
at what point does this lvalue become an rvalue and gets moved-from?
At the 1st time proxy is invoked v is converted to an rvalue, and then gets moved-from when passed to real_func.
void multicast_func(vector<int> v) {
// the return type of std::forward is T&&, i.e. vector<int>&& here
// for functions whose return type is rvalue reference to objec, the return value is an rvalue
// that means v is converted to an rvalue and passed to proxy
proxy(real_func, std::forward<vector<int>>(v));
// v has been moved when passed to real_func as argument
proxy(real_func, std::forward<vector<int>>(v));
}
The usage in proxy is the general usage of std::forward; according to the argument is an lvalue or rvalue, the template parameter will be deduced as T& or T. For T& std::forward will return an lvalue, for T std::forward will return an rvalue, so the value category is preserved. When you specify the template argument solely such capacity is lost.
std::forward, when not given a reference type, will cast provided object to an rvalue. This means the first call to
proxy(real_func, std::forward<vector<int>>(v));
will make v an rvalue which means it will move it into real_func. The second call then uses that moved from object and you get the size of 0 since it has been emptied.
This makes sense if we look at the function. The version of std::forward you are calling is
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
Since you passed std::vector<int> for T that means it will return a std::vector<int>&&. So, even though v is an lvalue, it is converted to an rvalue. If you want to maintain the lvalue-ness of v then you need to use std::vector<int>&. That gives you std::vector<int>& && and reference colapse rules turns that into std::vector<int>& leaving you with an lvalue.
In the first call of proxy, the parameter vector<int> v(in function real_func) is move constructed from the rvalue, so v (in function multicast_func) has been empty.
But if you change the type of paremeter as cosnt vector<int> &, the result will be 3, 3. Because the move constrctor has not been called.
void real_func(const vector<int>& v) {
cout << "size: " << v.size() << endl;// the output is 3, 3
}
Related
Quoting from item 3 ("Understand decltype") of "Effective Modern C++" :
However, we need to update the template’s implementation to bring it into accord
with Item 25’s admonition to apply std::forward to universal references:
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
authenticateUser();
return std::forward<Container>(c)[i];
}
Why are we forwarding 'c' in the last statement of 'authAndAccess'? I understand the need for forwarding when we're passing the param to another function from within the original function (to be able to correctly call the overloaded version which takes an rvalue reference or if there's an overloaded version which takes param by value then we can move instead of copy), but what benefit does std::forward give us while calling indexing-operator in the above example?
The example below also verifies that using std::forward does not allow us to return an rvalue reference from authAndAccess, for instance if we wanted to be able to move (instead of copy) using the return value.
#include <bits/stdc++.h>
void f1(int & param1) {
std::cout << "f1 called for int &\n";
}
void f1(int && param1) {
std::cout << "f1 called for int &&\n";
}
template<typename T>
void f2(T && param) {
f1(param[0]); // calls f1(int & param1)
f1(std::forward<T>(param)[0]); // also calls f1(int & param1) as [] returns an lvalue reference
}
int main()
{
std::vector<int> vec1{1, 5, 9};
f2(std::move(vec1));
return 0;
}
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.
You std::forward when you want your code to respect value categories. The most simplistic aspect of that is reference parameters to functions, and indeed classes with overloaded operators that can depend on value categories. But it goes beyond that, since value categories are ingrained into how expressions work in general, even basic ones.
Consider this toy example:
#include <iostream>
template<typename Container, typename Index>
decltype(auto) access(Container&& c, Index i)
{
return std::forward<Container>(c)[i];
}
struct A {
A() = default;
A(A const&) { std::cout << "Copy\n"; }
A(A &&) { std::cout << "Move\n"; }
};
int main()
{
using T = A[1];
[[maybe_unused]] auto _1 = access(T{}, 0);
{
T t;
[[maybe_unused]] auto _2 = access(t, 0);
}
}
It will print:
Move
Copy
Why? Because we passed an rvalue raw array into the function. As such, the indexing expression (after forwarding) respects the semantics of the build in []. If you index an rvalue array, you obtain an element that is also an rvalue in the taxonomy of value categories. So we move from it when initializing _1.
_2 on the other hand, is initialized from an lvalue, again because we passed an lvalue array into the function. If you want to let expressions inside a function do their things depending on the value category of the operands, your code can std::forward to accomplish that.
It doesn't mean this makes sense for every expression under the sun, but it's something that is useful to be aware of.
I know that the second overload of std::forward:
template< class T >
constexpr T&& forward( std::remove_reference_t<T>&& t ) noexcept;
is used for rvalues (as stated by Howard Hinnant in his answer: How does std::forward receive the correct argument?)
There is an example of when this overload is used at cppreference.com (that is also mentioned in How does std::forward receive the correct argument? by Praetorian):
Forwards rvalues as rvalues and prohibits forwarding of rvalues as lvalues This overload makes it possible to forward a result of an expression (such as function call), which may be rvalue or lvalue, as the original value category of a forwarding reference argument.
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
};
I really don't get this example. Why is the outer forward forward<decltype(forward<T>(arg).get())> even needed?
Cppreference states:
This overload makes it possible to forward a result of an expression (such as function call), which may be rvalue or lvalue, as the original value category of a forwarding reference argument.
As an example:
void func(int& lvalue)
{
std::cout << "I got an lvalue!" << std::endl;
}
void func(int&& rvalue)
{
std::cout << "I got an rvalue!" << std::endl;
}
template <typename T>
T&& myForward(typename std::remove_reference_t<T>& t)
{
return static_cast<T&&>(t);
}
struct foo
{
int i = 42;
int& get()& { return i; }
int get()&& { return i; }
};
template <typename T>
void wrapper(T&& t)
{
func(myForward<T>(t).get());
}
int main()
{
foo f;
wrapper(f);
wrapper(foo());
return 0;
}
This prints:
I got an lvalue!
I got an rvalue!
just fine, without the outer forward, while it also forwards the "result of an expression [...] as the original value category of a forwarding reference argument." It does not even need the second overload of std::forward. This overload is only necessary when calling func() like this:
func(myForward<decltype(myForward<T>(t).get())>(myForward<T>(t).get()));
Still, I can't wrap my head around why anyone would need to add the outer forward.
Edit: Edit moved to follow-up question: RValue-reference overload of std::forward potentially causing dangling reference?
Why is the outer forward forward<decltype(forward<T>(arg).get())> even needed?
It's not. The expression already is of its own correct value category. In C++17 (when returning by value bigger types) it's even a pessimization. All it does is turn a potential prvalue into an xvalue, and inhibiting copy elision. I'm tempted to say it's cargo cult programming.
I've understood how std::move works and implemented my own version for practice only. Now I'm trying to understand how std::forward works:
I've implemented this so far:
#include <iostream>
template <typename T>
T&& forward_(T&& x)
{
return static_cast<T&&>(x);
}
/*template <typename T>
T&& forward_(T& x)
{
return static_cast<T&&>(x);
}*/
void incr(int& i)
{
++i;
}
void incr2(int x)
{
++x;
}
void incr3(int&& x)
{
++x;
}
template <typename T, typename F>
void call(T&& a, F func)
{
func(forward_<T>(a));
}
int main()
{
int i = 10;
std::cout << i << '\n';
call(i, incr);
std::cout << i << '\n';
call(i, incr2);
std::cout << i << '\n';
call(0, incr3); // Error: cannot bind rvalue reference of type int&& to lvalue of type int.
std::cout << "\ndone!\n";
}
Why must I provide the overloaded forward(T&) version taking an lvalue reference? As I understand it a forwarding reference can yield an lvalue or an rvalue depending on the type of its argument. So passing the prvalue literal 0 to call along with the incr3 function that takes an rvalue reference of type int&& normally doesn't need forward<T>(T&)?!
If I un-comment the forward_(T&) version it works fine!?
I'm still confused about: why if I only use the forward_(T&) version does it work for any value category? Then what is the point in having the one taking a forwarding reference forward_(T&&)?
If I un-comment the version taking lvalue reference to T& and the one taking forwarding reference T&& then the code works fine and I've added some messages inside both to check which one called. the result is the the one with T&& never called!
template <typename T>
T&& forward_(T& x)
{
std::cout << "forward_(T&)\n";
return static_cast<T&&>(x);
}
template <typename T>
T&& forward_(T&& x)
{
std::cout << "forward_(T&&)\n";
return static_cast<T&&>(x);
}
I mean running the same code in the driver program I've shown above.
A T&& reference stops being a forwarding reference if you manually specify T (instead of letting the compiler deduce it). If the T is not an lvalue reference, then T&& is an rvalue reference and won't accept lvalues.
For example, if you do forward_<int>(...), then the parameter is an rvalue reference and ... can only be an rvalue.
But if you do forward_(...), then the parameter is a forwarding reference and ... can have any value category. (Calling it like this makes no sense though, since forward_(x) will have the same value category as x itself.)
It is clear that you wander why having two versions of std::forward; one takes an l-value reference to the type parameter T& and the other takes a universal reference (forwarding) to the type parameter. T&&.
In your case you are using forward_ from inside the function template call which has forwarding reference too. The problem is that even that function call called with an rvalue it always uses forward_ for an lvalue because there's no way that call can pass its arguments without an object (parameter). Remember that a name of an object is an lvlaue even if it's initialized from an r-value. That is why always in your example forward_(T&) is called.
Now you ask why there's second version taking forwarding reference?
It is so simple and as you may have already guessed: it is used for r-values (the values not the names of those objects).
Here is an example:
template <typename T>
T&& forward_(T& x)
{
std::cout << "forward_(T&)\n";
return static_cast<T&&>(x);
}
template <typename T>
T&& forward_(T&& x)
{
std::cout << "forward_(T&&)\n";
return static_cast<T&&>(x);
}
int main()
{
int i = 10;
forward_(i); // forward(T&) (1)
forward_(5); // forward(T&&) (2)
forward_("Hi"); // forward(T&) (3)
}
I know that the second overload of std::forward:
template< class T >
constexpr T&& forward( std::remove_reference_t<T>&& t ) noexcept;
is used for rvalues (as stated by Howard Hinnant in his answer: How does std::forward receive the correct argument?)
There is an example of when this overload is used at cppreference.com (that is also mentioned in How does std::forward receive the correct argument? by Praetorian):
Forwards rvalues as rvalues and prohibits forwarding of rvalues as lvalues This overload makes it possible to forward a result of an expression (such as function call), which may be rvalue or lvalue, as the original value category of a forwarding reference argument.
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
};
I really don't get this example. Why is the outer forward forward<decltype(forward<T>(arg).get())> even needed?
Cppreference states:
This overload makes it possible to forward a result of an expression (such as function call), which may be rvalue or lvalue, as the original value category of a forwarding reference argument.
As an example:
void func(int& lvalue)
{
std::cout << "I got an lvalue!" << std::endl;
}
void func(int&& rvalue)
{
std::cout << "I got an rvalue!" << std::endl;
}
template <typename T>
T&& myForward(typename std::remove_reference_t<T>& t)
{
return static_cast<T&&>(t);
}
struct foo
{
int i = 42;
int& get()& { return i; }
int get()&& { return i; }
};
template <typename T>
void wrapper(T&& t)
{
func(myForward<T>(t).get());
}
int main()
{
foo f;
wrapper(f);
wrapper(foo());
return 0;
}
This prints:
I got an lvalue!
I got an rvalue!
just fine, without the outer forward, while it also forwards the "result of an expression [...] as the original value category of a forwarding reference argument." It does not even need the second overload of std::forward. This overload is only necessary when calling func() like this:
func(myForward<decltype(myForward<T>(t).get())>(myForward<T>(t).get()));
Still, I can't wrap my head around why anyone would need to add the outer forward.
Edit: Edit moved to follow-up question: RValue-reference overload of std::forward potentially causing dangling reference?
Why is the outer forward forward<decltype(forward<T>(arg).get())> even needed?
It's not. The expression already is of its own correct value category. In C++17 (when returning by value bigger types) it's even a pessimization. All it does is turn a potential prvalue into an xvalue, and inhibiting copy elision. I'm tempted to say it's cargo cult programming.
Let's have a function called Y that overloads:
void Y(int& lvalue)
{ cout << "lvalue!" << endl; }
void Y(int&& rvalue)
{ cout << "rvalue!" << endl; }
Now, let's define a template function that acts like std::forward
template<class T>
void f(T&& x)
{
Y( static_cast<T&&>(x) ); // Using static_cast<T&&>(x) like in std::forward
}
Now look at the main()
int main()
{
int i = 10;
f(i); // lvalue >> T = int&
f(10); // rvalue >> T = int&&
}
As expected, the output is
lvalue!
rvalue!
Now come back to the template function f() and replace static_cast<T&&>(x) with static_cast<T>(x). Let's see the output:
lvalue!
rvalue!
It's the same! Why? If they are the same, then why std::forward<> returns a cast from x to T&&?
The lvalue vs rvalue classification remains the same, but the effect is quite different (and the value category does change - although not in an observable way in your example). Let's go over the four cases:
template<class T>
void f(T&& x)
{
Y(static_cast<T&&>(x));
}
template<class T>
void g(T&& x)
{
Y(static_cast<T>(x));
}
If we call f with an lvalue, T will deduce as some X&, so the cast reference collapses X& && ==> X&, so we end up with the same lvalue and nothing changes.
If we call f with an rvalue, T will deduce as some X so the cast just converts x to an rvalue reference to x, so it becomes an rvalue (specifically, an xvalue).
If we call g with an lvalue, all the same things happen. There's no reference collapsing necessary, since we're just using T == X&, but the cast is still a no-op and we still end up with the same lvalue.
But if we call g with an rvalue, we have static_cast<T>(x) which will copy x. That copy is an rvalue (as your test verifies - except now it's a prvalue instead of an xvalue), but it's an extra, unnecessary copy at best and would be a compilation failure (if T is movable but noncopyable) at worst. With static_cast<T&&>(x), we were casting to a reference, which doesn't invoke a copy.
So that's why we do T&&.