I'm trying to write a function that returns a subset of a variadic argument pack under the form of an std::tuple
. The function should ideally have no runtime overhead (no unnecessary copies), and it should allow users to access lvalue
references and modify them.
Value types, lvalue
references and const lvalue
references should be maintained. Temporaries (rvalue
references), should be "converted" to value types to avoid creating invalid references (references to temporaries).
Example of desired results:
int lr = 5;
const int& clr = lr;
auto t = make_subpack_tuple(lr, clr, 5);
static_assert(is_same
<
decltype(t),
std::tuple<int&, const int&, int>
>{}, "");
// Ok, modifies lr:
std::get<0>(t) = 10;
// Compile-time error, intended:
// std::get<1>(t) = 20;
// Ok, 5 was moved into the tuple:
std::get<2>(t) = 30;
Example incomplete implementation:
template<typename... Ts>
auto make_subpack_tuple(Ts&&... xs)
{
return std::tuple
<
some_type_trait<decltype(xs)>...
>
(
std::forward<decltype(xs)>(xs)...
);
}
Does what I am trying to do make sense?
Is there a standard type-trait that can be used in place of some_type_trait
? Or should I implement my own solution?
The solution for you will be
template<typename... Ts>
auto make_subpack_tuple(Ts&&... xs)
{
return std::tuple<Ts...>(std::forward<Ts>(xs)...);
}
According to template argument deduction rules, the parameter pack Ts...
will contain only cv-qualified types and lvalues. The information in this question may be useful too.
I'd just like to chime in that I ran into this same not-really-a-problem ("I think I need to decay rvalue references and keep lvalue references untouched") while implementing an efficient version of Nick Athanasios's foldable Op<operation>
. I had had this mess:
template<class Pack, class Op>
struct Foldable
{
mystery_trait_t<Pack> value;
const Op& op;
template<class RhsPack>
auto operator*(const Foldable<RhsPack, Op>& rhs) const {
return op(static_cast<std::decay_t<Pack>>(
(op.f)(std::move(value), std::move(rhs.value))
));
}
operator mystery_trait_t<Pack> () && {
return std::move(value);
}
};
template<class Pack>
auto NamedOperator::operator()(Pack&& value) const {
return Foldable<Pack, NamedOperator>(std::forward<Pack>(value), *this);
}
and (after puzzling for a bit, and then starting to ask a SO question, and finding this existing question/answer, and adding a static_assert
to my implementation of mystery_trait_t
to verify that it was never actually invoked with an rvalue reference type!) it turned out that all I actually needed was
template<class Pack, class Op>
struct Foldable
{
Pack value;
const Op& op;
template<class RhsPack>
auto operator*(const Foldable<RhsPack, Op>& rhs) const {
return op(
(op.f)(std::move(value), std::move(rhs.value))
);
}
operator Pack () && {
return std::move(value);
}
};
(See my whole code on Wandbox.)
This "answer" of mine doesn't contribute any new information, but I thought it would be useful to share, because it just goes to show that even if you think you're way deep in template metaprogramming and are sure you need this "conditional decay" behavior... you really don't need it!
There might be a corollary general rule that writing any_template<T&&>
is always a code smell. In Vittorio's original question, he effectively did that twice, although both times it was hidden by decltype
syntax:
some_type_trait<decltype(xs)>... // should have been `Ts...`
std::forward<decltype(xs)>(xs)... // could equally well be `std::forward<Ts>(xs)...`
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With