Avoid exponential grow of const references and rvalue references in constructor The 2019 Stack Overflow Developer Survey Results Are In Unicorn Meta Zoo #1: Why another podcast? Announcing the arrival of Valued Associate #679: Cesar Manara The Ask Question Wizard is Live! Data science time! April 2019 and salary with experiencec++ receive const lvalue and rvalue reference parameter without overloadmove/copy constructors combination of multiple parametersTo support move semantics, should function parameters be taken by unique_ptr, by value, or by rvalue?Some clarification on rvalue referencesRvalue reference usage within initialization listsHow to make template rvalue reference parameter ONLY bind to rvalue reference?rvalue references and constructor argumentsWould you ever mark a C++ RValue reference parameter as constGeneric copy-constructor with rvalue-reference membersPassing rvalue reference to const lvalue reference paremeterHow std::move can work with copy-constructor that takes non-const reference?What do you call a constructor with an rvalue reference parameter?invalid initialization of non-const reference from an rvalue

How do you keep chess fun when your opponent constantly beats you?

How did the audience guess the pentatonic scale in Bobby McFerrin's presentation?

Could an empire control the whole planet with today's comunication methods?

Sub-subscripts in strings cause different spacings than subscripts

Is every episode of "Where are my Pants?" identical?

Python - Fishing Simulator

How to determine omitted units in a publication

My body leaves; my core can stay

Do warforged have souls?

Keeping a retro style to sci-fi spaceships?

Huge performance difference of the command find with and without using %M option to show permissions

should truth entail possible truth

Accepted by European university, rejected by all American ones I applied to? Possible reasons?

Is 'stolen' appropriate word?

Word for: a synonym with a positive connotation?

Simulating Exploding Dice

How to politely respond to generic emails requesting a PhD/job in my lab? Without wasting too much time

Why did Peik Lin say, "I'm not an animal"?

Circular reasoning in L'Hopital's rule

What to do when moving next to a bird sanctuary with a loosely-domesticated cat?

Can we generate random numbers using irrational numbers like π and e?

"... to apply for a visa" or "... and applied for a visa"?

Do ℕ, mathbbN, BbbN, symbbN effectively differ, and is there a "canonical" specification of the naturals?

What other Star Trek series did the main TNG cast show up in?



Avoid exponential grow of const references and rvalue references in constructor



The 2019 Stack Overflow Developer Survey Results Are In
Unicorn Meta Zoo #1: Why another podcast?
Announcing the arrival of Valued Associate #679: Cesar Manara
The Ask Question Wizard is Live!
Data science time! April 2019 and salary with experiencec++ receive const lvalue and rvalue reference parameter without overloadmove/copy constructors combination of multiple parametersTo support move semantics, should function parameters be taken by unique_ptr, by value, or by rvalue?Some clarification on rvalue referencesRvalue reference usage within initialization listsHow to make template rvalue reference parameter ONLY bind to rvalue reference?rvalue references and constructor argumentsWould you ever mark a C++ RValue reference parameter as constGeneric copy-constructor with rvalue-reference membersPassing rvalue reference to const lvalue reference paremeterHow std::move can work with copy-constructor that takes non-const reference?What do you call a constructor with an rvalue reference parameter?invalid initialization of non-const reference from an rvalue



.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty height:90px;width:728px;box-sizing:border-box;








41















I am coding some templated classes for a machine learning library, and I'm facing this issue a lot of times. I'm using mostly the policy pattern, where classes receive as template argument policies for different functionalities, for example:



template <class Loss, class Optimizer> class LinearClassifier ... 


The problem is with the constructors. As the amount of policies (template parameters) grows, the combinations of const references and rvalue references grow exponentially. In the previous example:



LinearClassifier(const Loss& loss, const Optimizer& optimizer) : _loss(loss), _optimizer(optimizer) 

LinearClassifier(Loss&& loss, const Optimizer& optimizer) : _loss(std::move(loss)), _optimizer(optimizer)

LinearClassifier(const Loss& loss, Optimizer&& optimizer) : _loss(loss), _optimizer(std::move(optimizer))

LinearClassifier(Loss&& loss, Optimizer&& optimizer) : _loss(std::move(loss)), _optimizer(std::move(optimizer))


Is there some way to avoid this?










share|improve this question



















  • 17





    use forwarding references ?

    – Piotr Skotnicki
    Apr 26 '16 at 14:51






  • 1





    Just copy and move.

    – edmz
    Apr 26 '16 at 14:54






  • 2





    This question does not have an answer that is always right for all types that might be Loss and Optimizer. The best answer depends on details such as: Are both Loss and Optimizer expensive to copy but cheaply movable? Are the writers and maintainers of this code comfortable with constraining templates (e.g. enable_if)? The pass-by-value solution is sometimes the way to go. If you go with with the forwarding reference solution, I highly recommend properly constraining it. If only one of Loss and Optimizer is cheaply movable, a hybrid solution could be considered.

    – Howard Hinnant
    Apr 26 '16 at 15:26







  • 4





    I think that the code in the question is not only complex, but essentially incorrect. Look at the initializer _loss(loss). Even if loss is of type Loss&& then this initializer will still treat loss as an lvalue. This is important, if unintuitive. @Federico, were you under the impression that _loss(loss) would "move" from the Loss&& loss? In fact, it will be copied in.

    – Aaron McDaid
    Apr 26 '16 at 19:39







  • 1





    are _loss and _optimizer values or references?

    – M.M
    Apr 27 '16 at 12:08

















41















I am coding some templated classes for a machine learning library, and I'm facing this issue a lot of times. I'm using mostly the policy pattern, where classes receive as template argument policies for different functionalities, for example:



template <class Loss, class Optimizer> class LinearClassifier ... 


The problem is with the constructors. As the amount of policies (template parameters) grows, the combinations of const references and rvalue references grow exponentially. In the previous example:



LinearClassifier(const Loss& loss, const Optimizer& optimizer) : _loss(loss), _optimizer(optimizer) 

LinearClassifier(Loss&& loss, const Optimizer& optimizer) : _loss(std::move(loss)), _optimizer(optimizer)

LinearClassifier(const Loss& loss, Optimizer&& optimizer) : _loss(loss), _optimizer(std::move(optimizer))

LinearClassifier(Loss&& loss, Optimizer&& optimizer) : _loss(std::move(loss)), _optimizer(std::move(optimizer))


Is there some way to avoid this?










share|improve this question



















  • 17





    use forwarding references ?

    – Piotr Skotnicki
    Apr 26 '16 at 14:51






  • 1





    Just copy and move.

    – edmz
    Apr 26 '16 at 14:54






  • 2





    This question does not have an answer that is always right for all types that might be Loss and Optimizer. The best answer depends on details such as: Are both Loss and Optimizer expensive to copy but cheaply movable? Are the writers and maintainers of this code comfortable with constraining templates (e.g. enable_if)? The pass-by-value solution is sometimes the way to go. If you go with with the forwarding reference solution, I highly recommend properly constraining it. If only one of Loss and Optimizer is cheaply movable, a hybrid solution could be considered.

    – Howard Hinnant
    Apr 26 '16 at 15:26







  • 4





    I think that the code in the question is not only complex, but essentially incorrect. Look at the initializer _loss(loss). Even if loss is of type Loss&& then this initializer will still treat loss as an lvalue. This is important, if unintuitive. @Federico, were you under the impression that _loss(loss) would "move" from the Loss&& loss? In fact, it will be copied in.

    – Aaron McDaid
    Apr 26 '16 at 19:39







  • 1





    are _loss and _optimizer values or references?

    – M.M
    Apr 27 '16 at 12:08













41












41








41


14






I am coding some templated classes for a machine learning library, and I'm facing this issue a lot of times. I'm using mostly the policy pattern, where classes receive as template argument policies for different functionalities, for example:



template <class Loss, class Optimizer> class LinearClassifier ... 


The problem is with the constructors. As the amount of policies (template parameters) grows, the combinations of const references and rvalue references grow exponentially. In the previous example:



LinearClassifier(const Loss& loss, const Optimizer& optimizer) : _loss(loss), _optimizer(optimizer) 

LinearClassifier(Loss&& loss, const Optimizer& optimizer) : _loss(std::move(loss)), _optimizer(optimizer)

LinearClassifier(const Loss& loss, Optimizer&& optimizer) : _loss(loss), _optimizer(std::move(optimizer))

LinearClassifier(Loss&& loss, Optimizer&& optimizer) : _loss(std::move(loss)), _optimizer(std::move(optimizer))


Is there some way to avoid this?










share|improve this question
















I am coding some templated classes for a machine learning library, and I'm facing this issue a lot of times. I'm using mostly the policy pattern, where classes receive as template argument policies for different functionalities, for example:



template <class Loss, class Optimizer> class LinearClassifier ... 


The problem is with the constructors. As the amount of policies (template parameters) grows, the combinations of const references and rvalue references grow exponentially. In the previous example:



LinearClassifier(const Loss& loss, const Optimizer& optimizer) : _loss(loss), _optimizer(optimizer) 

LinearClassifier(Loss&& loss, const Optimizer& optimizer) : _loss(std::move(loss)), _optimizer(optimizer)

LinearClassifier(const Loss& loss, Optimizer&& optimizer) : _loss(loss), _optimizer(std::move(optimizer))

LinearClassifier(Loss&& loss, Optimizer&& optimizer) : _loss(std::move(loss)), _optimizer(std::move(optimizer))


Is there some way to avoid this?







c++ c++11 rvalue-reference const-reference






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Apr 27 '16 at 13:40







Federico Allocati

















asked Apr 26 '16 at 14:49









Federico AllocatiFederico Allocati

30738




30738







  • 17





    use forwarding references ?

    – Piotr Skotnicki
    Apr 26 '16 at 14:51






  • 1





    Just copy and move.

    – edmz
    Apr 26 '16 at 14:54






  • 2





    This question does not have an answer that is always right for all types that might be Loss and Optimizer. The best answer depends on details such as: Are both Loss and Optimizer expensive to copy but cheaply movable? Are the writers and maintainers of this code comfortable with constraining templates (e.g. enable_if)? The pass-by-value solution is sometimes the way to go. If you go with with the forwarding reference solution, I highly recommend properly constraining it. If only one of Loss and Optimizer is cheaply movable, a hybrid solution could be considered.

    – Howard Hinnant
    Apr 26 '16 at 15:26







  • 4





    I think that the code in the question is not only complex, but essentially incorrect. Look at the initializer _loss(loss). Even if loss is of type Loss&& then this initializer will still treat loss as an lvalue. This is important, if unintuitive. @Federico, were you under the impression that _loss(loss) would "move" from the Loss&& loss? In fact, it will be copied in.

    – Aaron McDaid
    Apr 26 '16 at 19:39







  • 1





    are _loss and _optimizer values or references?

    – M.M
    Apr 27 '16 at 12:08












  • 17





    use forwarding references ?

    – Piotr Skotnicki
    Apr 26 '16 at 14:51






  • 1





    Just copy and move.

    – edmz
    Apr 26 '16 at 14:54






  • 2





    This question does not have an answer that is always right for all types that might be Loss and Optimizer. The best answer depends on details such as: Are both Loss and Optimizer expensive to copy but cheaply movable? Are the writers and maintainers of this code comfortable with constraining templates (e.g. enable_if)? The pass-by-value solution is sometimes the way to go. If you go with with the forwarding reference solution, I highly recommend properly constraining it. If only one of Loss and Optimizer is cheaply movable, a hybrid solution could be considered.

    – Howard Hinnant
    Apr 26 '16 at 15:26







  • 4





    I think that the code in the question is not only complex, but essentially incorrect. Look at the initializer _loss(loss). Even if loss is of type Loss&& then this initializer will still treat loss as an lvalue. This is important, if unintuitive. @Federico, were you under the impression that _loss(loss) would "move" from the Loss&& loss? In fact, it will be copied in.

    – Aaron McDaid
    Apr 26 '16 at 19:39







  • 1





    are _loss and _optimizer values or references?

    – M.M
    Apr 27 '16 at 12:08







