Solved Template wizardry for QObject::connect-like method needed.
-
1 clarification, what do you mean by receiver in the context of a lambda?
bind<QMpiQuitMessage>(qApp, &QCoreApplication::quit);
is clear, you want to callqApp->quit();
but what do you mean bybind<QMpiQuitMessage>(qApp, [] (const QMpiQuitMessage &) -> void {});
? how shouldqApp
be used here? -
@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
- theQObject
in whose event loop the lambda is to be executed (the context parameter inQObject::connect
). -
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 thecontext,functor
casetemplate <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
-
Yes, I think that the problem is I have
std::enable_if
for the functor variant, which would mean there's nostd::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. -
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. preventscontext,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
-
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).
-
@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 -
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 conditionint(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 -
@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) -
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 oneconnect
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.