Search code examples
c++qtlambdatemplate-argument-deduction

Template Argument deduction fails to match with QT lambda


I have a project with QT 5.7 & Visual Studio 2015 that is emitting a compiler error about deduction rules. I'm a bit of a novice as far as deduction rules goes, so I would like to see if someone could show me how to work around this also explaining what is going on as far function signature matching goes.

I am trying to use a template based smart connector to manage the Signals and Slots in my QT project. I got the inspiration for this approach to signal/slot management from this Q&A in Stack Overflow. The template code which is supposed to automatically manage the signal/slots is as follows:

//! see https://stackoverflow.com/questions/26553029/
//! how-to-disconnect-a-lambda-function-without-storing-connection.
using ListenToken = std::shared_ptr<void>;

struct Disconnecter {
    QMetaObject::Connection mConnection;
    explicit Disconnecter(QMetaObject::Connection&& conn)
        : mConnection(std::move(conn))
    {}

    ~Disconnecter() {
        QObject::disconnect(mConnection);
    }
};

template<class F, class T, class M>
ListenToken QtConnect(T* source, M* method, F&& f) {
    return std::make_shared<Disconnecter>(
        QObject::connect(source, method, std::forward<F>(f))
    );
}

using SignalSlotInfo = std::vector<ListenToken>;

In my mainwindow class I have a SignalSlotInfo member that I use to keep track of the signal/slots so that these can auto disconnect when the application closes of some UI event occurs.

The QT object in question that I am trying to hook up is the QSerialPort. I am trying to connect the QSerialPort's 'bytes written' signal (inherited from its QIODevice parent) to a slot in a lambda, but it fails to compile with the following error:

1>mainwindow.cpp(1246): error C2672: 'QtConnect': no matching overloaded function found
1>mainwindow.cpp(1246): error C2784: 'ListenToken QtConnect(T *,M *,F &&)': could not deduce template argument for 'M *' from 'void (__cdecl QIODevice::* )(qint64)'
1>  c:\users\johnc\main\app739\app739\mainwindow.h(58): note: see declaration of 'QtConnect'

If I try to do this without the use of lambdas (using the QT connect calls) it works fine per....

// Connect Tx/Rx handlers
connect(mPort.get(), &QSerialPort::bytesWritten, this, &MainWindow::processTx);

However invoking the QtConnect template in mainwindow.cpp as follows:

// Connect Tx/Rx handlers
QtConnect(mPort.get(), &QSerialPort::bytesWritten, [&](qint64 bytes) {
    ...
});

Results in the errors indicated above. mPort is a pointer std::unique_ptr<QSerialPort>

The bytesWritten signal is inherited from QSerialPort's parent class QIODevice and has the signature void bytesWritten(qint64 bytes)


Solution

  • Replace M* by M:

    template<class F, class T, class M>
    ListenToken QtConnect(T* source, M method, F&& f) {
        return std::make_shared<Disconnecter>(
            QObject::connect(source, method, std::forward<F>(f))
        );
    }
    

    The reason behind this is that if you leave M* the compiler will try to resolve the template by having M* set to void (QIODevice::*)(qint64). However void (QIODevice::*)(qint64) is a pointer-to-member, not a pointer and M* is explicitly a pointer. This contradiction leaves the compiler unable to resolve the template.