4

Any suggestions for a better Title?

In Qt there's a nice feature of Signals and Slots. However it tells you if a particular signal can be connected to a particular slot only during the run time (afc).

Intend:

  • creating from template a class containing "Signal signatures" (function pointers as template parameters) to allow connecting "slots" of given signatures (number and types of passed arguments) only to "defined" signals with similar signatures;

  • must be simple to use.

Problems now: I get a compile error with "using declaration" in ISignalSlotMap class. template multiple variadic inheritance with variadic argument types - here it compiled fine.

Also, is there any way to simplify template algorithm?

UPDATE: the first block can compiled and run without dll

This can be compiled without linking to DLL

#include <iostream>
#include <type_traits>
#include <forward_list>
#include <memory>

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


//template to get Class type from pointer
template <class ReturnType, class ... ArgTypes>
constexpr ReturnType ClassFromPointer(void(ReturnType::*)(ArgTypes...));


//template to get pack of arguments' types
template <class ReturnType, class ... ArgTypes>
constexpr TW<ArgTypes...> ArgTypesPackFromPointer(void(ReturnType::*)(ArgTypes...));

template <auto ptr>
using FuncClass = decltype(ClassFromPointer(ptr));

template <auto ptr>
using FuncPack = decltype(ArgTypesPackFromPointer(ptr));


template <class ... ArgTypes>
struct Invoker
{
    virtual void Invoke(ArgTypes ... args) = 0;
};


template <class ClType, class ... ArgTypes>
class InvokerImpl : public Invoker<ArgTypes...>
{
    ClType *ptr_;
    void(ClType::*pFunc_)(ArgTypes...);

public:
    InvokerImpl(ClType* pObj, void(ClType::*pFunc)(ArgTypes...))
        : ptr_(pObj),
        pFunc_(pFunc)
    {}

    virtual void Invoke(ArgTypes ... args)
    {
        (ptr_->*pFunc_)(args...);
    }
};

template <class ClType, class ... ArgTypes>
Invoker<ArgTypes...>* CreateInvoker(ClType* pObj, void(ClType::*pFunc)(ArgTypes...))
{
    return new InvokerImpl<ClType, ArgTypes...>(pObj, pFunc);
}

template <class Pack>
class SlotContainerTranslated;

template <template <class ...> class Pack, class ... ArgTypes>
class SlotContainerTranslated<Pack<ArgTypes...>>
{
    typedef std::unique_ptr<Invoker<ArgTypes...>> pInvoker;
    std::forward_list<pInvoker> slots_;

public:
    void AddInvoker(Invoker<ArgTypes...>* pInv)
    {
        slots_.push_front(std::move(pInvoker(pInv)));
    }

    void DispatchSignal(ArgTypes ... args)
    {
        auto start = slots_.begin();
        while (start != slots_.end())
        {
            (*start)->Invoke(args...);
            ++start;
        }
    }
};

template <auto memfuncptr>
class ISlotContainer : SlotContainerTranslated<FuncPack<memfuncptr>>
{
public:
    using SlotContainerTranslated<FuncPack<memfuncptr>>::AddInvoker;
    using SlotContainerTranslated<FuncPack<memfuncptr>>::DispatchSignal;
};


template <auto ... memfuncPtrs>
class ISignalSlotMap : SlotContainerTranslated<FuncPack<memfuncPtrs>>...
{
public:
    //  using SlotContainerTranslated<FuncPack<memfuncPtrs>>::AddInvoker...;
    //  using SlotContainerTranslated<FuncPack<memfuncPtrs>>::DispatchSignal...;

};
////////////////////////////////////////////////////////////////////////

struct AlienSignals
{
    void MindControl() {};
    void MindControlPrint(int a, double b, int c, int d, const char* str) {};
    void MindControlAdvise(int i, bool b) {};
};



struct Alien
{
    static Alien* Invade();
    virtual ISlotContainer<&AlienSignals::MindControlAdvise>& AccessSignal() = 0;

    /*//this is what usage is expected to be like
    virtual ISignalSlotMap<&AlienSignals::MindControl,
        &AlienSignals::MindControlAdvise,
        &AlienSignals::MindControlPrint>& AccessSignalMap() = 0;
        */

