Created
November 14, 2019 21:02
-
-
Save bogado/ee817b4797ac1f6153896c7f529fee67 to your computer and use it in GitHub Desktop.
non throwing visitor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <variant> | |
#include <utility> | |
#include <type_traits> | |
#include <array> | |
#include <tuple> | |
#include <functional> | |
#include <optional> | |
namespace visit { | |
namespace visitation_details { | |
template <typename... VariantTypes> | |
struct visitor_traits | |
{ | |
using ident_t = size_t; | |
using arity_t = uint8_t; | |
using alternative_t = uint8_t; | |
constexpr static const arity_t arity_size = sizeof...(VariantTypes); | |
constexpr static const ident_t total_alternatives = (std::variant_size_v<std::remove_reference_t<VariantTypes>>*...); | |
constexpr static const auto all_locations = std::make_integer_sequence<arity_t, arity_size>{}; | |
constexpr static const auto all_identities = std::make_integer_sequence<ident_t, total_alternatives>{}; | |
template <arity_t Location> | |
struct argument | |
{ | |
using variant_ref = std::tuple_element_t<Location, std::tuple<VariantTypes...>>; | |
using variant_type = std::remove_reference_t<variant_ref>; | |
constexpr static const alternative_t dimention = std::variant_size_v<variant_type>; | |
constexpr static const bool is_const = std::is_const_v<variant_type>; | |
using erased_pointer = std::conditional_t<is_const, const void *, void *>; | |
constexpr static const auto all_dimentions = std::make_integer_sequence<alternative_t, dimention>{}; | |
template <alternative_t Selection> | |
struct alternative | |
{ | |
using value_type = std::variant_alternative_t<Selection, variant_type>; | |
using reference = value_type&; | |
using pointer = value_type*; | |
constexpr static erased_pointer erase(variant_ref variant) | |
{ | |
return static_cast<erased_pointer>(std::get_if<Selection>(&variant)); | |
} | |
constexpr static pointer from_erased(erased_pointer erased_value) | |
{ | |
return reinterpret_cast<pointer>(erased_value); | |
} | |
template <typename ArgumentType> | |
constexpr static auto& from_erased_arguments(ArgumentType&& arguments) | |
{ | |
return *from_erased(std::get<Location>(arguments)); | |
} | |
}; | |
private: | |
template <arity_t... Locations> | |
constexpr static alternative_t alternative_from_impl(const id_t id, std::integer_sequence<arity_t, Locations...>) noexcept | |
{ | |
ident_t divider = (1 * ... * argument<Locations>::dimention); | |
return (id/divider)%dimention; | |
} | |
constexpr static const auto all_previous_locations = std::make_integer_sequence<arity_t, Location>{}; | |
template <alternative_t...Selections> | |
constexpr static auto erasers(std::integer_sequence<alternative_t, Selections...>) | |
{ | |
return std::array { &alternative<Selections>::erase... }; | |
} | |
public: | |
constexpr static erased_pointer erase(variant_ref variant) | |
{ | |
return erasers(all_dimentions)[variant.index()](variant); | |
} | |
constexpr static alternative_t alternative_from(const id_t id) noexcept | |
{ return alternative_from_impl(id, all_previous_locations); } | |
}; | |
template<arity_t location, ident_t id> | |
using identity_alternative_trait = typename argument<location>:: template alternative<argument<location>::alternative_from(id)>; | |
template <arity_t... Locations> | |
struct location_aggregator | |
{ | |
template <typename Visitor, ident_t id> | |
using visitation_result_for = std::invoke_result_t<Visitor, typename identity_alternative_trait<Locations, id>::reference...>; | |
template <typename Visitor, ident_t id> | |
constexpr static bool is_result_reference = std::is_reference_v<visitation_result_for<Visitor, id>>; | |
template <typename Visitor, ident_t id> | |
constexpr static bool is_result_constant = std::is_const_v<visitation_result_for<Visitor, id>>; | |
template <typename Visitor, ident_t... Identities> | |
struct identity_aggregator | |
{ | |
using first_result_base_type = visitation_result_for<Visitor, 0>; | |
using result_base_type = std::common_type_t<visitation_result_for<Visitor, Identities>...>; | |
// Are all the types the same? | |
constexpr static bool are_all_results_the_same = (std::is_same_v<first_result_base_type, visitation_result_for<Visitor, Identities>> && ...); | |
// This is the common result type or false_type. | |
using common_result_base_type = std::conditional_t<are_all_results_the_same, first_result_base_type, std::false_type>; | |
constexpr static bool is_common_type_void = std::is_same_v<void, common_result_base_type>; | |
using common_result_type = std::conditional_t<is_common_type_void, std::false_type, common_result_base_type>; | |
// the result should only be a reference if all possibilities are references. | |
constexpr static bool is_final_result_a_reference = are_all_results_the_same && (is_result_reference<Visitor, Identities> && ...); | |
// A by value return type has no effect. So the const modifier is limited to reference results. | |
// A single const ref result should force all results become a const. | |
constexpr static bool is_final_result_constant = is_final_result_a_reference && (is_result_constant<Visitor, Identities> || ...); | |
using result = std::conditional_t<is_final_result_a_reference, | |
std::conditional_t<is_final_result_constant, | |
const common_result_type&, | |
common_result_type&>, | |
result_base_type>; | |
}; | |
template <typename Visitor, ident_t... Identities> | |
constexpr static auto aggrate_identies(std::integer_sequence<ident_t, Identities...>) | |
{ | |
return identity_aggregator<Visitor, Identities...>{}; | |
} | |
template <typename Visitor> | |
using aggregated_identities = decltype(aggrate_identies<Visitor>(all_identities)); | |
using erased_arguments_type = std::optional<std::tuple<typename argument<Locations>::erased_pointer...>>; | |
constexpr static erased_arguments_type build_arguments(VariantTypes&&... variants) noexcept | |
{ | |
const auto result = std::tuple{ argument<Locations>::erase(variants)...}; | |
if (!(std::get<Locations>(result) && ...)) | |
{ | |
return std::nullopt; | |
} | |
return result; | |
} | |
constexpr static ident_t get_id(VariantTypes&&... variants, std::integer_sequence<arity_t, Locations...>) noexcept | |
{ | |
const auto all_dimentions = std::array { argument<Locations>::dimention... }; | |
const auto all_indexs = std::array { std::forward<VariantTypes>(variants).index()...}; | |
ident_t id = 0; | |
ident_t index_multiplier = 1; | |
for (auto pos : { Locations... }) | |
{ | |
id += all_indexs[pos] * index_multiplier; | |
index_multiplier *= all_dimentions[pos]; | |
} | |
return id; | |
} | |
template <ident_t id, typename Visitor> | |
constexpr static decltype(auto) invoke_impl(Visitor&& visitor, erased_arguments_type& arguments) | |
{ | |
return std::invoke(std::forward<Visitor>(visitor), identity_alternative_trait<Locations, id>::from_erased_arguments(arguments.value())...); | |
} | |
}; | |
template<arity_t...Locations> | |
static auto aggregate_locations(std::integer_sequence<arity_t, Locations...>) | |
{ return location_aggregator<Locations...>{}; } | |
using aggregated_traits = decltype(aggregate_locations(all_locations)); | |
template <typename Visitor> | |
using visitation_result = typename aggregated_traits::template aggregated_identities<Visitor>::result; | |
using erased_arguments_type = typename aggregated_traits::erased_arguments_type; | |
template <typename Visitor> | |
struct invoker { | |
using visitation_result = visitation_result<Visitor>; | |
using invoke_impl = visitation_result(*)(Visitor&&, erased_arguments_type&); | |
using factory = invoker(*)(VariantTypes&&...); | |
invoke_impl impl; | |
erased_arguments_type arguments; | |
const ident_t id; | |
constexpr explicit operator bool() const | |
{ | |
return arguments.has_value(); | |
} | |
constexpr visitation_result operator()(Visitor&& visitor) | |
{ | |
return impl(std::forward<Visitor>(visitor), arguments); | |
} | |
}; | |
template <arity_t location, ident_t id> | |
using alternative_traits = typename argument<location>::template alternative<argument<location>::alternative_from(id)>; | |
constexpr static ident_t get_id(VariantTypes&&...variants) | |
{ | |
return aggregated_traits::get_id(std::forward<VariantTypes>(variants)..., all_locations); | |
} | |
template <typename Visitor> | |
using all_factories_array_type = std::array<typename invoker<Visitor>::factory, total_alternatives>; | |
template <typename Visitor, arity_t ... Locations, ident_t... Identities> | |
constexpr static all_factories_array_type<Visitor> build_all_factories(std::integer_sequence<arity_t, Locations...>, std::integer_sequence<ident_t, Identities...>) | |
{ | |
using invoker = invoker<Visitor>; | |
using factory = typename invoker::factory; | |
return std::array<factory, total_alternatives> | |
{ | |
static_cast<factory>([](VariantTypes&&... variants) noexcept | |
{ | |
return invoker { | |
&aggregated_traits::template invoke_impl<Identities, Visitor>, | |
aggregated_traits::build_arguments(std::forward<VariantTypes>(variants)...) | |
}; | |
})... | |
}; | |
} | |
template <typename Visitor> | |
constexpr static all_factories_array_type<Visitor> all_factories = build_all_factories<Visitor>(all_locations, all_identities); | |
}; | |
} | |
template <typename Visitor, typename FailureHandler, typename... VariantTypes> | |
inline constexpr decltype(auto) visit_optional(Visitor&& visitor, FailureHandler&& failure_handler, VariantTypes&&... variants) | |
{ | |
using traits = visitation_details::visitor_traits<VariantTypes...>; | |
using visitation_result = typename traits:: template visitation_result<Visitor>; | |
const auto id = traits::get_id(std::forward<VariantTypes>(variants)...); | |
static_assert(std::is_same_v<typename traits::template all_factories_array_type<Visitor>, std::array<typename traits::template invoker<Visitor>::factory, traits::total_alternatives>>); | |
auto & factories = traits :: template all_factories<Visitor>; | |
auto invoker = factories[id](std::forward<VariantTypes>(variants)...); | |
if (!static_cast<bool>(invoker)) | |
{ | |
if constexpr (std::is_convertible_v< | |
visitation_result, | |
std::invoke_result_t<FailureHandler> | |
>) | |
{ | |
return failure_handler(); | |
} else | |
{ | |
failure_handler(); // Must throw. | |
std::abort(); | |
} | |
} | |
return invoker(std::forward<Visitor>(visitor)); | |
} | |
namespace static_test { | |
struct reference_result_visitor | |
{ | |
using result = int&; | |
int value = 0; | |
template <typename... T> | |
constexpr result operator()(T...ts) { return value + (sizeof(T) + ...); } | |
}; | |
static_assert(std::is_same_v< | |
visitation_details::visitor_traits<std::variant<int, char, double>, std::variant<std::pair<int, int>, std::pair<char, char>>>::visitation_result<reference_result_visitor>, | |
reference_result_visitor::result>); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment