4

I have a variadic template function foo():

template <typename... Args>
void foo(Args &&... args);

This function is intended to be invoked with all arguments of size_t. I can enforce that using some metaprogramming. I need to take the resulting list of arguments two at a time and put them into a container of std::pair<size_t, size_t>. Conceptually, something like:

std::vector<std::pair<size_t, size_t> > = { 
    std::make_pair(args[0], args[1]), 
    std::make_pair(args[2], args[3]), ...
};

Is there a straightforward way to do this? I know that by pack expansion, I could put the arguments into a flat container, but is there a way to group them two by two into std::pair objects at the same time?

Community
  • 1
  • 1
Jason R
  • 11,159
  • 6
  • 50
  • 81
  • "*Is there a straightforward way to do this?*" No. – Nicol Bolas Jan 24 '17 at 19:58
  • 1
    I guess, one could make a recursive inserter which would consume two arguments, and push_back them as pairs into vector, with two stop-case specializations. – luk32 Jan 24 '17 at 19:59
  • Any chance you could forward to an internal helper function that has an additional `std::index_sequence` parameter? – Kerrek SB Jan 24 '17 at 20:00
  • @KerrekSB: That would be feasible, but I'm still getting used to working with C++11 features, so I'm not sure how it would work. `std::index_sequence<>` is C++14-only as well, isn't it? Not a showstopper, but I would want to be aware of it. – Jason R Jan 24 '17 at 20:02
  • @JasonR: You can define any template equivalent to `index_sequence` yourself trivially. – Kerrek SB Jan 24 '17 at 20:10

4 Answers4

8

Indexing into packs isn't really doable (yet?), but indexing into tuples is. Just stick everything into a tuple first, and then pull everything back out as you go. Since everything's a size_t, we can just copy:

template <size_t... Is, class Tuple>
std::vector<std::pair<size_t, size_t>> 
foo_impl(std::index_sequence<Is...>, Tuple tuple) {
    return std::vector<std::pair<size_t, size_t> >{ 
        std::make_pair(std::get<2*Is>(tuple), std::get<2*Is+1>(tuple))...
    };
}

template <typename... Args>
void foo(Args... args)
{
    auto vs = foo_impl(std::make_index_sequence<sizeof...(Args)/2>{},
        std::make_tuple(args...));

    // ...
}
Barry
  • 286,269
  • 29
  • 621
  • 977
2

Suppose you are allowed to refactor your logic into an internal helper function:

template <typename ...Args>
void foo(Args &&... args)
{
    foo_impl(std::make_index_sequence<sizeof...(Args) / 2>(),
             std::forward<Args>(args)...);
}

Now we can operate on the argument pack index by index:

template <std::size_t ...I, typename ...Args>
void foo_impl(std::index_sequence<I...>, Args &&... args)
{
    std::vector<std::pair<std::size_t, std::size_t>> v =
        { GetPair(std::integral_constant<std::size_t, I>(), args...)... };
}

It remains to implement the pair extractor:

template <typename A, typename B, typename ...Tail>
std::pair<std::size_t, std::size_t> GetPair(std::integral_constant<std::size_t, 0>,
                                            A a, B b, Tail ... tail)
{
    return { a, b };
}

template <std::size_t I, typename A, typename B, typename ...Tail>
std::pair<std::size_t, std::size_t> GetPair(std::integral_constant<std::size_t, I>,
                                            A a, B b, Tail ... tail)
{
    return GetPair<I - 1>(tail...);
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Excellent. I accepted Barry's answer since it was a bit more brief, but this looks like a good solution as well. – Jason R Jan 24 '17 at 20:13
  • @JasonR: Yes, `std::get` is effectively for tuples what my `GetPair` is for raw packs. Tuples are very powerful for this sort of generic code. – Kerrek SB Jan 24 '17 at 20:14
2

With range-v3, you may do

template <typename... Args>
void foo(Args&&... args)
{
    std::initializer_list<std::size_t> nbs = {static_cast<std::size_t>(args)...};
    const auto pair_view =
        ranges::view::zip(nbs | ranges::view::stride(2),
                          nbs | ranges::view::drop(1) |  ranges::view::stride(2));

    // And possibly
    std::vector<std::pair<std::size_t, std::size_t>> pairs = pair_view;
    // ...
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
1

Someone (cough @Barry cough) said that indexing into packs isn't possible.

This is C++. Impossible means we just haven't written it yet.

template<std::size_t I> struct index_t:std::integral_constant<std::size_t, I> {
  using std::integral_constant<std::size_t, I>::integral_constant;
  template<std::size_t J>
  constexpr index_t<I+J> operator+( index_t<J> ) const { return {}; }
  template<std::size_t J>
  constexpr index_t<I-J> operator-( index_t<J> ) const { return {}; }
  template<std::size_t J>
  constexpr index_t<I*J> operator*( index_t<J> ) const { return {}; }
  template<std::size_t J>
  constexpr index_t<I/J> operator/( index_t<J> ) const { return {}; }
};
template<std::size_t I>
constexpr index_t<I> index{};

template<std::size_t B>
constexpr index_t<1> exponent( index_t<B>, index_t<0> ) { return {}; }

template<std::size_t B, std::size_t E>
constexpr auto exponent( index_t<B>, index_t<E> ) {
  return index<B> * exponent( index<B>, index<E-1> );
}
template<std::size_t N>
constexpr index_t<0> from_base(index_t<N>) { return {}; }
template<std::size_t N, std::size_t c>
constexpr index_t<c-'0'> from_base(index_t<N>, index_t<c>) { return {}; }
template<std::size_t N, std::size_t c0, std::size_t...cs>
constexpr auto from_base(index_t<N>, index_t<c0>, index_t<cs>...) {
  return 
    from_base(index<N>, index<c0>) * exponent(index<N>, index<sizeof...(cs)>)
    + from_base(index<N>, index<cs>...)
  ;
}

template<char...cs>
constexpr auto operator""_idx(){
  return from_base(index<10>, index<cs>...);
}

auto nth = [](auto index_in){
  return [](auto&&...elems)->decltype(auto){
    using std::get;
    constexpr auto I= index<decltype(index_in){}>;
    return get<I>(std::forward_as_tuple(decltype(elems)(elems)...));
  };
};

Now we get:

using pair_vec = std::vector<std::pair<std::size_t, std::size_t>>;
template <typename... Args>
pair_vec foo(Args &&... args) {
  return
    index_over< sizeof...(args)/2 >()
    ([&](auto...Is)->pair_vec{
      return {
        {
          nth( Is*2_idx )( decltype(args)(args)... ),
          nth( Is*2_idx+1_idx )( decltype(args)(args)... )
        }...
      };
    });
}

where we "directly" index into our parameter packs using compile time constant indexes.

live example.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Very impressive. I'm a few years behind the modern C++ curve, so I'm a long way from being able to dream up a solution like this from scratch. Nice demonstration. – Jason R Jan 24 '17 at 21:36