0

Background:

I would like to create a settings class in C++ that mimics .NET's Properties.Settings.Default.

For people that don't know what is it, basically it's a singleton class in which you can store application settings.

My attempt:

The singleton class: so far so good, thanks to C++ Singleton design pattern

The properties: this is where I would like to get some infrastructure help

Here's a typical property with a getter and a setter:

QString GetTheme()
{
    return GetValue(ThemeKey, "Material").toString();
}

void SetTheme(const QString &value)
{
    SetValue(ThemeKey, value);
}

This works but you might already guess my question: How to avoid all this boilerplate code ?

Basically what I would like to achieve is some sort of one-liner to define a property, like:

// pseudo code ahead

// this property is of type QString and whose default value is "Material"

property<QString, "Material"> Theme;

// ideally it should be usable like that:

auto theme = Settings::Current.Theme(); // getter

Settings::Current.Theme("..."); // setter

There will be different type of properties, such as int, bool, QString etc.

Question:

Can C++ templates somehow help solving this problem or should I write a good old macro?

I am also ready to accept any decent alternative to this approach.

EDIT:

I just realized that I didn't fully explained myself, sorry about that.

The value of these properties will be fetched by Qt's QSettings class such as:

QSettings settings;
settings.setValue("editor/wrapMargin", 68);

Below I am pasting my current implementation so you have a better understanding of how properties' values are fetched:

#ifndef SETTINGS_H
#define SETTINGS_H

#include <QSettings>
#include <QString>

class Settings
{

private:

    Settings()
    {

    }

    QSettings Store;

    QVariant GetValue(const QString &key, const QVariant &defaultValue = QVariant())
    {
        return Store.value(key, defaultValue);
    }

    void SetValue(const QString &key, const QVariant &value)
    {
        Store.setValue(key, value);
    }

    static const QString ThemeKey;

public:

    Settings(Settings const&) = delete;

    void operator=(Settings const&) = delete;

    static Settings& Current()
    {
        // https://stackoverflow.com/a/1008289/361899

        static Settings instance;

        return instance;
    }


    QString GetTheme()
    {
        return GetValue(ThemeKey, "Material").toString();
    }

    void SetTheme(const QString &value)
    {
        SetValue(ThemeKey, value);
    }
};

const QString Settings::ThemeKey = "ThemeKey";

#endif // SETTINGS_H
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
aybe
  • 15,516
  • 9
  • 57
  • 105

2 Answers2

1

I wouldn't use "Material" and others as template parameter, I mean they are just strings and it doesn't seem necessary to have different types for them. Also I don't know "good old macros", I only know bad old macros ;).

You can write a Property:

template <typename T> 
struct Property {
     std::string category;
     T value;
     Property(const std::string& category) : category(category) {}
     T operator()() { 
         return value;
     }
    Property& operator()(const T& v) { 
        value = v; 
        return *this;
    }
};

Then have members of that in the singleton:

struct Settings {
    Property<int> Theme{"Material"};
};

And use it like this:

int main(){
    Settings s;
    s.Theme(42);
    std::cout << s.Theme();
}
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Looks interesting ! Sorry but I failed to explain one fact in my question and I have updated it if you could please have a look at. With your approach which is close to what I'd like to do, I fail to understand how I can fetched an actual setting value from within the template. Also, it's not about a category, it's the default value one would like to get in case the setting does not yet exist in the property store. – aybe Jun 07 '20 at 11:43
  • @Aybe I think you just need a reference to the `Store` in the `Property`. Pass it in the constructor und use that instead of storing the value directly in the property – 463035818_is_not_an_ai Jun 07 '20 at 11:46
  • @Aybe in general you need to be careful with references to keep the references object alive, but when it is a singleton this might not be a problem at all – 463035818_is_not_an_ai Jun 07 '20 at 12:09
0

Could a namespace be sufficient in your case? Namespaces are kind of singleton instanciated once and for all by the linker and the dynamic initialization at program start up. Moreover namespaces can be extended in different translation units.

namespace Properties{
     namespace Settings {
        namespace Default {
           inline constexpr auto myColor =...
     }}}
Oliv
  • 17,610
  • 1
  • 29
  • 72
  • The problem I see with your answer is, how can a setting that can be modified by the user at runtime be a `constexpr` unless I'm mistaken ? – aybe Jun 07 '20 at 11:44