Connecting signals to slots with less params allowed in Qt?
Asked Answered
P

2

16

Is it valid to call

QObject::connect(a, SIGNAL(somesig(someparam)), b, SLOT(someslot()));

without params? It seems to work (no runtime exception thrown) but I can't find a reference in the docs. All I found is that this is possible if someslot has a default parameter. It is valid in this case. But my method someslot has not the same parameter set as default (no parameter here in the example).

So it seems to be possible to wire signals to slots with less parameters?

Panelboard answered 22/8, 2013 at 18:33 Comment(0)
U
24

Yes, that's fine. There's a short sentence about it in the Signals & Slots documentation:

[...] The signature of a signal must match the signature of the receiving slot. (In fact a slot may have a shorter signature than the signal it receives because it can ignore extra arguments.) [...]

There's even an example like that further down the page where default arguments are explained.

Unsaddle answered 22/8, 2013 at 18:53 Comment(5)
Does this also comply with any C++ standard or is it a feature guaranteed to work per Qt? At least in plain C, this leads to UB via ISO/IEC 9899:TC2 §6.5.2.2p6.Grenadines
@Kamajii: slot invocations are not direct function calls. There is no UB, Qt takes care of calling the function correctly.Unsaddle
Of course are slot invocations just regular calls (+-queued connections etc.). When the signal emission finally reaches the autogenerated (moc) qt_static_metacall member, the very issue arises.Grenadines
It's not a direct function call with all the signals passed in directly precisely because of the metacall stuff that is generated by Qt. The issue does not arise.Unsaddle
I think I got it, and I tried to summarize it altogether in an answer. Thank you!Grenadines
G
3

In terms of standard C++ the Qt solution works out as well.

Emitting a signal is done by calling a method:

emit someSignal(3.14);

The emit keyword actually resolves to an empty #define, so the line above just calls the method someSignal with the given arguments. The method may have been declared inside a QObject-derived class like so:

class SomeObject: public QObject {
    Q_OBJECT
public slots:
    void firstSlot() { /* implementation */ }
    void secondSlot(double) {  /* implementation */ }

signals:
    void someSignal(double);  /* no implementation here */
};

This should seem familiar to you, yet you might have wondered where the actual implementation of your signals comes from. As you might guess, this is where Qt's meta object compiler (MOC) kicks in. For every method declared within the signals section it provides in its generated source an implementation that roughly looks like this:

void SomeObject::someSignal(double _t1)
{
    void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
}

The interesting part is the void *_a[] vector that is filled with pointers to the arguments passed to the signal. Nothing in particular here.

The argument vector is passed to QMetaObject::activate, which in turn does some thread-safety checks and other housekeeping and then starts calling the slots that have been connected to the signal, if any. As the signal-to-slot connections are resolved at runtime (the way that connect() works), a little help from MOC is required again. In particular, MOC also generates a qt_static_metacall() implementation your class:

void SomeObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        SomeObject *_t = static_cast<SomeObject *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->firstSlot(); break;
        case 1: _t->secondSlot((*reinterpret_cast< double(*)>(_a[1]))); break;
        default: ;
        }
    } /* some more magic */
}

As you can see, this method contains the other end to resolve the void *_a[] vector from before to a function call. Also you can see that there is no variadic argument list (using ellipsis, ...) or other questionable trickery involved.

So to enlighten the original question: When, e.g. someSignal(double) is connected to the secondSlot(double) that matches the signal's signature, the call resolves to case 1 in the qt_static_metacall and it just passes the argument as expected.

When connecting the signal to the firstSlot() that has fewer arguments than the signal, the call resolves to case 0 and firstSlot() gets called without arguments. The argument that has been passed to the signal simply stays untouched in its void *_a[] vector.

Grenadines answered 12/2, 2017 at 14:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.