Reputation: 9545
I've adapted some code from this answer to handle the case in which the target variant is a subset of the source variant as follows:
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);
}
The above works but I would like it to handle the case in which a bad conversion can be detected at compile time. The above handles all bad conversions at run time but both runtime and compile time errors are possible. Casting v of type std::variant<A,B,C>
to std::variant<A,B>
should fail at runtime if v holds a value of type C, but for example
std::variant<A, B, C, D> v1 = B();
std::variant<E,F> v2;
v2 = variant_cast(v1)
should not even compile.
I believe this could be done via std::enable_if but am not sure exactly how as it seems like it would require testing for set containment of variadic parameter packs which I have no idea how to do.
Upvotes: 1
Views: 1428
Reputation: 303606
I think convertible is the wrong question... unless you really want to be able to cast like a variant<int, long>
to a variant<string, double>
. I think the better check would be that every type in the source variant
appears in the destination variant
.
And for that, you can use Boost.Mp11 to make this check easy:
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);
}
};
Upvotes: 1
Reputation: 40881
You can add a static_assert
checking if any of the possibly-held variants are convertible:
static_assert((std::is_convertible_v<Args, std::variant<ToArgs...>> || ...),
"No possible variant that could be converted exists");
Or if you want SFINAE, you can do it in the template arguments:
// 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
{
// ...
}
Upvotes: 1