17




17





use forwarding references ?

– Piotr Skotnicki
Apr 26 '16 at 14:51





use forwarding references ?

– Piotr Skotnicki
Apr 26 '16 at 14:51




1




1





Just copy and move.

– edmz
Apr 26 '16 at 14:54





Just copy and move.

– edmz
Apr 26 '16 at 14:54




2




2





This question does not have an answer that is always right for all types that might be Loss and Optimizer. The best answer depends on details such as: Are both Loss and Optimizer expensive to copy but cheaply movable? Are the writers and maintainers of this code comfortable with constraining templates (e.g. enable_if)? The pass-by-value solution is sometimes the way to go. If you go with with the forwarding reference solution, I highly recommend properly constraining it. If only one of Loss and Optimizer is cheaply movable, a hybrid solution could be considered.

– Howard Hinnant
Apr 26 '16 at 15:26






This question does not have an answer that is always right for all types that might be Loss and Optimizer. The best answer depends on details such as: Are both Loss and Optimizer expensive to copy but cheaply movable? Are the writers and maintainers of this code comfortable with constraining templates (e.g. enable_if)? The pass-by-value solution is sometimes the way to go. If you go with with the forwarding reference solution, I highly recommend properly constraining it. If only one of Loss and Optimizer is cheaply movable, a hybrid solution could be considered.

– Howard Hinnant
Apr 26 '16 at 15:26





4




4





I think that the code in the question is not only complex, but essentially incorrect. Look at the initializer _loss(loss). Even if loss is of type Loss&& then this initializer will still treat loss as an lvalue. This is important, if unintuitive. @Federico, were you under the impression that _loss(loss) would "move" from the Loss&& loss? In fact, it will be copied in.

– Aaron McDaid
Apr 26 '16 at 19:39






I think that the code in the question is not only complex, but essentially incorrect. Look at the initializer _loss(loss). Even if loss is of type Loss&& then this initializer will still treat loss as an lvalue. This is important, if unintuitive. @Federico, were you under the impression that _loss(loss) would "move" from the Loss&& loss? In fact, it will be copied in.

– Aaron McDaid
Apr 26 '16 at 19:39





1




1





are _loss and _optimizer values or references?

– M.M
Apr 27 '16 at 12:08





are _loss and _optimizer values or references?

– M.M
Apr 27 '16 at 12:08












4 Answers
4






active

oldest

votes


















36














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.






share|improve this answer




















  • 3





    We have now two responses with 'this is [exactly the | the precise] use case for' - two different methods, both of which make sense to me. Could anyone clarify whether both are good or why we shouldn't even consider the other, or whether they are indeed interchangable?

    – peterchen
    Apr 26 '16 at 14:58







  • 1





    @FedericoAllocati Yes, it does.

    – lisyarus
    Apr 26 '16 at 15:01






  • 1





    Not all moves are cheap... some moves are copies.

    – Barry
    Apr 26 '16 at 15:04






  • 1





    Templated constructors are not always desirable, which can make "copy and move" more attractive.

    – Ilya Popov
    Apr 26 '16 at 15:09






  • 7





    This design is a decent direction to head, but as it stands, has a defect that might be serious: std::is_constructible<LinearClassifier, int, int>::value is true (and you can sub in anything you want for int). If you don't care, fine. But correct SFINAE is becoming more and more important. To fix this, you either go with the by-value solution from the other answer, or you constrain L and O such that they will only instantiate for Loss and Optimizer, and this answer does not (yet) explain how to do that.

    – Howard Hinnant
    Apr 26 '16 at 15:15


















30














This is exactly the use case for "pass by value and move" technique.
Although slighly less efficient than lvalue/rvalue overloads, it not too bad (one extra move) and saves you the hassle.



LinearClassifier(Loss loss, Optimizer optimizer) 
: _loss(std::move(loss)), _optimizer(std::move(optimizer))


In the case of lvalue argument, there will be one copy and one move, in the case of rvalue argument, there will be two moves (provided that you classes Loss and Optimizer implement move constructors).



Update: In general, perfect forwarding solution is more efficient. On the other hand, this solution avoids templated constructors which are not always desirable, because it will accept arguments of any type when not constrained with SFINAE and lead to hard errors inside the constructor if arguments are not compatible. In other words, unconstrained templated constructors are not SFINAE-friendly. See Barry's answer for a constrained template constructor which avoids this problem.



Another potential problem of a templated constructor is the need to place it in a header file.



Update 2: Herb Sutter talks about this problem in his CppCon 2014 talk "Back to the Basics" starting at 1:03:48. He discusses pass by value first, then overloading on rvalue-ref, then perfect forwarding at 1:15:22 including constraining. And finally he talks about constructors as the only good use case for passing by value at 1:25:50.






share|improve this answer

























  • "The perfect forwarding solution is more efficient." Realistically, it might be more efficient.

    – edmz
    Apr 26 '16 at 15:13











  • I didn't understood the part of " because it will every argument types then not constrained with SFINAE and lead to hard errors inside the constructor if arguments are not compatible". The header file placement is not a problem, because this is a header only library :)

    – Federico Allocati
    Apr 26 '16 at 15:14











  • The constructor shown by @lisyarus will fit any call with two arguments regardless of their types. This leads to several consequences: you cannot have any other constructor with two arguments, and if some other code tries to do any SFINAE tricks using your constructor, it won't work (because the constructor will accept any types and then produce an error inside the constructor body). (See Howard Hinnant's comment for an example).

    – Ilya Popov
    Apr 26 '16 at 15:17












  • "templated constructors are not SFINAE-friendly" That's just tautological. Non-SFINAE-friendly constructor templates aren't SFINAE-friendly... but SFINAE-friendly ones are...

    – Barry
    Apr 26 '16 at 16:08











  • @Barry thats why I said "then not constrained". Of course, they are SFINAE-friendly if constrained properly.

    – Ilya Popov
    Apr 26 '16 at 16:17


















29














For the sake of completeness, the optimal 2-argument constructor would take two forwarding references and use SFINAE to ensure that they're the correct types. We can introduce the following alias:



template <class T, class U>
using decays_to = std::is_convertible<std::decay_t<T>*, U*>;


And then:



template <class L, class O,
class = std::enable_if_t<decays_to<L, Loss>::value &&
decays_to<O, Optimizer>::value>>
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))



This ensures that we only accept arguments that are of type Loss and Optimizer (or are derived from them). Unfortunately, it is quite a mouthful to write and is very distracting from the original intent. This is pretty difficult to get right - but if performance matters, then it matters, and this is really the only way to go.



But if it doesn't matter, and if Loss and Optimizer are cheap to move (or, better still, performance for this constructor is completely irrelevant), prefer Ilya Popov's solution:



LinearClassifier(Loss loss, Optimizer optimizer)
: _loss(std::move(loss))
, _optimizer(std::move(optimizer))






share|improve this answer




















  • 1





    Interesting choice of constraint. I agree, this is tricky to get right, and I am on the hook for infamously getting it wrong (youtube.com/watch?v=xnqTKD8uD64) :-). What about using std::is_convertible<L, Loss> for the constraint? This would (for example) allow a const char* L to construct a std::string Loss. And would also allow your Derived -> Base example.

    – Howard Hinnant
    Apr 26 '16 at 16:30











  • @HowardHinnant Could use std::is_constructible<Loss, L&&> too. Just wanted to be as strict as possible for genericity. But yeah, hard to pick... What am I looking for in that video? :)

    – Barry
    Apr 26 '16 at 16:47












  • Just an hour before that talk, and on my way out of town, Herb asked me what the constraint should be for a problem like this. Toward the end of the talk (1:15:00 in?). I over-thought it and got it wrong. There's nothing like testing! :-)

    – Howard Hinnant
    Apr 26 '16 at 16:59












  • @Howard I think the constraint is fine! So you're requiring the user to be explicit - that's hardly infamous-worthy

    – Barry
    Apr 26 '16 at 17:39











  • here's some syntactic sugar using fold-expressions that allows to pass the -IMO pretty readible- expression forward_compatible_v<std::pair<L, Loss>, std::pair<O, Optimizer>> to the enable_if_t constraint.

    – TemplateRex
    Apr 27 '16 at 9:43



















15














How far down the rabbit hole do you want to go?



I'm aware of 4 decent ways to approach this problem. You should generally use the earlier ones if you match their preconditions, as each later one increases significantly in complexity.




For the most part, either move is so cheap doing it twice is free, or move is copy.



If move is copy, and copy is non-free, take the parameter by const&. If not, take it by value.



This will behave basically optimally, and makes your code far easier to understand.



LinearClassifier(Loss loss, Optimizer const& optimizer)
: _loss(std::move(loss))
, _optimizer(optimizer)



for a cheap-to-move Loss and move-is-copy optimizer.



This does 1 extra move over the "optimal" perfect forwarding below (note: perfect forwarding is not optimal) per value parameter in all cases. So long as move is cheap, this is the best solution, because it generates clean error messages, allows based construction, and is far easier to read than any of the other solutions.



Consider using this solution.




If move is cheaper than copy yet non-free, one approach is perfect forwarding based:
Either:



template<class L, class O >
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))



Or the more complex and more overload-friendly:



template<class L, class O,
std::enable_if_t<
std::is_same<std::decay_t<L>, Loss>
&& std::is_same<std::decay_t<O>, Optimizer>
, int> * = nullptr
>
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))



this costs you the ability to do based construction of your arguments. Also, up to exponential number of constructors can be generated by the above code if they are called (hopefully they will be inlined).



You can drop the std::enable_if_t clause at the cost of SFINAE failure; basically, the wrong overload of your constructor can be picked if you aren't careful with that std::enable_if_t clause. If you have constructor overloads with the same number of arguments, or care about early-failure, then you want the std::enable_if_t one. Otherwise, use the simpler one.



This solution is usually considered "most optimal". It is accepably optimal, but it is not most optimal.




The next step is to use emplace construction with tuples.



private:
template<std::size_t...LIs, std::size_t...OIs, class...Ls, class...Os>
LinearClassifier(std::piecewise_construct_t,
std::index_sequence<LIs...>, std::tuple<Ls...>&& ls,
std::index_sequence<OIs...>, std::tuple<Os...>&& os
)
: _loss(std::get<LIs>(std::move(ls))...)
, _optimizer(std::get<OIs>(std::move(os))...)

public:
template<class...Ls, class...Os>
LinearClassifier(std::piecewise_construct_t,
std::tuple<Ls...> ls,
std::tuple<Os...> os
):
LinearClassifier(std::piecewise_construct_t,
std::index_sequence_for<Ls...>, std::move(ls),
std::index_sequence_for<Os...>, std::move(os)
)



