簡體   English   中英

如何編寫一個輸入和輸出都是 std::variant 的函數

[英]How can I write a function that both input and output is a std::variant

我想寫一個輸入和輸出都是變體的函數。

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

但是我得到一個例外,說 std::visit` 要求訪問者有一個單一的返回類型。

我怎樣才能寫出這樣的函數? 我可以用 switch 代替嗎? 如何?

使用返回類型為VariantTypeA的訪問者。

如果變體具有重復的替代類型,則使用std::variant並讓訪問者推斷該元素不起作用。 這是一個函數模板variant_visit ,它保留了變體的index() 如果訪問者為某些參數返回voidvariant_visit還將結果變量中的相應替代類型設置為std::monostate variant_visit ,因為std::variant with 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&
&&

現場演示

您可以通過簡單地將訪問者包裝在執行轉換回變體的 lambda 中來實現:

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 上實時觀看

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM