59

Boost.Signals allows various strategies of using the return values of slots to form the return value of the signal. E.g. adding them, forming a vector out of them, or returning the last one.

The common wisdom (expressed in the Qt documentation) is that no such thing is possible with Qt signals.

However, when I run the moc on the following class definition:

class Object : public QObject {
    Q_OBJECT
public:
    explicit Object( QObject * parent=0 )
        : QObject( parent ) {}

public Q_SLOTS:
    void voidSlot();
    int intSlot();

Q_SIGNALS:
    void voidSignal();
    int intSignal();
};

Not only doesn't moc complain about the signal with the non-void return type, it seems to actively implement it in such a way as to allow a return value to pass:

// SIGNAL 1
int Object::intSignal()
{
    int _t0;
    void *_a[] = { const_cast<void*>(reinterpret_cast<const void*>(&_t0)) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
    return _t0;
}

So: according to the docs, this thing isn't possible. Then what is moc doing here?

Slots can have return values, so can we connect a slot with a return value to a signal with a return value now? May that be possible, after all? If so, is it useful?

I'm not asking for workarounds.

It obviously isn't useful in Qt::QueuedConnection mode (neither is the QPrintPreviewWidget API, though, and still it exists and is useful). But what about Qt::DirectConnection and Qt::BlockingQueuedConnection (or Qt::AutoConnection, when it resolves to Qt::DirectConnection).

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
Marc Mutz - mmutz
  • 24,485
  • 12
  • 80
  • 90

5 Answers5

46

It seems this is possible. I was able to emit a signal, and receive value from the slot the signal was connected to. But, the problem was that it only returned the last return value from the multiple connected slots:

Here's a simple class definition (main.cpp):

#include <QObject>
#include <QDebug>

class TestClass : public QObject
{
    Q_OBJECT
public:
    TestClass();

Q_SIGNALS:
    QString testSignal();

public Q_SLOTS:
    QString testSlot1() {
        return QLatin1String("testSlot1");
    }
    QString testSlot2() {
        return QLatin1String("testSlot2");
    }
};

TestClass::TestClass() {
    connect(this, SIGNAL(testSignal()), this, SLOT(testSlot1()));
    connect(this, SIGNAL(testSignal()), this, SLOT(testSlot2()));

    QString a = emit testSignal();
    qDebug() << a;
}

int main() {
    TestClass a;
}

#include "main.moc"

When main runs, it constructs one of the test classes. The constructor wires up two slots to the testSignal signal, and then emits the signal. It captures the return value from the slot(s) invoked.

Unfortunately, you only get the last return value. If you evaluate the code above, you'll get: "testSlot2", the last return value from the connected slots of the signal.

Here's why. Qt Signals are a syntax sugared interface to the signaling pattern. Slots are the recipients of a signal. In a direct connected signal-slot relationship, you could think of it similar to (pseudo-code):

foreach slot in connectedSlotsForSignal(signal):
    value = invoke slot with parameters from signal
return value

Obviously the moc does a little more to help in this process (rudimentary type checking, etc), but this helps paint the picture.

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
jsherer
  • 910
  • 6
  • 8
  • 2
    thanks for actually trying :) I've edited your code to be simpler and shorter. However, the question still stands: if it works (with "last-called" semantics), why do the docs say it doesn't? – Marc Mutz - mmutz May 05 '11 at 19:58
  • A good question. I would assume the docs say that it doesn't work because it is only a partial return value. The true return value of a signal emission *should* be an aggregate of all results based on some kind of aggregator (like in boost). But, without that, it's a partial and undefined result (especially in the context of a concurrent signal call). Perhaps there are some compiler differences as well? – jsherer May 05 '11 at 20:51
  • 1
    Undocumented behaviour means you're not guaranteed to have it still working in, say, Qt 5.0 :) – Torp Aug 10 '11 at 13:28
  • @jordan: I've accepted your answer, because it was closest, and no-one else had anything better for a year now. I've tried it myself, obviously, and the problem is that the return value is, indeed, returned, but not checked for compatibility at connect time. Thus, connecting, say, a `double`-returning slot to a `float`-returning signal has a high chance of crashing at runtime. – Marc Mutz - mmutz May 27 '12 at 10:37
  • I think we can say signals/slots are Qt's implementation of the observer pattern. So even if you manage to solve your issue, I'm still wondering if that's a good idea/design since it allows the observers to modify the behavior of the "observed" (ie. the subject)... Anyone has any thoughts on this? – gpalex Jul 31 '15 at 17:47
  • 5
    If everything is on the same thread it will work because you will have a synchronous function call on the slot. If you have a multi-threaded application the call will be asynchronous and won't have a return value. – Vincent Feb 11 '16 at 15:35
  • Would it work across threads if the connection is made using `Qt::BlockingQueuedConnection`? – Martin Kutny Aug 20 '16 at 05:57