where we defer construction until inside the LinearClassifier. This allows you to have non-copy/moveable objects in your object, and is arguably maximally efficient.



To see how this works, example now piecewise_construct works with std::pair. You pass piecewise construct first, then forward_as_tuple the arguments to construct each element afterwards (including a copy or move ctor).



By directly constructing objects, we can eliminate a move or a copy per object compared to the perfect-forwarding solution above. It also lets you forward a copy or a move if required.




A final cute technique is to type-erase construction. Practically, this requires something like std::experimental::optional<T> to be available, and might make the class a bit larger.



This is not faster than the piecewise construction one. It does abstract the work that the emplace construction one does, making it simpler on a per-use basis, and it permits you to split ctor body from the header file. But there is a small amount of overhead, in both runtime and space.



There is a bunch of boilerplate you need to start with. This generates a template class that represents the concept of "constructing an object, later, at a place someone else will tell me."



struct delayed_emplace_t ;
template<class T>
struct delayed_construct !std::is_same<std::decay_t<T>, delayed_construct>
,int>* = nullptr
>
delayed_construct(T&&t, Ts&&...ts):
delayed_construct( delayed_emplace_t, std::forward<T>(t), std::forward<Ts>(ts)... )

template<class T, class...Ts>
delayed_construct(delayed_emplace_t, T&&t, Ts&&...ts):
ctor([tup = std::forward_as_tuple(std::forward<T>(t), std::forward<Ts>(ts)...)]( auto& op ) mutable
ctor_helper(op, std::make_index_sequence<sizeof...(Ts)+1>, std::move(tup));
)
template<std::size_t...Is, class...Ts>
static void ctor_helper(std::experimental::optional<T>& op, std::index_sequence<Is...>, std::tuple<Ts...>&& tup)
op.emplace( std::get<Is>(std::move(tup))... );

void operator()(std::experimental::optional<T>& target)
ctor(target);
ctor = ;

explicit operator bool() const return !!ctor;
;


where we type-erase the action of constructing an optional from arbitrary arguments.



LinearClassifier( delayed_construct<Loss> loss, delayed_construct<Optimizer> optimizer ) 
loss(_loss);
optimizer(_optimizer);



where _loss are std::experimental::optional<Loss>. To remove the optionality of _loss you have to use std::aligned_storage_t<sizeof(Loss), alignof(Loss)> and be very careful about writing a ctor to handle exceptions and manually destroy things etc. It is a headache.



Some nice things about this last pattern is that the body of the ctor can move out of the header, and at most a linear amount of code is generated instead of an exponential amount of template constructors.



This solution is marginally less efficient than the placement construct version, as not all compilers will be able to inline the std::function use. But it also permits storing non-movable objects.



Code not tested, so there are probably typos.




In c++17 with guaranteed elision, the optional part of the delayed ctor becomes obsolete. Any function returning a T is all you need for a delayed ctor of T.






share|improve this answer

























  • I just love the way the code explodes with each iteration of "optimality" ;-) Somehow C++ seems to have left all the simplicity of C behind...

    – cmaster
    Apr 26 '16 at 19:31






  • 3





    I'm not sure if I should be disgusted or in awe.

    – isanae
    Apr 26 '16 at 19:33











  • @cmaster To some extent; but doing the same kind of operations in C would be bulkier and completely unmaintainable and next to impossible to do without repeating every time you want to use it. The delayed_construct<T> (which is the most insane) actually has a really short "per use" body (same length as first solution!), and what it does would be a real headache in C. You'd best give up long before you reached what is actually going on in that one, and no chance of doing it generically. In C++, I write the mess once (and the mess is shorter than the C equivalent), and can reuse it.

    – Yakk - Adam Nevraumont
    Apr 26 '16 at 23:28











  • @cmaster Now, I probably wouldn't use it; I'd argue for #1 barring extreme circumstances. And #1 is already ridiculously shorter than the equivalent C implementation. The C solution might be as short as #1, but that is because it usually wouldn't do the same amount of corner-case optimization stuff as even #1 does "under the hood".

    – Yakk - Adam Nevraumont
    Apr 26 '16 at 23:30












  • How can I provide that lvalue object will not be changed in such constructor with std::forward? Here we have L&& loss, if we give lvalue - we will have L& loss, and it can be changed. The good practice everytime was using const SomeType&, but here we can’t just write const L&& loss, because we want to have move ability. What is the solution?

    – I.S.M.
    Nov 26 '17 at 12:01











Your Answer






StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













draft saved

draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f36868442%2favoid-exponential-grow-of-const-references-and-rvalue-references-in-constructor%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown

























4 Answers
4






active

oldest

votes








4 Answers
4






active

oldest

votes









active

oldest

votes






active

oldest

votes









36














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.






share|improve this answer




















  • 3





    We have now two responses with 'this is [exactly the | the precise] use case for' - two different methods, both of which make sense to me. Could anyone clarify whether both are good or why we shouldn't even consider the other, or whether they are indeed interchangable?

    – peterchen
    Apr 26 '16 at 14:58







  • 1





    @FedericoAllocati Yes, it does.

    – lisyarus
    Apr 26 '16 at 15:01






  • 1





    Not all moves are cheap... some moves are copies.

    – Barry
    Apr 26 '16 at 15:04






  • 1





    Templated constructors are not always desirable, which can make "copy and move" more attractive.

    – Ilya Popov
    Apr 26 '16 at 15:09






  • 7





    This design is a decent direction to head, but as it stands, has a defect that might be serious: std::is_constructible<LinearClassifier, int, int>::value is true (and you can sub in anything you want for int). If you don't care, fine. But correct SFINAE is becoming more and more important. To fix this, you either go with the by-value solution from the other answer, or you constrain L and O such that they will only instantiate for Loss and Optimizer, and this answer does not (yet) explain how to do that.

    – Howard Hinnant
    Apr 26 '16 at 15:15















36














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.






share|improve this answer




















  • 3





    We have now two responses with 'this is [exactly the | the precise] use case for' - two different methods, both of which make sense to me. Could anyone clarify whether both are good or why we shouldn't even consider the other, or whether they are indeed interchangable?

    – peterchen
    Apr 26 '16 at 14:58







  • 1





    @FedericoAllocati Yes, it does.

    – lisyarus
    Apr 26 '16 at 15:01






  • 1





    Not all moves are cheap... some moves are copies.

    – Barry
    Apr 26 '16 at 15:04






  • 1





    Templated constructors are not always desirable, which can make "copy and move" more attractive.

    – Ilya Popov
    Apr 26 '16 at 15:09






  • 7





    This design is a decent direction to head, but as it stands, has a defect that might be serious: std::is_constructible<LinearClassifier, int, int>::value is true (and you can sub in anything you want for int). If you don't care, fine. But correct SFINAE is becoming more and more important. To fix this, you either go with the by-value solution from the other answer, or you constrain L and O such that they will only instantiate for Loss and Optimizer, and this answer does not (yet) explain how to do that.

    – Howard Hinnant
    Apr 26 '16 at 15:15













36












36








36







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.






share|improve this answer















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.







share|improve this answer














share|improve this answer



share|improve this answer








edited May 23 '17 at 12:34









Community

11




11










answered Apr 26 '16 at 14:53









lisyaruslisyarus

10.5k22952




10.5k22952







  • 3





    We have now two responses with 'this is [exactly the | the precise] use case for' - two different methods, both of which make sense to me. Could anyone clarify whether both are good or why we shouldn't even consider the other, or whether they are indeed interchangable?

    – peterchen
    Apr 26 '16 at 14:58







  • 1





    @FedericoAllocati Yes, it does.

    – lisyarus
    Apr 26 '16 at 15:01






  • 1





    Not all moves are cheap... some moves are copies.

    – Barry
    Apr 26 '16 at 15:04






  • 1





    Templated constructors are not always desirable, which can make "copy and move" more attractive.

    – Ilya Popov
    Apr 26 '16 at 15:09






  • 7





    This design is a decent direction to head, but as it stands, has a defect that might be serious: std::is_constructible<LinearClassifier, int, int>::value is true (and you can sub in anything you want for int). If you don't care, fine. But correct SFINAE is becoming more and more important. To fix this, you either go with the by-value solution from the other answer, or you constrain L and O such that they will only instantiate for Loss and Optimizer, and this answer does not (yet) explain how to do that.

    – Howard Hinnant
    Apr 26 '16 at 15:15












  • 3





    We have now two responses with 'this is [exactly the | the precise] use case for' - two different methods, both of which make sense to me. Could anyone clarify whether both are good or why we shouldn't even consider the other, or whether they are indeed interchangable?

    – peterchen
    Apr 26 '16 at 14:58







  • 1





    @FedericoAllocati Yes, it does.

    – lisyarus
    Apr 26 '16 at 15:01






  • 1





    Not all moves are cheap... some moves are copies.

    – Barry
    Apr 26 '16 at 15:04






  • 1





    Templated constructors are not always desirable, which can make "copy and move" more attractive.

    – Ilya Popov
    Apr 26 '16 at 15:09






  • 7





    This design is a decent direction to head, but as it stands, has a defect that might be serious: std::is_constructible<LinearClassifier, int, int>::value is true (and you can sub in anything you want for int). If you don't care, fine. But correct SFINAE is becoming more and more important. To fix this, you either go with the by-value solution from the other answer, or you constrain L and O such that they will only instantiate for Loss and Optimizer, and this answer does not (yet) explain how to do that.

    – Howard Hinnant
    Apr 26 '16 at 15:15







3




3





We have now two responses with 'this is [exactly the | the precise] use case for' - two different methods, both of which make sense to me. Could anyone clarify whether both are good or why we shouldn't even consider the other, or whether they are indeed interchangable?

– peterchen
Apr 26 '16 at 14:58






We have now two responses with 'this is [exactly the | the precise] use case for' - two different methods, both of which make sense to me. Could anyone clarify whether both are good or why we shouldn't even consider the other, or whether they are indeed interchangable?

– peterchen
Apr 26 '16 at 14:58





1




1





@FedericoAllocati Yes, it does.

– lisyarus
Apr 26 '16 at 15:01





@FedericoAllocati Yes, it does.

– lisyarus
Apr 26 '16 at 15:01




1




1





Not all moves are cheap... some moves are copies.

– Barry
Apr 26 '16 at 15:04





Not all moves are cheap... some moves are copies.

– Barry
Apr 26 '16 at 15:04




1




1





Templated constructors are not always desirable, which can make "copy and move" more attractive.

– Ilya Popov
Apr 26 '16 at 15:09





Templated constructors are not always desirable, which can make "copy and move" more attractive.

– Ilya Popov
Apr 26 '16 at 15:09




7




7





This design is a decent direction to head, but as it stands, has a defect that might be serious: std::is_constructible<LinearClassifier, int, int>::value is true (and you can sub in anything you want for int). If you don't care, fine. But correct SFINAE is becoming more and more important. To fix this, you either go with the by-value solution from the other answer, or you constrain L and O such that they will only instantiate for Loss and Optimizer, and this answer does not (yet) explain how to do that.

– Howard Hinnant
Apr 26 '16 at 15:15





