8

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?

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • I'm just curious. Why are you interested in doing this? What problem are you solving? – James Adkison Oct 29 '15 at 18:01
  • I'm implementing something similar to a `static_for` that executes a callable object over heterogeneous values with an user-specified arity, and also allows the user to retrieve the current iteration number at compile time and break/continue *(exit early)* at compile time using `static_if`. Part of the implementation requires passing the first `N` arguments of the variadic argument pack to another inner function, and I was trying to generalize that by defining some variadic argument pack manipulation functions. Other than `nth`, I require `subpack` to fully generalize that behavior – Vittorio Romeo Oct 29 '15 at 18:04
  • By user-specified arity, I mean that the `static_for` iterates over the heterogeneous values in groups of `N` (where `N` is a template parameter specified by the user). The callable object that represents the body of the `static_for` needs to have an `operator()` with the same arity. Also, I'm doing all of this for an open-source C++14 general purpose library ([vrm_core](https://github.com/SuperV1234/vrm_core)), written for fun and learning purposes. Hope that answers your question :) – Vittorio Romeo Oct 29 '15 at 18:07
  • 3
    Do not use `decltype(xs)`. The type pack `Ts...` is exactly what you need. So try `std::tuple` – Andrey Nasonov Oct 29 '15 at 18:19
  • @AndreyNasonov: works perfectly, not sure how I didn't think about that. Thanks! Could you post this solution again as an answer so that I can accept it? – Vittorio Romeo Oct 29 '15 at 18:27

2 Answers2

10

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.

Community
  • 1
  • 1
Andrey Nasonov
  • 2,619
  • 12
  • 26
3

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)...`
Quuxplusone
  • 23,928
  • 8
  • 94
  • 159