1
votes

I need to inherit multiple times the following class, taking variadic arguments as template parameters.

template <class SignalDispatcherClass, class ... ArgTypes>
class ISignalMap
{
//private
public:

    void RegisterSlot(SignalAddress pSignalFunc, ISlotInvoker<ArgTypes...>* pSlotInvoker)
    {
        //implementation
    }
};

So far I can expand a parameter pack and get multiple class specializations, but with functions taking only one argument.

template <class SignalDispatcherClass, class ... ArgTypes>
class ISignalStorage : public ISignalMap<SignalDispatcherClass, ArgTypes>...
{
};

///////
ISignalStorage<SignalA, int, double, bool> iss; 

For now this allows me to register slot functions with a single argument (int, double or bool - accordingly). What I need is something that would look like:

ISignalStorage<SignalA, <int, double, bool>, <int, int>, <const char*>> iss;

So far I've been looking into other questions and one appears to be somewhat close to the topic, though I failed to implement or understand it. Wish there were a simplier way (Variadic variadic template templates)

added: code example

struct IDummySlot
{
    void FuncDbl(double)
    {}
    void FuncInt(int)
    {}
    void FuncIntDbl(int, double)
    {}
};

template <class ... Args>
struct ISlotInvoker
{};

template <class SignalDispatcherClass, class ... ArgTypes>
class ISignalMap
{
public:

    void RegisterSlot(void(IDummySlot::*pSignalFunc)(ArgTypes...), ISlotInvoker<ArgTypes...>* pSlotInvoker)
    {
        return;
    }
};

template <class SignalDispatcherClass, class ... ArgTypes>
class ISignalStorage : public ISignalMap<SignalDispatcherClass, ArgTypes>...
{
};


int main()
{
    ISignalStorage<IDummySlot, int, double> iss;

    ISlotInvoker<int> slot_int;
    ISlotInvoker<double> slot_double;
    ISlotInvoker<int, double> slot_intDouble;

    //iss.RegisterSlot(&IDummySlot::FuncInt, &slot_int); //ambigous
    /*Appears to be that I didn't test it, I just saw that inheritance worked as I expected, but didn't try to invoke*/

    return 0;
}

enter image description here

2
Do you really need this or you just need to pass function signatures? Seems like XY problem. Why not just define ASignalStorage<...>, and create ISignalStorage<ASignalStorage<...>, ...> for example? You'll need to pass your args as part of some class/type anyway. - Dan M.
Could we have a minimal reproducible example? - YSC
@DanM. Kind of signatures. ISlotInvoker contains Invoke command of every function with alike signatures. So if I call RegisterSlot it automatically chooses the correct overloaded function - Sergey Kolesnik
@SergeyKolesnik what can you modify in this example? Can you provide us some minimal compilable code? There are several ways to go about this. One would be to pass signal signatures as void(int, char, ...) (like for std::function) and then "map" them to appropriate ISignalMap (ny either specializing it or creating helper class). - Dan M.
@DanM. I'll edit it in a minute. Just copied some code from project, the details does not matter. - Sergey Kolesnik

2 Answers

3
votes

The fundamental problem here is that there is no syntax for "squeezing" multiple variadic parameter packs out of a single parameter pack.

The usual approach in these kinds of situations is to use a std::tuple to wrap each individual parameter pack, and make a parameter pack out of those tuples:

    ISignalStorage<foo, std::tuple<int, double>, std::tuple<double, int>> a;

Then, it becomes a simple matter of unwrapping each parameter pack from the std::tuple using a specialization:

#include <tuple>


template <class SignalDispatcherClass, class ... ArgTypes>
class ISignalMap
{
};

// Take a class, and a tuple. Give me an ISignalMap for the class, and
// what's in the tuple.

template<typename cl, typename tuple_t> struct tuple_expansion;

template<typename cl, typename ...tuple_types>
struct tuple_expansion<cl, std::tuple<tuple_types...>> {

    typedef ISignalMap<cl, tuple_types...> type;
};

// Syntactic sugar.    
template<typename cl, typename tuple_t>
using tuple_expansion_t=typename tuple_expansion<cl, tuple_t>::type;

// And a variadic parameter pack of tuples...

template <class SignalDispatcherClass, class ... ArgTypes>
class ISignalStorage : public tuple_expansion_t<SignalDispatcherClass,
                        ArgTypes>...
{
};

class foo;

void bar()
{

    // Note the syntax: pass each "inner" parameter pack wrapped into a
    // tuple.
    ISignalStorage<foo, std::tuple<int, double>, std::tuple<double, int>> a;

    ISignalMap<foo, int, double> &b=a;
    ISignalMap<foo, double, int> &c=a;
}
1
votes

For now this allows me to register slot functions with a single argument (int, double or bool - accordingly). What I need is something that would look like:

ISignalStorage<SignalA, <int, double, bool>, <int, int>, <const char*>> iss;

As better explained by Sam Varshavchik, this type of needs is usually managed wrapping packs of types as template arguments for another class. Usually is used std::tuple, that offer some handy tools to manage pack of types, but you can also define a trivial template class/struct as follows (TW for "type wrapper")

template <typename...>
struct TW
 { };

I propose a slightly different solution that is wrapper neutral (so you can use with classic std::tuple, with a custom TW or also mixing they)

template <typename, typename>
struct ISignalStorageHelper;

template <typename T, template <typename...> class C, typename ... Ts>
struct ISignalStorageHelper<T, C<Ts...>> : public ISignalMap<T, Ts...>
 { };

template <typename SignalDispatcherClass, typename ... Tuples>
class ISignalStorage
   : public ISignalStorageHelper<SignalDispatcherClass, Tuples>...
 { };

This way we can avoid the using to extract the type inside the intermediate struct but ISignalStorage inherit also from ISignalStorageHelper structs (I hope isn't a problem).

You can define ISignalStorage objects using tuples

ISignalStorage<foo, std::tuple<int, double>, std::tuple<double, int>> a;

custom pack wrapper as TW

ISignalStorage<foo, TW<int, double>, TW<double, int>> a;

or also mixing

ISignalStorage<foo, std::tuple<int, double>, TW<double, int>> a;