This is already optimal:
Comp(T&& t, U&& u) : t_m(std::move(t)) , u_m(std::move(u)) { }
If T
has a move constructor, this will be a step. If it is not, it will be a copy - but then there is no way to make a copy somewhere. And it is not copied, then the whole question is controversial.
Of course, this only works for rvalues, so you need something for lvalues. This, unfortunately, is a bit more complicated:
template <class Tx, class Ux, class = std::enable_if_t<std::is_convertible<std::decay_t<Tx>*, T*>::value && std::is_convertible<std::decay_t<Ux>*, U*>::value>> Comp(Tx&& t, Ux&& u) : t_m(std::forward<Tx>(t)) , u_m(std::forward<Ux>(u)) { }
Here we want to resolve the derivation of Tx
such that it is either T
, T&
, or D
, D&
, where D
comes from T
std::decay
returns a reference and is_convertible
for pointers, if one is received.
Ok, can we do better? Not really. This will either make 1 move or 1 instance for each participant. But what if we want to build them? We must be able to:
template <class... TArgs, class... UArgs, class = std::enable_if_t<std::is_constructible<T, TArgs...>::value && std::is_constructible<U, UArgs...>::value>> Comp(std::piecewise_construct_t pc, std::tuple<TArgs...> const& t, std::tuple<UArgs...> const& u) : Comp(t, u, std::index_sequence_for<TArgs...>{}, std::index_sequence_for<UArgs...>{}) { } private: template <class TTuple, class UTuple, size_t... Is, size_t... Js> Comp(TTuple const& t, UTuple const& u, std::index_sequence<Is...>, std::index_sequence<Js...> ) : t_m(std::get<Is>(t)...) , u_m(std::get<Js>(u)...) { }
With this, we can potentially avoid any copy or even move by simply creating it in place. Whether it is profitable or not, depends on the use of Comp
.