Can Qt signals return a value?
Asked Answered
C

5

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).

Cavite answered 30/4, 2011 at 13:43 Comment(0)
S
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.

Sidon answered 5/5, 2011 at 19:28 Comment(7)
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?Cavite
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?Sidon
Undocumented behaviour means you're not guaranteed to have it still working in, say, Qt 5.0 :)Apiculture
@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.Cavite
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?Deeprooted
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.Perversion
Would it work across threads if the connection is made using Qt::BlockingQueuedConnection?Saire
R
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.

Richellericher answered 30/4, 2011 at 13:57 Comment(3)
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 :)Richellericher
So you also think that QPrintPreviewWidget::paintRequested() is marticularly bad API. Me too. Still, it's there, and works.Cavite
I think this is the only correct answer to the question, since Qt signals/slots mechanism is designed to work also asynchronously.Foretop
S
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);
Sidon answered 5/5, 2011 at 14:22 Comment(1)
I believe this would require a non-async connection, unless you somehow managed to wrangle in a "future" object with more signals.Sidon
H
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();.

Hemlock answered 18/7, 2013 at 17:1 Comment(0)
G
-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.

Gallbladder answered 30/4, 2011 at 16:9 Comment(1)
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.Gorgon

© 2022 - 2024 — McMap. All rights reserved.