c++ - Constructor using std::forward -
to knowledge, 2 common ways of efficiently implementing constructor in c++11 using 2 of them
foo(const bar& bar) : bar_{bar} {}; foo(bar&& bar) : bar_{std::move(bar)} {};
or 1 in fashion of
foo(bar bar) : bar_{std::move(bar)} {};
with first option resulting in optimal performance (e.g. single copy in case of lvalue , single move in case of rvalue), needing 2n overloads n variables, whereas second option needs 1 function @ cost of additional move when passing in lvalue.
this shouldn't make of impact in cases, surely neither choice optimal. 1 following:
template<typename t> foo(t&& bar) : bar_{std::forward<t>(bar)} {};
this has disadvantage of allowing variables of possibly unwanted types bar
parameter (which problem i'm sure resolved using template specialization), in case performance optimal , code grows linearly amount of variables.
why nobody using forward purpose? isn't optimal way?
people perfect forward constructors.
there costs.
first, cost must in header file. second, each use tends result in different constructor being created. third, cannot use {}
-like initialization syntax objects constructing from.
fourth, interacts poorly foo(foo const&)
, foo(foo&&)
constructors. not replace them (due language rules), selected on them foo(foo&)
. can fixed bit of boilerplate sfinae:
template<class t, std::enable_if_t<!std::is_same<std::decay_t<t>, foo>{},int> =0 > foo(t&& bar) : bar_{std::forward<t>(bar)} {};
which no longer preferred on foo(foo const&)
arguments of type foo&
. while @ can do:
bar bar_; template<class t, std::enable_if_t<!std::is_same<std::decay_t<t>, foo>{},int> =0, std::enable_if_t<std::is_constructible<bar, t>{},int> =0 > foo(t&& bar) : bar_{std::forward<t>(bar)} {};
and constructor works if argument can used construct bar
.
the next thing you'll want either support {}
style construction of bar
, or piecewise construction, or varargs construction forward bar.
here varargs variant:
bar bar_; template<class t0, class...ts, std::enable_if_t<sizeof...(ts)||!std::is_same<std::decay_t<t0>, foo>{},int> =0, std::enable_if_t<std::is_constructible<bar, t0, ts...>{},int> =0 > foo(t0&&t0, ts&&...ts) : bar_{std::forward<t0>(t0), std::forward<ts>(ts)...} {}; foo()=default;
on other hand, if add:
foo(bar&& bin):bar_(std::move(bin));
we support foo( {construct_bar_here} )
syntax, nice. isn't required if have above varardic (or similar piecewise construct). still, initializer list nice forward, if don't know type of bar_
when write code (generics, say):
template<class t0, class...ts, std::enable_if_t<std::is_constructible<bar, std::initializer_list<t0>, ts...>{},int> =0 > foo(std::initializer_list<t0> t0, ts&&...ts) : bar_{t0, std::forward<ts>(ts)...} {};
so if bar
std::vector<int>
can foo( {1,2,3} )
, end {1,2,3}
within bar_
.
at point, gotta wonder "why didn't write foo(bar)
". expensive move bar
?
in generic library-esque code, you'll want go far above. objects both known , cheap move. write simple, rather correct, foo(bar)
, done of tomfoolery.
there case have n variables not cheap move , want efficiency, , don't want put implementation in header file.
then write type-erasing bar
creator takes can used create bar
either directly, or via std::make_from_tuple
, , stores creation later date. uses rvo directly construct bar
in-place within target location.
template<class t> struct make { using maker_t = t(*)(void*); template<class tuple> static maker_t make_tuple_maker() { return [](void* vtup)->t{ return make_from_tuple<t>( std::forward<tuple>(*static_cast<std::remove_reference_t<tuple>*>(vtup)) ); }; } template<class u> static maker_t make_element_maker() { return [](void* velem)->t{ return t( std::forward<u>(*static_cast<std::remove_reference_t<u>*>(velem)) ); }; } void* ptr = nullptr; maker_t maker = nullptr; template<class u, std::enable_if_t< std::is_constructible<t, u>{}, int> =0, std::enable_if_t<!std::is_same<std::decay_t<u>, make>{}, int> =0 > make( u&& u ): ptr( (void*)std::addressof(u) ), maker( make_element_maker<u>() ) {} template<class tuple, std::enable_if_t< !std::is_constructible<t, tuple>{}, int> =0, std::enable_if_t< !std::is_same<std::decay_t<tuple>, make>{}, int> =0, std::enable_if_t<(0<=std::tuple_size<std::remove_reference_t<tuple>>{}), int> = 0 // sfinae test tuple tuple-like // todo: sfinae test using tuple construct t works > make( tuple&& tup ): ptr( std::addressof(tup) ), maker( make_tuple_maker<tuple>() ) {} t operator()() const { return maker(ptr); } };
code uses c++17 feature, std::make_from_tuple
, relatively easy write in c++11. in c++17 guaranteed elision means works non-movable types, cool.
now can write:
foo( make<bar> bar_in ):bar_( bar_in() ) {}
and body of foo::foo
can moved out of header file.
but more insane above alternatives.
again, have considered writing foo(bar)
?
Comments
Post a Comment