As a follow-up, I came to this workaround:
template<class T>
struct mover
{
mover(T const& val) : val(val) {}
mover(T&& val) : val(std::move(val)) {}
mover(mover const& other) = default;
mover(mover&& other) = default;
mover(mover& other) : val(std::move(other.val)) {}
operator T const&() const
{
return val;
}
T val;
};
template<class T>
using wrap_t = typename std::conditional
<
std::is_move_constructible<T>::value
&& !std::is_trivially_copy_constructible<T>::value
, mover<T>
, T
>::type;
template<class... Ts>
auto pack_impl(wrap_t<Ts>... ts)
{
return [=](auto&& f)->decltype(auto)
{
return f(static_cast<Ts const&>(ts)...);
};
}
auto pack = [](auto&&... ts)
{
return pack_impl<std::decay_t<decltype(ts)>...>(static_cast<decltype(ts)>(ts)...);
};
It utilizes mover
as a proxy, which allows lambda to capture it by move (it's a bit hacky). And wrap_t
decides when is needed or beneficial to apply mover
.
Now we can test it:
struct A
{
A() = default;
A(A&&)
{
std::cout << "move\n";
}
A(A const&)
{
std::cout << "copy\n";
}
};
A a;
std::cout <<"p1------------\n";
auto p1 = pack(std::move(a));
std::cout <<"p2------------\n";
auto p2 = std::move(p1);
std::cout <<"p3------------\n";
auto p3 = p2;
Will print:
p1------------
move
move
p2------------
move
p3------------
copy