很多人与 C++17/boost 变体一起使用的模式看起来与 switch 语句非常相似。例如:(来自 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 语句更安全,因为您不能使用不属于枚举的值编写 case 语句。同样,我认为如果变体访问者仅限于变体中保存的类型子集以及默认处理程序,那将非常有用。是否有可能实现这样的东西?
编辑:s/隐式转换/隐式转换/
编辑2:我想要一个有意义的包罗万象的[](auto)
处理程序。我知道如果您不处理变体中的每个类型,删除它会导致编译错误,但这也会从访问者模式中删除功能。
如果您只想允许类型子集,则可以在 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
调用运算符将匹配,但由于它被删除,你将收到编译时错误。
您可以添加一个额外的图层来添加这些额外的检查,例如:
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 << "doublen"; }
void operator() (std::string) const override { std::cout << "stringn"; }
};
std::variant<double, std::string> v = //...;
my_visit(Visitor{}, v);
在某种程度上基于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,通常是默认的 lambda,或者您可能会获得您没有计划的隐式转换。
您故意不能将其与"默认"/自动处理程序结合使用。