In the an earlier post regarding moves in C++, I explained what rvalue references are and did a speed comparison of a move with a copy. Here in this post, I would be highlighting the need of std::move and std::forward in a C++ program. Before that, let's just quickly see what an std::move and std::forward really do. I found this interesting description from one of the presentations by Scott Meyers.
What std::move does?
- it doesn't really move
- it does a rvalue cast on the object
Note, that decision to move or not is taken by the overload resolution process which looks at whether it can call a move assignment/constructor or should it fallback to the "copy" counterparts. For example:
What std::forward does?
- it does not really forward
- it does a conditional cast to the rvalue, unlike std::move where the cast is unconditional.
Note: Both std::move and std::forward accept a template type argument, but with std::forward template type deduction is forbidden, while with std::move it is allowed. So, you need to call std::forward always with an explicitly specified template type. This is needed because otherwise there is no way to know with-in std::forward(T&& arg)'s definition if the argument was bound to a lvalue or rvalue. This is because every argument has a name, which causes compiler to always deduce a lvalue-reference type for it. So:
- Calling std::forward<T&>(x) implies 'x' is bound to a lvalue (going by reference collapsing rules)
- Calling std::forward<T>(x) implies 'x' is bound to a rvalue.
A profuse explanation of this can be found in this stackoverflow post
Corollary: std::move is exactly equivalent to a 'std::forward<T>'.
So, let's see the usage of std::move and std::forward with-in the programs, beginning with std::move.
std::move scenarios
1. A class constructor accepting a rvalue reference that needs to be moved into one of its members:
std::forward scenarios
For std::forward, there can be similar scenarios, but it's mainly used in the context of arguments passing to preserve move semantics during calls. Basically, its used in the context of "universal references" i.e. on name of templatized types like T&&, auto type like auto&& where type-deduction distinguishes between whether the reference is bound to a lvalue or rvalue.
1. To pass on the argument preserving its reference-ness type:
typecast to x ensures that its holds its intended reference-ness. A rough definition of std::forward would clarify this further:
. Full code for these 2 cases can be found at std_forward_example.cpp
What std::move does?
- it doesn't really move
- it does a rvalue cast on the object
Note, that decision to move or not is taken by the overload resolution process which looks at whether it can call a move assignment/constructor or should it fallback to the "copy" counterparts. For example:
class string
{
string& operator = (const string&); // copy assignment
string& operator = (string&&); // move assignment
...
}
void foo(const std::string s)
{
std::string other_str;
other_str = std::move(s); // still, calls copy assignment because 's' is const
}
What std::forward does?
- it does not really forward
- it does a conditional cast to the rvalue, unlike std::move where the cast is unconditional.
Note: Both std::move and std::forward accept a template type argument, but with std::forward template type deduction is forbidden, while with std::move it is allowed. So, you need to call std::forward always with an explicitly specified template type. This is needed because otherwise there is no way to know with-in std::forward(T&& arg)'s definition if the argument was bound to a lvalue or rvalue. This is because every argument has a name, which causes compiler to always deduce a lvalue-reference type for it. So:
- Calling std::forward<T&>(x) implies 'x' is bound to a lvalue (going by reference collapsing rules)
- Calling std::forward<T>(x) implies 'x' is bound to a rvalue.
A profuse explanation of this can be found in this stackoverflow post
Corollary: std::move is exactly equivalent to a 'std::forward<T>'.
So, let's see the usage of std::move and std::forward with-in the programs, beginning with std::move.
std::move scenarios
1. A class constructor accepting a rvalue reference that needs to be moved into one of its members:
class Test
{
std::vector mVec;
public:
// std::move needed because 'in' is a lvalue (as it has a name, though it is of rvalue-ref type)
Test(std::vector&& in) : mVec(std::move(in)) { }
Test& add (const Test& t) { ... }
Test& add (Test&& t) { ... }
...
}
2. A function returning by value, returns a name bound to a rvalue referenceTest foo(Test&& x) {
x.add(std::vector({1, 22, 13, 56}));
return std::move(x); // std::move required because x is a l-value, otherwise it will copy constructor
}
3. To invoke the move constructor/assignment when moving named objects:Test t (std::vector4. To create a rvalue reference seating a named object:({1, 22, 13, 56})); Test t2 = std::move(t); // use std::move to invoke the move constructor or move assignment
Test&& rt2 = std::move(t2); // use std::move to create a rvalue reference5. To invoke a rvalue overload of a function:
t1.add(std::move(rt2)); // use std::move to invoke rvalue ref overload of a function
std::forward scenarios
For std::forward, there can be similar scenarios, but it's mainly used in the context of arguments passing to preserve move semantics during calls. Basically, its used in the context of "universal references" i.e. on name of templatized types like T&&, auto type like auto&& where type-deduction distinguishes between whether the reference is bound to a lvalue or rvalue.
1. To pass on the argument preserving its reference-ness type:
void bar(std::string& x) { ... }
void bar(std::string&& x) { ... }
template<typename T>
void foo(T&& x)
{
bar(std::forward<T>(x));
}
int main()
{
auto f = [](){ std::string s = "I am R value"; return s; }; // a rvalue reference creator
std::string&& rrs = f(); // a rvalue reference
foo(rrs); // will call the lvalue version of bar, as rrs is a name
foo(f()); // will call the rvalue version
}
In the first call to foo, T is inferred as std::string&, while in the second call its inferred as std::string. The std::forwardtemplate<typename T>
T&& forward(T&& param) {
return static_cast<T&&>(param);
}
2. When moving/passing arguments inside a universal constructorclass Test
{
std::string ob;
public:
// INCORRECT: moving a univeral reference is wrong as it may be bound to a l-value
template <typename T>
Test(T&& t) : ob(std::move(t)) { }
};
Use of std::move is wrong as it will unintentionally move the contents of a l-value, if it is passed to the constructor. Hence, std::move should be replaced by std::forward
No comments:
Post a Comment