0

I want to write an unmarshaller to extract arguments stored in a msgpack array for individual arguments to a call of sigc::signal::emit(...). I tried this:

template<class... T> class MsgpackAdapter: public MsgpackAdapterBase {
public:
    MsgpackAdapter (sigc::signal<void, T...> &sig)
            : MsgpackAdapterBase (), signal_ (sig)
    {}
protected:
    virtual void do_emit (const msgpack::object_array &mp_args) override;
private:
    sigc::signal<void, T...> signal_;
};

template<class T1>
void MsgpackAdapter<T1>::do_emit (const msgpack::object_array &mp_args)
{
    T1 a1;
    mp_args.ptr[0].convert (a1);
    signal_.emit (a1);
}

template<class T1, class T2>
void MsgpackAdapter<T1, T2>::do_emit (const msgpack::object_array &mp_args)
{
    T1 a1;
    T2 a2;
    mp_args.ptr[0].convert (a1);
    mp_args.ptr[1].convert (a2);
    signal_.emit (a1, a2);
}

and so on for up to 4 arguments. But I get this error message (from vim-youcompleteme using clang 3.9):

'MsgpackAdapter<T1>::' for declaration does not refer into a class, class template or class template partial specialization  

It seems I can make partial specializations of the entire class:

template<class T1> class MsgpackAdapter<T1>: public MsgpackAdapterBase { ... };

but I'd rather be able to specialize just the emit method to reduce the amount of copy & paste code. Have I missed something obvious? I think the main difficulty is that do_emit doesn't take templatized arguments.

Another curiosity is that if I try to do it without the variadic template, and use:

template<class T1> class MsgpackAdapter: public MsgpackAdapterBase { ... };
template<class T1, class T2> class MsgpackAdapter: public MsgpackAdapterBase { ... };

I get an error that the second class definition clashes with the first. That's understandable, but I wonder how sigc managed a similar thing without variadic templates.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
realh
  • 962
  • 2
  • 7
  • 22

2 Answers2

2

I have always felt that the most maintainable way to specialise a member function is to defer to a specialised function object:

#include <iostream>

struct object_array
{
};


template<class...Ts>
struct implement_do_emit
{
    template<class This>
    void operator ()(This *that, const object_array& arg) const;
};


template<class... Ts>
struct MsgpackAdapter
{
    friend class implement_do_emit<Ts...>;

    virtual void do_emit(const object_array& mp_args)
    {
        auto impl = implement_do_emit<Ts...>();
        impl(this, mp_args);
    }

};

template<class T>
struct implement_do_emit<T>
{
    template<class This>
    void operator ()(This *that, const object_array& arg) const
    {
        std::cout << "one type version\n";
    }
};

template<class T1, class T2>
struct implement_do_emit<T1, T2>
{
    template<class This>
    void operator ()(This *that, const object_array& arg) const
    {
        std::cout << "two type version\n";
    }
};


int main()
{
    MsgpackAdapter<int> pi {};
    pi.do_emit(object_array());

    MsgpackAdapter<int, int> pii {};
    pii.do_emit(object_array());
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
1

In C++14:

template<class F>
auto foreach( F&& f ) {
  return [f=std::forward<F>(f)](auto&&...args)mutable{
    using discard=int[];
    (void)discard{0,(void(
      f(decltype(args)(args))
    ),0)...};
  };
}
template<std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
  return [](auto&& f)->decltype(auto){
    return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
  };
}
template<std::size_t N>
auto index_upto( std::integral_constant<std::size_t, N> ={} ) {
  return index_over( std::make_index_sequence<N>{} );
}


template<class...Ts>
void MsgpackAdapter<Ts...>::do_emit (const msgpack::object_array &mp_args)
{
  std::tuple<Ts...> args;
  index_upto<sizeof...(Ts)>()(
    foreach(
      [&](auto I){
        mp_args.ptr[I].convert(std::get<I>(args));
      }
    )
  );
  index_upto<sizeof...(Ts)>()(
    [&](auto...Is){
      signal_.emit(std::get<Is>(args)...);
    }
  );
}

or somesuch. Live example.

Basically, make a tuple.

Create a pack of indexes into that tuple.

For each index into the tuple, call convert.

Then, call emit getting each element of the tuple.

There are many examples of code on stack overflow that involve passing each argument of a tuple to a function call. That is the emit part.

There are many examples on stack overflow of doing something for each element of a tuple. Doing so with an index is a bit trickier, but at worst you can count if the for each element does things in order.

These can be done in C++11, but in C++14 I can do it all in the function without helper functions.


Description of the above magic code. index_upto returns a lambda. This lambda takes another lambda, then calls it with compile time constants from 0 up to N-1. It does this by calling index_over, which takes a list of indexes.

foreach takes a lambda f. It then returns a lambda that takes any number of arguments, and calls the f once with each one of those arguments. Its implementation is a bit deep mojo involving parameter packs and array initialization.

Composing index_upto and foreach lets you do something for each compile-time value from 0 to N-1. This is how we call .convert.

Just calling index_upto lets us pass all of the arguments at once to emit.


We can do something similar in C++11, but we'd instead write helper functions that take parameter packs and such. It is more than a bit of a pain.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Something like this would be necessary for an indefinite number of arguments, but seeing as I only need up to four I think specializing each case is simpler. – realh Mar 21 '17 at 21:25
  • For a little more explanation of how the array trick there works... [expanding the pack inside a list-initialiser guarantees left-to-right evaluation; however, the lambda passed to `foreach()` returns `void`, and thus a type needs to be given, hence the use of the comma operator and the trailing `0` to make an expression of type `int`. The leading `0` is to catch cases where the pack has zero members, because zero-sized arrays aren't allowed.](http://stackoverflow.com/a/17340003/5386374) – Justin Time - Reinstate Monica Mar 21 '17 at 21:55
  • @JustinTime My `foreach` does not assume `f` returns `void` (and it shouldn't in general). – Yakk - Adam Nevraumont Mar 22 '17 at 02:49
  • @Yakk I probably misinterpreted something, then. My bad. – Justin Time - Reinstate Monica Mar 22 '17 at 04:52
  • @justin no, you just missed the explicit `void` cast. Your link omits it as well, as it operates on a fixed function. In the generic case, you have to add a void cast to avoid pathological `operator,` overloads. – Yakk - Adam Nevraumont Mar 22 '17 at 12:01
  • @Yakk Ah, that makes sense. Useful to know, I made a note of it in case I need to know that in the future. – Justin Time - Reinstate Monica Mar 28 '17 at 20:52