9

No, they can't.

Boost::signals are quite different from those in Qt. The former provide an advanced callback mechanism, whereas the latter implement the signaling idiom. In the context of multithreading, Qt's (cross-threaded) signals depend on message queues, so they are called asynchronously at some (unknown to the emitter's thread) point in time.

vines
  • 5,160
  • 1
  • 27
  • 49
  • 1
    How can you rely on the type of connections, which will occur in run-time, while you write the code using them? These are not templates, Qt is mainly runtime library :) – vines Apr 30 '11 at 14:28
  • So you also think that [QPrintPreviewWidget::paintRequested()](http://doc.trolltech.com/latest/qprintpreviewwidget.html#paintRequested) is marticularly bad API. Me too. Still, it's there, and works. – Marc Mutz - mmutz Apr 30 '11 at 16:20
  • I think this is the only correct answer to the question, since Qt signals/slots mechanism is designed to work also asynchronously. – Emerald Weapon Sep 08 '16 at 09:02
1

You may get a return value from Qt signal with the following code:

My example shows how to use a Qt signal to read the text of a QLineEdit. I'm just extending what @jordan has proposed:

It should be possible to modify your code in such a way to use "out" parameters that act as your "return".

#include <QtCore>
#include <QtGui>

class SignalsRet : public QObject
{
    Q_OBJECT

public:
    SignalsRet()
    {
        connect(this, SIGNAL(Get(QString*)), SLOT(GetCurrentThread(QString*)), Qt::DirectConnection);
        connect(this, SIGNAL(GetFromAnotherThread(QString*)), SLOT(ReadObject(QString*)), Qt::BlockingQueuedConnection);
        edit.setText("This is a test");
    }

public slots:
    QString call()
    {
        QString text;
        emit Get(&text);
        return text;
    }

signals:
    void Get(QString *value);
    void GetFromAnotherThread(QString *value);

private slots:
    void GetCurrentThread(QString *value)
    {
        QThread *thread = QThread::currentThread();
        QThread *mainthread = this->thread();
        if(thread == mainthread) //Signal called from the same thread that SignalsRet class was living
            ReadObject(value);
        else //Signal called from another thread
            emit GetFromAnotherThread(value);
    }

    void ReadObject(QString *value)
    {
        QString text = edit.text();
        *value = text;
    }

private:
    QLineEdit edit;

};

To use this, just request call();.

Antonio Dias
  • 2,751
  • 20
  • 40
1

Qt's qt_metacall function returns an integer status code. Because of this, I believe this makes an actual return value impossible (unless you fudge around with the meta object system and moc files after precompilation).

You do, however, have normal function parameters at your disposal. It should be possible to modify your code in such a way to use "out" parameters that act as your "return".

void ClassObj::method(return_type * return_)
{
    ...

    if(return_) *return_ = ...;
}

// somewhere else in the code...

return_type ret;
emit this->method(&ret);
jsherer
  • 910
  • 6
  • 8
  • I believe this would require a non-async connection, unless you somehow managed to wrangle in a "future" object with more signals. – jsherer May 05 '11 at 14:25
-1

You can try to workaround this with following:

  1. All your connected slots must save their results in some place (container) accessible from signaling object
  2. The last connected slot should somehow (select max or last value) process collected values and expose the only one
  3. The emitting object can try to access this result

Just as an idea.

Baris Demiray
  • 1,539
  • 24
  • 35
Sergey Leyko
  • 369
  • 6
  • 14
  • Unfortunately you can't tell which is the last connected slot, because the library won't tell you and carefully controlling the order of connection kind of defeats the purpose of using signals in the first place. – Jan Hudec May 03 '11 at 13:33