c++qtqobjectstd-variantstd-visit

std::visit for QObject's


I have the following code that does not compile with Qt 6.7.2 on Debian :

#include <QCoreApplication>

#include <iostream>    
#include <variant>

class Foo : public QObject
{
    Q_OBJECT
};

class Bar : public QObject
{
    Q_OBJECT
};

struct Vis
{
    void operator()(const Foo &f)
    {
        std::cout << "foo visitor" << std::endl;
    }
    void operator()(const Bar &b)
    {
        std::cout << "bar visitor" << std::endl;
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    std::variant<Bar, Foo> v;
    Foo f;
    Bar b;

    v = b;
    std::visit(Vis{}, v);
    v = f;
    std::visit(Vis{}, v);

    return a.exec();
}

#include "main.moc"

The error is :

error: no match for ‘operator=’ (operand types are ‘std::variant<Bar, Foo>’ and ‘Bar’)
v = b;

As a workaround it seems ok to use raw pointers to QObject's instead :

struct Vis
{
    void operator()(const Foo *f)
    {
        std::cout << "foo visitor" << std::endl;
    }
    void operator()(const Bar *b)
    {
        std::cout << "bar visitor" << std::endl;
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    std::variant<Bar *, Foo *> v;
    Foo *f;
    Bar *b;

    v = b;
    std::visit(Vis{}, v);
    v = f;
    std::visit(Vis{}, v);

    return a.exec();
}

Am i missing something to store a QObject derived class inside a std::variant?


Solution

  • QObject has no assignment, hence neither Foo nor Bar have an assignment operator. For the reasons I refer you to https://doc.qt.io/qt-5/object.html#identity-vs-value. This would fail with similar eror for the same reason:

    Foo f;
    Foo g;
    f = g;
    

    As Foo and Bar already share a common base you can make use of polymorphism. Rather than using std::visit use virtual functions:

    struct foobarable { 
        virtual ~foobarable() {}
        virtual void foobar() = 0; 
    };
    
    class Foo : public QObject,foobarable
    {
        Q_OBJECT
        void foobar() override { std::cout << "foo" << std::endl; }
    };
    
    class Bar : public QObject,foobarable
    {
        Q_OBJECT
        void foobar() override { std::cout << "bar" << std::endl; }
    };
    

    As mentioned in a comment, if you really want to use std::variant<Foo,Bar> a workaround would be to use emplace to construct the instance in place:

    std::variant<Bar, Foo> v;
    v.emplace<Bar>();
    std::visit(Vis{}, v);
    v.emplace<Foo>();
    std::visit(Vis{}, v);
    

    Full example