使用C++20概念(Concepts)限制函数的类型

huangapple go评论59阅读模式
英文:

restrict types of function using C++20 Concepts

问题

我已经创建了以下类:

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_;
};

并且这可以正常工作,例如在以下示例中:

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;

输出

foo: 0
bar: 1
foobar: 500

如果我尝试使用set方法传递的类型不适用于value类型(boollong):

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

我们会得到如下错误:

\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
        ]

使用get_or_default方法会产生类似的错误:

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

我们会得到以下错误:

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
        ]

这些错误是可以接受的,但是我想使用C++ 20的concepts限制setget_or_default方法的类型,以便给类的用户提供一个更简单的错误消息,指示只有boollong有效的类型

是否可以使用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.

答案1

得分: 2

添加一个声明:

#include <type_traits>

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

然后对模板进行微小的更改:

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

这就是全部。对另一个模板函数做相同的更改。

英文:

Add a declaration:

#include &lt;type_traits&gt;
template&lt;typename T&gt;
concept is_value=std::is_same_v&lt;T,long&gt; || std::is_same_v&lt;T,bool&gt;;

and then make a tiny change to the template:

    template&lt;is_value T&gt;
[[nodiscard]] inline const T &amp;get_or_default(const string &amp;name, const T &amp;val) noexcept {

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

答案2

得分: 2

不使用概念,但你可以以一种不需要模板的方式编写你的设置,如下所示:

template <typename... Ts>
struct settings_base
{
    std::unordered_map<std::string, std::variant<Ts...>> data_;
};

template <typename Base, typename T>
struct settings_node : virtual Base
{
    void set(const std::string& name, const T& value)
    {
        Base::data_[name] = value;
    }

    [[nodiscard]] const T& get_or_default(const std::string &name, const T &val) noexcept
    {
        auto it = Base::data_.find(name);
        if (it != Base::data_.end()) {
            if (auto value = std::get_if<T>(&it->second); value) {
                return *value;
            }
        }
        return val;
    }
};

template <typename... Ts>
class settings_impl : settings_node<settings_base<Ts...>, Ts>...
{
public:
    using settings_node<settings_base<Ts...>, Ts>::set...;
    using settings_node<settings_base<Ts...>, Ts>::get_or_default...;
}

// 然后
using settings = settings_impl<bool, long /*std::string*/>;

这允许与模板方式相反的转换。请注意,"xxxx"const char [N])将选择bool重载而不是std::string重载,这是由于重载规则造成的。

英文:

Not using concept, but you could write your settings in a way that set/get_or_default are not template:

template &lt;typename... Ts&gt;
struct settings_base
{
std::unordered_map&lt;std::string, std::variant&lt;Ts...&gt;&gt; data_;
};
template &lt;typename Base, typename T&gt;
struct settings_node : virtual Base
{
void set(const std::string&amp; name, const T&amp; value)
{
Base::data_[name] = value;
}
[[nodiscard]] const T &amp;get_or_default(const std::string &amp;name, const T &amp;val) noexcept
{
auto it = Base::data_.find(name);
if (it != Base::data_.end()) {
if (auto value = std::get_if&lt;T&gt;(&amp;it-&gt;second); value) {
return *value;
}
}
return val;
}
};
template &lt;typename... Ts&gt;
class settings_impl : settings_node&lt;settings_base&lt;Ts...&gt;, Ts&gt;...
{
public:
using settings_node&lt;settings_base&lt;Ts...&gt;, Ts&gt;::set...;
using settings_node&lt;settings_base&lt;Ts...&gt;, Ts&gt;::get_or_default...;
// So equivalent to:
// void set(const std::string&amp;, const T1&amp; value);
// ..
// void set(const std::string&amp;, const TN&amp; value);
};

And then

using settings = settings_impl&lt;bool, long /*std::string*/&gt;;

That allows conversion contrary to template way.

Note though that &quot;xxxx&quot; (const char [N]) would select bool overload and not std::string one, due to overload rules.

答案3

得分: 1

不是一个概念,而是一种不同的方法:

如果您希望将 getset 限制为仅接受 longbool,您可以使用非模板重载:

class settings {
public:
    void set(const std::string &name, const long &val) noexcept {
        set_impl(name, val);
    }

    [[nodiscard]] const long &get_or_default(const std::string &name, const long &val) noexcept {
        return get_impl(name, val);
    }

    void set(const std::string &name, const bool &val) noexcept {
        set_impl(name, val);
    }

    [[nodiscard]] const bool &get_or_default(const std::string &name, const bool &val) noexcept {
        return get_impl(name, val);
    }
private:
    template<class T>
    void set_impl(const std::string &name, const T &val) noexcept {
        data_[name] = val;
    }

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

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

请注意,上述代码部分中的注释和代码不被翻译。

英文:

Not a concept, but a different approach:

If you want to limit get and set to exactly long or bool, you can have non-template overloads:

class settings {
public:
void set(const std::string &amp;name, const long &amp;val) noexcept {
set_impl(name, val);
}
[[nodiscard]] const long &amp;get_or_default(const std::string &amp;name, const long &amp;val) noexcept {
return get_impl(name, val);
}
void set(const std::string &amp;name, const bool &amp;val) noexcept {
set_impl(name, val);
}
[[nodiscard]] const bool &amp;get_or_default(const std::string &amp;name, const bool &amp;val) noexcept {
return get_impl(name, val);
}
private:
template&lt;class T&gt;
void set_impl(const std::string &amp;name, const T &amp;val) noexcept {
data_[name] = val;
}
template&lt;class T&gt;
[[nodiscard]] const T &amp;get_impl(const std::string &amp;name, const T &amp;val) noexcept {
if (data_.contains(name)) {
if (auto value = get_if&lt;T&gt;(&amp;data_[name])) {
return *value;
}
}
return val;
}
std::unordered_map&lt;std::string, value&gt; data_;
};

huangapple
  • 本文由 发表于 2023年2月8日 19:51:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/75385413.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定