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

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