Here is one way that does not use macros:
class MySettings
{
public:
template <size_t N>
void setParam(QString param) { _settings.setValue(names[N], param); }
template <size_t N, typename T>
T param() { return _settings.value(names[N]).toString(); }
private:
QSettings _settings;
const char* names[3] = { "param1", "param2", "param3" };
}
You change the syntax a little so say e.g. settings.setParam<1>("string")
and settings.param<1, string>()
but in any case names param1, param2
etc were not so informative.
The only inconvenience is that the caller needs to specify the return type of param()
apart from the parameter number. To get rid of this, you can specify all parameter types within MySettings
, like this:
class MySettings
{
using types = std::tuple<string, int, int>;
public:
template<size_t N>
void setParam(QString param) { _settings.setValue(names[N], param); }
template<size_t N>
typename std::tuple_element<N, types>::type
param() { return _settings.value(names[N]).toString(); }
private:
QSettings _settings;
const char* names[3] = { "param1", "param2", "param3" };
}
You could of course further generalize this class to be used as a base for other settings classes. Within the base, the only things that need to be customized are members types
and names
.
However, keep in mind that if parameter names are informative indeed unlike your example, e.g. setTitle
, setColor
etc. then most probably there is no way to avoid macros. In this case, I prefer a macro that generates an entire struct rather than a piece of code within another class, hence probably polluting its scope. So there could be a struct for each individual parameter, generated by a macro given the parameter name. The settings class would then inherit all those individual structs.
EDIT
I "forgot" generalizing toString()
in param()
(thanks @Joker_vD). One way is this:
template<size_t N>
typename std::tuple_element<N, types>::type
param() {
using T = typename std::tuple_element<N, types>::type;
return get_value(type<T>(), _settings.value(names[N]));
}
where get_value<T>()
is a helper function that you need to define and overload for the types supported by QSettings
, calling the appropriate conversion member function for each type, for instance
template<typename V>
string get_value(type<string>, const V& val) { return val.toString(); }
template<typename V>
int get_value(type<int>, const V& val) { return val.toInt(); }
and type
is just a helper struct:
template<typename T>
struct type { };
If QSettings
itself was designed with templates in mind, you wouldn't need this. But you probably wouldn't need a wrapper in the first place.