Minimal Property system in c++17 So I needed to represent by objects in terms in ini format and change their values from the ini as well, so I thought why don't implement a property system for this. I want it to be bit fast so I would really like if anyone can remove the std::function if possible but its not necessary. The target application is GUI so small performance should not be a big issue, but still, I think this can be done in a faster way but I can't think of any. no one likes lag. Also, the properties are around 100 or more

#include &lt;functional&gt;
#include &lt;stdexcept&gt;
#include &lt;string&gt;
#include &lt;type_traits&gt;
#include &lt;unordered_map&gt;
#include &lt;variant&gt;

#include &lt;QDebug&gt;

template &lt;typename C&gt; class PropertySet {
public:
    using PropertyValue = std::variant&lt;bool, int, float, std::string&gt;;

    void set(const std::string &amp;prop, PropertyValue v) {
        auto p = m_properties.find(prop);
        if (p == m_properties.end()) {
            throw std::runtime_error("Invalid property key");
        }
        // p.value().setter(v);
        p-&gt;second.setter(v);
    }

    void set(const std::string &amp;prop, const std::string &amp;v) {
        auto p = m_properties.find(prop);
        if (p == m_properties.end()) {
            throw std::runtime_error("Invalid property key");
        }
        // p.value().setter(p.value().fromStr(v));
        p-&gt;second.setter(p-&gt;second.fromStr(v));
    }

    std::unordered_map&lt;std::string, std::string&gt; getAllValues() {
        std::unordered_map&lt;std::string, std::string&gt; r;
        qDebug() &lt;&lt; m_properties.size();
        for (const auto &amp;p : m_properties) {
            // r[p.key()] = p.value().toStr();
            r[p.first] = p.second.toStr();
        }
        return r;
    }

private:
    struct Payload {
        std::function&lt;std::string()&gt; toStr;
        std::function&lt;PropertyValue(const std::string &amp;)&gt; fromStr;
        std::function&lt;PropertyValue()&gt; getter;
        std::function&lt;void(PropertyValue)&gt; setter;
    };
    std::unordered_map&lt;std::string, Payload&gt; m_properties;

protected:
    template &lt;typename T&gt; static auto fromStr(const std::string &amp;s) {
        if constexpr (std::is_same&lt;T, int&gt;::value) {
            return std::stoi(s);
        } else if constexpr (std::is_same&lt;T, bool&gt;::value) {
            return s == "true" || s == "1";
        }
        static_assert("Error");
    }

    template &lt;typename T, typename Getter, typename Setter&gt;
    void add(const std::string &amp;name, Getter getter, Setter setter) {
        auto cptr = static_cast&lt;C *&gt;(this);
        Payload p;
        p.toStr = [getter, cptr]() { return std::to_string(std::invoke(getter, cptr)); };
        p.fromStr = [](const std::string &amp;s) { return PropertySet::fromStr&lt;T&gt;(s); };
        p.getter = [getter, cptr]() { return PropertyValue(std::invoke(getter, cptr)); };
        p.setter = [setter, cptr](PropertyValue v) { std::invoke(setter, cptr, std::get&lt;int&gt;(v)); };
        m_properties[name] = p;
    }
};


Also, I'm using Qt, so I was thinking of replacing std::string with QString or QByteArray and std::unordered_map with QHash.
There are several ways to approach this.

The classic object oriented way is to make Payload be a simple interface class

class IPayload {
    virtual ~IPayload() = default;
    virtual std::string   toStr() = 0;
    virtual PropertyValue fromStr(const std::string &amp;) = 0;
    virtual PropertyValue get() = 0;
    virtual void          set(PropertyValue) = 0;
}; 


And just create for each property its own instance of an implementation of the interface - depending on what kind of property one uses. This can be helpful when you need variety updating mechanisms to work simultaneously. Say, one Payload that stores the field only in memory, one that stores into both in memory and in a file, and one that forwards it to GUI - or a combination of these. This can be also helpful when you require specialized treatment for some properties - like time input property - that upon receiving std::string input it processes it according to some time conversion, i.e., read double and then converts it to seconds depending on the time unit specification, e.g., "5min" converts to 300 (seconds). The only issue is that you ought to instantiate a treatment for each property - which can be a hassle and make the class unsuitable for some trivial basic usages.

I don't see any circumstances when the std::function approach would better - unless one has to runtime change setters or something - which is kinda odd honestly.

Also you can try going in the opposite direction and detach the storage class from the "customized actions". Say, make a simple multipurpose ini file parser wrapping std::unordered_map&lt;std::string,std::string&gt; - or use an existing one like boost::ptree that can read and write xml and json in addition. While in the GUI simply call the right functions when user sets the fields. Whether it is viable / not viable / preferred option depends a lot on your development platform for the GUI as well as other nuances.

You can in fact combine the two methods. Use the boost::ptree for storing / parsing and for internal usage whilst class PropertySet make responsible for the GUI handling and just instantiate one from each other upon loading / saving / updating.