英文:
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
类型(bool
或long
):
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
来限制set
和get_or_default
方法的类型,以便给类的用户提供一个更简单的错误消息,指示只有bool
和long
是有效的类型。
是否可以使用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 <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.
答案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 <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...;
// So equivalent to:
// void set(const std::string&, const T1& value);
// ..
// void set(const std::string&, const TN& value);
};
And then
using settings = settings_impl<bool, long /*std::string*/>;
That allows conversion contrary to template way.
Note though that "xxxx"
(const char [N]
) would select bool
overload and not std::string
one, due to overload rules.
答案3
得分: 1
不是一个概念,而是一种不同的方法:
如果您希望将 get
和 set
限制为仅接受 long
或 bool
,您可以使用非模板重载:
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 &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_;
};
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论