[英]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()
。 如果訪問者為某些參數返回void
, variant_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)
);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.