如何制作更安全的C ++变体访问者,类似于switch语句? [英] How to make a safer C++ variant visitor, similar to switch statements?
问题描述
许多人在C ++ 17/boost变体中使用的模式看起来与switch语句非常相似.例如:( cppreference.com中的代码段)
The pattern that a lot of people use with C++17 / boost variants looks very similar to switch statements. For example: (snippet from cppreference.com)
std::variant<int, long, double, std::string> v = ...;
std::visit(overloaded {
[](auto arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
}, v);
问题是当您在访问者中输入错误的类型或更改变体签名而忘记更改访问者时.而不是得到编译错误,您将调用错误的lambda,通常是默认的lambda,或者您可能会得到您未计划的隐式转换.例如:
The problem is when you put the wrong type in the visitor or change the variant signature, but forget to change the visitor. Instead of getting a compile error, you will have the wrong lambda called, usually the default one, or you might get an implicit conversion that you didn't plan. For example:
v = 2.2;
std::visit(overloaded {
[](auto arg) { std::cout << arg << ' '; },
[](float arg) { std::cout << std::fixed << arg << ' '; } // oops, this won't be called
}, v);
枚举类上的
switch语句更安全,因为您不能使用不属于枚举的值编写case语句.同样,我认为,如果变体访问者仅限于变体中包含的类型的子集以及默认处理程序,那将非常有用.可以实现类似的东西吗?
Switch statements on enum classes are way more secure, because you can't write a case statement using a value that isn't part of the enum. Similarly, I think it would be very useful if a variant visitor was limited to a subset of the types held in the variant, plus a default handler. Is it possible to implement something like that?
s/隐式强制转换/隐式转换/
s/implicit cast/implicit conversion/
我想拥有一个有意义的包罗万象的[](auto)
处理程序.我知道,如果不处理变体中的每种类型,将其删除都会导致编译错误,但这也会从访问者模式中删除功能.
I would like to have a meaningful catch-all [](auto)
handler. I know that removing it will cause compile errors if you don't handle every type in the variant, but that also removes functionality from the visitor pattern.
推荐答案
如果只允许类型的子集,则可以在lambda的开头使用static_assert
,例如:
If you want to only allow a subset of types, then you can use a static_assert
at the beginning of the lambda, e.g.:
template <typename T, typename... Args>
struct is_one_of:
std::disjunction<std::is_same<std::decay_t<T>, Args>...> {};
std::visit([](auto&& arg) {
static_assert(is_one_of<decltype(arg),
int, long, double, std::string>{}, "Non matching type.");
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>)
std::cout << "int with value " << arg << '\n';
else if constexpr (std::is_same_v<T, double>)
std::cout << "double with value " << arg << '\n';
else
std::cout << "default with value " << arg << '\n';
}, v);
如果您在变体中添加或更改一个类型,或者添加一个类型,则此操作将失败,因为T
需要精确地为给定类型之一
This will fails if you add or change a type in the variant, or add one, because T
needs to be exactly one of the given types.
您还可以使用std::visit
的变体,例如具有默认"访问者的用户,例如:
You can also play with your variant of std::visit
, e.g. with a "default" visitor like:
template <typename... Args>
struct visit_only_for {
// delete templated call operator
template <typename T>
std::enable_if_t<!is_one_of<T, Args...>{}> operator()(T&&) const = delete;
};
// then
std::visit(overloaded {
visit_only_for<int, long, double, std::string>{}, // here
[](auto arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
}, v);
如果添加的类型不是int
,long
,double
或std::string
之一,则visit_only_for
调用运算符将匹配,并且您将有一个模棱两可的调用(在此之间)和默认的).
If you add a type that is not one of int
, long
, double
or std::string
, then the visit_only_for
call operator will be matching and you will have an ambiguous call (between this one and the default one).
这也应该没有默认设置,因为visit_only_for
调用运算符将匹配,但是由于将其删除,因此会出现编译时错误.
This should also works without default because the visit_only_for
call operator will be match, but since it is deleted, you'll get a compile-time error.
这篇关于如何制作更安全的C ++变体访问者,类似于switch语句?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!