Are these valid usage of move and forward?
Are f3 and f4 the same?
Is it dangerous to do so?
Thank you!
#include <utility>
class A {};
A f1() {
A a;
return a; // Move constructor is called
}
A f2(A&& a) {
return a; // Copy constructor is called, which is what I try to avoid.
}
A f3(A&& a) {
return std::forward<A&&>(a); // Move constructor is called
}
A f4(A&& a) {
return std::move(a); // Move constructor is called
}
std::forward exists because of a quirk in how && works under type deduction.
Under type deduction, the T in T&& will bind to one of 3 possibilities. If being deduced from an lvalue int&, T will bind to int&. Then int& && is just a int&. If being deduced from an lvalue int const&, T will bind to int const&, and int const& && is int const&. If being deduced from an rvalue int of some kind, T will bind to int, and int&& is int&&.
std::forward is a utility function to reverse that map. The three pertinent signatures of std::forward<> are: T& std::forward<T&>(T&) or T const& std::forward<T const&>(T const&) or T&& std::forward<T>(T&&)
All of this ends up being exceedingly useful when doing the technique known as "perfect forwarding", where you use T&&t in a type deduction context, then std::forward<T>(t) to pass on the "same type" as was deduced from to another call.
Note that there are a few simplifying lies above. There are is also the possibility of T const&& which is pretty obscure type-wise, as an example. I probably glossed over some details of how the type deduction works, and the terms rvalue and lvalue don't fully reflect the full 5-fold (or is it 6?) different kinds of variable values in C++11.
Use std::forward with a universal reference, i.e. a template <typename T> ... T&&.
Use std::move with an rvalue reference (like your A&&).
So both f1 and f4 are plausible solutions. They do different things, so you have to decide which one you want.
Do not use f2 or f3.
For your example, they will do the same thing, but it is idiomatic to use std::move
A f(A&& a) {
// use std::move(a)
}
A slightly different case is with function templates
template<typename A>
A f(A&& a) {
// use std::forward<A>(a)
}
The difference is that the second version can receive both lvalues and rvalues (Scott Meyers named them "universal references"), whereas the first version can only receive rvalues.
Related
In VS2010 std::forward is defined as such:
template<class _Ty> inline
_Ty&& forward(typename identity<_Ty>::type& _Arg)
{ // forward _Arg, given explicitly specified type parameter
return ((_Ty&&)_Arg);
}
identity appears to be used solely to disable template argument deduction. What's the point of purposefully disabling it in this case?
If you pass an rvalue reference to an object of type X to a template function that takes type T&& as its parameter, template argument deduction deduces T to be X. Therefore, the parameter has type X&&. If the function argument is an lvalue or const lvalue, the compiler deduces its type to be an lvalue reference or const lvalue reference of that type.
If std::forward used template argument deduction:
Since objects with names are lvalues the only time std::forward would correctly cast to T&& would be when the input argument was an unnamed rvalue (like 7 or func()). In the case of perfect forwarding the arg you pass to std::forward is an lvalue because it has a name. std::forward's type would be deduced as an lvalue reference or const lvalue reference. Reference collapsing rules would cause the T&& in static_cast<T&&>(arg) in std::forward to always resolve as an lvalue reference or const lvalue reference.
Example:
template<typename T>
T&& forward_with_deduction(T&& obj)
{
return static_cast<T&&>(obj);
}
void test(int&){}
void test(const int&){}
void test(int&&){}
template<typename T>
void perfect_forwarder(T&& obj)
{
test(forward_with_deduction(obj));
}
int main()
{
int x;
const int& y(x);
int&& z = std::move(x);
test(forward_with_deduction(7)); // 7 is an int&&, correctly calls test(int&&)
test(forward_with_deduction(z)); // z is treated as an int&, calls test(int&)
// All the below call test(int&) or test(const int&) because in perfect_forwarder 'obj' is treated as
// an int& or const int& (because it is named) so T in forward_with_deduction is deduced as int&
// or const int&. The T&& in static_cast<T&&>(obj) then collapses to int& or const int& - which is not what
// we want in the bottom two cases.
perfect_forwarder(x);
perfect_forwarder(y);
perfect_forwarder(std::move(x));
perfect_forwarder(std::move(y));
}
Because std::forward(expr) is not useful. The only thing it can do is a no-op, i.e. perfectly-forward its argument and act like an identity function. The alternative would be that it's the same as std::move, but we already have that. In other words, assuming it were possible, in
template<typename Arg>
void generic_program(Arg&& arg)
{
std::forward(arg);
}
std::forward(arg) is semantically equivalent to arg. On the other hand, std::forward<Arg>(arg) is not a no-op in the general case.
So by forbidding std::forward(arg) it helps catch programmer errors and we lose nothing since any possible use of std::forward(arg) are trivially replaced by arg.
I think you'd understand things better if we focus on what exactly std::forward<Arg>(arg) does, rather than what std::forward(arg) would do (since it's an uninteresting no-op). Let's try to write a no-op function template that perfectly forwards its argument.
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return arg; }
This naive first attempt isn't quite valid. If we call noop(0) then NoopArg is deduced as int. This means that the return type is int&& and we can't bind such an rvalue reference from the expression arg, which is an lvalue (it's the name of a parameter). If we then attempt:
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return std::move(arg); }
then int i = 0; noop(i); fails. This time, NoopArg is deduced as int& (reference collapsing rules guarantees that int& && collapses to int&), hence the return type is int&, and this time we can't bind such an lvalue reference from the expression std::move(arg) which is an xvalue.
In the context of a perfect-forwarding function like noop, sometimes we want to move, but other times we don't. The rule to know whether we should move depends on Arg: if it's not an lvalue reference type, it means noop was passed an rvalue. If it is an lvalue reference type, it means noop was passed an lvalue. So in std::forward<NoopArg>(arg), NoopArg is a necessary argument to std::forward in order for the function template to do the right thing. Without it, there's not enough information. This NoopArg is not the same type as what the T parameter of std::forward would be deduced in the general case.
Short answer:
Because for std::forward to work as intended(, i.e. to faitfully pass the original type info), it is meant to be used INSIDE TEMPLATE CONTEXT, and it must use the deduced type param from the enclosing template context, instead of deducing the type param by itself(, since only the enclosing templates have the chance to deduce the true type info, this will be explained in the details), hence the type param must be provided.
Though using std::forward inside non-template context is possible, it is pointless(, will be explained in the details).
And if anyone dares to try implementing std::forward to allow type deducing, he/she is doomed to fail painfully.
Details:
Example:
template <typename T>
auto someFunc(T&& arg){ doSomething(); call_other_func(std::forward<T>(para)); }
Observer that arg is declared as T&&,( it is the key to deduce the true type passed, and) it is not a rvalue reference, though it has the same syntax, it is called an universal reference (Terminology coined by Scott Meyers), because T is a generic type, (likewise, in string s; auto && ss = s; ss is not a rvalue reference).
Thanks to universal reference, some type deduce magic happens when someFunc is being instantiated, specifically as following:
If an rvalue object, which has the type _T or _T &, is passed to someFunc, T will be deduced as _T &(, yeah, even if the type of X is just _T, please read Meyers' artical);
If an rvalue of type _T && is passed to someFunc,T will be deduced as _T &&
Now, you can replace T with the true type in above code:
When lvalue obj is passed:
auto someFunc(_T & && arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T & arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
When rvalue obj is passed:
auto someFunc(_T && && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
Now, you can guess what std::forwrd does eseentially is just static_cast<T>(para)(, in fact, in clang 11's implementation it is static_cast<T &&>(para), which is the same after applying reference collapsing rule). Everything works out fine.
But if you think about let std::fowrd deducing the type param by itself, you'll quickly find out that inside someFunc, std::forward literally IS NOT ABLE TO deduce the original type of arg.
If you try to make the compiler do it, it will never be deduced as _T &&(, yeah, even when arg is bind to an _T &&, it is still an lvaule obj inside someFunc, hence can only be deduceed as _T or _T &.... you really should read Meyers' artical).
Last, why should you only use std::forward inside templates? Because in non-templates context, you know exactly what type of obj you have. So, if you have an lvalue bind to an rvalue reference, and you need to pass it as an lvaule to another function, just pass it, or if you need to pass it as rvalue, just do std::move. You simply DON'T NEED std::forward inside non-template context.
I was looking at the std::function implementation and its call operator()
template<typename Ret, typename... ArgTypes>
Ret function< Ret (ArgTypes...)>::operator()(ArgTypes...args) const
{
// some stuff
return invoker(functor, std::forward<ArgTypes>(args)...);
}
I was particularly wondering, why it uses std::forward here? Does this anything have to do with perfect forwarding?
Because perfect forwarding could only be done if operator() is a template with a variadic template declaration template<typename... Args> (which it is not, the declaration is a partial specialization of std::function).
What is the intention of using std::forward here? I am confused :-)?
You are correct that this is not your typical "perfect forwarding" scenario. A brief example can help illustrate the motivation. Assume a type A with instrumented constructors and destructor:
#include "A.h"
#include <functional>
#include <iostream>
int
main()
{
A a1{1};
A a2{2};
std::function<void(A, A&)> f{[](A x, A& y){}};
f(a1, a2);
}
This will output:
A(int state): 1
A(int state): 2
A(A const& a): 1
A(A&& a): 1
~A(1)
~A(-1)
~A(2)
~A(1)
Explanation:
a1 and a2 are constructed on the stack. Then when passed into the function invoker, a1 is first copied to bind to the first by-value parameter, and then std::forward<A> is called on a1 which moves it from the by-value parameter into the lambda.
In contrast, a2 need not be copied to bind to the function A& parameter, and then std::forward<A&>(a2) is called, which forwards a2 as an lvalue instead of rvalue, and this binds to the A& parameter of the lambda.
Then things get destructed. The ~A(-1) indicates the destruction of an A in a move-constructed-from state with this instrumented A.
In summary, even though ArgTypes isn't deduced as in the usual perfect forwarding idiom, we still want to forward by-value ArgTypes as rvalues, and by-reference ArgTypes as lvalues. So std::forward just happens to do exactly what we want here.
I think you are confused by many things here.
First, perfect forwarding has nothing to do with variadic templates. You could create a wrapper class that has a function that takes one argument and forward it to the wrapped object :
template<typename T>
struct Wrapper {
template<typename Arg>
decltype(auto) test(Arg&& arg) {
return t.test(std::forward<Arg>(arg));
}
T t;
};
Notice the use of perfect forwarding here without any variadic templates. If t.test would require a move only type as parameter, it would not be possible to call it without the forward<Arg>(arg).
The second thing happening here is the parameter not being followed by &&. Adding && to ArgTypes would be a mistake and would make some cases fail to compile. Consider this simple case :
std::function<void(int)> f;
int i = 0;
f(i);
That would fail to compile. If you add && to ArgTypes, every parameters that are not reference (eg. int) would become an rvalue reference on the call operator (in our case, int&&). Since all parameter types are already qualified correctly in the std::function argument list, what you want to recieve in the call operator is exactly those types, not transformed.
The why you need std::forward if you don't use &&? Because even though you don't need to infer value categories, you still need to not copy every arguments to the contained function. If one of the std::function's parameter is int&, you don't want to move it. But if one of the parameter is std::unique_ptr<int>, you must move it! And this is exactly what std::forward is for. Moving only what should be moved.
std::forward just appends rvalue reference to the type, so taking reference collapsing rules into account, it effectively passes reference arguments as-is and moves the object arguments.
An example here for std::forward,
// forward example
#include <utility> // std::forward
#include <iostream> // std::cout
// function with lvalue and rvalue reference overloads:
void overloaded (const int& x) {std::cout << "[lvalue]";}
void overloaded (int&& x) {std::cout << "[rvalue]";}
// function template taking rvalue reference to deduced type:
template <class T> void fn (T&& x) {
overloaded (x); // always an lvalue
overloaded (std::forward<T>(x)); // rvalue if argument is rvalue
}
int main () {
int a;
std::cout << "calling fn with lvalue: ";
fn (a);
std::cout << '\n';
std::cout << "calling fn with rvalue: ";
fn (0);
std::cout << '\n';
return 0;
}
Output:
calling fn with lvalue: [lvalue][lvalue]
calling fn with rvalue: [lvalue][rvalue]
mentions that
the fact that all named values (such as function parameters) always
evaluate as lvalues (even those declared as rvalue references)
Whereas, the typical move constructor looks like
ClassName(ClassName&& other)
: _data(other._data)
{
}
which looks like _data(other._data) should invoke the move constructor of _data's class. But, how is it possible without using std::forward? In other words, shouldn't it be
ClassName(ClassName&& other)
: _data(std::forward(other._data))
{
}
?
Because, as pointed out in std:forward case,
all then named values should evaluate as lvalue
I more and more like C++ because of the depth of issue like this and the fact that the language is bold enough to provide such features :) Thank you!
A typical move constructor looks like this (assuming it is explicitly implemented: you might want to prefer = default):
ClassName::ClassName(ClassName&& other)
: _data(std::move(other._data)) {
}
Without the std::move() the member is copied: since it has a name other is an lvalue. The object the reference is bound to is an rvalue or an object considered as such, however.
std::forward<T>(obj) is always used with an explicit template argument. In practice the type is that deduced for a forwarding reference. These look remarkably like rvalue references but are something entirely different! In particular, a forwarding reference may refer to an lvalue.
You may be interested in my Two Daemons article which describes the difference in detail.
std::forward should be used with a forwarding reference.
std::move should be used with an rvalue reference.
There is nothing particular about constructors. The rules apply the same to any function, member function or constructor.
The most important thing is to realize when you have a forwarding reference and when you have an rvalue reference. They look similar but are not.
A forwarding reference is always in the form:
T&& ref
for T some deduced type.
For instance, this is a forwarding reference:
template <class T>
auto foo(T&& ref) -> void;
All these are rvalue references:
auto foo(int&& ref) -> void; // int not deduced
template <class T>
auto foo(const T&& ref); // not in form `T&&` (note the const)
template <class T>
auto foo(std::vector<T>&& ref) -> void; // not in form `T&&`
template <class T>
struct X {
auto foo(T&& ref) -> T; // T not deduced. (It was deduced at class level)
};
For more please check this excellent in-depth article by Scott Meyers with the note that when the article was written the term "universal reference" was used (actually introduced by Scott himself). Now it is agreed that "forwarding reference" better describes it's purpose and usage.
So your example should be:
ClassName(ClassName&& other)
: _data(std::move(other._data))
{
}
as other is an rvalue reference because ClassName is not a deduced type.
This Ideone example should make things pretty clear for you. If not, keep reading.
The following constructor accepts Rvalues only. However, since the argument "other" got a name it lost its "rvalueness" and now is a Lvalue. To cast it back to Rvalue, you have to use std::move. There's no reason to use std::forward here because this constructor does not accept Lvalues. If you try to call it with a Lvalue, you will get compile error.
ClassName(ClassName&& other)
: _data(std::move(other._data))
{
// If you don't use move, you could have:
// cout << other._data;
// And you will notice "other" has not been moved.
}
The following constructor accepts both Lvalues and Rvalues. Scott Meyers called it "Universal Rerefences", but now it's called "Forwarding References". That's why, here, it's a must to use std::forward so that if other was an Rvalue, _data constructor will get called with an Rvalue. If other was an Lvalue, _data will be constructed with an Lvalue. That's why it's called perfect-forwarding.
template<typename T>
ClassName(T&& other)
: _data(std::forward<decltype(_data)>(other._data))
{
}
I've tried to use your constructors as an example so you could understand, but this is not specific to constructors. This applies to functions as well.
With the first example tho, since your first constructor only accepts Rvalues, you could perfectly use std::forward instead, and both would do the same thing. But it's best not to do it, because people may think that your constructor accepts a forwarding reference, when it actually doesn't.
In VS2010 std::forward is defined as such:
template<class _Ty> inline
_Ty&& forward(typename identity<_Ty>::type& _Arg)
{ // forward _Arg, given explicitly specified type parameter
return ((_Ty&&)_Arg);
}
identity appears to be used solely to disable template argument deduction. What's the point of purposefully disabling it in this case?
If you pass an rvalue reference to an object of type X to a template function that takes type T&& as its parameter, template argument deduction deduces T to be X. Therefore, the parameter has type X&&. If the function argument is an lvalue or const lvalue, the compiler deduces its type to be an lvalue reference or const lvalue reference of that type.
If std::forward used template argument deduction:
Since objects with names are lvalues the only time std::forward would correctly cast to T&& would be when the input argument was an unnamed rvalue (like 7 or func()). In the case of perfect forwarding the arg you pass to std::forward is an lvalue because it has a name. std::forward's type would be deduced as an lvalue reference or const lvalue reference. Reference collapsing rules would cause the T&& in static_cast<T&&>(arg) in std::forward to always resolve as an lvalue reference or const lvalue reference.
Example:
template<typename T>
T&& forward_with_deduction(T&& obj)
{
return static_cast<T&&>(obj);
}
void test(int&){}
void test(const int&){}
void test(int&&){}
template<typename T>
void perfect_forwarder(T&& obj)
{
test(forward_with_deduction(obj));
}
int main()
{
int x;
const int& y(x);
int&& z = std::move(x);
test(forward_with_deduction(7)); // 7 is an int&&, correctly calls test(int&&)
test(forward_with_deduction(z)); // z is treated as an int&, calls test(int&)
// All the below call test(int&) or test(const int&) because in perfect_forwarder 'obj' is treated as
// an int& or const int& (because it is named) so T in forward_with_deduction is deduced as int&
// or const int&. The T&& in static_cast<T&&>(obj) then collapses to int& or const int& - which is not what
// we want in the bottom two cases.
perfect_forwarder(x);
perfect_forwarder(y);
perfect_forwarder(std::move(x));
perfect_forwarder(std::move(y));
}
Because std::forward(expr) is not useful. The only thing it can do is a no-op, i.e. perfectly-forward its argument and act like an identity function. The alternative would be that it's the same as std::move, but we already have that. In other words, assuming it were possible, in
template<typename Arg>
void generic_program(Arg&& arg)
{
std::forward(arg);
}
std::forward(arg) is semantically equivalent to arg. On the other hand, std::forward<Arg>(arg) is not a no-op in the general case.
So by forbidding std::forward(arg) it helps catch programmer errors and we lose nothing since any possible use of std::forward(arg) are trivially replaced by arg.
I think you'd understand things better if we focus on what exactly std::forward<Arg>(arg) does, rather than what std::forward(arg) would do (since it's an uninteresting no-op). Let's try to write a no-op function template that perfectly forwards its argument.
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return arg; }
This naive first attempt isn't quite valid. If we call noop(0) then NoopArg is deduced as int. This means that the return type is int&& and we can't bind such an rvalue reference from the expression arg, which is an lvalue (it's the name of a parameter). If we then attempt:
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return std::move(arg); }
then int i = 0; noop(i); fails. This time, NoopArg is deduced as int& (reference collapsing rules guarantees that int& && collapses to int&), hence the return type is int&, and this time we can't bind such an lvalue reference from the expression std::move(arg) which is an xvalue.
In the context of a perfect-forwarding function like noop, sometimes we want to move, but other times we don't. The rule to know whether we should move depends on Arg: if it's not an lvalue reference type, it means noop was passed an rvalue. If it is an lvalue reference type, it means noop was passed an lvalue. So in std::forward<NoopArg>(arg), NoopArg is a necessary argument to std::forward in order for the function template to do the right thing. Without it, there's not enough information. This NoopArg is not the same type as what the T parameter of std::forward would be deduced in the general case.
Short answer:
Because for std::forward to work as intended(, i.e. to faitfully pass the original type info), it is meant to be used INSIDE TEMPLATE CONTEXT, and it must use the deduced type param from the enclosing template context, instead of deducing the type param by itself(, since only the enclosing templates have the chance to deduce the true type info, this will be explained in the details), hence the type param must be provided.
Though using std::forward inside non-template context is possible, it is pointless(, will be explained in the details).
And if anyone dares to try implementing std::forward to allow type deducing, he/she is doomed to fail painfully.
Details:
Example:
template <typename T>
auto someFunc(T&& arg){ doSomething(); call_other_func(std::forward<T>(para)); }
Observer that arg is declared as T&&,( it is the key to deduce the true type passed, and) it is not a rvalue reference, though it has the same syntax, it is called an universal reference (Terminology coined by Scott Meyers), because T is a generic type, (likewise, in string s; auto && ss = s; ss is not a rvalue reference).
Thanks to universal reference, some type deduce magic happens when someFunc is being instantiated, specifically as following:
If an rvalue object, which has the type _T or _T &, is passed to someFunc, T will be deduced as _T &(, yeah, even if the type of X is just _T, please read Meyers' artical);
If an rvalue of type _T && is passed to someFunc,T will be deduced as _T &&
Now, you can replace T with the true type in above code:
When lvalue obj is passed:
auto someFunc(_T & && arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T & arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
When rvalue obj is passed:
auto someFunc(_T && && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
Now, you can guess what std::forwrd does eseentially is just static_cast<T>(para)(, in fact, in clang 11's implementation it is static_cast<T &&>(para), which is the same after applying reference collapsing rule). Everything works out fine.
But if you think about let std::fowrd deducing the type param by itself, you'll quickly find out that inside someFunc, std::forward literally IS NOT ABLE TO deduce the original type of arg.
If you try to make the compiler do it, it will never be deduced as _T &&(, yeah, even when arg is bind to an _T &&, it is still an lvaule obj inside someFunc, hence can only be deduceed as _T or _T &.... you really should read Meyers' artical).
Last, why should you only use std::forward inside templates? Because in non-templates context, you know exactly what type of obj you have. So, if you have an lvalue bind to an rvalue reference, and you need to pass it as an lvaule to another function, just pass it, or if you need to pass it as rvalue, just do std::move. You simply DON'T NEED std::forward inside non-template context.
I've been watching Scott Meyers' talk on Universal References from the C++ and Beyond 2012 conference, and everything makes sense so far. However, an audience member asks a question at around 50 minutes in that I was also wondering about. Meyers says that he does not care about the answer because it is non-idiomatic and would silly his mind, but I'm still interested.
The code presented is as follows:
// Typical function bodies with overloading:
void doWork(const Widget& param) // copy
{
// ops and exprs using param
}
void doWork(Widget&& param) // move
{
// ops and exprs using std::move(param)
}
// Typical function implementations with universal reference:
template <typename T>
void doWork(T&& param) // forward => copy and move
{
// ops and exprs using std::forward<T>(param)
}
The point being that when we take an rvalue reference, we know we have an rvalue, so we should std::move it to preserve the fact that it's an rvalue. When we take a universal reference (T&&, where T is a deduced type), we want std::forward to preserve the fact that it may have been an lvalue or an rvalue.
So the question is: since std::forward preserves whether the value passed into the function was either an lvalue or an rvalue, and std::move simply casts its argument to an rvalue, could we just use std::forward everywhere? Would std::forward behave like std::move in all cases where we would use std::move, or are there some important differences in behaviour that are missed out by Meyers' generalisation?
I'm not suggesting that anybody should do it because, as Meyers correctly says, it's completely non-idiomatic, but is the following also a valid use of std::move:
void doWork(Widget&& param) // move
{
// ops and exprs using std::forward<Widget>(param)
}
The two are very different and complementary tools.
std::move deduces the argument and unconditionally creates an rvalue expression. This makes sense to apply to an actual object or variable.
std::forward takes a mandatory template argument (you must specify this!) and magically creates an lvalue or an rvalue expression depending on what the type was (by virtue of adding && and the collapsing rules). This only makes sense to apply to a deduced, templated function argument.
Maybe the following examples illustrate this a bit better:
#include <utility>
#include <memory>
#include <vector>
#include "foo.hpp"
std::vector<std::unique_ptr<Foo>> v;
template <typename T, typename ...Args>
std::unique_ptr<T> make_unique(Args &&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); // #1
}
int main()
{
{
std::unique_ptr<Foo> p(new Foo('a', true, Bar(1,2,3)));
v.push_back(std::move(p)); // #2
}
{
v.push_back(make_unique<Foo>('b', false, Bar(5,6,7))); // #3
}
{
Bar b(4,5,6);
char c = 'x';
v.push_back(make_unique<Foo>(c, b.ready(), b)); // #4
}
}
In situation #2, we have an existing, concrete object p, and we want to move from it, unconditionally. Only std::move makes sense. There's nothing to "forward" here. We have a named variable and we want to move from it.
On the other hand, situation #1 accepts a list of any sort of arguments, and each argument needs to be forwarded as the same value category as it was in the original call. For example, in #3 the arguments are temporary expressions, and thus they will be forwarded as rvalues. But we could also have mixed in named objects in the constructor call, as in situation #4, and then we need forwarding as lvalues.
Yes, if param is a Widget&&, then the following three expressions are equivalent (assuming that Widget is not a reference type):
std::move(param)
std::forward<Widget>(param)
static_cast<Widget&&>(param)
In general (when Widget may be a reference), std::move(param) is equivalent to both of the following expressions:
std::forward<std::remove_reference<Widget>::type>(param)
static_cast<std::remove_reference<Widget>::type&&>(param)
Note how much nicer std::move is for moving stuff. The point of std::forward is that it mixes well with template type deduction rules:
template<typename T>
void foo(T&& t) {
std::forward<T>(t);
std::move(t);
}
int main() {
int a{};
int const b{};
//Deduced T Signature Result of `forward<T>` Result of `move`
foo(a); //int& foo(int&) lvalue int xvalue int
foo(b); //int const& foo(int const&) lvalue int const xvalue int const
foo(int{});//int foo(int&&) xvalue int xvalue int
}