This design is a decent direction to head, but as it stands, has a defect that might be serious: std::is_constructible<LinearClassifier, int, int>::value is true (and you can sub in anything you want for int). If you don't care, fine. But correct SFINAE is becoming more and more important. To fix this, you either go with the by-value solution from the other answer, or you constrain L and O such that they will only instantiate for Loss and Optimizer, and this answer does not (yet) explain how to do that.

– Howard Hinnant
Apr 26 '16 at 15:15













30














This is exactly the use case for "pass by value and move" technique.
Although slighly less efficient than lvalue/rvalue overloads, it not too bad (one extra move) and saves you the hassle.



LinearClassifier(Loss loss, Optimizer optimizer) 
: _loss(std::move(loss)), _optimizer(std::move(optimizer))


In the case of lvalue argument, there will be one copy and one move, in the case of rvalue argument, there will be two moves (provided that you classes Loss and Optimizer implement move constructors).



Update: In general, perfect forwarding solution is more efficient. On the other hand, this solution avoids templated constructors which are not always desirable, because it will accept arguments of any type when not constrained with SFINAE and lead to hard errors inside the constructor if arguments are not compatible. In other words, unconstrained templated constructors are not SFINAE-friendly. See Barry's answer for a constrained template constructor which avoids this problem.



Another potential problem of a templated constructor is the need to place it in a header file.



Update 2: Herb Sutter talks about this problem in his CppCon 2014 talk "Back to the Basics" starting at 1:03:48. He discusses pass by value first, then overloading on rvalue-ref, then perfect forwarding at 1:15:22 including constraining. And finally he talks about constructors as the only good use case for passing by value at 1:25:50.






share|improve this answer

























  • "The perfect forwarding solution is more efficient." Realistically, it might be more efficient.

    – edmz
    Apr 26 '16 at 15:13











  • I didn't understood the part of " because it will every argument types then not constrained with SFINAE and lead to hard errors inside the constructor if arguments are not compatible". The header file placement is not a problem, because this is a header only library :)

    – Federico Allocati
    Apr 26 '16 at 15:14











  • The constructor shown by @lisyarus will fit any call with two arguments regardless of their types. This leads to several consequences: you cannot have any other constructor with two arguments, and if some other code tries to do any SFINAE tricks using your constructor, it won't work (because the constructor will accept any types and then produce an error inside the constructor body). (See Howard Hinnant's comment for an example).

    – Ilya Popov
    Apr 26 '16 at 15:17












  • "templated constructors are not SFINAE-friendly" That's just tautological. Non-SFINAE-friendly constructor templates aren't SFINAE-friendly... but SFINAE-friendly ones are...

    – Barry
    Apr 26 '16 at 16:08











  • @Barry thats why I said "then not constrained". Of course, they are SFINAE-friendly if constrained properly.

    – Ilya Popov
    Apr 26 '16 at 16:17















30














This is exactly the use case for "pass by value and move" technique.
Although slighly less efficient than lvalue/rvalue overloads, it not too bad (one extra move) and saves you the hassle.



LinearClassifier(Loss loss, Optimizer optimizer) 
: _loss(std::move(loss)), _optimizer(std::move(optimizer))


In the case of lvalue argument, there will be one copy and one move, in the case of rvalue argument, there will be two moves (provided that you classes Loss and Optimizer implement move constructors).



Update: In general, perfect forwarding solution is more efficient. On the other hand, this solution avoids templated constructors which are not always desirable, because it will accept arguments of any type when not constrained with SFINAE and lead to hard errors inside the constructor if arguments are not compatible. In other words, unconstrained templated constructors are not SFINAE-friendly. See Barry's answer for a constrained template constructor which avoids this problem.



Another potential problem of a templated constructor is the need to place it in a header file.



Update 2: Herb Sutter talks about this problem in his CppCon 2014 talk "Back to the Basics" starting at 1:03:48. He discusses pass by value first, then overloading on rvalue-ref, then perfect forwarding at 1:15:22 including constraining. And finally he talks about constructors as the only good use case for passing by value at 1:25:50.






share|improve this answer

























  • "The perfect forwarding solution is more efficient." Realistically, it might be more efficient.

    – edmz
    Apr 26 '16 at 15:13











  • I didn't understood the part of " because it will every argument types then not constrained with SFINAE and lead to hard errors inside the constructor if arguments are not compatible". The header file placement is not a problem, because this is a header only library :)

    – Federico Allocati
    Apr 26 '16 at 15:14











  • The constructor shown by @lisyarus will fit any call with two arguments regardless of their types. This leads to several consequences: you cannot have any other constructor with two arguments, and if some other code tries to do any SFINAE tricks using your constructor, it won't work (because the constructor will accept any types and then produce an error inside the constructor body). (See Howard Hinnant's comment for an example).

    – Ilya Popov
    Apr 26 '16 at 15:17












  • "templated constructors are not SFINAE-friendly" That's just tautological. Non-SFINAE-friendly constructor templates aren't SFINAE-friendly... but SFINAE-friendly ones are...

    – Barry
    Apr 26 '16 at 16:08











  • @Barry thats why I said "then not constrained". Of course, they are SFINAE-friendly if constrained properly.

    – Ilya Popov
    Apr 26 '16 at 16:17













30












30








30







This is exactly the use case for "pass by value and move" technique.
Although slighly less efficient than lvalue/rvalue overloads, it not too bad (one extra move) and saves you the hassle.



LinearClassifier(Loss loss, Optimizer optimizer) 
: _loss(std::move(loss)), _optimizer(std::move(optimizer))


In the case of lvalue argument, there will be one copy and one move, in the case of rvalue argument, there will be two moves (provided that you classes Loss and Optimizer implement move constructors).



Update: In general, perfect forwarding solution is more efficient. On the other hand, this solution avoids templated constructors which are not always desirable, because it will accept arguments of any type when not constrained with SFINAE and lead to hard errors inside the constructor if arguments are not compatible. In other words, unconstrained templated constructors are not SFINAE-friendly. See Barry's answer for a constrained template constructor which avoids this problem.



Another potential problem of a templated constructor is the need to place it in a header file.



Update 2: Herb Sutter talks about this problem in his CppCon 2014 talk "Back to the Basics" starting at 1:03:48. He discusses pass by value first, then overloading on rvalue-ref, then perfect forwarding at 1:15:22 including constraining. And finally he talks about constructors as the only good use case for passing by value at 1:25:50.






share|improve this answer















This is exactly the use case for "pass by value and move" technique.
Although slighly less efficient than lvalue/rvalue overloads, it not too bad (one extra move) and saves you the hassle.



LinearClassifier(Loss loss, Optimizer optimizer) 
: _loss(std::move(loss)), _optimizer(std::move(optimizer))


In the case of lvalue argument, there will be one copy and one move, in the case of rvalue argument, there will be two moves (provided that you classes Loss and Optimizer implement move constructors).



Update: In general, perfect forwarding solution is more efficient. On the other hand, this solution avoids templated constructors which are not always desirable, because it will accept arguments of any type when not constrained with SFINAE and lead to hard errors inside the constructor if arguments are not compatible. In other words, unconstrained templated constructors are not SFINAE-friendly. See Barry's answer for a constrained template constructor which avoids this problem.



Another potential problem of a templated constructor is the need to place it in a header file.



Update 2: Herb Sutter talks about this problem in his CppCon 2014 talk "Back to the Basics" starting at 1:03:48. He discusses pass by value first, then overloading on rvalue-ref, then perfect forwarding at 1:15:22 including constraining. And finally he talks about constructors as the only good use case for passing by value at 1:25:50.







share|improve this answer














share|improve this answer



share|improve this answer








edited May 23 '17 at 11:54









Community

11




11










answered Apr 26 '16 at 14:54









Ilya PopovIlya Popov

2,7791927




2,7791927












  • "The perfect forwarding solution is more efficient." Realistically, it might be more efficient.

    – edmz
    Apr 26 '16 at 15:13











  • I didn't understood the part of " because it will every argument types then not constrained with SFINAE and lead to hard errors inside the constructor if arguments are not compatible". The header file placement is not a problem, because this is a header only library :)

    – Federico Allocati
    Apr 26 '16 at 15:14











  • The constructor shown by @lisyarus will fit any call with two arguments regardless of their types. This leads to several consequences: you cannot have any other constructor with two arguments, and if some other code tries to do any SFINAE tricks using your constructor, it won't work (because the constructor will accept any types and then produce an error inside the constructor body). (See Howard Hinnant's comment for an example).

    – Ilya Popov
    Apr 26 '16 at 15:17












  • "templated constructors are not SFINAE-friendly" That's just tautological. Non-SFINAE-friendly constructor templates aren't SFINAE-friendly... but SFINAE-friendly ones are...

    – Barry
    Apr 26 '16 at 16:08











  • @Barry thats why I said "then not constrained". Of course, they are SFINAE-friendly if constrained properly.

    – Ilya Popov
    Apr 26 '16 at 16:17

















  • "The perfect forwarding solution is more efficient." Realistically, it might be more efficient.

    – edmz
    Apr 26 '16 at 15:13











  • I didn't understood the part of " because it will every argument types then not constrained with SFINAE and lead to hard errors inside the constructor if arguments are not compatible". The header file placement is not a problem, because this is a header only library :)

    – Federico Allocati
    Apr 26 '16 at 15:14











  • The constructor shown by @lisyarus will fit any call with two arguments regardless of their types. This leads to several consequences: you cannot have any other constructor with two arguments, and if some other code tries to do any SFINAE tricks using your constructor, it won't work (because the constructor will accept any types and then produce an error inside the constructor body). (See Howard Hinnant's comment for an example).

    – Ilya Popov
    Apr 26 '16 at 15:17












  • "templated constructors are not SFINAE-friendly" That's just tautological. Non-SFINAE-friendly constructor templates aren't SFINAE-friendly... but SFINAE-friendly ones are...

    – Barry
    Apr 26 '16 at 16:08











  • @Barry thats why I said "then not constrained". Of course, they are SFINAE-friendly if constrained properly.

    – Ilya Popov
    Apr 26 '16 at 16:17
















"The perfect forwarding solution is more efficient." Realistically, it might be more efficient.

– edmz
Apr 26 '16 at 15:13





"The perfect forwarding solution is more efficient." Realistically, it might be more efficient.

– edmz
Apr 26 '16 at 15:13













I didn't understood the part of " because it will every argument types then not constrained with SFINAE and lead to hard errors inside the constructor if arguments are not compatible". The header file placement is not a problem, because this is a header only library :)

– Federico Allocati
Apr 26 '16 at 15:14





I didn't understood the part of " because it will every argument types then not constrained with SFINAE and lead to hard errors inside the constructor if arguments are not compatible". The header file placement is not a problem, because this is a header only library :)

– Federico Allocati
Apr 26 '16 at 15:14













The constructor shown by @lisyarus will fit any call with two arguments regardless of their types. This leads to several consequences: you cannot have any other constructor with two arguments, and if some other code tries to do any SFINAE tricks using your constructor, it won't work (because the constructor will accept any types and then produce an error inside the constructor body). (See Howard Hinnant's comment for an example).

– Ilya Popov
Apr 26 '16 at 15:17






The constructor shown by @lisyarus will fit any call with two arguments regardless of their types. This leads to several consequences: you cannot have any other constructor with two arguments, and if some other code tries to do any SFINAE tricks using your constructor, it won't work (because the constructor will accept any types and then produce an error inside the constructor body). (See Howard Hinnant's comment for an example).

