rec/fplus/variant.hpp
2020-03-18 14:42:46 +08:00

363 lines
10 KiB
C++
Executable File

// elm architecture
#include <iostream>
#include <memory>
#include <tuple>
namespace fplus
{
namespace internal
{
// http://stackoverflow.com/a/18987405/1866775
template <typename...>
struct is_one_of;
template <typename F>
struct is_one_of<F>
{
static constexpr bool value = false;
};
template <typename F, typename S, typename... T>
struct is_one_of<F, S, T...>
{
static constexpr bool value = std::is_same<F, S>::value
|| is_one_of<F, T...>::value;
};
template <typename F, typename... T>
struct is_one_of<F, std::tuple<T...>>
{
static constexpr bool value = is_one_of<F, T...>::value;
};
template <typename...>
struct is_unique;
template <>
struct is_unique<> {
static constexpr bool value = true;
};
template<typename F, typename... T>
struct is_unique<F, T...>
{
static constexpr bool value = is_unique<T...>::value
&& !is_one_of<F, T...>::value;
};
template<typename... T>
struct is_unique<std::tuple<T...>>
{
static constexpr bool value = is_unique<T...>::value;
};
template <typename...>
struct are_same;
template <>
struct are_same<> {
static constexpr bool value = true;
};
template<typename F1, typename F2>
struct are_same<F1, F2>
{
static constexpr bool value = std::is_same<F1, F2>::value;
};
template<typename F1, typename F2, typename... T>
struct are_same<F1, F2, T...>
{
static constexpr bool value = are_same<F2, T...>::value
&& std::is_same<F1, F2>::value;
};
template<typename... T>
struct are_same<std::tuple<T...>>
{
static constexpr bool value = are_same<T...>::value;
};
// http://stackoverflow.com/a/3273571/1866775
template<template<typename...> class List,
template<typename> class Mod,
typename ...Args>
struct transform_parameter_pack {
typedef List<typename Mod<Args>::type...> type;
};
template<typename T>
struct as_shared_pointer
{
typedef std::shared_ptr<T> type;
};
// http://stackoverflow.com/a/27588263/1866775
template <typename T, typename... Ts> struct get_index;
template <typename T, typename... Ts>
struct get_index<T, T, Ts...> : std::integral_constant<std::size_t, 0> {};
template <typename T, typename Tail, typename... Ts>
struct get_index<T, Tail, Ts...> :
std::integral_constant<std::size_t, 1 + get_index<T, Ts...>::value> {};
template <typename T>
struct get_index<T>
{
// condition is always false, but should be dependant of T
static_assert(sizeof(T) == 0, "element not found");
};
template <typename T, typename ... Ts>
struct parameter_pack_head
{
typedef T type;
};
template <typename F>
struct function_first_input_type
{
typedef typename std::remove_const<
typename std::remove_reference<
typename utils::function_traits<
F>::template arg<0>::type>::type>::type
type;
};
template <typename F>
struct unary_function_result_type
{
static_assert(utils::function_traits<F>::arity == 1,
"Wrong arity.");
typedef typename function_first_input_type<F>::type T;
typedef std::decay_t<internal::invoke_result_t<F, T>> type;
};
// http://stackoverflow.com/a/42493805/1866775
template <typename T>
struct tag { };
template <typename... Ts>
struct type_set_eq_helper: tag<Ts>... { };
template <typename, typename, typename = void>
struct type_set_eq: std::false_type { };
template <bool...>
struct bool_pack { };
template <bool... Bs>
using my_and = std::is_same<bool_pack<Bs..., true>, bool_pack<true, Bs...>>;
template <typename... Ts1, typename... Ts2>
struct type_set_eq<std::tuple<Ts1...>, std::tuple<Ts2...>, typename std::enable_if< (sizeof...(Ts1) == sizeof...(Ts2)) && my_and< std::is_base_of<tag<Ts2>, type_set_eq_helper<Ts1...>>::value... >::value >::type >:
std::true_type { };
// http://stackoverflow.com/a/42581257/1866775
template <typename Lhs, typename Rhs>
struct is_superset_of;
template <typename Tuple, typename T, typename... Ts>
struct is_superset_of<Tuple, std::tuple<T, Ts...>>
{
static const bool value = is_one_of<T, Tuple>::value && is_superset_of<Tuple, std::tuple<Ts...>>::value;
};
template <typename Tuple>
struct is_superset_of<Tuple, std::tuple<>>
{
static const bool value = true;
};
// http://stackoverflow.com/a/36934374/1866775
template<bool... bs>
using all_true = std::is_same<bool_pack<bs..., true>, bool_pack<true, bs...>>;
} // namespace internal
template<typename ... Types>
struct variant
{
static_assert(internal::is_unique<Types...>::value, "Types must be unique.");
static_assert(internal::all_true<(!std::is_reference<Types>::value)...>::value, "No reference types allowed.");
static_assert(internal::all_true<(!std::is_const<Types>::value)...>::value, "No const types allowed.");
static_assert(sizeof...(Types) >= 1, "Please provide at least one type.");
template <typename T>
variant(const T& val) : shared_ptrs_({})
{
std::get<internal::get_index<T, Types...>::value>(shared_ptrs_) =
std::make_shared<T>(val);
}
template <typename T>
bool is() const
{
static_assert(
internal::is_one_of<T, Types...>::value
, "Type must match one possible variant type.");
const auto ptr =
std::get<internal::get_index<T, Types...>::value>(shared_ptrs_);
return static_cast<bool>(ptr);
}
friend bool operator== (
const variant<Types...>& a, const variant<Types...>& b)
{
return a.shared_ptrs_ == b.shared_ptrs_;
}
friend bool operator!= (
const variant<Types...>& a, const variant<Types...>& b)
{
return a.shared_ptrs_ != b.shared_ptrs_;
}
template <typename F>
auto visit_one(F f) const
{
using T = typename internal::function_first_input_type<F>::type;
using Ret = internal::invoke_result_t<F, T>;
internal::trigger_static_asserts<internal::unary_function_tag, F, T>();
static_assert(
internal::is_one_of<
typename internal::function_first_input_type<F>::type,
Types...>::value
, "Function input must match one variant type.");
static_assert(!std::is_same<std::decay_t<Ret>, void>::value,
"Function must return non-void type.");
const auto ptr =
std::get<internal::get_index<T, Types...>::value>(shared_ptrs_);
if (ptr)
{
return just(internal::invoke(f, *ptr));
}
return nothing<std::decay_t<Ret>>();
}
template <typename ...Fs>
auto visit(Fs ... fs) const ->
typename internal::unary_function_result_type<
typename internal::parameter_pack_head<Fs...>::type>::type
{
typedef typename internal::unary_function_result_type<
typename internal::parameter_pack_head<Fs...>::type>::type
Res;
static_assert(
sizeof...(Fs) >= std::tuple_size<shared_ptr_pack>::value,
"Too few functions provided.");
static_assert(
sizeof...(Fs) <= std::tuple_size<shared_ptr_pack>::value,
"Too many functions provided.");
typedef typename internal::transform_parameter_pack<
std::tuple,
internal::unary_function_result_type,
Fs...
>::type return_types_tuple;
typedef typename internal::transform_parameter_pack<
std::tuple,
internal::function_first_input_type,
Fs...
>::type function_first_input_types_tuple;
static_assert(
internal::is_unique<function_first_input_types_tuple>::value,
"Only one function per input type allowed.");
static_assert(
internal::are_same<return_types_tuple>::value,
"All Functions must return the same type.");
static_assert(
internal::type_set_eq<function_first_input_types_tuple, std::tuple<Types...>>::value,
"Functions do not cover all possible types.");
const auto results = justs(visit_helper<Res>(fs...));
assert(size_of_cont(results) == 1);
return head(results);
}
template <typename ...Fs>
variant<Types...> transform(Fs ... fs) const
{
static_assert(
sizeof...(Fs) >= std::tuple_size<shared_ptr_pack>::value,
"Too few functions provided.");
static_assert(
sizeof...(Fs) <= std::tuple_size<shared_ptr_pack>::value,
"Too many functions provided.");
typedef typename internal::transform_parameter_pack<
std::tuple,
internal::unary_function_result_type,
Fs...
>::type return_types_tuple;
typedef typename internal::transform_parameter_pack<
std::tuple,
internal::function_first_input_type,
Fs...
>::type function_first_input_types_tuple;
static_assert(
internal::type_set_eq<function_first_input_types_tuple, std::tuple<Types...>>::value,
"Functions do not cover all possible types.");
static_assert(
internal::is_superset_of<std::tuple<Types...>, return_types_tuple>::value,
"All Functions must return a possible variant type.");
return visit(fs...);
}
private:
template <typename Res, typename F>
std::vector<fplus::maybe<Res>> visit_helper(F f) const
{
return {visit_one(f)};
}
template <typename Res, typename F, typename ...Fs>
std::vector<fplus::maybe<Res>> visit_helper(F f, Fs ... fs) const
{
return fplus::append(visit_helper<Res>(f), visit_helper<Res>(fs...));
}
typedef typename internal::transform_parameter_pack<
std::tuple,
internal::as_shared_pointer,
Types...
>::type shared_ptr_pack;
shared_ptr_pack shared_ptrs_;
};
} // namespace fplus