Search code examples
c++c++20c++-concepts

restrict types of function using C++20 Concepts


I've created the following class:

using namespace std;

typedef std::variant<long, bool> value;

class settings {
public:
    template<class T>
    void set(const string &name, const T &val) noexcept {
        data_[name] = val;
    }

    template<class T>
    [[nodiscard]] inline const T &get_or_default(const string &name, const T &val) noexcept {
        if (data_.contains(name)) {
            if (auto value = get_if<T>(&data_[name]); value) {
                return *value;
            }
        }
        return val;
    }

private:
    unordered_map<string, value> data_;
};

And this works fine, for example with this usage:

    auto config = settings{};
    config.set("foo", 100L);
    config.set("bar", true);
    cout << "foo: " << config.get_or_default("foo", 0L) << endl;
    cout << "bar: " << config.get_or_default("bar", false) << endl;
    cout << "foobar: " << config.get_or_default("foobar", 500L) << endl;

output

foo: 0
bar: 1
foobar: 500

If I try to use the method set with something that is not valid for the type value (bool or long):

    auto config = settings{};
    config.set("foo", 100.0);

We get an error like this:

\main.cpp(13): error C2679: binary '=': no operator found which takes a right-hand operand of type 'const T' (or there is no acceptable conversion)
        with
        [
            T=double
        ]
variant(1200): note: could be 'std::variant<long,bool> &std::variant<long,bool>::operator =(std::variant<long,bool> &&)'
variant(1200): note: or       'std::variant<long,bool> &std::variant<long,bool>::operator =(const std::variant<long,bool> &)'
variant(1042): note: or       'std::variant<long,bool> &std::variant<long,bool>::operator =(_Ty &&) noexcept(<expr>)'
main.cpp(12): note: 'std::variant<long,bool> &std::variant<long,bool>::operator =(_Ty &&) noexcept(<expr>)': could not deduce template argument for '__formal'
main.cpp(13): note: while trying to match the argument list '(std::variant<long,bool>, const T)'
        with
        [
            T=double
        ]
main.cpp(32): note: see reference to function template instantiation 'void settings::set<double>(const std::string &,const T &) noexcept' being compiled
        with
        [
            T=double
        ]

Similar using the method get_or_default:

    auto config = settings{};
    cout << "foo: " << config.get_or_default("foo", 100) <<endl;

We get this error:

variant(1302): error C2338: static_assert failed: 'get_if<T>(variant<Types...> *) requires T to occur exactly once in Types. (N4835 [variant.get]/9)'
main.cpp(17): note: see reference to function template instantiation 'int *std::get_if<int,long,bool>(std::variant<long,bool> *) noexcept' being compiled
main.cpp(32): note: see reference to function template instantiation 'const T &settings::get_or_default<int>(const std::string &,const T &) noexcept' being compiled
        with
        [
            T=int
        ]

These errors are fine, however I like to use c++ 20 concepts to restrict the types for the method set and get_or_default to give a more simple error to the user of the class that indicates that the types valid are only bool and long.

Is possible as well to give a specific assertion message for those messages using only concepts? Very specifically, not third parties libraries.


Solution

  • Add a declaration:

    #include <type_traits>
    
    template<typename T>
    concept is_value=std::is_same_v<T,long> || std::is_same_v<T,bool>;
    

    and then make a tiny change to the template:

        template<is_value T>
        [[nodiscard]] inline const T &get_or_default(const string &name, const T &val) noexcept {
    

    That's it. Make the same change to the other template function.