– Ilya Popov
Apr 26 '16 at 15:17














"templated constructors are not SFINAE-friendly" That's just tautological. Non-SFINAE-friendly constructor templates aren't SFINAE-friendly... but SFINAE-friendly ones are...

– Barry
Apr 26 '16 at 16:08





"templated constructors are not SFINAE-friendly" That's just tautological. Non-SFINAE-friendly constructor templates aren't SFINAE-friendly... but SFINAE-friendly ones are...

– Barry
Apr 26 '16 at 16:08













@Barry thats why I said "then not constrained". Of course, they are SFINAE-friendly if constrained properly.

– Ilya Popov
Apr 26 '16 at 16:17





@Barry thats why I said "then not constrained". Of course, they are SFINAE-friendly if constrained properly.

– Ilya Popov
Apr 26 '16 at 16:17











29














For the sake of completeness, the optimal 2-argument constructor would take two forwarding references and use SFINAE to ensure that they're the correct types. We can introduce the following alias:



template <class T, class U>
using decays_to = std::is_convertible<std::decay_t<T>*, U*>;


And then:



template <class L, class O,
class = std::enable_if_t<decays_to<L, Loss>::value &&
decays_to<O, Optimizer>::value>>
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))



This ensures that we only accept arguments that are of type Loss and Optimizer (or are derived from them). Unfortunately, it is quite a mouthful to write and is very distracting from the original intent. This is pretty difficult to get right - but if performance matters, then it matters, and this is really the only way to go.



But if it doesn't matter, and if Loss and Optimizer are cheap to move (or, better still, performance for this constructor is completely irrelevant), prefer Ilya Popov's solution:



LinearClassifier(Loss loss, Optimizer optimizer)
: _loss(std::move(loss))
, _optimizer(std::move(optimizer))






share|improve this answer




















  • 1





    Interesting choice of constraint. I agree, this is tricky to get right, and I am on the hook for infamously getting it wrong (youtube.com/watch?v=xnqTKD8uD64) :-). What about using std::is_convertible<L, Loss> for the constraint? This would (for example) allow a const char* L to construct a std::string Loss. And would also allow your Derived -> Base example.

    – Howard Hinnant
    Apr 26 '16 at 16:30











  • @HowardHinnant Could use std::is_constructible<Loss, L&&> too. Just wanted to be as strict as possible for genericity. But yeah, hard to pick... What am I looking for in that video? :)

    – Barry
    Apr 26 '16 at 16:47












  • Just an hour before that talk, and on my way out of town, Herb asked me what the constraint should be for a problem like this. Toward the end of the talk (1:15:00 in?). I over-thought it and got it wrong. There's nothing like testing! :-)

    – Howard Hinnant
    Apr 26 '16 at 16:59












  • @Howard I think the constraint is fine! So you're requiring the user to be explicit - that's hardly infamous-worthy

    – Barry
    Apr 26 '16 at 17:39











  • here's some syntactic sugar using fold-expressions that allows to pass the -IMO pretty readible- expression forward_compatible_v<std::pair<L, Loss>, std::pair<O, Optimizer>> to the enable_if_t constraint.

    – TemplateRex
    Apr 27 '16 at 9:43
















29














For the sake of completeness, the optimal 2-argument constructor would take two forwarding references and use SFINAE to ensure that they're the correct types. We can introduce the following alias:



template <class T, class U>
using decays_to = std::is_convertible<std::decay_t<T>*, U*>;


And then:



template <class L, class O,
class = std::enable_if_t<decays_to<L, Loss>::value &&
decays_to<O, Optimizer>::value>>
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))



This ensures that we only accept arguments that are of type Loss and Optimizer (or are derived from them). Unfortunately, it is quite a mouthful to write and is very distracting from the original intent. This is pretty difficult to get right - but if performance matters, then it matters, and this is really the only way to go.



But if it doesn't matter, and if Loss and Optimizer are cheap to move (or, better still, performance for this constructor is completely irrelevant), prefer Ilya Popov's solution:



LinearClassifier(Loss loss, Optimizer optimizer)
: _loss(std::move(loss))
, _optimizer(std::move(optimizer))






share|improve this answer




















  • 1





    Interesting choice of constraint. I agree, this is tricky to get right, and I am on the hook for infamously getting it wrong (youtube.com/watch?v=xnqTKD8uD64) :-). What about using std::is_convertible<L, Loss> for the constraint? This would (for example) allow a const char* L to construct a std::string Loss. And would also allow your Derived -> Base example.

    – Howard Hinnant
    Apr 26 '16 at 16:30











  • @HowardHinnant Could use std::is_constructible<Loss, L&&> too. Just wanted to be as strict as possible for genericity. But yeah, hard to pick... What am I looking for in that video? :)

    – Barry
    Apr 26 '16 at 16:47












  • Just an hour before that talk, and on my way out of town, Herb asked me what the constraint should be for a problem like this. Toward the end of the talk (1:15:00 in?). I over-thought it and got it wrong. There's nothing like testing! :-)

    – Howard Hinnant
    Apr 26 '16 at 16:59












  • @Howard I think the constraint is fine! So you're requiring the user to be explicit - that's hardly infamous-worthy

    – Barry
    Apr 26 '16 at 17:39











  • here's some syntactic sugar using fold-expressions that allows to pass the -IMO pretty readible- expression forward_compatible_v<std::pair<L, Loss>, std::pair<O, Optimizer>> to the enable_if_t constraint.

    – TemplateRex
    Apr 27 '16 at 9:43














29












29








29







For the sake of completeness, the optimal 2-argument constructor would take two forwarding references and use SFINAE to ensure that they're the correct types. We can introduce the following alias:



template <class T, class U>
using decays_to = std::is_convertible<std::decay_t<T>*, U*>;


And then:



template <class L, class O,
class = std::enable_if_t<decays_to<L, Loss>::value &&
decays_to<O, Optimizer>::value>>
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))



This ensures that we only accept arguments that are of type Loss and Optimizer (or are derived from them). Unfortunately, it is quite a mouthful to write and is very distracting from the original intent. This is pretty difficult to get right - but if performance matters, then it matters, and this is really the only way to go.



But if it doesn't matter, and if Loss and Optimizer are cheap to move (or, better still, performance for this constructor is completely irrelevant), prefer Ilya Popov's solution:



LinearClassifier(Loss loss, Optimizer optimizer)
: _loss(std::move(loss))
, _optimizer(std::move(optimizer))






share|improve this answer















For the sake of completeness, the optimal 2-argument constructor would take two forwarding references and use SFINAE to ensure that they're the correct types. We can introduce the following alias:



template <class T, class U>
using decays_to = std::is_convertible<std::decay_t<T>*, U*>;


And then:



template <class L, class O,
class = std::enable_if_t<decays_to<L, Loss>::value &&
decays_to<O, Optimizer>::value>>
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))



This ensures that we only accept arguments that are of type Loss and Optimizer (or are derived from them). Unfortunately, it is quite a mouthful to write and is very distracting from the original intent. This is pretty difficult to get right - but if performance matters, then it matters, and this is really the only way to go.



But if it doesn't matter, and if Loss and Optimizer are cheap to move (or, better still, performance for this constructor is completely irrelevant), prefer Ilya Popov's solution:



LinearClassifier(Loss loss, Optimizer optimizer)
: _loss(std::move(loss))
, _optimizer(std::move(optimizer))







share|improve this answer














share|improve this answer



share|improve this answer








edited May 23 '17 at 12:34









Community

11




11










answered Apr 26 '16 at 16:03









BarryBarry

187k21329606




187k21329606







  • 1





    Interesting choice of constraint. I agree, this is tricky to get right, and I am on the hook for infamously getting it wrong (youtube.com/watch?v=xnqTKD8uD64) :-). What about using std::is_convertible<L, Loss> for the constraint? This would (for example) allow a const char* L to construct a std::string Loss. And would also allow your Derived -> Base example.

    – Howard Hinnant
    Apr 26 '16 at 16:30











  • @HowardHinnant Could use std::is_constructible<Loss, L&&> too. Just wanted to be as strict as possible for genericity. But yeah, hard to pick... What am I looking for in that video? :)

    – Barry
    Apr 26 '16 at 16:47












  • Just an hour before that talk, and on my way out of town, Herb asked me what the constraint should be for a problem like this. Toward the end of the talk (1:15:00 in?). I over-thought it and got it wrong. There's nothing like testing! :-)

    – Howard Hinnant
    Apr 26 '16 at 16:59












  • @Howard I think the constraint is fine! So you're requiring the user to be explicit - that's hardly infamous-worthy

    – Barry
    Apr 26 '16 at 17:39











  • here's some syntactic sugar using fold-expressions that allows to pass the -IMO pretty readible- expression forward_compatible_v<std::pair<L, Loss>, std::pair<O, Optimizer>> to the enable_if_t constraint.

    – TemplateRex
    Apr 27 '16 at 9:43













  • 1





    Interesting choice of constraint. I agree, this is tricky to get right, and I am on the hook for infamously getting it wrong (youtube.com/watch?v=xnqTKD8uD64) :-). What about using std::is_convertible<L, Loss> for the constraint? This would (for example) allow a const char* L to construct a std::string Loss. And would also allow your Derived -> Base example.

    – Howard Hinnant
    Apr 26 '16 at 16:30











  • @HowardHinnant Could use std::is_constructible<Loss, L&&> too. Just wanted to be as strict as possible for genericity. But yeah, hard to pick... What am I looking for in that video? :)

    – Barry
    Apr 26 '16 at 16:47












  • Just an hour before that talk, and on my way out of town, Herb asked me what the constraint should be for a problem like this. Toward the end of the talk (1:15:00 in?). I over-thought it and got it wrong. There's nothing like testing! :-)

    – Howard Hinnant
    Apr 26 '16 at 16:59












  • @Howard I think the constraint is fine! So you're requiring the user to be explicit - that's hardly infamous-worthy

    – Barry
    Apr 26 '16 at 17:39











  • here's some syntactic sugar using fold-expressions that allows to pass the -IMO pretty readible- expression forward_compatible_v<std::pair<L, Loss>, std::pair<O, Optimizer>> to the enable_if_t constraint.

    – TemplateRex
    Apr 27 '16 at 9:43








1




1





Interesting choice of constraint. I agree, this is tricky to get right, and I am on the hook for infamously getting it wrong (youtube.com/watch?v=xnqTKD8uD64) :-). What about using std::is_convertible<L, Loss> for the constraint? This would (for example) allow a const char* L to construct a std::string Loss. And would also allow your Derived -> Base example.

– Howard Hinnant
Apr 26 '16 at 16:30





Interesting choice of constraint. I agree, this is tricky to get right, and I am on the hook for infamously getting it wrong (youtube.com/watch?v=xnqTKD8uD64) :-). What about using std::is_convertible<L, Loss> for the constraint? This would (for example) allow a const char* L to construct a std::string Loss. And would also allow your Derived -> Base example.

– Howard Hinnant
Apr 26 '16 at 16:30













@HowardHinnant Could use std::is_constructible<Loss, L&&> too. Just wanted to be as strict as possible for genericity. But yeah, hard to pick... What am I looking for in that video? :)

– Barry
Apr 26 '16 at 16:47






@HowardHinnant Could use std::is_constructible<Loss, L&&> too. Just wanted to be as strict as possible for genericity. But yeah, hard to pick... What am I looking for in that video? :)

