Vittorio Romeo
Vittorio Romeo

Reputation: 93364

Getting nth variadic argument value (not type)

Ignore the missing perfect forwarding. (Assume arguments are perfectly forwarded in the real implementation.)

// Base case: no args
template<typename TF> void forEach2Args(TF) { }

// Recursive case: some args
template<typename TF, typename... Ts> void forEach2Args(TF mFn, Ts... mXs)
{
    mFn(getNth<0>(mXs...), getNth<1>(mXs...));
    forEach2Args(mFn, getAllAfter<2>(mXs...));
}

int main()
{
    int result{0};
    forEach2Args([&result](auto a1, auto a2)
    {
        result += (a1 * a2);
    }, 2, 4, 3, 6);

    // roughly evaluates to:
    //     result += (2 * 4);
    //     result += (3 * 6);
}

Is it possible to implement getNth and getAllAfter avoiding any possible runtime overhead? The only solution I've found so far is putting every Ts... inside of an std::tuple on the first forEach2Args call and then passing a non-const reference to that tuple to every recursive call. I'm almost sure there are unnecessary move/ctor/dtor calls though.

Another solution is using something like:

// Base case: no args
template<typename TF> void forEach2Args(TF) { }

// Recursive case: some args
template<typename TF, typename T1, typename T2, typename... Ts> 
void forEach2Args(TF mFn, T1 mX1, T2 mX2, Ts... mXs)
{
    mFn(mX1, mX2);
    forEach2Args(mFn, mXs...);
}

But this solution needs to be implemented again if I want to pass parameters in groups of 3 instead of 2, or any other number. I wanted something dynamic where I can specify how many arguments to pass to every mFn call through a template parameter. Something like:

forEachNArgs<3>([](auto a1, auto a2, auto a3){ /*...*/ }, /*...*/);
forEachNArgs<4>([](auto a1, auto a2, auto a3, auto a4){ /*...*/ }, /*...*/);

Upvotes: 2

Views: 1536

Answers (4)

David G
David G

Reputation: 96845

Here's a variation of what was presented at C++Now2014:

#include <utility>
#include <tuple>
#include <cassert>

struct type_erasure { };

template<class T>
struct wrapper : type_erasure {
    wrapper(T&& w) : w_(std::forward<T>(w)) { }
    T&& w_;
    decltype(auto) get() { return std::forward<T>(w_); }
};

template<class T>
wrapper<T> wrapper_for(T&& x) {
    return { std::forward<T>(x) };
}

template <typename ignore>
struct lookup;

template <std::size_t... ignore>
struct lookup<std::index_sequence<ignore...>> {
    template <typename nth>
    static decltype(auto)
    at_position(decltype(ignore, type_erasure())..., wrapper<nth> w, ...) {
        return w.get();
    }

    template<typename... Ts>
    static auto
    all_after(decltype(ignore, type_erasure())..., Ts&&... args) {
        return std::forward_as_tuple(args.get()...);
    }
};

template<std::size_t index, typename... Args>
auto getNth(Args&&... args) {
    return lookup<std::make_index_sequence<index>>::at_position(
        wrapper_for(std::forward<Args>(args))...
    );
}

template<std::size_t index, typename... Args>
auto getAllAfter(Args&&... args) {
    return lookup<std::make_index_sequence<index + 1>>::all_after(
        wrapper_for(std::forward<Args>(args))...
    );
}

int main() 
{
    assert(getNth<0>(1, 2, 3) == 1);
    assert(getNth<1>(1, 2, 3) == 2);
    assert(getNth<2>(1, 2, 3) == 3);

    assert(getAllAfter<2>(2, 4, 6, 8, 10) == std::make_tuple(8, 10));
}

Upvotes: 2

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275878

The core of this is call_with_some, that takes a callable and a package of indexes and varargs, and calls the callable with the indexes of the varargs.

Some index helpers:

template<size_t K, class indexes>
struct offset_indexes;
template<size_t K, size_t...Is>
struct offset_indexes<K, std::index_sequence<Is...>>:
  std::index_sequence<(K+Is)...>
{};

call_with_some, SFINAE enabled.

// SFINAE test optional, but why not:
template<class F, class...Ts, size_t... Is>
std::result_of_t< F( std::tuple_element_t< Is, std::tuple<Ts&&...> >... ) >
call_with_some( F&& f, std::index_sequence<Is...>, Ts&&... ts ) {
  return std::forward<F>(f)(
    std::get<Is>(
      std::forward_as_tuple(std::forward<Ts>(ts)...)
    )...
  );
}

