将变体转换为超集变体或子集变体
Casting a variant to super set variant or a subset variant
我改编了 中的一些代码来处理目标变体是源变体的子集的情况,如下所示:
template <class... Args>
struct variant_cast_proxy
{
std::variant<Args...> v;
template <class... ToArgs>
operator std::variant<ToArgs...>() const
{
return std::visit(
[](auto&& arg) -> std::variant<ToArgs...> {
if constexpr (std::is_convertible_v<decltype(arg), std::variant<ToArgs...>>)
return arg;
else
throw std::runtime_error("bad variant cast");
},
v
);
}
};
template <class... Args>
auto variant_cast(const std::variant<Args...>& v) -> variant_cast_proxy<Args...>
{
return { v };
}
struct A {};
struct B {};
struct C {};
struct D {};
struct E {};
struct F {};
int main() {
std::variant<A, B, C, D> v1 = B();
std::variant<B,C> v2;
v2 = variant_cast(v1);
}
上面的方法有效,但我希望它能处理在编译时检测到错误转换的情况。以上处理了 运行 时间的所有错误转换,但 运行 时间和编译时间错误都是可能的。如果 v 持有类型 C 的值,则将 std::variant<A,B,C>
类型的 v 转换为 std::variant<A,B>
应该在 运行 时失败,但是例如
std::variant<A, B, C, D> v1 = B();
std::variant<E,F> v2;
v2 = variant_cast(v1)
甚至不应该编译。
我相信这可以通过 std::enable_if 来完成,但我不确定它究竟是如何需要测试可变参数包的集合包含的,我不知道该怎么做。
您可以添加一个 static_assert
检查是否有任何可能持有的变体是可转换的:
static_assert((std::is_convertible_v<Args, std::variant<ToArgs...>> || ...),
"No possible variant that could be converted exists");
或者如果你想要 SFINAE,你可以在模板参数中进行:
// extracted into helper function
template <class... ToArgs>
static constexpr bool is_convertible() noexcept {
return (std::is_convertible_v<Args, std::variant<ToArgs...>> || ...);
}
template<class... ToArgs, std::enable_if_t<is_convertible<ToArgs...>(), int> = 0>
operator std::variant<ToArgs...>() const
{
// ...
}
我认为 convertible 是错误的问题...除非你真的希望能够像 variant<int, long>
一样转换为 variant<string, double>
。我认为更好的检查是源 variant
中的每种类型都出现在目标 variant
.
中
为此,您可以使用 Boost.Mp11 来简化此检查:
template <class... Args>
struct variant_cast_proxy
{
std::variant<Args...> v;
template <class... ToArgs,
class V = std::variant<ToArgs...>,
std::enable_if_t<
// every type in the source variant is present in the destination
(mp_contains<V, Args>::value && ...)
// and the destination id all distinct
&& mp_is_set<V>::value
, int> = 0>
operator std::variant<ToArgs...>() const
{
return std::visit([&](auto const& arg){ return V(arg); }, v);
}
};
我改编了
template <class... Args>
struct variant_cast_proxy
{
std::variant<Args...> v;
template <class... ToArgs>
operator std::variant<ToArgs...>() const
{
return std::visit(
[](auto&& arg) -> std::variant<ToArgs...> {
if constexpr (std::is_convertible_v<decltype(arg), std::variant<ToArgs...>>)
return arg;
else
throw std::runtime_error("bad variant cast");
},
v
);
}
};
template <class... Args>
auto variant_cast(const std::variant<Args...>& v) -> variant_cast_proxy<Args...>
{
return { v };
}
struct A {};
struct B {};
struct C {};
struct D {};
struct E {};
struct F {};
int main() {
std::variant<A, B, C, D> v1 = B();
std::variant<B,C> v2;
v2 = variant_cast(v1);
}
上面的方法有效,但我希望它能处理在编译时检测到错误转换的情况。以上处理了 运行 时间的所有错误转换,但 运行 时间和编译时间错误都是可能的。如果 v 持有类型 C 的值,则将 std::variant<A,B,C>
类型的 v 转换为 std::variant<A,B>
应该在 运行 时失败,但是例如
std::variant<A, B, C, D> v1 = B();
std::variant<E,F> v2;
v2 = variant_cast(v1)
甚至不应该编译。
我相信这可以通过 std::enable_if 来完成,但我不确定它究竟是如何需要测试可变参数包的集合包含的,我不知道该怎么做。
您可以添加一个 static_assert
检查是否有任何可能持有的变体是可转换的:
static_assert((std::is_convertible_v<Args, std::variant<ToArgs...>> || ...),
"No possible variant that could be converted exists");
或者如果你想要 SFINAE,你可以在模板参数中进行:
// extracted into helper function
template <class... ToArgs>
static constexpr bool is_convertible() noexcept {
return (std::is_convertible_v<Args, std::variant<ToArgs...>> || ...);
}
template<class... ToArgs, std::enable_if_t<is_convertible<ToArgs...>(), int> = 0>
operator std::variant<ToArgs...>() const
{
// ...
}
我认为 convertible 是错误的问题...除非你真的希望能够像 variant<int, long>
一样转换为 variant<string, double>
。我认为更好的检查是源 variant
中的每种类型都出现在目标 variant
.
为此,您可以使用 Boost.Mp11 来简化此检查:
template <class... Args>
struct variant_cast_proxy
{
std::variant<Args...> v;
template <class... ToArgs,
class V = std::variant<ToArgs...>,
std::enable_if_t<
// every type in the source variant is present in the destination
(mp_contains<V, Args>::value && ...)
// and the destination id all distinct
&& mp_is_set<V>::value
, int> = 0>
operator std::variant<ToArgs...>() const
{
return std::visit([&](auto const& arg){ return V(arg); }, v);
}
};