– Barry
Apr 26 '16 at 16:47














Just an hour before that talk, and on my way out of town, Herb asked me what the constraint should be for a problem like this. Toward the end of the talk (1:15:00 in?). I over-thought it and got it wrong. There's nothing like testing! :-)

– Howard Hinnant
Apr 26 '16 at 16:59






Just an hour before that talk, and on my way out of town, Herb asked me what the constraint should be for a problem like this. Toward the end of the talk (1:15:00 in?). I over-thought it and got it wrong. There's nothing like testing! :-)

– Howard Hinnant
Apr 26 '16 at 16:59














@Howard I think the constraint is fine! So you're requiring the user to be explicit - that's hardly infamous-worthy

– Barry
Apr 26 '16 at 17:39





@Howard I think the constraint is fine! So you're requiring the user to be explicit - that's hardly infamous-worthy

– Barry
Apr 26 '16 at 17:39













here's some syntactic sugar using fold-expressions that allows to pass the -IMO pretty readible- expression forward_compatible_v<std::pair<L, Loss>, std::pair<O, Optimizer>> to the enable_if_t constraint.

– TemplateRex
Apr 27 '16 at 9:43






here's some syntactic sugar using fold-expressions that allows to pass the -IMO pretty readible- expression forward_compatible_v<std::pair<L, Loss>, std::pair<O, Optimizer>> to the enable_if_t constraint.

– TemplateRex
Apr 27 '16 at 9:43












15














How far down the rabbit hole do you want to go?



I'm aware of 4 decent ways to approach this problem. You should generally use the earlier ones if you match their preconditions, as each later one increases significantly in complexity.




For the most part, either move is so cheap doing it twice is free, or move is copy.



If move is copy, and copy is non-free, take the parameter by const&. If not, take it by value.



This will behave basically optimally, and makes your code far easier to understand.



LinearClassifier(Loss loss, Optimizer const& optimizer)
: _loss(std::move(loss))
, _optimizer(optimizer)



for a cheap-to-move Loss and move-is-copy optimizer.



This does 1 extra move over the "optimal" perfect forwarding below (note: perfect forwarding is not optimal) per value parameter in all cases. So long as move is cheap, this is the best solution, because it generates clean error messages, allows based construction, and is far easier to read than any of the other solutions.



Consider using this solution.




If move is cheaper than copy yet non-free, one approach is perfect forwarding based:
Either:



template<class L, class O >
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))



Or the more complex and more overload-friendly:



template<class L, class O,
std::enable_if_t<
std::is_same<std::decay_t<L>, Loss>
&& std::is_same<std::decay_t<O>, Optimizer>
, int> * = nullptr
>
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))



this costs you the ability to do based construction of your arguments. Also, up to exponential number of constructors can be generated by the above code if they are called (hopefully they will be inlined).



You can drop the std::enable_if_t clause at the cost of SFINAE failure; basically, the wrong overload of your constructor can be picked if you aren't careful with that std::enable_if_t clause. If you have constructor overloads with the same number of arguments, or care about early-failure, then you want the std::enable_if_t one. Otherwise, use the simpler one.



This solution is usually considered "most optimal". It is accepably optimal, but it is not most optimal.




The next step is to use emplace construction with tuples.



private:
template<std::size_t...LIs, std::size_t...OIs, class...Ls, class...Os>
LinearClassifier(std::piecewise_construct_t,
std::index_sequence<LIs...>, std::tuple<Ls...>&& ls,
std::index_sequence<OIs...>, std::tuple<Os...>&& os
)
: _loss(std::get<LIs>(std::move(ls))...)
, _optimizer(std::get<OIs>(std::move(os))...)

public:
template<class...Ls, class...Os>
LinearClassifier(std::piecewise_construct_t,
std::tuple<Ls...> ls,
std::tuple<Os...> os
):
LinearClassifier(std::piecewise_construct_t,
std::index_sequence_for<Ls...>, std::move(ls),
std::index_sequence_for<Os...>, std::move(os)
)



where we defer construction until inside the LinearClassifier. This allows you to have non-copy/moveable objects in your object, and is arguably maximally efficient.



To see how this works, example now piecewise_construct works with std::pair. You pass piecewise construct first, then forward_as_tuple the arguments to construct each element afterwards (including a copy or move ctor).



By directly constructing objects, we can eliminate a move or a copy per object compared to the perfect-forwarding solution above. It also lets you forward a copy or a move if required.




A final cute technique is to type-erase construction. Practically, this requires something like std::experimental::optional<T> to be available, and might make the class a bit larger.



This is not faster than the piecewise construction one. It does abstract the work that the emplace construction one does, making it simpler on a per-use basis, and it permits you to split ctor body from the header file. But there is a small amount of overhead, in both runtime and space.



There is a bunch of boilerplate you need to start with. This generates a template class that represents the concept of "constructing an object, later, at a place someone else will tell me."



struct delayed_emplace_t ;
template<class T>
struct delayed_construct !std::is_same<std::decay_t<T>, delayed_construct>
,int>* = nullptr
>
delayed_construct(T&&t, Ts&&...ts):
delayed_construct( delayed_emplace_t, std::forward<T>(t), std::forward<Ts>(ts)... )

template<class T, class...Ts>
delayed_construct(delayed_emplace_t, T&&t, Ts&&...ts):
ctor([tup = std::forward_as_tuple(std::forward<T>(t), std::forward<Ts>(ts)...)]( auto& op ) mutable
ctor_helper(op, std::make_index_sequence<sizeof...(Ts)+1>, std::move(tup));
)
template<std::size_t...Is, class...Ts>
static void ctor_helper(std::experimental::optional<T>& op, std::index_sequence<Is...>, std::tuple<Ts...>&& tup)
op.emplace( std::get<Is>(std::move(tup))... );

void operator()(std::experimental::optional<T>& target)
ctor(target);
ctor = ;

explicit operator bool() const return !!ctor;
;


where we type-erase the action of constructing an optional from arbitrary arguments.



LinearClassifier( delayed_construct<Loss> loss, delayed_construct<Optimizer> optimizer ) 
loss(_loss);
optimizer(_optimizer);



where _loss are std::experimental::optional<Loss>. To remove the optionality of _loss you have to use std::aligned_storage_t<sizeof(Loss), alignof(Loss)> and be very careful about writing a ctor to handle exceptions and manually destroy things etc. It is a headache.



Some nice things about this last pattern is that the body of the ctor can move out of the header, and at most a linear amount of code is generated instead of an exponential amount of template constructors.



This solution is marginally less efficient than the placement construct version, as not all compilers will be able to inline the std::function use. But it also permits storing non-movable objects.



Code not tested, so there are probably typos.




In c++17 with guaranteed elision, the optional part of the delayed ctor becomes obsolete. Any function returning a T is all you need for a delayed ctor of T.






share|improve this answer

























  • I just love the way the code explodes with each iteration of "optimality" ;-) Somehow C++ seems to have left all the simplicity of C behind...

    – cmaster
    Apr 26 '16 at 19:31






  • 3





    I'm not sure if I should be disgusted or in awe.

    – isanae
    Apr 26 '16 at 19:33











  • @cmaster To some extent; but doing the same kind of operations in C would be bulkier and completely unmaintainable and next to impossible to do without repeating every time you want to use it. The delayed_construct<T> (which is the most insane) actually has a really short "per use" body (same length as first solution!), and what it does would be a real headache in C. You'd best give up long before you reached what is actually going on in that one, and no chance of doing it generically. In C++, I write the mess once (and the mess is shorter than the C equivalent), and can reuse it.

    – Yakk - Adam Nevraumont
    Apr 26 '16 at 23:28











  • @cmaster Now, I probably wouldn't use it; I'd argue for #1 barring extreme circumstances. And #1 is already ridiculously shorter than the equivalent C implementation. The C solution might be as short as #1, but that is because it usually wouldn't do the same amount of corner-case optimization stuff as even #1 does "under the hood".

    – Yakk - Adam Nevraumont
    Apr 26 '16 at 23:30












  • How can I provide that lvalue object will not be changed in such constructor with std::forward? Here we have L&& loss, if we give lvalue - we will have L& loss, and it can be changed. The good practice everytime was using const SomeType&, but here we can’t just write const L&& loss, because we want to have move ability. What is the solution?

    – I.S.M.
    Nov 26 '17 at 12:01















15














How far down the rabbit hole do you want to go?



I'm aware of 4 decent ways to approach this problem. You should generally use the earlier ones if you match their preconditions, as each later one increases significantly in complexity.




For the most part, either move is so cheap doing it twice is free, or move is copy.



If move is copy, and copy is non-free, take the parameter by const&. If not, take it by value.



This will behave basically optimally, and makes your code far easier to understand.



LinearClassifier(Loss loss, Optimizer const& optimizer)
: _loss(std::move(loss))
, _optimizer(optimizer)



for a cheap-to-move Loss and move-is-copy optimizer.



This does 1 extra move over the "optimal" perfect forwarding below (note: perfect forwarding is not optimal) per value parameter in all cases. So long as move is cheap, this is the best solution, because it generates clean error messages, allows based construction, and is far easier to read than any of the other solutions.



Consider using this solution.




If move is cheaper than copy yet non-free, one approach is perfect forwarding based:
Either:



template<class L, class O >
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))



Or the more complex and more overload-friendly:



template<class L, class O,
std::enable_if_t<
std::is_same<std::decay_t<L>, Loss>
&& std::is_same<std::decay_t<O>, Optimizer>
, int> * = nullptr
>
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))



this costs you the ability to do based construction of your arguments. Also, up to exponential number of constructors can be generated by the above code if they are called (hopefully they will be inlined).



You can drop the std::enable_if_t clause at the cost of SFINAE failure; basically, the wrong overload of your constructor can be picked if you aren't careful with that std::enable_if_t clause. If you have constructor overloads with the same number of arguments, or care about early-failure, then you want the std::enable_if_t one. Otherwise, use the simpler one.



This solution is usually considered "most optimal". It is accepably optimal, but it is not most optimal.




The next step is to use emplace construction with tuples.



private:
template<std::size_t...LIs, std::size_t...OIs, class...Ls, class...Os>
LinearClassifier(std::piecewise_construct_t,
std::index_sequence<LIs...>, std::tuple<Ls...>&& ls,
std::index_sequence<OIs...>, std::tuple<Os...>&& os
)
: _loss(std::get<LIs>(std::move(ls))...)
, _optimizer(std::get<OIs>(std::move(os))...)

public:
template<class...Ls, class...Os>
LinearClassifier(std::piecewise_construct_t,
std::tuple<Ls...> ls,
std::tuple<Os...> os
):
LinearClassifier(std::piecewise_construct_t,
std::index_sequence_for<Ls...>, std::move(ls),
std::index_sequence_for<Os...>, std::move(os)
)



where we defer construction until inside the LinearClassifier. This allows you to have non-copy/moveable objects in your object, and is arguably maximally efficient.



To see how this works, example now piecewise_construct works with std::pair. You pass piecewise construct first, then forward_as_tuple the arguments to construct each element afterwards (including a copy or move ctor).



By directly constructing objects, we can eliminate a move or a copy per object compared to the perfect-forwarding solution above. It also lets you forward a copy or a move if required.




