Приведение варианта к варианту супермножества или варианту подмножества

Я адаптировал некоторый код из этого ответа для обработки случая, когда целевой вариант является подмножеством исходного варианта как следует:

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 типа std::variant<A,B,C> в std::variant<A,B> должно завершиться ошибкой во время выполнения, если v содержит значение типа C, но, например,

std::variant<A, B, C, D> v1 = B();
std::variant<E,F> v2;
v2 = variant_cast(v1)

даже не должен компилироваться.

Я считаю, что это можно сделать с помощью std::enable_if, но я не уверен, как именно, так как кажется, что это потребует тестирования на включение множества пакетов параметров с переменным числом параметров, что я понятия не имею, как это сделать.


person jwezorek    schedule 05.04.2020    source источник
comment
Интересно... Я также использую код из связанного сообщения, но когда я компилирую ваш пример, я получаю следующую ошибку компилятора: error: could not convert ‘arg’ from ‘const A’ to ‘std::variant<E, F>’   -  person Hillary Ryan    schedule 27.10.2020


Ответы (2)


Вы можете добавить 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
    {
        // ...
    }
person Artyer    schedule 05.04.2020
comment
спасибо, я пытался понять SFINAE, но static_assert намного лучше. - person jwezorek; 05.04.2020
comment
Вы хотите &&, а не || - person Barry; 05.04.2020
comment
@Barry Тогда вы не сможете выполнить приведение к подмножеству (например, пример с std::variant<A,B,C> по std::variant<A,B>, приведенный в вопросе) - person Artyer; 05.04.2020

Я думаю, что конвертируемый - неправильный вопрос... если вы действительно не хотите иметь возможность приводить 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);
    }
};
person Barry    schedule 05.04.2020