Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Special Interest Groups
  3. C++ Gurus
  4. Template wizardry for QObject::connect-like method needed.
Forum Updated to NodeBB v4.3 + New Features

Template wizardry for QObject::connect-like method needed.

Scheduled Pinned Locked Moved Solved C++ Gurus
11 Posts 2 Posters 4.3k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • VRoninV Offline
    VRoninV Offline
    VRonin
    wrote on last edited by
    #2

    1 clarification, what do you mean by receiver in the context of a lambda?
    bind<QMpiQuitMessage>(qApp, &QCoreApplication::quit); is clear, you want to call qApp->quit(); but what do you mean by bind<QMpiQuitMessage>(qApp, [] (const QMpiQuitMessage &) -> void {});? how should qApp be used here?

    "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
    ~Napoleon Bonaparte

    On a crusade to banish setIndexWidget() from the holy land of Qt

    kshegunovK 1 Reply Last reply
    0
    • VRoninV VRonin

      1 clarification, what do you mean by receiver in the context of a lambda?
      bind<QMpiQuitMessage>(qApp, &QCoreApplication::quit); is clear, you want to call qApp->quit(); but what do you mean by bind<QMpiQuitMessage>(qApp, [] (const QMpiQuitMessage &) -> void {});? how should qApp be used here?

      kshegunovK Offline
      kshegunovK Offline
      kshegunov
      Moderators
      wrote on last edited by kshegunov
      #3

      @VRonin said in Template wizardry for QObject::connect-like method needed.:

      what do you mean by receiver in the context of a lambda?

      Exactly the same meaning as with QObject::connect - the QObject in whose event loop the lambda is to be executed (the context parameter in QObject::connect).

      Read and abide by the Qt Code of Conduct

      1 Reply Last reply
      0
      • VRoninV Offline
        VRoninV Offline
        VRonin
        wrote on last edited by VRonin
        #4

        ok, so can you use what QObject::connect uses? You are using QtPrivate already so it shouldn't be much of a burden.

        basically this QtPrivate::FunctionPointer<Slot>::IsPointerToMemberFunction covers the slot case, !QtPrivate::FunctionPointer<Slot>::IsPointerToMemberFunction covers the context,functor case

        template <class MessageType, class Func1>
        inline typename std::enable_if<int(QtPrivate::FunctionPointer<Slot>::ArgumentCount) == 1 &&
        !QtPrivate::FunctionPointer<Func1>::IsPointerToMemberFunction, QMetaObject::Connection>::type QMpiCommunicator::bind(const QObject *context, Func1 slot, Qt::ConnectionType type)
        {
            static_assert(std::is_base_of<QMpiMessage, MessageType>::value, "All MPI message classes must derive from QMpiMessage");
            return QObject::connect(notifier(QtMpi::messageType<MessageType>()), &QMpiMessageNotifier::received, context, [slot] (const QSharedPointer<QMpiMessage> & message) -> void {
                std::bind(slot,reinterpret_cast<const MessageType &>(*message.data())();
            }, type);
        }
        

        EDIT

        I see in your snippet you already did this

        "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
        ~Napoleon Bonaparte

        On a crusade to banish setIndexWidget() from the holy land of Qt

        kshegunovK 1 Reply Last reply
        1
        • VRoninV VRonin

          ok, so can you use what QObject::connect uses? You are using QtPrivate already so it shouldn't be much of a burden.

          basically this QtPrivate::FunctionPointer<Slot>::IsPointerToMemberFunction covers the slot case, !QtPrivate::FunctionPointer<Slot>::IsPointerToMemberFunction covers the context,functor case

          template <class MessageType, class Func1>
          inline typename std::enable_if<int(QtPrivate::FunctionPointer<Slot>::ArgumentCount) == 1 &&
          !QtPrivate::FunctionPointer<Func1>::IsPointerToMemberFunction, QMetaObject::Connection>::type QMpiCommunicator::bind(const QObject *context, Func1 slot, Qt::ConnectionType type)
          {
              static_assert(std::is_base_of<QMpiMessage, MessageType>::value, "All MPI message classes must derive from QMpiMessage");
              return QObject::connect(notifier(QtMpi::messageType<MessageType>()), &QMpiMessageNotifier::received, context, [slot] (const QSharedPointer<QMpiMessage> & message) -> void {
                  std::bind(slot,reinterpret_cast<const MessageType &>(*message.data())();
              }, type);
          }
          

          EDIT

          I see in your snippet you already did this

          kshegunovK Offline
          kshegunovK Offline
          kshegunov
          Moderators
          wrote on last edited by
          #5

          Yes, I think that the problem is I have std::enable_if for the functor variant, which would mean there's no std::enable_if<...>::type defined if the condition's false. This corresponds to the error I'm getting, but I'm unsure how to proceed so I can do the typecast when I have a functor with one argument and just ignore it when I have a functor with no arguments.

          Read and abide by the Qt Code of Conduct

          1 Reply Last reply
          0
          • VRoninV Offline
            VRoninV Offline
            VRonin
            wrote on last edited by VRonin
            #6

            Ok, now I got it. the link I posted above had the answer:

            //Connect a signal to a pointer to qobject member function
                template <class MessageType,typename Func1, typename Func2>
                QMetaObject::Connection bind(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
                                                 const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot,
            Qt::ConnectionType type = Qt::AutoConnection)
            
            //connect to a function pointer  (not a member)
                template <class MessageType,typename Func1, typename Func2>
                typename std::enable_if<int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) >= 0, QMetaObject::Connection>::type
            bind(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, Func2 slot)
            
            //connect to a function pointer  (not a member)
                template <class MessageType,typename Func1, typename Func2>
               typename std::enable_if<int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) >= 0 &&
                                                      !QtPrivate::FunctionPointer<Func2>::IsPointerToMemberFunction, QMetaObject::Connection>::type
                        bind(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const QObject *context, Func2 slot,
            Qt::ConnectionType type = Qt::AutoConnection)
            

            Basically you are restricting too much the first case which triggers the error. QtPrivate::FunctionPointer<Func2>::Object* is what actually resolves the overload in the correct way (i.e. prevents context,functor to bind to the first version)

            EDIT:

            to discriminate between 1 and 0 arguments you can just use, in the body,

            #if int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) == 0
            
            #elif  int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) == 1
            
            #else
            static_assert(false,"the slot can't have more than 1 argument");
            #endif
            

            "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
            ~Napoleon Bonaparte

            On a crusade to banish setIndexWidget() from the holy land of Qt

            kshegunovK 1 Reply Last reply
            1
            • VRoninV VRonin

              Ok, now I got it. the link I posted above had the answer:

              //Connect a signal to a pointer to qobject member function
                  template <class MessageType,typename Func1, typename Func2>
                  QMetaObject::Connection bind(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
                                                   const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot,
              Qt::ConnectionType type = Qt::AutoConnection)
              
              //connect to a function pointer  (not a member)
                  template <class MessageType,typename Func1, typename Func2>
                  typename std::enable_if<int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) >= 0, QMetaObject::Connection>::type
              bind(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, Func2 slot)
              
              //connect to a function pointer  (not a member)
                  template <class MessageType,typename Func1, typename Func2>
                 typename std::enable_if<int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) >= 0 &&
                                                        !QtPrivate::FunctionPointer<Func2>::IsPointerToMemberFunction, QMetaObject::Connection>::type
                          bind(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const QObject *context, Func2 slot,
              Qt::ConnectionType type = Qt::AutoConnection)
              

              Basically you are restricting too much the first case which triggers the error. QtPrivate::FunctionPointer<Func2>::Object* is what actually resolves the overload in the correct way (i.e. prevents context,functor to bind to the first version)

              EDIT:

              to discriminate between 1 and 0 arguments you can just use, in the body,

              #if int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) == 0
              
              #elif  int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) == 1
              
              #else
              static_assert(false,"the slot can't have more than 1 argument");
              #endif
              
              kshegunovK Offline
              kshegunovK Offline
              kshegunov
              Moderators
              wrote on last edited by kshegunov
              #7

              Okay, using:

                  //Connect a signal to a pointer to qobject member function
                  template <class MessageType, typename Slot>
                  typename std::enable_if<QtPrivate::FunctionPointer<Slot>::IsPointerToMemberFunction, QMetaObject::Connection>::type bind(const typename QtPrivate::FunctionPointer<Slot>::Object * receiver, Slot slot, Qt::ConnectionType type = Qt::AutoConnection)
                  {
                  }
              
                  //connect to a function pointer  (not a member)
                  template <class MessageType, typename Functor>
                  typename std::enable_if<!QtPrivate::FunctionPointer<Functor>::IsPointerToMemberFunction, QMetaObject::Connection>::type bind(Functor slot)
                  {
                      return bind<MessageType, Functor>(this, slot, Qt::DirectConnection);
                  }
              
                  //connect to a function pointer  (not a member)
                  template <class MessageType, typename Functor>
                  typename std::enable_if<!QtPrivate::FunctionPointer<Functor>::IsPointerToMemberFunction, QMetaObject::Connection>::type bind(const QObject * context, Functor slot, Qt::ConnectionType type = Qt::AutoConnection)
                  {
                  }
              

              Matches and compiles. However the second part - with the preprocessor - seems rather odd. I don't see how it'd work (and g++ agrees).

              Read and abide by the Qt Code of Conduct

              VRoninV 1 Reply Last reply
              0
              • kshegunovK kshegunov

                Okay, using:

                    //Connect a signal to a pointer to qobject member function
                    template <class MessageType, typename Slot>
                    typename std::enable_if<QtPrivate::FunctionPointer<Slot>::IsPointerToMemberFunction, QMetaObject::Connection>::type bind(const typename QtPrivate::FunctionPointer<Slot>::Object * receiver, Slot slot, Qt::ConnectionType type = Qt::AutoConnection)
                    {
                    }
                
                    //connect to a function pointer  (not a member)
                    template <class MessageType, typename Functor>
                    typename std::enable_if<!QtPrivate::FunctionPointer<Functor>::IsPointerToMemberFunction, QMetaObject::Connection>::type bind(Functor slot)
                    {
                        return bind<MessageType, Functor>(this, slot, Qt::DirectConnection);
                    }
                
                    //connect to a function pointer  (not a member)
                    template <class MessageType, typename Functor>
                    typename std::enable_if<!QtPrivate::FunctionPointer<Functor>::IsPointerToMemberFunction, QMetaObject::Connection>::type bind(const QObject * context, Functor slot, Qt::ConnectionType type = Qt::AutoConnection)
                    {
                    }
                

                Matches and compiles. However the second part - with the preprocessor - seems rather odd. I don't see how it'd work (and g++ agrees).

                VRoninV Offline
                VRoninV Offline
                VRonin
                wrote on last edited by VRonin
                #8

                @kshegunov said in Template wizardry for QObject::connect-like method needed.:

                However the second part - with the preprocessor - seems rather odd. I don't see how it'd work (and g++ agrees).

                I thought the pre-processor was smart enough. It might sound "dirty" then but I'd just use a if/elseif or switch in the body and let the compiler optimise out the part that will never be executed. This is one of the cases described in your post where you'd do constexpr injection

                "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
                ~Napoleon Bonaparte

                On a crusade to banish setIndexWidget() from the holy land of Qt

                VRoninV kshegunovK 2 Replies Last reply
                0
                • VRoninV VRonin

                  @kshegunov said in Template wizardry for QObject::connect-like method needed.:

                  However the second part - with the preprocessor - seems rather odd. I don't see how it'd work (and g++ agrees).

                  I thought the pre-processor was smart enough. It might sound "dirty" then but I'd just use a if/elseif or switch in the body and let the compiler optimise out the part that will never be executed. This is one of the cases described in your post where you'd do constexpr injection

                  VRoninV Offline
                  VRoninV Offline
                  VRonin
                  wrote on last edited by VRonin
                  #9

                  Actually I have a better solution:

                  // private:
                  template <class MessageType, typename Functor, 1>
                      typename std::enable_if<!QtPrivate::FunctionPointer<Functor>::IsPointerToMemberFunction, QMetaObject::Connection>::type bind(const QObject * context, Functor slot, Qt::ConnectionType type = Qt::AutoConnection)
                      {
                  // 1 argument
                      }
                  template <class MessageType, typename Functor, 0>
                      typename std::enable_if<!QtPrivate::FunctionPointer<Functor>::IsPointerToMemberFunction, QMetaObject::Connection>::type bind(const QObject * context, Functor slot, Qt::ConnectionType type = Qt::AutoConnection)
                      {
                  // 0 arguments
                      }
                  // public:
                  template <class MessageType, typename Functor>
                      typename std::enable_if<!QtPrivate::FunctionPointer<Functor>::IsPointerToMemberFunction, QMetaObject::Connection>::type bind(const QObject * context, Functor slot, Qt::ConnectionType type = Qt::AutoConnection)
                      {
                  return bind<MessageType, Functor, int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) >(context,slot,type);
                      }
                  

                  P.S.
                  I suspect the condition int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) >= 0 put in by Qt's dev has a reason to live (even though I can't think of one at the moment) so I would not trash it just yet

                  "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
                  ~Napoleon Bonaparte

                  On a crusade to banish setIndexWidget() from the holy land of Qt

                  1 Reply Last reply
                  1
                  • VRoninV VRonin

                    @kshegunov said in Template wizardry for QObject::connect-like method needed.:

                    However the second part - with the preprocessor - seems rather odd. I don't see how it'd work (and g++ agrees).

                    I thought the pre-processor was smart enough. It might sound "dirty" then but I'd just use a if/elseif or switch in the body and let the compiler optimise out the part that will never be executed. This is one of the cases described in your post where you'd do constexpr injection

                    kshegunovK Offline
                    kshegunovK Offline
                    kshegunov
                    Moderators
                    wrote on last edited by kshegunov
                    #10

                    @VRonin said in Template wizardry for QObject::connect-like method needed.:

                    I thought the pre-processor was smart enough.

                    At the time it runs there's no notion of C++ code, so it can't know what the hell we're trying to feed it ... ;)

                    This is one of the cases described in your post where you'd do constexpr injection

                    Yeah, I sp'ose that's true. I'll try to make it work from here.

                    @VRonin said in Template wizardry for QObject::connect-like method needed.:

                    Actually I have a better solution

                    Yes, that was what I was thinking of too. It's called tagging or something akin.

                    I can't think of one at the moment

                    QtPrivate::FunctionPointer<Func2>::ArgumentCount == -1 means unknown number of arguments (for whatever reason)

                    Read and abide by the Qt Code of Conduct

                    1 Reply Last reply
                    2
                    • kshegunovK Offline
                      kshegunovK Offline
                      kshegunov
                      Moderators
                      wrote on last edited by
                      #11

                      After dissecting Qt's QObject::connect implementation thoroughly I can say I have now a much better understanding of what is going on. It's a mess out there folks, the amount of instantiations for each one connect call is just mind-boggling. Anyway, the problem is now solved. One needs 4 distinct implementations to support what I want to have, one each for pointer to members, global/static functions with context object, functors (lambdas and such) with context object and one for function-like objects without context.

                      In the end I had to do some special substitutions to fool the compatibility checks but ultimately the code compiles and matches correctly now. Just for completeness here's what I came up:

                      // Bind to pointer-to-members
                      template <class MessageType, typename Slot>
                      typename QMetaObject::Connection bind(const typename QtPrivate::FunctionPointer<Slot>::Object * receiver, Slot slot, Qt::ConnectionType type = Qt::AutoConnection)
                      {
                          typedef QtPrivate::FunctionPointer<decltype(&QMpiMessageNotifier::received)> SignalType;
                          typedef QtPrivate::FunctionPointer<Slot> SlotType;
                      
                          typedef QtPrivate::List<const MessageType &> SignalArguments;
                      
                          // Check the compatibility of arguments (from Qt's source).
                          Q_STATIC_ASSERT_X(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount), "The slot requires more arguments than the signal provides.");
                          Q_STATIC_ASSERT_X((QtPrivate::CheckCompatibleArguments<SignalArguments, typename SlotType::Arguments>::value), "Signal and slot arguments are not compatible.");
                          Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<typename SlotType::ReturnType, SignalType::ReturnType>::value), "Return type of the slot is not compatible with the return type of the signal.");
                      
                          return QObject::connect(notifier(QtMpi::messageType<MessageType>()), &QMpiMessageNotifier::received, receiver, [slot] (const QSharedPointer<QMpiMessage> & message) -> void {
                              QMpiMessage * msg = message.data();
                              SlotType::template call<typename SlotType::Arguments, typename SlotType::ReturnType>(slot, nullptr, reinterpret_cast<void **>(&msg));
                          } , type);
                      }
                      
                      // Bind to static/global functions with a context object
                      template <class MessageType, typename Functor>
                      typename std::enable_if<QtPrivate::FunctionPointer<Functor>::ArgumentCount >= 0 && !QtPrivate::FunctionPointer<Functor>::IsPointerToMemberFunction, QMetaObject::Connection>::type bind(const QObject * context, Functor slot, Qt::ConnectionType type = Qt::AutoConnection)
                      {
                          typedef QtPrivate::FunctionPointer<decltype(&QMpiMessageNotifier::received)> SignalType;
                          typedef QtPrivate::FunctionPointer<Functor> SlotType;
                      
                          typedef QtPrivate::List<const MessageType &> SignalArguments;
                      
                          // Check the compatibility of arguments (from Qt's source).
                          Q_STATIC_ASSERT_X(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount), "The slot requires more arguments than the signal provides.");
                          Q_STATIC_ASSERT_X((QtPrivate::CheckCompatibleArguments<SignalArguments, typename SlotType::Arguments>::value), "Signal and slot arguments are not compatible.");
                          Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<typename SlotType::ReturnType, typename SignalType::ReturnType>::value), "Return type of the slot is not compatible with the return type of the signal.");
                      
                          return QObject::connect(notifier(QtMpi::messageType<MessageType>()), &QMpiMessageNotifier::received, context, [slot] (const QSharedPointer<QMpiMessage> & message) -> void {
                              QMpiMessage * msg = message.data();
                              QtPrivate::FunctionPointer<Functor>::template call<typename QtPrivate::FunctionPointer<Functor>::Arguments, void>(slot, nullptr, reinterpret_cast<void **>(&msg));
                          }, type);
                      }
                      
                      // Bind to functors (lambdas included) with a context object
                      template <class MessageType, typename Functor>
                      typename std::enable_if<QtPrivate::FunctionPointer<Functor>::ArgumentCount == -1, QMetaObject::Connection>::type bind(const QObject * context, Functor slot, Qt::ConnectionType type = Qt::AutoConnection)
                      {
                          typedef QtPrivate::List<const MessageType &> SignalArguments;
                      
                          const int FunctorArgumentCount = QtPrivate::ComputeFunctorArgumentCount<Functor, SignalArguments>::Value;
                      
                          Q_STATIC_ASSERT_X((FunctorArgumentCount >= 0), "Signal and slot arguments are not compatible.");
                      
                          const int SlotArgumentCount = (FunctorArgumentCount >= 0) ? FunctorArgumentCount : 0;
                          auto functor = new QtPrivate::QFunctorSlotObject<Functor, SlotArgumentCount, typename QtPrivate::List_Left<SignalArguments, SlotArgumentCount>::Value, void>(slot);
                      
                          return QObject::connect(notifier(QtMpi::messageType<MessageType>()), &QMpiMessageNotifier::received, context, [functor] (const QSharedPointer<QMpiMessage> & message) -> void {
                              QMpiMessage * msg = message.data();
                              functor->call(nullptr, reinterpret_cast<void **>(&msg));
                          }, type);
                      }
                      
                      // Bind to functors or functions (no context object)
                      template <class MessageType, typename Functor>
                      typename QMetaObject::Connection bind(Functor slot)
                      {
                          return bind<MessageType, Functor>(this, slot, Qt::DirectConnection);
                      }
                      

                      A big thanks to Luca for playing along, and I hope this may be useful to someone in the future.

                      If you have any questions that I can answer, don't hesitate to ask.

                      Read and abide by the Qt Code of Conduct

                      1 Reply Last reply
                      1

                      • Login

                      • Login or register to search.
                      • First post
                        Last post
                      0
                      • Categories
                      • Recent
                      • Tags
                      • Popular
                      • Users
                      • Groups
                      • Search
                      • Get Qt Extensions
                      • Unsolved