Как я могу написать функцию, которая как ввод, так и вывод является std :: варианта - PullRequest
1 голос
/ 01 апреля 2020

Я хочу написать функцию, в которой ввод и вывод являются вариантами.

VariantTypeA GetA(const VariantTypeB& b) {
  return std::visit(MyVisitor(), b);
}

Но я получил исключение, сказав, что std :: visit` требует, чтобы у посетителя был один тип возвращаемого значения.

Как я могу написать такую ​​функцию? Могу ли я использовать переключатель вместо? Как?

Ответы [ 3 ]

1 голос
/ 01 апреля 2020

Использование std::variant и разрешение посетителю выводить элемент не работает, если у варианта есть повторяющиеся альтернативные типы. Вот шаблон функции variant_visit, который сохраняет index() варианта. variant_visit также устанавливает соответствующий альтернативный тип в варианте результата на std::monostate, если посетитель возвращает void для какого-либо аргумента, потому что std::variant с void неверно сформирован. SFINAE для простоты опущен.

namespace detail {
    template <typename T>
    using wrap_void_t = std::conditional_t<std::is_void_v<T>, std::monostate, T>;

    template <typename Variant, typename Func,
              typename = std::make_index_sequence<std::variant_size_v<
                  std::remove_reference_t<Variant>
              >>>
    struct result_variant;
    template <typename Variant, typename Func, std::size_t... Is>
    struct result_variant<Variant, Func, std::index_sequence<Is...>> {
        using type = std::variant<
            wrap_void_t<
                std::invoke_result_t<
                    Func,
                    decltype(std::get<Is>(std::declval<Variant>()))
                >
            > ...
        >;
    };
    template <typename Variant, typename Func>
    using result_variant_t = typename result_variant<Variant, Func>::type;

    template <typename Variant, typename Visitor, std::size_t... Is>
    auto variant_visit(Variant&& variant, Visitor&& visitor, std::index_sequence<Is...>)
    {
        using Ret = result_variant_t<Variant, Visitor>;
        using fp_t = Ret (*)(Variant&&, Visitor&&);
        const fp_t fp_array[] = {
            [](Variant&&, Visitor&&) -> Ret { throw std::bad_variant_access{}; },
            [](Variant&& variant, Visitor&& visitor) -> Ret {
                if constexpr (std::is_same_v<std::variant_alternative_t<Is, Ret>,
                                             std::monostate>) {
                    std::invoke(std::forward<Visitor>(visitor),
                                std::get<Is>(std::forward<Variant>(variant)));
                    return Ret(std::in_place_index<Is>);
                } else {
                    return Ret(
                        std::in_place_index<Is>,
                        std::invoke(std::forward<Visitor>(visitor),
                                    std::get<Is>(std::forward<Variant>(variant)))
                    );
                }
            } ...
        };
        auto fp = fp_array[static_cast<std::size_t>(variant.index() + 1)];
        return fp(std::forward<Variant>(variant), std::forward<Visitor>(visitor));
    }
}

template <typename Variant, typename Visitor>
auto variant_visit(Variant&& variant, Visitor&& visitor)
{
    return detail::variant_visit(
        std::forward<Variant>(variant),
        std::forward<Visitor>(visitor),
        std::make_index_sequence<
            std::variant_size_v<std::remove_reference_t<Variant>>
        >{}
    );
}

Пример использования:

int main()
{
    {
        std::variant<int, int, double> var{std::in_place_index<1>, 10};
        auto result = variant_visit(var, std::negate{});
        std::cout << std::get<1>(result) << '\n';
    }
    {
        std::variant<int, int, double> var{std::in_place_index<2>, 2e20};
        auto result = variant_visit(var, std::negate{});
        std::cout << std::get<2>(result) << '\n';
    }
    {
        std::variant<std::unique_ptr<int>> var{std::make_unique<int>(30)};
        auto result = variant_visit(var, [](auto& ptr) { return -*ptr; });
        std::cout << std::get<0>(result) << '\n';
    }
    {
        auto inspector = [](auto&& ptr) {
            if constexpr (std::is_const_v<std::remove_reference_t<decltype(ptr)>>) {
                std::cout << "const";
            }
            if constexpr (std::is_lvalue_reference_v<decltype(ptr)>) {
                std::cout << "&\n";
            } else {
                std::cout << "&&\n";
            }
        };

        std::variant<std::unique_ptr<int>> var{std::make_unique<int>(30)};
        variant_visit(var, inspector);
        variant_visit(std::as_const(var), inspector);
        variant_visit(std::move(var), inspector);
    }
}

Вывод:

-10
-2e+20
-30
&
const&
&&

( живая демонстрация )

1 голос
/ 01 апреля 2020

Использовать посетителя с типом возврата VariantTypeA.

0 голосов
/ 01 апреля 2020

Вы можете сделать это, просто обернув посетителя в лямбду, которая выполняет преобразование обратно в вариант:

template <class Visitor, class Variant>
auto variant_visit(Visitor &&visitor, Variant &&variant) {
    return std::visit(
        [&](auto &&in) -> std::remove_reference_t<Variant> {
            return visitor(static_cast<decltype(in)>(in));
        },
        std::forward<Variant>(variant)
    );
}

Посмотреть вживую на Wandbox

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...