    virtual ~Alien() = default;
};

class AlienImpl : public Alien
{
    std::unique_ptr<ISlotContainer<&AlienSignals::MindControlAdvise>> signalMindControlAdvise_
    { new ISlotContainer<&AlienSignals::MindControlAdvise> };

    // Inherited via Alien
    virtual ISlotContainer<&AlienSignals::MindControlAdvise>& AccessSignal() override
    {
        return *signalMindControlAdvise_;
    }

    virtual ~AlienImpl() = default;
};

Alien * Alien::Invade()
{
    return new AlienImpl;
}


struct Human
{
    int id = 0;

    Human(int i)
        : id(i)
    {}

    void Print()
    {
        std::cout << "Human: " << id << "! " << std::endl;
    }

    void mPrint(int a, double b, int c, int d, const char* str)
    {
        std::cout << "Human: " << id << "! " << a << " " << b << " " << c << " " << d << " " << str << std::endl;
    }

    void Advise(int i, bool b)
    {
        auto colour = b ? "red" : "blue";
        std::cout << "Human: " << id << "! I will take " << i << " of " << colour << " pills" << std::endl;
    }
};

template <auto memfuncptr>
constexpr auto GetType()
{
    return memfuncptr;
}

template <auto memfunc>
using PtrType = decltype(GetType<memfunc>());

int main()
{
    Human person1{ 1 }, person2{ 2 }, person3{ 3 };

    std::unique_ptr<Alien>alien{ Alien::Invade() };
    alien->AccessSignal().AddInvoker(CreateInvoker(&person1, &Human::Advise));
    alien->AccessSignal().AddInvoker(CreateInvoker(&person2, &Human::Advise));
    alien->AccessSignal().AddInvoker(CreateInvoker(&person3, &Human::Advise));
    alien->AccessSignal().DispatchSignal(42, false);

    return 0;
}

UPDATE2: I found out that the problem is in expanding non-type template parameter pack, so "using" could work. I still can not overcome this problem.

c++ non-type parameter pack expansion a similar question but about functions. I couldn't find any usages of folding expression with inheritance also.

There's an answer which shows a promising approach: https://stackoverflow.com/a/53112843/9363996

But there're major drawbacks. One is using a template function to invoke inherited functions. This example compiles and works, but:

  • I don't know how to force methods generation from templates in case i want to compile an interface for a DLL;
  • It is very inconvinient due to intellisense not showing what arguments what arguments are expected and you have to specify function pointer explicitly.

EXAMPLE 2

#include <iostream>

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

template <class ClType, class ... ArgTypes>
constexpr ClType ClassType(void(ClType::*)(ArgTypes...));

template <class ClType, class ... ArgTypes>
constexpr TW<ArgTypes...> ArgsType(void(ClType::*)(ArgTypes...));

template <auto pFunc>
using class_trait = decltype(ClassType(pFunc));

template <auto pFunc>
using args_trait = decltype(ArgsType(pFunc));

template <class, class>
struct _func_trait;

template <class ClType, template <class...> class Pack, class ... ArgTypes>
struct _func_trait<ClType, Pack<ArgTypes...>>
{
    typedef void(ClType::*FuncPtr)(ArgTypes...);
    typedef ClType ClassType;
    typedef Pack<ArgTypes...> Args;
};

template <auto pFunc>
struct func_traits : public _func_trait<class_trait<pFunc>, args_trait<pFunc>>
{};


template <auto L, class Pack>
struct ClassImpl;

template <auto L, template <class ...> class Pack, class ... ArgTypes>
struct ClassImpl<L, Pack<ArgTypes...>>
{
    void invoke(ArgTypes ... args)
    {
        (std::cout << ... << args) << std::endl;
    }
};

template <auto L, auto ...R>
class My_class;

template <auto L>
class My_class<L> : public ClassImpl <L, args_trait<L>>
{

};

template <auto L, auto ... R>
class My_class : public My_class<L>, public My_class<R...>
{
public:

    template <auto T, class ... ArgTypes>
    void Invoke(ArgTypes... args)
    {
        My_class<T>::invoke(args...);
        return;
    }

};