A final cute technique is to type-erase construction. Practically, this requires something like std::experimental::optional<T> to be available, and might make the class a bit larger.



This is not faster than the piecewise construction one. It does abstract the work that the emplace construction one does, making it simpler on a per-use basis, and it permits you to split ctor body from the header file. But there is a small amount of overhead, in both runtime and space.



There is a bunch of boilerplate you need to start with. This generates a template class that represents the concept of "constructing an object, later, at a place someone else will tell me."



struct delayed_emplace_t ;
template<class T>
struct delayed_construct !std::is_same<std::decay_t<T>, delayed_construct>
,int>* = nullptr
>
delayed_construct(T&&t, Ts&&...ts):
delayed_construct( delayed_emplace_t, std::forward<T>(t), std::forward<Ts>(ts)... )

template<class T, class...Ts>
delayed_construct(delayed_emplace_t, T&&t, Ts&&...ts):
ctor([tup = std::forward_as_tuple(std::forward<T>(t), std::forward<Ts>(ts)...)]( auto& op ) mutable
ctor_helper(op, std::make_index_sequence<sizeof...(Ts)+1>, std::move(tup));
)
template<std::size_t...Is, class...Ts>
static void ctor_helper(std::experimental::optional<T>& op, std::index_sequence<Is...>, std::tuple<Ts...>&& tup)
op.emplace( std::get<Is>(std::move(tup))... );

void operator()(std::experimental::optional<T>& target)
ctor(target);
ctor = ;

explicit operator bool() const return !!ctor;
;


where we type-erase the action of constructing an optional from arbitrary arguments.



LinearClassifier( delayed_construct<Loss> loss, delayed_construct<Optimizer> optimizer ) 
loss(_loss);
optimizer(_optimizer);



where _loss are std::experimental::optional<Loss>. To remove the optionality of _loss you have to use std::aligned_storage_t<sizeof(Loss), alignof(Loss)> and be very careful about writing a ctor to handle exceptions and manually destroy things etc. It is a headache.



Some nice things about this last pattern is that the body of the ctor can move out of the header, and at most a linear amount of code is generated instead of an exponential amount of template constructors.



This solution is marginally less efficient than the placement construct version, as not all compilers will be able to inline the std::function use. But it also permits storing non-movable objects.



Code not tested, so there are probably typos.




In c++17 with guaranteed elision, the optional part of the delayed ctor becomes obsolete. Any function returning a T is all you need for a delayed ctor of T.






share|improve this answer

























  • I just love the way the code explodes with each iteration of "optimality" ;-) Somehow C++ seems to have left all the simplicity of C behind...

    – cmaster
    Apr 26 '16 at 19:31






  • 3





    I'm not sure if I should be disgusted or in awe.

    – isanae
    Apr 26 '16 at 19:33











  • @cmaster To some extent; but doing the same kind of operations in C would be bulkier and completely unmaintainable and next to impossible to do without repeating every time you want to use it. The delayed_construct<T> (which is the most insane) actually has a really short "per use" body (same length as first solution!), and what it does would be a real headache in C. You'd best give up long before you reached what is actually going on in that one, and no chance of doing it generically. In C++, I write the mess once (and the mess is shorter than the C equivalent), and can reuse it.

    – Yakk - Adam Nevraumont
    Apr 26 '16 at 23:28











  • @cmaster Now, I probably wouldn't use it; I'd argue for #1 barring extreme circumstances. And #1 is already ridiculously shorter than the equivalent C implementation. The C solution might be as short as #1, but that is because it usually wouldn't do the same amount of corner-case optimization stuff as even #1 does "under the hood".

    – Yakk - Adam Nevraumont
    Apr 26 '16 at 23:30












  • How can I provide that lvalue object will not be changed in such constructor with std::forward? Here we have L&& loss, if we give lvalue - we will have L& loss, and it can be changed. The good practice everytime was using const SomeType&, but here we can’t just write const L&& loss, because we want to have move ability. What is the solution?

    – I.S.M.
    Nov 26 '17 at 12:01













15












15








15







How far down the rabbit hole do you want to go?



I'm aware of 4 decent ways to approach this problem. You should generally use the earlier ones if you match their preconditions, as each later one increases significantly in complexity.




For the most part, either move is so cheap doing it twice is free, or move is copy.



If move is copy, and copy is non-free, take the parameter by const&. If not, take it by value.



This will behave basically optimally, and makes your code far easier to understand.



LinearClassifier(Loss loss, Optimizer const& optimizer)
: _loss(std::move(loss))
, _optimizer(optimizer)



for a cheap-to-move Loss and move-is-copy optimizer.



This does 1 extra move over the "optimal" perfect forwarding below (note: perfect forwarding is not optimal) per value parameter in all cases. So long as move is cheap, this is the best solution, because it generates clean error messages, allows based construction, and is far easier to read than any of the other solutions.



Consider using this solution.




If move is cheaper than copy yet non-free, one approach is perfect forwarding based:
Either:



template<class L, class O >
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))



Or the more complex and more overload-friendly:



template<class L, class O,
std::enable_if_t<
std::is_same<std::decay_t<L>, Loss>
&& std::is_same<std::decay_t<O>, Optimizer>
, int> * = nullptr
>
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))



this costs you the ability to do based construction of your arguments. Also, up to exponential number of constructors can be generated by the above code if they are called (hopefully they will be inlined).



You can drop the std::enable_if_t clause at the cost of SFINAE failure; basically, the wrong overload of your constructor can be picked if you aren't careful with that std::enable_if_t clause. If you have constructor overloads with the same number of arguments, or care about early-failure, then you want the std::enable_if_t one. Otherwise, use the simpler one.



This solution is usually considered "most optimal". It is accepably optimal, but it is not most optimal.




The next step is to use emplace construction with tuples.



private:
template<std::size_t...LIs, std::size_t...OIs, class...Ls, class...Os>
LinearClassifier(std::piecewise_construct_t,
std::index_sequence<LIs...>, std::tuple<Ls...>&& ls,
std::index_sequence<OIs...>, std::tuple<Os...>&& os
)
: _loss(std::get<LIs>(std::move(ls))...)
, _optimizer(std::get<OIs>(std::move(os))...)

public:
template<class...Ls, class...Os>
LinearClassifier(std::piecewise_construct_t,
std::tuple<Ls...> ls,
std::tuple<Os...> os
):
LinearClassifier(std::piecewise_construct_t,
std::index_sequence_for<Ls...>, std::move(ls),
std::index_sequence_for<Os...>, std::move(os)
)



where we defer construction until inside the LinearClassifier. This allows you to have non-copy/moveable objects in your object, and is arguably maximally efficient.



To see how this works, example now piecewise_construct works with std::pair. You pass piecewise construct first, then forward_as_tuple the arguments to construct each element afterwards (including a copy or move ctor).



By directly constructing objects, we can eliminate a move or a copy per object compared to the perfect-forwarding solution above. It also lets you forward a copy or a move if required.




A final cute technique is to type-erase construction. Practically, this requires something like std::experimental::optional<T> to be available, and might make the class a bit larger.



This is not faster than the piecewise construction one. It does abstract the work that the emplace construction one does, making it simpler on a per-use basis, and it permits you to split ctor body from the header file. But there is a small amount of overhead, in both runtime and space.



There is a bunch of boilerplate you need to start with. This generates a template class that represents the concept of "constructing an object, later, at a place someone else will tell me."



struct delayed_emplace_t ;
template<class T>
struct delayed_construct !std::is_same<std::decay_t<T>, delayed_construct>
,int>* = nullptr
>
delayed_construct(T&&t, Ts&&...ts):
delayed_construct( delayed_emplace_t, std::forward<T>(t), std::forward<Ts>(ts)... )

template<class T, class...Ts>
delayed_construct(delayed_emplace_t, T&&t, Ts&&...ts):
ctor([tup = std::forward_as_tuple(std::forward<T>(t), std::forward<Ts>(ts)...)]( auto& op ) mutable
ctor_helper(op, std::make_index_sequence<sizeof...(Ts)+1>, std::move(tup));
)
template<std::size_t...Is, class...Ts>
static void ctor_helper(std::experimental::optional<T>& op, std::index_sequence<Is...>, std::tuple<Ts...>&& tup)
op.emplace( std::get<Is>(std::move(tup))... );

void operator()(std::experimental::optional<T>& target)
ctor(target);
ctor = ;

explicit operator bool() const return !!ctor;
;


where we type-erase the action of constructing an optional from arbitrary arguments.



LinearClassifier( delayed_construct<Loss> loss, delayed_construct<Optimizer> optimizer ) 
loss(_loss);
optimizer(_optimizer);



where _loss are std::experimental::optional<Loss>. To remove the optionality of _loss you have to use std::aligned_storage_t<sizeof(Loss), alignof(Loss)> and be very careful about writing a ctor to handle exceptions and manually destroy things etc. It is a headache.



Some nice things about this last pattern is that the body of the ctor can move out of the header, and at most a linear amount of code is generated instead of an exponential amount of template constructors.



This solution is marginally less efficient than the placement construct version, as not all compilers will be able to inline the std::function use. But it also permits storing non-movable objects.



Code not tested, so there are probably typos.




In c++17 with guaranteed elision, the optional part of the delayed ctor becomes obsolete. Any function returning a T is all you need for a delayed ctor of T.






share|improve this answer















How far down the rabbit hole do you want to go?



I'm aware of 4 decent ways to approach this problem. You should generally use the earlier ones if you match their preconditions, as each later one increases significantly in complexity.




For the most part, either move is so cheap doing it twice is free, or move is copy.



If move is copy, and copy is non-free, take the parameter by const&. If not, take it by value.



This will behave basically optimally, and makes your code far easier to understand.



LinearClassifier(Loss loss, Optimizer const& optimizer)
: _loss(std::move(loss))
, _optimizer(optimizer)



for a cheap-to-move Loss and move-is-copy optimizer.



This does 1 extra move over the "optimal" perfect forwarding below (note: perfect forwarding is not optimal) per value parameter in all cases. So long as move is cheap, this is the best solution, because it generates clean error messages, allows based construction, and is far easier to read than any of the other solutions.



Consider using this solution.




If move is cheaper than copy yet non-free, one approach is perfect forwarding based:
Either:



template<class L, class O >
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))



Or the more complex and more overload-friendly:



template<class L, class O,
std::enable_if_t<
std::is_same<std::decay_t<L>, Loss>
&& std::is_same<std::decay_t<O>, Optimizer>
, int> * = nullptr
>
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))



this costs you the ability to do based construction of your arguments. Also, up to exponential number of constructors can be generated by the above code if they are called (hopefully they will be inlined).



You can drop the std::enable_if_t clause at the cost of SFINAE failure; basically, the wrong overload of your constructor can be picked if you aren't careful with that std::enable_if_t clause. If you have constructor overloads with the same number of arguments, or care about early-failure, then you want the std::enable_if_t one. Otherwise, use the simpler one.



This solution is usually considered "most optimal". It is accepably optimal, but it is not most optimal.




The next step is to use emplace construction with tuples.



private:
template<std::size_t...LIs, std::size_t...OIs, class...Ls, class...Os>
LinearClassifier(std::piecewise_construct_t,
std::index_sequence<LIs...>, std::tuple<Ls...>&& ls,
std::index_sequence<OIs...>, std::tuple<Os...>&& os
)
: _loss(std::get<LIs>(std::move(ls))...)
, _optimizer(std::get<OIs>(std::move(os))...)

