4
template< typename ... Args >
class Message {
public:
    Message( Args&& ... args ) {
        mArgs = std::make_tuple( args ...  );
    }

    std::tuple< Args ... > mArgs;
    typedef std::function< void ( Args ... ) > HandlerType;

    void Consume( HandlerType handler ) {
        // handler( mArgs ); 
        // How does one unpack this?
    }
};

// Testing code
Message<int, int> msg(1, 2);

msg.Consume( [] ( int i, int j ) {
    std::cout << i << ',' << j << '\n';
});

I'm attempting a simple message passing API, trying to provide a simple templated interface for messages and arguments. I'm running into an issue when I want to pass the arguments into a function.

I've not used variadic templates too much, and was wondering if there is an elegant solution to my problem.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
DubyaDubyaDubyaDot
  • 1,224
  • 2
  • 14
  • 24
  • This might be what you're looking for: http://stackoverflow.com/questions/687490/how-do-i-expand-a-tuple-into-variadic-template-functions-arguments/1547118#1547118 – chris May 29 '13 at 22:11
  • Also http://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer – jrok May 29 '13 at 22:11
  • I've looked at those and have been contemplating implementing those. Since they were a bit dated I was seeing if there were anything a little more elegant. I guess when you start delving into templates the code has to suffer a little bit. Since C++ templates are compile time evaluated I guess it doesn't hurt too bad. – DubyaDubyaDubyaDot May 29 '13 at 22:14

2 Answers2

7

Here is a simple framework for unpacking a tuple and providing its elements as arguments to a given function:

namespace detail
{
    template<int... Is>
    struct seq { };

    template<int N, int... Is>
    struct gen_seq : gen_seq<N - 1, N - 1, Is...> { };

    template<int... Is>
    struct gen_seq<0, Is...> : seq<Is...> { };

    template<typename F, typename... Ts, int... Is>
    void call_with_tuple(F&& f, std::tuple<Ts...> const& t, seq<Is...>)
    {
        (std::forward<F>(f))(std::get<Is>(t)...);
    }
}

template<typename F, typename... Ts>
void call_with_tuple(F&& f, std::tuple<Ts...> const& t)
{
    detail::call_with_tuple(std::forward<F>(f), t, 
        detail::gen_seq<sizeof...(Ts)>());
}

Inside your Consume() function you could then just do:

call_with_tuple(handler, mArgs);

With some more context:

#include <iostream>
#include <functional>

template< typename ... Args >
class Message {
public:
    Message( Args&& ... args ) {
        mArgs = std::make_tuple( args ...  );
    }

    std::tuple< Args ... > mArgs;
    typedef std::function< void ( Args ... ) > HandlerType;

    void Consume( HandlerType handler ) {
        call_with_tuple(handler, mArgs);
//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    }
};

And here is a live example.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Wow, that'so good, works like a charm. Suddenly I realize I don't know as much about C++ templates as I should. Guess I should read a book or three. It's not too hard on the eyes either. Between yours and mfont's answers, this was such a hard decision for choosing. – DubyaDubyaDubyaDot May 29 '13 at 22:26
  • @DubyaDubyaDubyaDot: OK, glad you found that useful :) – Andy Prowl May 29 '13 at 22:32
  • I'm really not understanding what `gen_seq` does or how it's being used in this example? You don't even touch it in the first overload of `call_with_tuple`... – user2030677 May 29 '13 at 22:38
  • @user2030677: I do: `call_with_tuple` passes a `gen_seq` object as the third argument to `detail::call_with_tuple`. That argument is just used for deducing the integer parameter pack `Is` – Andy Prowl May 29 '13 at 22:39
  • So what does `gen_seq` actually do (in your implementation thereof)? How is it important to the intent of this program? – user2030677 May 29 '13 at 23:56
  • It indicates how the `get` should be expanded. `gen_seq<3>` has a base type `seq<0,1,2>`. When matched to `seq`, this means `Is` is the pack `0,1,2`. When used like this `(std::get(t)...)` in the right context, the expression `std::get(t)` contains an unexpanded pack `Is`, which is then expanded by the `...`, generating `(std::get<0>(t), std::get<1>(t), std::get<2>(t) )`. The instance of `get_seq<2>::type` created and passed to the function is only used for type deduction purposes: with an extra level of indirection you could have passed a pure type. – Yakk - Adam Nevraumont May 30 '13 at 04:16
  • @AndyProwl I'd want to support rvalue `tuple`, and `const&` and `&`, in my `call_with_tuple`. I mean, `Consume` isn't even `const`, why should the arguments be `const`! – Yakk - Adam Nevraumont May 30 '13 at 04:21
5

How about this?

template<size_t... indexes>
struct index_tuple {};

template<size_t head, size_t... indexes>
struct index_tuple<head, indexes...> {
    typedef typename index_tuple<head-1, head-1, indexes...>::type type;
};

template<size_t... indexes>
struct index_tuple<0, indexes...> {
    typedef index_tuple<indexes...> type;
};

template<typename... Args>
struct index_tuple_maker {
    typedef typename index_tuple<sizeof...(Args)>::type type;
};


template< typename ... Args >
class Message {
public:
    Message( Args&& ... args ) {
        mArgs = std::make_tuple( args ...  );
    }

    std::tuple< Args ... > mArgs;
    typedef std::function< void ( Args ... ) > HandlerType;

    void Consume( const HandlerType &handler ) {
        Consume(handler, typename index_tuple_maker<Args...>::type());
    }
private:
    template<size_t... ns>
    void Consume(const HandlerType &handler, index_tuple<ns...>) {
        handler(std::get<ns>(mArgs)...);
    }
};

Live demo here.

mfontanini
  • 21,410
  • 4
  • 65
  • 73
  • This is amazingly good, unfortunately I had two great answers at the same time. This was probably the hardest decision for an answer I've ever had to give on StackOverflow. – DubyaDubyaDubyaDot May 29 '13 at 22:25