struct Signals
{
    void func1(int a, double b) {}

    void func2(const char*, const char*) {}

    constexpr void func3(int a, double b, int c, bool d);
};


int main()
{

    Signals s;
    My_class<&Signals::func1, &Signals::func2, &Signals::func3> mSignls;
    mSignls.Invoke<&Signals::func1>(4, 6.31);
    mSignls.Invoke<&Signals::func2>("Invoking funcion:", "function 2");

    return 0;
}
Sergey Kolesnik
  • 3,009
  • 1
  • 8
  • 28
  • 2
    Since Qt5, You can connect via member function pointer instead of text, so type in safer than "old" string connection. – Jarod42 Jan 31 '19 at 15:32
  • Please provide a [mcve]. – Passer By Jan 31 '19 at 15:32
  • have a look at boost.signals2. No idea why Qt reinvented a perfectly good wheel here. https://www.boost.org/doc/libs/1_69_0/doc/html/signals2.html – Richard Hodges Jan 31 '19 at 15:34
  • In particular, is all the `.dll` stuff needed for this example? You should be able to recreate the compilation error with a much smaller sample (and you will probably find the problem in the process of reducing the sample). – Max Langhof Jan 31 '19 at 15:34
  • Don't use `new` and raw owning pointer to transfer ownership... – Jarod42 Jan 31 '19 at 15:38
  • Definitions of `ClassFromPointer` and `ArgTypesPackFromPointer` are wrong and unneeded. – Jarod42 Jan 31 '19 at 15:41
  • @MaxLanghof you can add Alien into main and use it without dll. Dll is needed if the problem with "using" is solved. You can access ISignalMap member and check if it calls functions from dll, not from exe. – Sergey Kolesnik Jan 31 '19 at 15:52
  • 2
    @RichardHodges I don't know which of boost or Qt did have signals slots first, both are there for quite a while... But if I were a library vendor, I'd keep the public interface free from references to third party libraries, too (even if I used them internally...). – Aconcagua Jan 31 '19 at 15:54
  • @Jarod42 I couldn't get Class type and Arguments Pack without them. IF you can suggest a better approach which would eliminate them, you are welcome. – Sergey Kolesnik Jan 31 '19 at 15:54
  • I would go with template struct specialization for the trait. but I meant that `template ReturnType ClassFromPointer(void(ReturnType::*)(ArgTypes...));` is enough, as you use `decltype` on it, so no definition required. – Jarod42 Jan 31 '19 at 15:57
  • Most likely of interest: http://doc.qt.io/qt-5/signalsandslots.html, http://doc.qt.io/qt-5/signalsandslots-syntaxes.html – Aconcagua Jan 31 '19 at 15:57
  • I would implement function traits something like [that](http://coliru.stacked-crooked.com/a/c5f7891d2d01cad7). – Jarod42 Jan 31 '19 at 16:04
  • @Jarod42 this was the first that I had tried, but it didnt compile when I tried to pass class::args_tuple as a parameter pack. I also get error on visual studio express with my last ISignalMap – Sergey Kolesnik Jan 31 '19 at 16:07
  • @SergeyKolesnik That last example code is pretty useful. Note that `return ReturnType;` is not actually legal C++ (if you intend to call the default constructor, use `return ReturnType{};` - similar for the next function). Could you please clarify what the intended usage should look like? After fixing some more basic inconsistencies I can get things to compile for the non-commented version, but it's unclear to me what exactly you are looking to get to. For the record, you can play around with your code on https://godbolt.org/ to check whether your examples compile. – Max Langhof Jan 31 '19 at 16:36
  • @MaxLanghof as Jarod42 has mentioned it is not necessary to have a definition, therefore i will remove them. The way it is expected to be used is just including in your class a member `ISignalMap<&SignalClass::memfunc1, &SignalClass::memfunc2, ...etc> `(where you obviously just defie all the function pointers you want.) Then, when you want to connect a slot you just call `YourClassPtr->AccessSignalMap().RegisterSLot(CreateSlot(pointer to class obj and its function));`. – Sergey Kolesnik Jan 31 '19 at 16:51
  • @MaxLanghof i found a solution. You can check my answer and see how it works) – Sergey Kolesnik Feb 02 '19 at 19:35

