Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to partition a parameter pack?

I'd like to write a function template, apply, which receives some function f, an integer i, and a parameter pack. apply needs to unpack the parameters and apply f to them, except for the ith parameter, pi. For pi, it needs to call some other function g before passing it as a parameter to f.

It seems that I need a way to partition the parameter pack into a left side, the ith parameter, and the right side. Is this possible? In code:

template<int i, typename Function, typename... Parms>
  void apply(Function f, Parms... parms)
{
  auto lhs = // what goes here?
  auto pi =  // what goes here?
  auto rhs = // what goes here?

  f(lhs..., g(pi), rhs...);
}
like image 202
Jared Hoberock Avatar asked Jan 11 '12 01:01

Jared Hoberock


2 Answers

OK, here we go! It really ugly but I couldn't come up with a nicer version in a hurry ;) Most of the stuff is bog standard template specialization. The biggest issue is creating a list of integers of the proper size. I seem to recall that I came up with a nice version but somehow I can't recall what I did. Enjoy!

#include <iostream>
#include <utility>

// printing the values
void print_args() {}
template <typename F> void print_args(F f) { std::cout << f; }
template <typename F, typename... T>
void print_args(F f, T... args)
{
    std::cout << f << ", ";
    print_args(args...);
}

// the function object to be called:
struct Functor
{
    template <typename... T>
    void operator()(T... args)
    {
        std::cout << "f(";
        print_args(args...);
        std::cout << ")\n";
    }
};

// conditionally apply g():
template <typename T> T g(T value) { return 1000 + value; }
template <int i, int j, typename T>
typename std::enable_if<i != j, T>::type forward(T t) { return t; }
template <int i, int j, typename T>
typename std::enable_if<i == j, T>::type forward(T t) { return g(t); }

// create a series of integers:
template <int... Values> struct values {};

template <int Add, typename> struct combine_values;
template <int Add, int... Values>
struct combine_values<Add, values<Values...>>
{
    typedef values<Values..., Add> type;
};

template <int Size> struct make_values;
template <> struct make_values<0> { typedef values<> type; };
template <int Size>
struct make_values
{
    typedef typename combine_values<Size, typename make_values<Size -1>::type>::type type;
};

// applying f(t...) except for ti where g(ti) is called
template <int i, int... Values, typename Function, typename... T>
void apply_aux(values<Values...>, Function f, T... t)
{
    f(forward<i, Values>(t)...);
}

template <int i, typename Function, typename... T>
void apply(Function f, T... t)
{
    apply_aux<i>(typename make_values<sizeof...(T)>::type(), f, t...);
}

int main()
{
    apply<3>(Functor(), 1, 2, 3, 4, 5, 6, 7, 8);
    apply<4>(Functor(), 1, 2, 3, 4, 5, 6, 7, 8);
    apply<5>(Functor(), 1, 2, 3, 4, 5, 6, 7, 8);
}
like image 136
Dietmar Kühl Avatar answered Oct 14 '22 04:10

Dietmar Kühl


Iactually did code something similar a little while ago. So try the following code:

template<unsigned N, unsigned M>
struct call_up_impl{
    template<class Func, class Mutator, class Tuple, class... Args>
    static void do_call(const Func& func, const Mutator& mutator, const Tuple& args, Args&&... unpacked_args) {
        call_up_impl<N-1, M>::do_call(func, mutator, args, std::get<N-1>(args), std::forward<Args>(unpacked_args)...);
    }
};

template<unsigned M>
struct call_up_impl<0, M> {
    template<class Func, class Mutator, class Tuple, class... Args>
    static void do_call(const Func& func, const Mutator&, const Tuple&, Args&&... unpacked_args) {
        func(std::forward<Args>(unpacked_args)...);
    }
};
template<unsigned M>
struct call_up_impl<M, M> {
    template<class Func, class Mutator, class Tuple, class... Args>
    static void do_call(const Func& func, const Mutator& mutator, const Tuple& args, Args&&... unpacked_args) {
        call_up_impl<M-1, M>::do_call(func, mutator, args, mutator(std::get<M-1>(args)), std::forward<Args>(unpacked_args)...);
    }
};
template<int i, typename Function, typename... Parms>
void apply(Function f, Parms... parms) {
      std::tuple<Parms...> t(parms...);
      call_up_impl<std::tuple_size<decltype(t)>::value, i + 1>::do_call(f, &g, t);
}

This is a quick adaption of my original code, so it isn't thoroughly tested and maybe not the not optimal way to do this, but it should work at least (at least according to a quick test and depending what exactly you want). It should be possible to do this without the tuple, but I haven't gotten that to compile with g++ (it doesn't seem to like the nested variadic templates needed). However changing apply to:

template<int i, typename Function, typename... Parms>
void apply(Function f, Parms&&... parms) {
      std::tuple<Parms&&...> t(std::forward<Parms>(parms)...);
      call_up_impl<std::tuple_size<decltype(t)>::value, i + 1>::do_call(f, &g, t);
}

will probably avoid most of the overhead introduced by the tuple. It would be even better to make correct forwarding of the results of the std::get calls, but I'm too tired to work that out write now.

like image 34
Grizzly Avatar answered Oct 14 '22 03:10

Grizzly