1

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.

jwezorek
  • 8,592
  • 1
  • 29
  • 46
  • Interesting... I also use the code from the linked post, but when I compile your example I get the following compiler error: `error: could not convert ‘arg’ from ‘const A’ to ‘std::variant’` – Hillary Ryan Oct 26 '20 at 23:04

2 Answers2

1

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
    {
        // ...
    }
Artyer
  • 31,034
  • 3
  • 47
  • 75
1

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);
    }
};
Barry
  • 286,269
  • 29
  • 621
  • 977