Connect signal to slot, but call the slot only once and then auto-disconnect them

Viewed 5200

Consider this JS code:

function handleSig() {
    emitter.someSig.disconnect(handleSig);
    // do some work here
}

emitter.someSig.connect(handleSig);

Can it be written without the explicit disconnect and the named function?

Ideally, I'd like something like this:

emitter.someSig.connect(
    function() {
        // do some work here
    },
    Qt.SingleShotConnection
);

Near-duplicate: Automatically disconnect after first signal emission - but that question is about Python and mine is about QML and C++.

3 Answers

It would probably most convenient to make a template implementing the same syntax as a regular QObject::connect call.

I've made these two templates below, using Stefan Monov's own answer. The first is method pointer based, the second one is lambda based.

#ifndef  CONNECT_ONCE_H
# define CONNECT_ONCE_H

# include <QObject>
# include <memory>

template<typename EMITTER, typename SIGNAL, typename RECEIVER, typename... ARGS>
void connectOnce(EMITTER* emitter, SIGNAL signal, RECEIVER* receiver, void (RECEIVER::*slot)(ARGS...), Qt::ConnectionType connectionType = Qt::AutoConnection)
{
  auto connection = std::make_shared<QMetaObject::Connection>();
  auto onTriggered = [connection, receiver, slot](ARGS... arguments){
    (receiver->*slot)(arguments...);
    QObject::disconnect(*connection);
  };

  *connection = QObject::connect(emitter, signal, receiver, onTriggered, connectionType);
}

template<typename EMITTER, typename SIGNAL, typename RECEIVER, typename SLOT, typename... ARGS>
void connectOnce(EMITTER* emitter, SIGNAL signal, RECEIVER* receiver, SLOT slot, Qt::ConnectionType connectionType = Qt::AutoConnection)
{
  std::function<void (ARGS...)> callback = slot;
  auto connection = std::make_shared<QMetaObject::Connection>();
  auto onTriggered = [connection, callback](ARGS... arguments) {
    callback(arguments...);
    QObject::disconnect(*connection);
  };

  *connection = QObject::connect(emitter, signal, receiver, onTriggered, connectionType);
}

#endif

About the lambda based template, I couldn't get it to compile when directly declaring std::function as a parameter, as the compiler couldn't recognize a lambda parameter as a valid candidate... which is why I'm declaring SLOT as a template parameter, and then casting it to std::function. It's not as concise as I would've liked, but it works.

Related