Now the meat of the problem. call_by_n is a function object that stores another function object. It takes a sequence of offsets, which it then uses to invoke F on that offset (times n) of the arguments, passing in the n arguments:

template<class F, size_t n>
struct call_by_n {
  F&& f;
  // Offset... should be `<0, ..., sizeof...(Args)/n -1>`
  template<size_t...Offset, class...Args>
  void operator()(std::index_sequence<Offset...>, Args&&...args) {
    static_assert(0==(sizeof...(Args)%n), "Number of args must be divisible by n");
    // <0,1,2,3,4,...,n-1> sequence:
    using indexes = std::make_index_sequence<n>;

    using discard=int[];

    // the unused array trick to expand an arbitrary call:
    (void)discard{0,(
      ( call_with_some( f, offset_indexes<Offset*n, indexes>{}, std::forward<Args>(args)...) )
    ,void(),0)...};
  }
  void operator()() {} // do nothing, naturally
};

now we just wrap the above up in your interface:

template<size_t n, class F, class...Args>
void forEachNArgs(F&& f, Args&&...args) {
  static_assert( (sizeof...(Args)%n)==0, "Wrong number of arguments" );
  call_by_n<F,n>{std::forward<F>(f)}(std::make_index_sequence<sizeof...(Args)/n>{}, std::forward<Args>(args)...);
}

I leave forEach2Args as an exercise.

live example -- nice, had no typos.

This version now does "flat" style calls, without unbounded recursion. The number of recursive calls does not grow with either Args... or n.

The discard trick is a bit of a mess. We create a temporary array of integers full of zeros, and as a side effect execute arbitrary code in a parameter pack expansion. The temporary array of integers is never read nor is its address taken, so the compiler can eliminate it as-if it was never there.

In C++1z, fold expressions with , will allow us to do something similar without nearly as much boilerplate or magic.

Upvotes: 2

Jarod42
Jarod42

Reputation: 218238

Following may help:

namespace detail
{

    template<std::size_t...IsN, std::size_t...Is, typename F>
    void forEachNArgsImpl(std::index_sequence<IsN...>, std::index_sequence<Is...>, F) { }

    template<std::size_t...IsN, std::size_t...Is, typename F, typename... Ts>
    void forEachNArgsImpl(std::index_sequence<IsN...> isn, std::index_sequence<Is...>, F f, Ts... mXs)
    {
        f(std::get<IsN>(std::forward_as_tuple(std::forward<Ts>(mXs)...))...);
        constexpr std::size_t N = sizeof...(IsN);
        constexpr std::size_t is = sizeof...(Is);
        forEachNArgsImpl(isn,
                         std::make_index_sequence<(is > N) ? sizeof...(Is) - N : 0>{},
                         f,
                         std::get<N + Is>(std::forward_as_tuple(std::forward<Ts>(mXs)...))...);
    }

}

template<std::size_t N, typename F, typename... Ts> void forEachNArgs(F f, Ts... args)
{
    static_assert(sizeof...(Ts) % N == 0, "Wrong number of arguments");
    detail::forEachNArgsImpl(std::make_index_sequence<N>{}, std::make_index_sequence<sizeof...(Ts) - N>{}, f, std::forward<Ts>(args)...);
}

Demo

Upvotes: 2

Daniel Frey
Daniel Frey

Reputation: 56921

Ignoring the perfect forwarding as requested, this should work:

template<typename B, typename C>
struct forEachNArgsImpl;

template<std::size_t... Bs, std::size_t... Cs>
struct forEachNArgsImpl<
    std::index_sequence<Bs...>,
    std::index_sequence<Cs...>
>
{
    template<std::size_t N, typename TF, typename... Ts>
    static void execN(TF mFn, const std::tuple<Ts...>& mXs)
    {
        mFn( std::get< N + Cs >( mXs )... );
    }

    template<typename TF, typename... Ts>
    static void exec(TF mFn, const std::tuple<Ts...>& mXs)
    {
        using swallow = bool[];
        (void)swallow{ (execN< Bs * sizeof...(Cs) >( mFn, mXs ), true)... };
    }
};

template<std::size_t N, typename TF, typename... Ts>
void forEachNArgs(TF mFn, Ts... mXs)
{
    static_assert( sizeof...(Ts) % N == 0, "Wrong number of arguments" );
    forEachNArgsImpl<
        std::make_index_sequence<sizeof...(Ts)/N>,
        std::make_index_sequence<N>
    >::exec(mFn, std::forward_as_tuple( mXs... ) );
}

Live example

Upvotes: 7

Related Questions