我根据这个答案改编了一些代码,以处理目标变体是源变体的子集的情况,如下所示:
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
{
// ...
}
我认为可转换是个错误的问题。。。除非您真的希望能够像CCD_ 4到CCD_。我认为更好的检查是源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);
}
};