1 Answers1

0

Finally I came up with the solution, it's usage is pretty simple, as i wanted.

Here is my working example!

#include <tuple>
#include <iostream>

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

template <class ClType, class ... ArgTypes>
constexpr ClType ClassType(void(ClType::*)(ArgTypes...));

template <class ClType, class ... ArgTypes>
constexpr TW<ArgTypes...> ArgsType(void(ClType::*)(ArgTypes...));

template <auto pFunc>
using class_trait = decltype(ClassType(pFunc));

template <auto pFunc>
using args_trait = decltype(ArgsType(pFunc));

template <class, class>
struct _func_trait;

template <class ClType, template <class...> class Pack, class ... ArgTypes>
struct _func_trait<ClType, Pack<ArgTypes...>>
{
    typedef void(ClType::*FuncPtr)(ArgTypes...);
    typedef ClType ClassType;
    typedef Pack<ArgTypes...> Args;
};

template <auto pFunc>
struct func_traits : public _func_trait<class_trait<pFunc>, args_trait<pFunc>>
{};


template <auto L, class Pack = args_trait<L>>
struct ClassImpl;

template <auto L, template <class ...> class Pack, class ... ArgTypes>
struct ClassImpl<L, Pack<ArgTypes...>>
{
    void invoke(decltype(L), ArgTypes ... args)
    {
        (std::cout << ... << args) << std::endl;
    }
};



template <class ... Impls>
struct ISignalMap : protected Impls...
{
    using Impls::invoke...;
};

template <auto ... L>
struct SignalsMap
{
    //just to see the pointers' values
    static constexpr std::tuple<decltype(L)...> t{ std::make_tuple(L...) };
    ISignalMap<ClassImpl<L>...> Signals{};
};

struct Signals
{
    void func1(int a, double b) {}
    void func12(int a, double b) {}

    void func2(double a, double b, int c) {}

    constexpr void func3(const char*) {}
};


int main(void)
{
    auto& ref = SignalsMap<&Signals::func1, &Signals::func2, &Signals::func3>::t;

    //add SignalsMap as member to your class and pass the pointers to
    //methods you need to be signals
    SignalsMap<&Signals::func1, &Signals::func2, &Signals::func3> sm;

    //first parameter is a pointer to a signal you want to invoke
    sm.Signals.invoke(&Signals::func2, 4.8, 15.16, 23);
    sm.Signals.invoke(&Signals::func1, 23, 42.108);
    sm.Signals.invoke(&Signals::func12, 23, 42.108);

    sm.Signals.invoke(&Signals::func3, "Eat this!");

    return 0;
}
Sergey Kolesnik
  • 3,009
  • 1
  • 8
  • 28
  • 1
    Unclear what you want, but [Following](http://coliru.stacked-crooked.com/a/a896cc351d0a1758) is a simplification IMO. – Jarod42 Feb 04 '19 at 12:53
  • @Jarod42 it surely is. You can post it as an aswer I suppose, or I can modify mine – Sergey Kolesnik Feb 04 '19 at 13:02
  • @Jarod42 the idea is to use function addresses as tags – Sergey Kolesnik Feb 04 '19 at 13:12
  • `tag{}` might be better then. Currently `invoke(&Signals::func1` and `invoke(&Signals::func12` are identical, which might be surprising. – Jarod42 Feb 04 '19 at 18:02
  • By unclear, I mean that the binding is unclear, What should user implement to use your "signal" code ? `ClassImpl`. what is the purpose of `struct Signals` (have signatures) ? – Jarod42 Feb 04 '19 at 18:08
  • @Jarod42 all the user needs to do is to add a SignalMap with function pointers as non-type argumets. Each inherited ClassImpl stores a function address as a non-type parameter, making its invoke functions unique. So when you call invoke(&Signals:func12...) it invokes its own slots, not the slots of func1. Maybe I don't quiet understand what you mean by unclear binding. – Sergey Kolesnik Feb 04 '19 at 18:14
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/187879/discussion-between-sergey-kolesnik-and-jarod42). – Sergey Kolesnik Feb 04 '19 at 18:21