Vahagn
Vahagn

Reputation: 4840

How to "duplicate" template parameter pack expansion?

Consider this code:

template < size_t... Indices >
void something(std::index_sequence<Indices...>)
{
  // how to obtain the following call where N is sizeof...(Indices)?
  // foo(f(0),g(0),f(1),g(1),...,f(N-1),g(N-1));
}

Upvotes: 4

Views: 422

Answers (2)

Barry
Barry

Reputation: 303337

The key to answering a question like:

// how to obtain the following call where N is sizeof...(Indices)?
// foo(f(0),g(0),f(1),g(1),...,f(N-1),g(N-1));

is to formulate how to actually generate such a pack expansion. We have 2N arguments, which would like:

+=====+======+
| idx | expr |
+-----+------+
|  0  | f(0) |
|  1  | g(0) |
|  2  | f(1) |
|  3  | g(1) |
|   ....
+=====+======+

In C++17, we can write such an expression with if constexpr:

template <size_t I>
auto expr() {
    if constexpr (I % 2 == 0) { 
        // even indices are fs
        return f(I / 2);
    } else {
        // odds are gs
        return g(I / 2);
    }
}

Which can even be a lambda that takes an integral_constant as an argument. So we really just need to turn our N arguments into 2N arguments, which is just a matter of adding more indices:

template <auto V>
using constant = std::integral_constant<decltype(V), V>;

template < size_t... Indices >
void something(std::index_sequence<Indices...>) {
    auto expr = [](auto I) {
        if constexpr (I % 2 == 0) {
            return f(I / 2);
        } else {
            return g(I / 2);
        }
    }

    return foo(
        expr(constant<Indices>{})...,                     // 0, ..., N-1
        expr(constant<Indices + sizeof...(Indices)>{})... // N, ..., 2N-1
    );
}

Upvotes: 3

max66
max66

Reputation: 66230

The best I can imagine is the use of std::tuple_cat and std::make_pair to make a std::tuple of arguments of foo().

Unfortunately I know how to do it only with an helper function to call foo()

template <typename T, std::size_t... I>
void somethingH (T const & t, std::index_sequence<I...> const &)
 { foo(std::get<I>(t)...); }

template <std::size_t... I>
void something (std::index_sequence<I...> const &)
 {
   somethingH(std::tuple_cat(std::make_pair(f(I), g(I))...),
              std::make_index_sequence<(sizeof...(I) << 1)>{});
 }

Using std::apply, available only starting from C++17, you can use a lambda function to select the correct foo (as SirGuy suggested; thanks!) and avoid the helper function

template <std::size_t... I>
void something (std::index_sequence<I...> const &)
 {
   std::apply([](auto && ... as)
                 { return foo(std::forward<decltype(as)>(as)...); },
              std::tuple_cat(std::make_pair(f(I), g(I))...));
 }

The following is a full C++17 working example

#include <iostream>
#include <utility>
#include <tuple>

int f (std::size_t n)
 { return n; }

int g (std::size_t n)
 { return -n; }

template <typename ... Args>
void foo (Args ... as)
 { (std::cout << ... << as) << std::endl; }

template <std::size_t... I>
void something (std::index_sequence<I...> const &)
 {
   std::apply([](auto && ... as)
                 { return foo(std::forward<decltype(as)>(as)...); },
              std::tuple_cat(std::make_pair(f(I), g(I))...));
 }

int main()
 {
   something(std::make_index_sequence<7U>{});
 }

Upvotes: 2

Related Questions