public:
template<class...Ls, class...Os>
LinearClassifier(std::piecewise_construct_t,
std::tuple<Ls...> ls,
std::tuple<Os...> os
):
LinearClassifier(std::piecewise_construct_t,
std::index_sequence_for<Ls...>, std::move(ls),
std::index_sequence_for<Os...>, std::move(os)
)



where we defer construction until inside the LinearClassifier. This allows you to have non-copy/moveable objects in your object, and is arguably maximally efficient.



To see how this works, example now piecewise_construct works with std::pair. You pass piecewise construct first, then forward_as_tuple the arguments to construct each element afterwards (including a copy or move ctor).



By directly constructing objects, we can eliminate a move or a copy per object compared to the perfect-forwarding solution above. It also lets you forward a copy or a move if required.




A final cute technique is to type-erase construction. Practically, this requires something like std::experimental::optional<T> to be available, and might make the class a bit larger.



This is not faster than the piecewise construction one. It does abstract the work that the emplace construction one does, making it simpler on a per-use basis, and it permits you to split ctor body from the header file. But there is a small amount of overhead, in both runtime and space.



There is a bunch of boilerplate you need to start with. This generates a template class that represents the concept of "constructing an object, later, at a place someone else will tell me."



struct delayed_emplace_t ;
template<class T>
struct delayed_construct !std::is_same<std::decay_t<T>, delayed_construct>
,int>* = nullptr
>
delayed_construct(T&&t, Ts&&...ts):
delayed_construct( delayed_emplace_t, std::forward<T>(t), std::forward<Ts>(ts)... )

template<class T, class...Ts>
delayed_construct(delayed_emplace_t, T&&t, Ts&&...ts):
ctor([tup = std::forward_as_tuple(std::forward<T>(t), std::forward<Ts>(ts)...)]( auto& op ) mutable
ctor_helper(op, std::make_index_sequence<sizeof...(Ts)+1>, std::move(tup));
)
template<std::size_t...Is, class...Ts>
static void ctor_helper(std::experimental::optional<T>& op, std::index_sequence<Is...>, std::tuple<Ts...>&& tup)
op.emplace( std::get<Is>(std::move(tup))... );

void operator()(std::experimental::optional<T>& target)
ctor(target);
ctor = ;

explicit operator bool() const return !!ctor;
;


where we type-erase the action of constructing an optional from arbitrary arguments.



LinearClassifier( delayed_construct<Loss> loss, delayed_construct<Optimizer> optimizer ) 
loss(_loss);
optimizer(_optimizer);



where _loss are std::experimental::optional<Loss>. To remove the optionality of _loss you have to use std::aligned_storage_t<sizeof(Loss), alignof(Loss)> and be very careful about writing a ctor to handle exceptions and manually destroy things etc. It is a headache.



Some nice things about this last pattern is that the body of the ctor can move out of the header, and at most a linear amount of code is generated instead of an exponential amount of template constructors.



This solution is marginally less efficient than the placement construct version, as not all compilers will be able to inline the std::function use. But it also permits storing non-movable objects.



Code not tested, so there are probably typos.




In c++17 with guaranteed elision, the optional part of the delayed ctor becomes obsolete. Any function returning a T is all you need for a delayed ctor of T.







share|improve this answer














share|improve this answer



share|improve this answer








edited Feb 20 at 1:16

























answered Apr 26 '16 at 17:18









Yakk - Adam NevraumontYakk - Adam Nevraumont

189k21199385




189k21199385












  • I just love the way the code explodes with each iteration of "optimality" ;-) Somehow C++ seems to have left all the simplicity of C behind...

    – cmaster
    Apr 26 '16 at 19:31






  • 3





    I'm not sure if I should be disgusted or in awe.

    – isanae
    Apr 26 '16 at 19:33











  • @cmaster To some extent; but doing the same kind of operations in C would be bulkier and completely unmaintainable and next to impossible to do without repeating every time you want to use it. The delayed_construct<T> (which is the most insane) actually has a really short "per use" body (same length as first solution!), and what it does would be a real headache in C. You'd best give up long before you reached what is actually going on in that one, and no chance of doing it generically. In C++, I write the mess once (and the mess is shorter than the C equivalent), and can reuse it.

    – Yakk - Adam Nevraumont
    Apr 26 '16 at 23:28











  • @cmaster Now, I probably wouldn't use it; I'd argue for #1 barring extreme circumstances. And #1 is already ridiculously shorter than the equivalent C implementation. The C solution might be as short as #1, but that is because it usually wouldn't do the same amount of corner-case optimization stuff as even #1 does "under the hood".

    – Yakk - Adam Nevraumont
    Apr 26 '16 at 23:30












  • How can I provide that lvalue object will not be changed in such constructor with std::forward? Here we have L&& loss, if we give lvalue - we will have L& loss, and it can be changed. The good practice everytime was using const SomeType&, but here we can’t just write const L&& loss, because we want to have move ability. What is the solution?

    – I.S.M.
    Nov 26 '17 at 12:01

















  • I just love the way the code explodes with each iteration of "optimality" ;-) Somehow C++ seems to have left all the simplicity of C behind...

    – cmaster
    Apr 26 '16 at 19:31






  • 3





    I'm not sure if I should be disgusted or in awe.

    – isanae
    Apr 26 '16 at 19:33











  • @cmaster To some extent; but doing the same kind of operations in C would be bulkier and completely unmaintainable and next to impossible to do without repeating every time you want to use it. The delayed_construct<T> (which is the most insane) actually has a really short "per use" body (same length as first solution!), and what it does would be a real headache in C. You'd best give up long before you reached what is actually going on in that one, and no chance of doing it generically. In C++, I write the mess once (and the mess is shorter than the C equivalent), and can reuse it.

    – Yakk - Adam Nevraumont
    Apr 26 '16 at 23:28











  • @cmaster Now, I probably wouldn't use it; I'd argue for #1 barring extreme circumstances. And #1 is already ridiculously shorter than the equivalent C implementation. The C solution might be as short as #1, but that is because it usually wouldn't do the same amount of corner-case optimization stuff as even #1 does "under the hood".

    – Yakk - Adam Nevraumont
    Apr 26 '16 at 23:30












  • How can I provide that lvalue object will not be changed in such constructor with std::forward? Here we have L&& loss, if we give lvalue - we will have L& loss, and it can be changed. The good practice everytime was using const SomeType&, but here we can’t just write const L&& loss, because we want to have move ability. What is the solution?

    – I.S.M.
    Nov 26 '17 at 12:01
















I just love the way the code explodes with each iteration of "optimality" ;-) Somehow C++ seems to have left all the simplicity of C behind...

– cmaster
Apr 26 '16 at 19:31





I just love the way the code explodes with each iteration of "optimality" ;-) Somehow C++ seems to have left all the simplicity of C behind...

– cmaster
Apr 26 '16 at 19:31




3




3





I'm not sure if I should be disgusted or in awe.

– isanae
Apr 26 '16 at 19:33





I'm not sure if I should be disgusted or in awe.

– isanae
Apr 26 '16 at 19:33













@cmaster To some extent; but doing the same kind of operations in C would be bulkier and completely unmaintainable and next to impossible to do without repeating every time you want to use it. The delayed_construct<T> (which is the most insane) actually has a really short "per use" body (same length as first solution!), and what it does would be a real headache in C. You'd best give up long before you reached what is actually going on in that one, and no chance of doing it generically. In C++, I write the mess once (and the mess is shorter than the C equivalent), and can reuse it.

– Yakk - Adam Nevraumont
Apr 26 '16 at 23:28





@cmaster To some extent; but doing the same kind of operations in C would be bulkier and completely unmaintainable and next to impossible to do without repeating every time you want to use it. The delayed_construct<T> (which is the most insane) actually has a really short "per use" body (same length as first solution!), and what it does would be a real headache in C. You'd best give up long before you reached what is actually going on in that one, and no chance of doing it generically. In C++, I write the mess once (and the mess is shorter than the C equivalent), and can reuse it.

– Yakk - Adam Nevraumont
Apr 26 '16 at 23:28













@cmaster Now, I probably wouldn't use it; I'd argue for #1 barring extreme circumstances. And #1 is already ridiculously shorter than the equivalent C implementation. The C solution might be as short as #1, but that is because it usually wouldn't do the same amount of corner-case optimization stuff as even #1 does "under the hood".

– Yakk - Adam Nevraumont
Apr 26 '16 at 23:30






@cmaster Now, I probably wouldn't use it; I'd argue for #1 barring extreme circumstances. And #1 is already ridiculously shorter than the equivalent C implementation. The C solution might be as short as #1, but that is because it usually wouldn't do the same amount of corner-case optimization stuff as even #1 does "under the hood".

– Yakk - Adam Nevraumont
Apr 26 '16 at 23:30














How can I provide that lvalue object will not be changed in such constructor with std::forward? Here we have L&& loss, if we give lvalue - we will have L& loss, and it can be changed. The good practice everytime was using const SomeType&, but here we can’t just write const L&& loss, because we want to have move ability. What is the solution?

– I.S.M.
Nov 26 '17 at 12:01





How can I provide that lvalue object will not be changed in such constructor with std::forward? Here we have L&& loss, if we give lvalue - we will have L& loss, and it can be changed. The good practice everytime was using const SomeType&, but here we can’t just write const L&& loss, because we want to have move ability. What is the solution?

– I.S.M.
Nov 26 '17 at 12:01

















draft saved

draft discarded
















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid


  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f36868442%2favoid-exponential-grow-of-const-references-and-rvalue-references-in-constructor%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Save data to MySQL database using ExtJS and PHP [closed]2019 Community Moderator ElectionHow can I prevent SQL injection in PHP?Which MySQL data type to use for storing boolean valuesPHP: Delete an element from an arrayHow do I connect to a MySQL Database in Python?Should I use the datetime or timestamp data type in MySQL?How to get a list of MySQL user accountsHow Do You Parse and Process HTML/XML in PHP?Reference — What does this symbol mean in PHP?How does PHP 'foreach' actually work?Why shouldn't I use mysql_* functions in PHP?

Compiling GNU Global with universal-ctags support Announcing the arrival of Valued Associate #679: Cesar Manara Planned maintenance scheduled April 23, 2019 at 23:30 UTC (7:30pm US/Eastern) Data science time! April 2019 and salary with experience The Ask Question Wizard is Live!Tags for Emacs: Relationship between etags, ebrowse, cscope, GNU Global and exuberant ctagsVim and Ctags tips and trickscscope or ctags why choose one over the other?scons and ctagsctags cannot open option file “.ctags”Adding tag scopes in universal-ctagsShould I use Universal-ctags?Universal ctags on WindowsHow do I install GNU Global with universal ctags support using Homebrew?Universal ctags with emacsHow to highlight ctags generated by Universal Ctags in Vim?

Add ONERROR event to image from jsp tldHow to add an image to a JPanel?Saving image from PHP URLHTML img scalingCheck if an image is loaded (no errors) with jQueryHow to force an <img> to take up width, even if the image is not loadedHow do I populate hidden form field with a value set in Spring ControllerStyling Raw elements Generated from JSP tagds with Jquery MobileLimit resizing of images with explicitly set width and height attributeserror TLD use in a jsp fileJsp tld files cannot be resolved