Skip to main content
added 25 characters in body
Source Link
LRDPRDX
  • 941
  • 5
  • 16

The presented code is a MREMinimal Reproducible Example.

The presented code is a MRE.

The presented code is a Minimal Reproducible Example.

Source Link
LRDPRDX
  • 941
  • 5
  • 16

Handle C++ templates in Qt5

Searching for a solution to my problem I've read many posts regarding templates in Qt (including those on SE) and didn't find a complete solution (or I should say a complete example of a solution) so this post. It is not only to review the code but I think it could be useful for others with similar needs.

Motivation

I have a library that provides config structs for different modules like this :

struct UConfig<V2718> // V2718 is a class representing the V895 module
{
    // V2718 specific parameters
};

In the library I also have two functions:

template <typename M_TYPE>
WriteConfigToFile( const UConfig<M_TYPE>& cfg, /*...*/ )

template <typename M_TYPE>
ReadConfigFromFile( UConfig<M_TYPE>& cfg, /*...*/ )

which serialize/deserialize config-struct and write/read it to/from a file.

And I am developing a GUI for these modules with Qt. For every such module there is a window widget that provides interface to the module. The thing is those windows have something in common : for example, every such window have a menu that allows a user to save/load configuration to/from a file. And from the above we conclude that the procedure should be the same regardless the module (window). The scheme is the following (writing):

enter image description here

Of course, the CreateConfig() function is module-specific, but the scheme is kind of polymorphic. And this is true for everything that involves UConfig<M_TYPE>. So the obvious solution would be for every concrete window to inherit from a class template:

template <typename M_TYPE>
class ModuleWindow : public QWidget
{
    Q_OBJECT

    virtual UConfig<M_TYPE> CreateConfig() = 0;
};

class V2718Window : public ModuleWindow<V2718>
{
    Q_OBJECT

    UConfig<V2718> CreateConfig() override;
};

Oops! Qt's Meta Object Compiler doesn't support class templates.

Solution

The solution I'm suggesting is to mix the QVariant and template member functions, and the fact that although you cannot have template slots you can connect the template instance to a signal.

Code

The presented code is a MRE.

config.h

This file simulates a library : something that you cannot (or don't want to) change and what you want to work with.

#include <iostream>
#include <typeinfo>

template <typename M_TYPE>
struct Config;

template<>
struct Config<int> { int a; }; // <--- config for the Int window

template<>
struct Config<double> { double a; }; // <--- config for the Double window

template <typename M_TYPE>
void WriteConfigToFile( const Config<M_TYPE>& cfg ) // Doesn't actually write anything to a file
{
    std::cout << "The a field of " << typeid( cfg ).name() << "is " << cfg.a << "\n";
}

test.h

#include <QWidget>
#include <QVariant>

#include "config.h"

/* This is necessary to represent our configs as QVariant */
Q_DECLARE_METATYPE(Config<int>)
Q_DECLARE_METATYPE(Config<double>)


class QPushButton;

class Base : public QWidget
{
    Q_OBJECT

    protected :
        QPushButton* fButton;

        virtual QVariant CreateConfig() = 0; // <--- builds the config from widgets' data and puts it in QVariant

        template <typename M_TYPE>
        void SaveConfig() // <--- The procedure to save config : implemented once and for all
        {
            Config<M_TYPE> cfg = qvariant_cast<Config<M_TYPE>>( this->CreateConfig() );
            WriteConfigToFile( cfg );
        }

    public :
        Base( QWidget* parent = nullptr );
        virtual ~Base();
};


class Int : public Base
{
    protected :
        QVariant CreateConfig() override;

    public :
        Int( QWidget* parent = nullptr );
        virtual ~Int();
};

class Double : public Base
{
    protected :
        QVariant CreateConfig() override;

    public :
        Double( QWidget* parent = nullptr );
        virtual ~Double();
};

test.cpp

#include <QPushButton>
#include <QHBoxLayout>

#include "test.h"

Base::Base( QWidget* parent ) :
    QWidget( parent )
{
    /* Create the window skeleton : the button to save config
     * BUT not connect its click to anything - it will be done
     * in the concrete module class */
    QHBoxLayout* layout = new QHBoxLayout();
    fButton = new QPushButton( "Write config to file" );
    layout->addWidget( fButton );
    setLayout( layout );
}

Base::~Base() { };


Int::Int( QWidget* parent ) :
    Base( parent )
{
    connect( fButton, &QPushButton::clicked, this, &Base::SaveConfig<int> ); // <--- this window knows the M_TYPE so connect
}

Int::~Int() { }

QVariant Int::CreateConfig()
{
    Config<int> cfg;
    cfg.a = 6;
    QVariant qv;
    qv.setValue( cfg );
    return qv;
}

Double::Double( QWidget* parent ) :
    Base( parent )
{
    connect( fButton, &QPushButton::clicked, this, &Base::SaveConfig<double> );
}

Double::~Double() { }

QVariant Double::CreateConfig()
{
    Config<double> cfg;
    cfg.a = 28.0;
    QVariant qv;
    qv.setValue( cfg );
    return qv;
}

The main function is trivial. To run :

qmake -project "QT += widgets"
qmake
make