2

My question is essentially the inverse of this Declare abstract signal in interface class. In that problem, you hold a pointer to a slotted QObject and are trying to test another object for an interface containing the signal you want to wire into your slot object.

Suppose the problem is inverted: you hold a pointer to a QObject that emits a signal and you wish to test a series of other QObjects to see if they implement a receiving slot. If so, you would like to connect them all to your signaler.

The design I would like to use would be that the receivers each have a primary (QObject-derived) interface and a secondary slot interface that I might test for, i.e. with Q_DECLARE_INTERACE, qobject_cast, etc.

It seems as though that secondary interface must also be QObject-derived else you cannot connect to it, but that opens up the multiple inheritance problem with QObject.

Is there a way around this other than using a single inheritance chain?

// receiver's primary interface
class IPrimary : public QObject
{
    Q_OBJECT
public:
    // etc.
};

// receiver's secondary interface with slot
class ISecondary : public QObject // QObject won't fly
{
    Q_OBJECT  // nope
public slots:
    void OnReceiptOfSomething(...);

public:
    // etc.

};

// signaler
class Signaler : public QObject
{
   Q_OBJECT

signals:
    void SignalOfSomething(...);

public:
   // etc.
};
Phil K
  • 318
  • 2
  • 7
  • Isn't there already enough information available in [`QMetaObject`](https://doc.qt.io/qt-5/qmetaobject.html) and [`QMetaMethod`](https://doc.qt.io/qt-5/qmetamethod.html) to allow you to do what you want? Although I'm really not sure why you would want to do this. – G.M. Mar 01 '19 at 08:57
  • The meta system will allow me to see that a slot method is there, sure, but I don't think I can write a connect() statement without an interface that is known at compile time. The slot objects are plugin widgets, each doing different things upon receipt of the signal and I want their primary interface to be independent from the slot. I suppose the path of least resistance is to place the slot interface somewhere in a single-line class hierarchy, instead of using a second interface and multiple inheritance. – Phil K Mar 01 '19 at 09:58

1 Answers1

0

I'm not entirely sure I understand what you're after but it appears that given a QObject *emitter and a signal name signal_one you want to find all slots in a second QObject *receiver that are compatible with signal_one and automatically create the required connections. If so then I think that's possible using just the Qt metadata infrastructure -- QMetaObject, QMetaMethod etc.

Firstly defined a function are_compatible which compares two QMetaMethod instances for calling compatibility.

bool are_compatible (const QMetaMethod &signal_meta,
                     const QMetaMethod &slot_meta)
{

  /*
   * Work on the assumption that the arity of the signal must be at least that
   * of the slot.
   */
  if (signal_meta.parameterCount() < slot_meta.parameterCount())
    return false;

  /*
   * Now check that all required parameters have the same type.
   */
  for (int i = 0; i < slot_meta.parameterCount(); ++i) {
    if (signal_meta.parameterType(i) != slot_meta.parameterType(i))
      return false;
  }
  return true;
}

Now define the main function we need that should automatically connect a named signal in the specified emitter to all compatible slots in a specified receiver.

void connect_where_possible (const char *signal_name,
                             QObject *emitter,
                             QObject *receiver)
{
  const auto *emitter_meta_object = emitter->metaObject();

  /*
   * Look for signal_name in the emitter's metadata.
   */
  auto index_of_signal = emitter_meta_object->indexOfSignal(signal_name);
  if (index_of_signal == -1)
    return;

  /*
   * Get the signal's associated QMetaMethod.
   */
  const auto signal_meta_method = emitter_meta_object->method(index_of_signal);

  /*
   * Now go through the receiver's methods.  We could naively attempt to
   * connect to each and every slot knowing that the Qt runtime will only
   * succeed when the signal and slot are compatible.  A nicer/cleaner
   * implementation is to use the metadata available to check for
   * compatibility _before_ attempting to connect -- if only to avoid unwanted
   * warning messages on the console.
   */
  const auto *receiver_meta_object = receiver->metaObject();
  for (int method_index = 0; method_index < receiver_meta_object->methodCount(); ++method_index) {
    const auto receiver_slot_method = receiver_meta_object->method(method_index);
    if (receiver_slot_method.methodType() == QMetaMethod::Slot) {

      /*
       * Found a slot so check it's compatibility and, if ok, try to connect.
       */
      if (are_compatible(signal_meta_method, receiver_slot_method)) {
        QObject::connect(emitter, signal_meta_method, receiver, receiver_slot_method);
      }
    }
  }
}

By way of an example try the following code...

class signaler: public QObject {
  Q_OBJECT;
signals:
  void sig1(int i, float f);
};

class receiver: public QObject {
  Q_OBJECT;
public slots:
  void slot1 (int i, float f) const
    {
      std::cerr << "\nreceiver::slot1(i = " << i << ", f = " << f << ")";
    }
  void slot2 (int i) const
    {
      std::cerr << "\nreceiver::slot2(i = " << i << ")";
    }
  void slot3 (float f) const
    {
      std::cerr << "\nreceiver::slot3(f = " << f << ")";
    }
};

...

signaler signaler;
receiver receiver;
connect_where_possible(QMetaObject::normalizedSignature("sig1(int, float)"), &signaler, &receiver);
signaler.sig1(10, 20.0f);

I see the output...

receiver::slot1(i = 10, f = 20)
receiver::slot2(i = 10)

So, as expected, receiver::slot1 and receiver::slot2 are connected but receiver::slot3 isn't as it's deemed incompatible.

G.M.
  • 12,232
  • 2
  • 15
  • 18
  • This is a very helpful response demonstrating Qt introspection. I suppose I could more simply ask the object "Do you have a slot named OnReceiptOfSomething" and wire it up similarly instead of looking for signature compatibility which you've outlined. – Phil K Mar 01 '19 at 16:23