Skip to content

Instantly share code, notes, and snippets.

@bogado
Created November 14, 2019 21:02
Show Gist options
  • Save bogado/ee817b4797ac1f6153896c7f529fee67 to your computer and use it in GitHub Desktop.
Save bogado/ee817b4797ac1f6153896c7f529fee67 to your computer and use it in GitHub Desktop.
non throwing visitor
#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