许多人使用C ++ 17 / boost变体的模式看起来与switch语句非常相似。例如:(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,而不是获得编译错误,或者您可能会得到一个您没有计划的隐式转换。例如:
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语句更安全,因为你不能使用不是enum的一部分的值来编写case语句。同样,我认为如果变体访问者仅限于变体中包含的类型的子集,加上默认处理程序,那将非常有用。是否可以实现类似的东西?
编辑:s /隐式演员/隐式转换/
EDIT2:我想拥有一个有意义的catch-all [](auto)
处理程序。我知道如果您不处理变体中的每个类型,删除它将导致编译错误,但这也会从访问者模式中删除功能。
答案 0 :(得分:24)
如果您只想允许类型的子集,那么您可以在lambda的开头使用static_assert
,例如:
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
需要完全其中一个给定类型。
您还可以使用std::visit
的变体,例如使用“默认”访问者,如:
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
来电运营商将匹配并且你会有一个模糊的呼叫(在这个和默认呼叫之间)。
这也应该没有默认值,因为visit_only_for
调用运算符将匹配,但由于它被删除,您将收到编译时错误。
答案 1 :(得分:1)
您可以添加额外的图层来添加这些额外的检查,例如:
template <typename Ret, typename ... Ts> struct IVisitorHelper;
template <typename Ret> struct IVisitorHelper<Ret> {};
template <typename Ret, typename T>
struct IVisitorHelper<Ret, T>
{
virtual ~IVisitorHelper() = default;
virtual Ret operator()(T) const = 0;
};
template <typename Ret, typename T, typename T2, typename ... Ts>
struct IVisitorHelper<Ret, T, T2, Ts...> : IVisitorHelper<Ret, T2, Ts...>
{
using IVisitorHelper<Ret, T2, Ts...>::operator();
virtual Ret operator()(T) const = 0;
};
template <typename Ret, typename V> struct IVarianVisitor;
template <typename Ret, typename ... Ts>
struct IVarianVisitor<Ret, std::variant<Ts...>> : IVisitorHelper<Ret, Ts...>
{
};
template <typename Ret, typename V>
Ret my_visit(const IVarianVisitor<Ret, std::decay_t<V>>& v, V&& var)
{
return std::visit(v, var);
}
使用方法:
struct Visitor : IVarianVisitor<void, std::variant<double, std::string>>
{
void operator() (double) const override { std::cout << "double\n"; }
void operator() (std::string) const override { std::cout << "string\n"; }
};
std::variant<double, std::string> v = //...;
my_visit(Visitor{}, v);
答案 2 :(得分:0)
有点基于 Holt 的 visit_only_for
示例,我目前正在尝试类似的方法,以便在我的 std::visit
调用中插入一个“标签”,以防止忘记显式处理程序/运营商:
//! struct visit_all_types_explicitly
//!
//! If this callable is used in the overload set for std::visit
//! its templated call operator will be bound to any type
//! that is not explicitly handled by a better match.
//! Since the instantiation of operator()<T> will trigger
//! a static_assert below, using this in std::visit forces
//! the user to handle all type cases.
//! Specifically, since the templated call operator is a
//! better match than call operators found via implicit argument
//! conversion, one is forced to implement all types even if
//! they are implicitly convertible without warning.
struct visit_all_types_explicitly {
template<class> static inline constexpr bool always_false_v = false;
// Note: Uses (T const&) instead of (T&&) because the const-ref version
// is a better "match" than the universal-ref version, thereby
// preventing the use of this in a context where another
// templated call operator is supplied.
template<typename T>
void operator()(T const& arg) const {
static_assert(always_false_v<T>, "There are unbound type cases! [visit_all_types_explicitly]");
}
};
using MyVariant = std::variant<int, double>;
void test_visit() {
const MyVariant val1 = 42;
// This compiles:
std::visit(
overloaded{
kse::visit_all_types_explicitly(),
[](double arg) {},
[](int arg) {},
},
val1
);
// does not compile because missing int-operator causes
// visit_all_types_explicitly::operator()<int> to be instantiated
std::visit(
overloaded{
visit_all_types_explicitly(),
[](double arg) {},
// [](int arg) { },
},
val1
);
// does also not compile: (with static assert from visit_all_types_explicitly)
std::visit(
overloaded{
visit_all_types_explicitly(),
[](double arg) {},
// [](int arg) { },
[](auto&& arg) {}
},
val1
);
// does also not compile: (with std::visit not being able to match the overloads)
std::visit(
overloaded{
visit_all_types_explicitly(),
[](double arg) {},
// [](int arg) { },
[](auto const& arg) {}
},
val1
);
}
就目前而言,这似乎做我想做的,以及 OP 要求的:
<块引用>您不会得到编译错误,而是会调用错误的 lambda,通常是默认的,或者您可能会得到一个您没有计划的隐式转换。
您不能故意将其与“默认”/自动处理程序结合使用。