1

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

max66
  • 65,235
  • 10
  • 71
  • 111
Sergey Kolesnik
  • 3,009
  • 1
  • 8
  • 28
  • 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, ...> for example? You'll need to pass your args as part of some class/type anyway. – Dan M. Nov 22 '18 at 16:09
  • 1
    Could we have a [mcve]? – YSC Nov 22 '18 at 16:13
  • @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 Nov 22 '18 at 16:13
  • @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. Nov 22 '18 at 16:15
  • @DanM. I'll edit it in a minute. Just copied some code from project, the details does not matter. – Sergey Kolesnik Nov 22 '18 at 16:20
  • C++11, C++14 or C++17? – max66 Nov 22 '18 at 18:10
  • @max66 C++17 supported – Sergey Kolesnik Nov 22 '18 at 18:11
  • OK: I add the C++17 tag. – max66 Nov 22 '18 at 18:12

2 Answers2

3

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;
}
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Are you reinventing [`std::apply`](https://en.cppreference.com/w/cpp/utility/apply)? – YSC Nov 22 '18 at 16:28
  • I don't think so. It would be quite a trick for a class to inherit from a function. – Sam Varshavchik Nov 22 '18 at 16:40
  • Works fine, though unfortunately I can not call overloaded ISignalMap's function from a due to it being ambigous. I guess I need to do some rethinking – Sergey Kolesnik Nov 22 '18 at 16:51
  • 2
    A [strategically-placed `using` declaration](https://en.cppreference.com/w/cpp/language/parameter_pack#Using-declarations) should solve this problem. – Sam Varshavchik Nov 22 '18 at 16:56
  • @SamVarshavchik I'm afraid but yet I am not familiar with this term. However, example from the link compiles. I tried to put `using tuple_expansion_t::Invoke...;` in ISignalStorage. Now I don't get an ambiguity error, but the function is inaccessable: `public: void Invoke(ArgTypes ... args)` – Sergey Kolesnik Nov 22 '18 at 17:17
  • @SamVarshavchik My Bad)) I forgot to add public before using... lol – Sergey Kolesnik Nov 22 '18 at 17:22
  • @SamVarshavchik I'm trying to clarify what's going on there. So, first we have a forward template class declaration. Then it looks like a temlate specialisation (with `tuple_t = std::tuple`), which defines ISignalMap with expanded parameters pack as `type`. Follows a template declaration of a class (just with `using`? Is it a clas, that inherits from `tuple_expansion`?) tuple_expansion_t, wich uses a defined in `tuple_expansion` type. Did I get it right? – Sergey Kolesnik Nov 22 '18 at 18:05
  • The `using` declaration in my example is a type alias, that's very similar to a `typedef`. Basically it declares that `tuple_expansion_t` is equivalent to `tuple_expansion::type`. A `typedef` does not declare a new type, just declares an alias for some existing type. Similarly, this `using` declaration declares an alias for a templated type, sort of like a `typedef` but with template parameters. This lets you piece things together in smaller chunks, instead of cramming everything into your `ISignalStorage` declaration. not really needed, but makes things clearer. – Sam Varshavchik Nov 22 '18 at 18:31
1

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;
Community
  • 1
  • 1
max66
  • 65,235
  • 10
  • 71
  • 111
  • I am trying to understand that "syntactic sugar". Is it that tuple_expansion_t is in fact an `ISignalMap` ? If so, than `ISignalStorageHelper`is an intermidiate class indeed. But since it is empty, for now I see no problems with it. I am just not familiar with that using of using. – Sergey Kolesnik Nov 22 '18 at 18:24
  • 1
    @SergeyKolesnik - my solution is almost equal to the Sam's one: one difference is that his intermediate struct (`tuple_expansion`) define inside it `type` (`using`, in this case, is another way to write `typedef`), so there is the need of inherit from this internal `type`; so the `using tuple_expansion_t` that simplify this inheritance (this `using` is another `typedef` like but more powerful because is a template one). My solution simplify a little this part, avoiding the needs of the template `using`, inheriting directly from the type that is used for `type` in Sam's solution. – max66 Nov 22 '18 at 18:32