To my knowledge, the two common ways of efficiently implementing a constructor in C++11 are using two of them
Foo(const Bar& bar) : bar_{bar} {};
Foo(Bar&& bar) : bar_{std::move(bar)} {};
or just one in the fashion of
Foo(Bar bar) : bar_{std::move(bar)} {};
with the first option resulting in optimal performance (e.g. hopefully a single copy in case of an lvalue and a single move in case of an rvalue), but needing 2N overloads for N variables, whereas the second option only needs one function at the cost of an additional move when passing in an lvalue.
This shouldn't make too much of an impact in most cases, but surely neither choice is optimal. However one could also do the following:
template<typename T>
Foo(T&& bar) : bar_{std::forward<T>(bar)} {};
This has the disadvantage of allowing variables of possibly unwanted types as the bar
parameter (which is a problem I'm sure is easily resolved using template specialization), but in any case performance is optimal and the code grows linearly with the amount of variables.
Why is nobody using something like forward for this purpose? Isn't it the most optimal way?
People do perfect forward constructors.
There are costs.
First, the cost is that they must be in the header file. Second, each use tends to result in a different constructor being created. Third, you cannot use
{}
-like initialization syntax for the objects you are constructing from.Fourth, it interacts poorly with the
Foo(Foo const&)
andFoo(Foo&&)
constructors. It will not replace them (due to language rules), but it will be selected over them forFoo(Foo&)
. This can be fixed with a bit of boilerplate SFINAE:which now is no longer preferred over
Foo(Foo const&)
for arguments of typeFoo&
. While we are at it we can do:and now this constructor only works if the argument can be used to construct
bar
.The next thing you'll want to do is to either support
{}
style construction of thebar
, or piecewise construction, or varargs construction where you forward into bar.Here is a varargs variant:
On the other hand, if we add:
we now support
Foo( {construct_bar_here} )
syntax, which is nice. However this isn't required if we already have the above varardic (or a similar piecewise construct). Still, sometimes an initializer list is nice to forward, especially if we don't know the type ofbar_
when we write the code (generics, say):so if
Bar
is astd::vector<int>
we can doFoo( {1,2,3} )
and end up with{1,2,3}
withinbar_
.At this point, you gotta wonder "why didn't I just write
Foo(Bar)
". Is it really that expensive to move aBar
?In generic library-esque code, you'll want to go as far as the above. But very often your objects are both known and cheap to move. So write the really simple, rather correct,
Foo(Bar)
and be done with all of the tomfoolery.There is a case where you have N variables that are not cheap to move and you want efficiency, and you don't want to put the implementation in the header file.
Then you just write a type-erasing
Bar
creator that takes anything that can be used to create aBar
either directly, or viastd::make_from_tuple
, and stores the creation for a later date. It then uses RVO to directly construct theBar
in-place within the target location.Code uses a C++17 feature,
std::make_from_tuple
, which is relatively easy to write in C++11. In C++17 guaranteed elision means it even works with non-movable types, which is really cool.Live example.
Now you can write:
and the body of
Foo::Foo
can be moved out of the header file.But that is more insane than the above alternatives.
Again, have you considered just writing
Foo(Bar)
?