Avoid exponential grow of const references and rvalue references in constructor
Actually, this is the precise reason why perfect forwarding was introduced. Rewrite the constructor as
template <typename L, typename O>
LinearClassifier(L && loss, O && optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))
{}
But it will probably be much simpler to do what Ilya Popov suggests in his answer. To be honest, I usually do it this way, since moves are intended to be cheap and one more move does not change things dramatically.
As Howard Hinnant has told, my method can be SFINAE-unfriendly, since now LinearClassifier accepts any pair of types in constructor. Barry's answer shows how to deal with it.
To support move semantics, should function parameters be taken by unique_ptr, by value, or by rvalue?
Unless you have a reason for the vector to live on the heap, I would advise against using unique_ptr
The vector's internal storage lives on the heap anyway, so you'll be requiring 2 degrees of indirection if you use unique_ptr
, one to dereference the pointer to the vector, and again to dereference the internal storage buffer.
As such, I would advise to use either 2 or 3.
If you go with option 3 (requiring an rvalue reference), you are foisting a requirement on the users of your class that they pass an rvalue (either directly from a temporary, or move from an lvalue), when calling someFunction
.
The requirement of moving from an lvalue is onerous.
If your users want to keep a copy of the vector, they have to jump through hoops to do so.
std::vector<string> items = { "1", "2", "3" };
Test t;
std::vector<string> copy = items; // have to copy first
t.someFunction(std::move(items));
However, if you go with option 2, the user can decide if they want to keep a copy, or not - the choice is theirs
Keep a copy:
std::vector<string> items = { "1", "2", "3" };
Test t;
t.someFunction(items); // pass items directly - we keep a copy
Don't keep a copy:
std::vector<string> items = { "1", "2", "3" };
Test t;
t.someFunction(std::move(items)); // move items - we don't keep a copy
C++ Function taking lvalue and rvalue parameters transparently
If you don't need to modify the objects, you can simply accept by const reference:
void test(T const &);
This will bind to both lvalues and rvalues.
Why do we need reference collapsing rules
My question is why do we need these rules if T is already deduced to int&&?
This is not quite true. The rules for type deduction won't deduce the argument to be a reference. That is, in:
template <typename T>
void f(T);
And the expressions:
X g();
X& h();
X a;
f(g()); // argument is an rvalue, cannot be bound by lvalue-ref
f(h()); // argument is an lvalue
f(a); // argument is an lvalue
The deduced type will be X
in last two cases and it will fail to compile in the first. The type deduced will be the value type, not a reference type.
The next step is to figure out what the deduced type would be if the template took the argument by lvalue or rvalue reference. In the case of lvalue references, the options are clear, with a modified f
:
template <typename T>
void f(T &);
f(g()); // only const& can bind an rvalue: f(const X&), T == const int
f(h()); // f(X&)
f(a); // f(X&)
Up to here it was already defined in the previous version of the standard. Now the question is what should the deduced types be if the template takes an rvalue-references. This is what was added in C++11. Consider now:
template <typename T>
void f(T &&);
And rvalue will only bind to an rvalue, and never to an lvalue. This would imply that using the same simple rules as for lvalue-references (what type T would make the call compile) the second and third calls would not compile:
f(g()); // Fine, and rvalue-reference binds the rvalue
f(h()); // an rvalue-reference cannot bind an lvalue!
f(a); // an rvalue-reference cannot bind an lvalue!
Without the reference collapsing rules, the user would have to provide two overloads for the template, one that takes an rvalue-reference, another that takes an lvalue-reference. The problem is that as the number of arguments increases the number of alternatives grows exponentially, and implementing perfect forwarding becomes almost as hard in C++03 (with the only advantage of being able to detect an rvalue with an rvalue-reference).
So something different needs to be done, and that is reference collapsing, which are really a way of describing the desired semantics. A different way of describing them is that when you type &&
by a template argument you don't really ask for an rvalue-reference, as that would not allow the call with an lvalue, but you are rather asking the compiler to give you the best type of reference matching.
Why do we copy then move?
Before I answer your questions, one thing you seem to be getting wrong: taking by value in C++11 does not always mean copying. If an rvalue is passed, that will be moved (provided a viable move constructor exists) rather than being copied. And std::string
does have a move constructor.
Unlike in C++03, in C++11 it is often idiomatic to take parameters by value, for the reasons I am going to explain below. Also see this Q&A on StackOverflow for a more general set of guidelines on how to accept parameters.
Why aren't we taking an rvalue-reference to
str
?
Because that would make it impossible to pass lvalues, such as in:
std::string s = "Hello";
S obj(s); // s is an lvalue, this won't compile!
If S
only had a constructor that accepts rvalues, the above would not compile.
Won't a copy be expensive, especially given something like
std::string
?
If you pass an rvalue, that will be moved into str
, and that will eventually be moved into data
. No copying will be performed. If you pass an lvalue, on the other hand, that lvalue will be copied into str
, and then moved into data
.
So to sum it up, two moves for rvalues, one copy and one move for lvalues.
What would be the reason for the author to decide to make a copy then a move?
First of all, as I mentioned above, the first one is not always a copy; and this said, the answer is: "Because it is efficient (moves of std::string
objects are cheap) and simple".
Under the assumption that moves are cheap (ignoring SSO here), they can be practically disregarded when considering the overall efficiency of this design. If we do so, we have one copy for lvalues (as we would have if we accepted an lvalue reference to const
) and no copies for rvalues (while we would still have a copy if we accepted an lvalue reference to const
).
This means that taking by value is as good as taking by lvalue reference to const
when lvalues are provided, and better when rvalues are provided.
P.S.: To provide some context, I believe this is the Q&A the OP is referring to.
Related Topics
Is Std::Vector<T> a 'User-Defined Type'
When Do We Need to Have a Default Constructor
Implementing Component System from Unity in C++
Passing Member Function Pointer to Member Object in C++
Determining If an Unordered Vector<T> Has All Unique Elements
Can #If Pre-Processor Directives Be Nested in C++
What Could Go Wrong If Copy-List-Initialization Allowed Explicit Constructors
Win32 - Get Main Wnd Handle of Application
Cmake Simple Config File Example
How to Pass an Std::String to Glshadersource
Pointers as Keys in Map C++ Stl
Purpose of Explicit Default Constructors
Opencv Image Loading for Opengl Texture
What Are Differences Between Std, Tr1 and Boost (As Namespaces And/Or Libraries)