Использование 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&
&&
( живая демонстрация )