Last active
May 10, 2020 20:22
-
-
Save oliverlee/fae8135c1c0f3c5d9d8e382f08e2efb3 to your computer and use it in GitHub Desktop.
Compile-time creation of transitions
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 <type_traits> | |
#include <utility> | |
#include <iostream> | |
#include <memory> | |
#include <string> | |
#include <algorithm> | |
#include <stdexcept> | |
#include <limits> | |
struct A { | |
A() { | |
std::cout << "A::A()" << '\n'; | |
} | |
A(int a) :a_{a} { | |
std::cout << "A::A()" << '\n'; | |
} | |
A(A&&) { | |
std::cout << "A::A(A&&)" << '\n'; | |
} | |
A& operator=(A&&) { | |
std::cout << "A& A::operator(A&&)" << '\n'; | |
return *this; | |
} | |
~A() { | |
std::cout << "A::~A()" << '\n'; | |
} | |
void print() { | |
std::cout << "A{ a_: " << a_ << "}" << '\n'; | |
} | |
int a_; | |
}; | |
struct B { | |
B() { | |
std::cout << "B::B()" << '\n'; | |
} | |
B(int x) : x_{x}, y_{0} { | |
std::cout << "B::B(int)" << '\n'; | |
} | |
//B(B&&) { | |
// std::cout << "B::B(B&&)" << '\n'; | |
//} | |
B(B&&) = delete; | |
B(const B& other) :x_{other.x_}, y_{other.y_} { | |
std::cout << "B::B(const B&)" << '\n'; | |
} | |
//B& operator=(B&&) { | |
// std::cout << "B& B::operator(B&&)" << '\n'; | |
// return *this; | |
//} | |
B& operator=(B&&) = delete; | |
~B() { | |
std::cout << "B::~B()" << '\n'; | |
} | |
void print() { | |
std::cout << "B{ x_: " << x_ << ", y_: " << y_ << "}" << '\n'; | |
} | |
void from(const A& a) { | |
x_ = a.a_; | |
} | |
int x_; | |
int y_; | |
}; | |
struct C { | |
C() { | |
std::cout << "C::C()" << '\n'; | |
} | |
C(const C&) { | |
std::cout << "C::C(const C&)" << '\n'; | |
} | |
C& operator=(const C&) { | |
std::cout << "C::operator=(const C&)" << '\n'; | |
return *this; | |
} | |
C(C&&) = delete; | |
C& operator=(C&&) = delete; | |
~C() { | |
std::cout << "C::~C()" << '\n'; | |
} | |
double c_; | |
}; | |
// Expand type_traits to check is a type is a template specialization | |
template <template <class...> class Template, class T > | |
struct is_specialization_of : std::false_type {}; | |
template <template <class...> class Template, class... Args > | |
struct is_specialization_of<Template, Template<Args...>> : std::true_type {}; | |
// Backport std::conjunction (C++17) | |
// https://en.cppreference.com/w/cpp/experimental/conjunction#Possible_implementation | |
template<class...> struct conjunction : std::true_type { }; | |
template<class B1> struct conjunction<B1> : B1 { }; | |
template<class B1, class... Bn> | |
struct conjunction<B1, Bn...> | |
: std::conditional_t<bool(B1::value), conjunction<Bn...>, B1> {}; | |
// Backport std::disjunction (C++17) | |
// https://en.cppreference.com/w/cpp/experimental/disjunction#Possible_implementation | |
template<class...> struct disjunction : std::false_type { }; | |
template<class B1> struct disjunction<B1> : B1 { }; | |
template<class B1, class... Bn> | |
struct disjunction<B1, Bn...> | |
: std::conditional_t<bool(B1::value), B1, disjunction<Bn...>> { }; | |
// Backport std::negation (C++17) | |
// https://en.cppreference.com/w/cpp/types/negation#Possible_implementation | |
template<class B> | |
struct negation : std::integral_constant<bool, !bool(B::value)> { }; | |
// Backport std::void_t (C++17) | |
template <typename...> | |
using void_t = void; | |
// Backport std::bool_constant (C++17) | |
template <bool B> | |
using bool_constant = std::integral_constant<bool, B>; | |
template <class T> struct identity { using type = T; }; | |
template <class...> struct list { using type = list; }; | |
template <class... Types> struct inherit : Types... { using type = inherit; }; | |
namespace detail { | |
template <class Key, class... Set> | |
using set_contains = std::is_base_of<Key, inherit<Set...>>; | |
template <class...> struct unique_impl; | |
template <class... Rs, class T, class... Ts> | |
struct unique_impl<list<Rs...>, T, Ts...> | |
: std::conditional_t<set_contains<T, Rs...>::value, | |
unique_impl<list<Rs...>, Ts...>, | |
unique_impl<list<Rs..., T>, Ts...>> {}; | |
template <class... Rs> struct unique_impl<list<Rs...>> : list<Rs...> {}; | |
} // namespace detail | |
template <class... Ts> using unique = typename detail::unique_impl<list<>, Ts...>::type; | |
template <class... Types> | |
struct map : inherit<Types...> { | |
static_assert(conjunction<is_specialization_of<std::pair, Types>...>::value, ""); | |
static_assert(std::is_same<list<Types...>, unique<Types...>>::value, ""); | |
using type = map<Types...>; | |
template <class, class Default> | |
static constexpr auto at_key(...) -> Default; | |
template <class Key, class, class Value> | |
static constexpr auto at_key(std::pair<Key, Value>*) -> Value; | |
template<class Key, class Default = void> | |
using at_key_t = decltype(type::at_key<Key, Default>(std::declval<inherit<Types...>*>())); | |
template<class Key> | |
using contains_key = negation<std::is_same<void, at_key_t<Key>>>; | |
template <class, class Default> | |
static constexpr auto at_value(...) -> Default; | |
template <class Value, class, class Key> | |
static constexpr auto at_value(std::pair<Key, Value>*) -> Key; | |
template<class Value, class Default = void> | |
using at_value_t = decltype(type::at_value<Value, Default>(std::declval<inherit<Types...>*>())); | |
template<class Value> | |
using contains_value = negation<std::is_same<void, at_value_t<Value>>>; | |
}; | |
namespace detail { | |
template <class...> struct index_map_impl; | |
template <class... Args> | |
struct index_map_impl<list<Args...>> : index_map_impl<list<>, list<Args...>> {}; | |
template <class... Entries, class Arg, class... Args> | |
struct index_map_impl<list<Entries...>, list<Arg, Args...>> | |
: index_map_impl<list<Entries..., | |
std::pair<Arg, std::integral_constant<size_t, sizeof...(Entries)>> | |
>, | |
list<Args...> | |
> {}; | |
template <class... Entries> | |
struct index_map_impl<list<Entries...>, list<>> : map<Entries...> {}; | |
} // namespace detail | |
struct empty_t {}; | |
template <class... Ts> | |
using index_map = typename detail::index_map_impl<list<Ts...>>::type; | |
template <class T> | |
struct State { | |
private: | |
template<class, class = void_t<>> | |
struct has_on_entry : std::false_type { }; | |
template<class U> | |
struct has_on_entry<U, void_t<typename U::on_entry>> : std::true_type { }; | |
template<class, class = void_t<>> | |
struct has_on_exit : std::false_type { }; | |
template<class U> | |
struct has_on_exit<U, void_t<typename U::on_exit>> : std::true_type { }; | |
public: | |
using type = T; | |
template <class U = T, std::enable_if_t<!has_on_entry<U>::value, int> = 0 > | |
static auto on_entry(T&) -> void { | |
std::cout << "on_entry skipped\n"; | |
} | |
template <class U = T, std::enable_if_t<has_on_entry<U>::value, int> = 0 > | |
static auto on_entry(T& t) -> void { | |
std::cout << "on_entry called\n"; | |
t.on_entry(); | |
} | |
template <class U = T, std::enable_if_t<!has_on_exit<U>::value, int> = 0 > | |
static auto on_exit(T&) -> void { | |
std::cout << "on_exit skipped\n"; | |
} | |
template <class U = T, std::enable_if_t<has_on_exit<U>::value, int> = 0 > | |
static auto on_exit(T& t) -> void { | |
std::cout << "on_exit called\n"; | |
t.on_entry(); | |
} | |
}; | |
class bad_state_variant_access : public std::exception {}; | |
template <class T> | |
using is_copy_or_move_constructible = disjunction<std::is_copy_constructible<T>, std::is_move_constructible<T>>; | |
template <class... Ts> | |
class StateVariant { | |
using index_map_t = index_map<empty_t, Ts...>; | |
using index_type = uint8_t; | |
using storage_type = std::aligned_union_t<0, empty_t, Ts...>; | |
static_assert(conjunction<is_copy_or_move_constructible<Ts>...>::value, | |
"StateVariant can only contain types that are copy or move constructible."); | |
static_assert(conjunction<std::is_destructible<Ts>...>::value, | |
"StateVariant can only contain types that are destructible."); | |
static_assert(!disjunction<std::is_reference<Ts>...>::value, | |
"StateVariant cannot contain reference types."); | |
static_assert(!disjunction<std::is_array<Ts>...>::value, | |
"StateVariant cannot contain array types."); | |
static_assert(sizeof...(Ts) < std::numeric_limits<StateVariant::index_type>::max(), | |
"Number of template type parameters exceeds StateVariant maximum."); | |
public: | |
StateVariant() noexcept : alternative_index_{ index_map_t::template at_key_t<empty_t>::value } { | |
std::cout << "sizeof v: " << sizeof(*this) << "\n"; | |
std::cout << "sizeof v.storage_: " << sizeof(storage_) << "\n"; | |
std::cout << "sizeof v.alternative_index_: " << sizeof(alternative_index_) << "\n"; | |
} | |
StateVariant(const StateVariant&) = default; | |
StateVariant& operator=(const StateVariant&) = default; | |
StateVariant(StateVariant&&) = default; | |
StateVariant& operator=(StateVariant&& other) = default; | |
~StateVariant() noexcept(noexcept(std::declval<StateVariant>().destroy_internal())) { | |
destroy_internal(); | |
} | |
template <class T, class D = std::remove_reference_t<T>, | |
std::enable_if_t<index_map_t::template contains_key<D>::value && | |
std::is_move_constructible<D>::value, int> = 0 > | |
auto set(T&& t) noexcept(noexcept(std::declval<StateVariant>().destroy_internal()) && | |
std::is_nothrow_move_constructible<D>::value) -> D& { | |
destroy_internal(); | |
alternative_index_ = index<D>(); | |
return *(new (static_cast<void*>(std::addressof(storage_))) D{std::forward<T>(t)}); | |
} | |
template <class T, class D = std::remove_reference_t<T>, | |
std::enable_if_t<index_map_t::template contains_key<D>::value && | |
!std::is_move_constructible<D>::value, int> = 0 > | |
auto set(const T& t) noexcept(noexcept(std::declval<StateVariant>().destroy_internal()) && | |
std::is_nothrow_copy_constructible<D>::value) -> D& { | |
destroy_internal(); | |
alternative_index_ = index<D>(); | |
return *(new (static_cast<void*>(std::addressof(storage_))) D{t}); | |
} | |
template <class T, class... Args, | |
std::enable_if_t<index_map_t::template contains_key<T>::value, int> = 0 > | |
auto emplace(Args&&... args) noexcept(noexcept(std::declval<StateVariant>().destroy_internal()) && | |
std::is_nothrow_constructible<T>::value) -> T& { | |
destroy_internal(); | |
alternative_index_ = index<T>(); | |
return *(new (static_cast<void*>(std::addressof(storage_))) T{std::forward<Args>(args)...}); | |
} | |
template <class T, | |
std::enable_if_t<index_map_t::template contains_key<T>::value, int> = 0 > | |
constexpr auto is() const noexcept -> bool { | |
return index<T>() == alternative_index_; | |
} | |
template <class T, | |
std::enable_if_t<index_map_t::template contains_key<T>::value, int> = 0 > | |
auto get() -> T& { | |
if (index<T>() != alternative_index_) { | |
throw bad_state_variant_access{}; | |
} | |
return *reinterpret_cast<T*>(std::addressof(storage_)); | |
} | |
template <class T, | |
std::enable_if_t<index_map_t::template contains_key<T>::value, int> = 0 > | |
auto get() const -> const T& { | |
if (index<T>() != alternative_index_) { | |
throw bad_state_variant_access{}; | |
} | |
return *reinterpret_cast<T*>(std::addressof(storage_)); | |
} | |
template <class T, | |
std::enable_if_t<index_map_t::template contains_key<T>::value, int> = 0 > | |
auto get_if() noexcept -> T* { | |
if (index<T>() != alternative_index_) { | |
throw nullptr; | |
} | |
return *reinterpret_cast<T*>(std::addressof(storage_)); | |
} | |
template <class T, | |
std::enable_if_t<index_map_t::template contains_key<T>::value, int> = 0 > | |
auto get_if() const noexcept -> const T* { | |
if (index<T>() != alternative_index_) { | |
return nullptr; | |
} | |
return *reinterpret_cast<T*>(std::addressof(storage_)); | |
} | |
template <class T, | |
std::enable_if_t< | |
index_map_t::template contains_key<T>::value && | |
std::is_move_constructible<T>::value, int> = 0 > | |
auto take() -> T { | |
if (index<T>() != alternative_index_) { | |
throw bad_state_variant_access{}; | |
} | |
auto retval = std::move(*reinterpret_cast<T*>(std::addressof(storage_))); | |
emplace<empty_t>(); | |
return retval; | |
} | |
template <class T, | |
std::enable_if_t<index_map_t::template contains_key<T>::value && | |
!std::is_move_constructible<T>::value, int> = 0 > | |
auto take() -> const T { | |
if (index<T>() != alternative_index_) { | |
throw bad_state_variant_access{}; | |
} | |
const auto retval = *reinterpret_cast<T*>(std::addressof(storage_)); | |
emplace<empty_t>(); | |
// Return a const value to force binding to the object copy constructor. | |
return retval; | |
} | |
template <class Closure> | |
auto visit(Closure closure) { | |
return on_alternate::invoke(alternative_index_, storage_, closure); | |
} | |
private: | |
template <class Map> struct on_alternate_impl; | |
template <class Entry> | |
struct on_alternate_impl<map<Entry>> { | |
static constexpr auto type_destructor(size_t index) noexcept { | |
using T = typename Entry::first_type; | |
return (index == Entry::second_type::value) ? | |
+[](void* storage) -> void { static_cast<T*>(storage)->~T(); } : | |
nullptr; | |
} | |
template <class Closure> | |
static constexpr auto invoke(size_t index, storage_type& storage, const Closure& closure) { | |
if (index != Entry::second_type::value) { | |
throw bad_state_variant_access{}; | |
} | |
using T = typename Entry::first_type; | |
return closure(reinterpret_cast<T&>(storage)); | |
} | |
}; | |
template <class Entry, class... Entries> | |
struct on_alternate_impl<map<Entry, Entries...>> : private on_alternate_impl<map<Entries...>> { | |
static constexpr auto type_destructor(size_t index) noexcept { | |
using T = typename Entry::first_type; | |
return (index == Entry::second_type::value) ? | |
+[](void* storage) -> void { static_cast<T*>(storage)->~T(); } : | |
on_alternate_impl<map<Entries...>>::type_destructor(index); | |
} | |
template <class Closure> | |
static constexpr auto invoke(size_t index, storage_type& storage, const Closure& closure) { | |
using T = typename Entry::first_type; | |
return (index == Entry::second_type::value) ? | |
closure(reinterpret_cast<T&>(storage)) : | |
on_alternate_impl<map<Entries...>>::invoke(index, storage, closure); | |
} | |
}; | |
using on_alternate = on_alternate_impl<index_map_t>; | |
template <class T, | |
std::enable_if_t<index_map_t::template contains_key<T>::value, int> = 0 > | |
static constexpr auto index() noexcept { | |
return static_cast<index_type>(index_map_t::template at_key_t<T>::value); | |
} | |
auto destroy_internal() noexcept(disjunction<std::is_nothrow_destructible<Ts>...>::value) -> void { | |
// Given `alternative_index_` corresponds to a value in `index_map_t`, this lookup should always succeed. | |
auto destroy = on_alternate::type_destructor(alternative_index_); | |
(*destroy)(static_cast<void*>(std::addressof(storage_))); | |
} | |
storage_type storage_; | |
index_type alternative_index_; | |
}; | |
int main() { | |
std::cout << "creating variant\n"; | |
//StateVariant<A, B, C> v{}; | |
StateVariant<A, B> v{}; | |
v.visit([](auto& a) -> void { State<decltype(a)>::on_entry(a); }); | |
auto action = [](auto& a) -> B { | |
return {a.a_}; | |
}; | |
std::cout << "\nemplacing A\n"; | |
v.emplace<A>(42); | |
std::cout << "\nget A\n"; | |
auto& a = v.get<A>(); | |
std::cout << "\nprint A\n"; | |
a.print(); | |
v.visit([](auto& a) -> void { State<decltype(a)>::on_entry(a); }); | |
std::cout << "\naction -> set B\n"; | |
v.set<B>(action(a)); | |
v.visit([](auto& a) -> void { State<decltype(a)>::on_entry(a); }); | |
std::cout << "\nprint B\n"; | |
auto& b = v.get<B>(); | |
b.print(); | |
v.visit([](auto& a) -> void { State<decltype(a)>::on_entry(a); }); | |
std::cout << "\ntake B\n"; | |
auto b2 = v.take<B>(); | |
b2.print(); | |
std::cout << "\nedit taken B\n"; | |
b2.y_ = 43; | |
b2.print(); | |
v.visit([](auto& a) -> void { State<decltype(a)>::on_entry(a); }); | |
// std::cout << "sizeof A: " << sizeof(A) << "\n"; | |
// std::cout << "sizeof B: " << sizeof(B) << "\n"; | |
// | |
// std::cout << "passing A to variant\n"; | |
// v.set(A{}); | |
// | |
// std::cout << "taking A from variant\n"; | |
// auto a = v.take<A>(); | |
// | |
// std::cout << "emplacing B in variant\n"; | |
// v.emplace<B>(); | |
// | |
// auto& b = v.get<B>(); | |
// State<B>::on_entry(b); | |
// | |
// std::cout << "Emplacing C in variant\n"; | |
// v.emplace<C>(); | |
// std::cout << "Creating C\n"; | |
// C c{}; | |
// std::cout << "Setting C in variant \n"; | |
// v.set(c); | |
// std::cout << "Taking C from variant \n"; | |
// auto d = v.take<C>(); | |
return 0; | |
} |
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 <functional> | |
#include <type_traits> | |
#include <tuple> | |
#include <utility> | |
#include <iostream> | |
#include <memory> | |
#include <string> | |
#include <algorithm> | |
#include <boost/type_index.hpp> | |
#include <boost/algorithm/string/replace.hpp> | |
// Expand type_traits to check is a type is a template specialization | |
template <template <class...> class Template, class T > | |
struct is_specialization_of : std::false_type {}; | |
template <template <class...> class Template, class... Args > | |
struct is_specialization_of<Template, Template<Args...>> : std::true_type {}; | |
// Backport std::conjunction (C++17) | |
// https://en.cppreference.com/w/cpp/experimental/conjunction#Possible_implementation | |
template<class...> struct conjunction : std::true_type { }; | |
template<class B1> struct conjunction<B1> : B1 { }; | |
template<class B1, class... Bn> | |
struct conjunction<B1, Bn...> | |
: std::conditional_t<bool(B1::value), conjunction<Bn...>, B1> {}; | |
// Backport std::disjunction (C++17) | |
// https://en.cppreference.com/w/cpp/experimental/disjunction#Possible_implementation | |
template<class...> struct disjunction : std::false_type { }; | |
template<class B1> struct disjunction<B1> : B1 { }; | |
template<class B1, class... Bn> | |
struct disjunction<B1, Bn...> | |
: std::conditional_t<bool(B1::value), B1, disjunction<Bn...>> { }; | |
// Backport std::negation (C++17) | |
// https://en.cppreference.com/w/cpp/types/negation#Possible_implementation | |
template<class B> | |
struct negation : std::integral_constant<bool, !bool(B::value)> { }; | |
// Backport std::is_invocable_r (C++17) | |
// https://stackoverflow.com/questions/51187974/can-stdis-invocable-be-emulated-within-c11 | |
template <typename R, typename F, typename... Args> | |
struct is_invocable_r : | |
std::is_constructible< | |
std::function<R(Args ...)>, | |
std::reference_wrapper<typename std::remove_reference<F>::type> | |
> {}; | |
// Backport std::void_t (C++17) | |
template <typename...> | |
using void_t = void; | |
// Backport std::bool_constant (C++17) | |
template <bool B> | |
using bool_constant = std::integral_constant<bool, B>; | |
template <class T> struct identity { using type = T; }; | |
template <class...> struct list { using type = list; }; | |
template <class... Types> struct inherit : Types... { using type = inherit; }; | |
template <class T> | |
struct State { | |
using type = T; | |
T type_; | |
}; | |
template <class T> | |
struct Event { | |
using type = T; | |
T type_; | |
}; | |
template <class T> | |
struct is_state : is_specialization_of<State, T> {}; | |
template <class T> | |
struct is_event: is_specialization_of<Event, T> {}; | |
template <class T> | |
using as_callable_dependency = std::add_lvalue_reference_t<T>; | |
template <class Result, class Function, class Source, class Event> | |
using is_transition_callable = | |
conjunction< | |
negation<std::is_null_pointer<Function>>, | |
disjunction< | |
is_invocable_r<Result, Function>, | |
is_invocable_r<Result, Function, as_callable_dependency<Event>>, | |
is_invocable_r<Result, Function, as_callable_dependency<Source>>, | |
is_invocable_r<Result, Function, as_callable_dependency<Source>, as_callable_dependency<Event>> | |
> | |
>; | |
template <class T> struct key : identity<T> {}; | |
template <class Source, class Event, class Guard, class Action, class Destination> | |
struct Transition : Guard, Action | |
{ | |
static_assert(is_state<Source>::value, ""); | |
static_assert(is_event<Event>::value, ""); | |
static_assert(is_state<Destination>::value || | |
std::is_same<identity<void>, Destination>::value , ""); | |
using type = Transition<Source, Event, Guard, Action, Destination>; | |
using source_type = typename Source::type; | |
using event_type = typename Event::type; | |
using guard_type = Guard; | |
using action_type = Action; | |
using destination_type = typename Destination::type; | |
using key_type = key<std::pair<Source, Event>>; | |
static constexpr auto internal = std::is_void<destination_type>::value; | |
static_assert(is_transition_callable<bool, Guard, source_type, event_type>::value, ""); | |
static_assert(is_transition_callable<destination_type, Action, source_type, event_type>::value, ""); | |
// We should also check if `Guard` is constexpr and returns true, | |
// but we assume it to be the case if `Guard` is convertable to a | |
// stateless function that takes no arguments and returns a bool. | |
static constexpr auto has_empty_guard = std::is_convertible<Guard, bool(*)(void)>::value; | |
Transition(Guard&& guard, Action&& action) : | |
Guard{std::forward<Guard>(guard)}, | |
Action{std::forward<Action>(action)} {} | |
template<typename... Ts> | |
auto guard(Ts&&... ts) const noexcept(noexcept(Guard::operator()())) -> bool | |
{ | |
return Guard::operator()(std::forward<Ts>(ts)...); | |
} | |
template<typename... Ts> | |
auto action(Ts&&... ts) const noexcept(noexcept(Action::operator()())) -> destination_type | |
{ | |
return Action::operator()(std::forward<Ts>(ts)...); | |
} | |
}; | |
// This should be private and hidden | |
struct empty {}; | |
namespace placeholder { | |
constexpr auto _ = identity<empty> {}; | |
} // namespace placeholder | |
template <class T> | |
struct is_empty : std::is_same<empty, std::decay_t<T>> {}; | |
template <class T> | |
struct is_empty_placeholder : std::is_same< | |
std::decay_t<decltype(placeholder::_)>, | |
std::decay_t<T> | |
> {}; | |
template <class T> | |
struct is_wrapped : is_specialization_of<identity, std::decay_t<T>> {}; | |
namespace detail { | |
template <class Guard, | |
std::enable_if_t<!is_empty_placeholder<Guard>::value, int> = 0 > | |
auto create_guard_if_empty(Guard&& guard) { | |
return std::forward<Guard>(guard); | |
} | |
template <class Guard, | |
std::enable_if_t<is_empty_placeholder<Guard>::value, int> = 0 > | |
auto create_guard_if_empty(Guard&&) { | |
return []{ return true; }; | |
} | |
} // namespace detail | |
template <class T> | |
using unwrap_t = typename std::decay_t<T>::type; | |
template <class WrappedSource, class WrappedEvent, class Guard, class Action, class WrappedDestination> | |
constexpr auto make_transition(WrappedSource, WrappedEvent, Guard&& guard, Action&& action, WrappedDestination) | |
{ | |
static_assert(is_wrapped<WrappedSource>::value, ""); | |
static_assert(is_wrapped<WrappedEvent>::value, ""); | |
static_assert(is_wrapped<WrappedDestination>::value, ""); | |
using Source = unwrap_t<WrappedSource>; | |
using Event = unwrap_t<WrappedEvent>; | |
using Destination = unwrap_t<WrappedDestination>; | |
auto updated_guard = detail::create_guard_if_empty(std::forward<Guard>(guard)); | |
using UpdatedGuard = decltype(updated_guard); | |
using UpdatedDestination = std::conditional_t< | |
is_empty<Destination>::value, | |
identity<void>, | |
Destination | |
>; | |
return Transition<Source, Event, UpdatedGuard, Action, UpdatedDestination>{ | |
std::forward<decltype(updated_guard)>(updated_guard), | |
std::forward<Action>(action) | |
}; | |
} | |
namespace detail { | |
template <class Key, class... Set> | |
using set_contains = std::is_base_of<Key, inherit<Set...>>; | |
template <class...> struct unique_impl; | |
template <class... Rs, class T, class... Ts> | |
struct unique_impl<list<Rs...>, T, Ts...> | |
: std::conditional_t<set_contains<T, Rs...>::value, | |
unique_impl<list<Rs...>, Ts...>, | |
unique_impl<list<Rs..., T>, Ts...>> {}; | |
template <class... Rs> struct unique_impl<list<Rs...>> : list<Rs...> {}; | |
} // namespace detail | |
template <class... Ts> using unique = typename detail::unique_impl<list<>, Ts...>::type; | |
template <class... Types> | |
struct map : inherit<Types...> { | |
static_assert(conjunction<is_specialization_of<std::pair, Types>...>::value, ""); | |
static_assert(std::is_same<list<Types...>, unique<Types...>>::value, ""); | |
using type = map<Types...>; | |
template <class, class Default> | |
static constexpr auto at_key(...) -> Default; | |
template <class Key, class, class Value> | |
static constexpr auto at_key(std::pair<Key, Value>*) -> Value; | |
template<class Key, class Default = void> | |
using at_key_t = decltype(at_key<Key, Default>(std::declval<inherit<Types...>*>())); | |
template<class Key> | |
using contains_key = negation<std::is_same<void, at_key_t<Key>>>; | |
template <class, class Default> | |
static constexpr auto at_value(...) -> Default; | |
template <class Value, class, class Key> | |
static constexpr auto at_value(std::pair<Key, Value>*) -> Key; | |
template<class Value, class Default = void> | |
using at_value_t = decltype(at_value<Value, Default>(std::declval<inherit<Types...>*>())); | |
template<class Value> | |
using contains_value = negation<std::is_same<void, at_value_t<Value>>>; | |
}; | |
namespace detail { | |
template <class T, class FirstGroup, class... Groups, | |
class TKey = typename T::key_type, class FKey = typename FirstGroup::first_type, | |
std::enable_if_t<!std::is_same<TKey, FKey>::value, int> = 0 > | |
constexpr auto update_matching_group(T&& transition, FirstGroup&& first_group, Groups&&... groups) { | |
return std::tuple_cat( | |
std::make_tuple(std::forward<FirstGroup>(first_group)), | |
update_matching_group(std::forward<T>(transition), std::forward<Groups>(groups)...) | |
); | |
} | |
template <class T, class FirstGroup, class... Groups, | |
class TKey = typename T::key_type, class FKey = typename FirstGroup::first_type, | |
std::enable_if_t<std::is_same<TKey, FKey>::value, int> = 0 > | |
constexpr auto update_matching_group(T&& transition, FirstGroup&& first_group, Groups&&... groups) { | |
return std::tuple_cat( | |
std::make_tuple( | |
std::make_pair( | |
TKey{}, | |
std::tuple_cat( | |
std::make_tuple(std::forward<T>(transition)), | |
std::get<1>(std::forward<FirstGroup>(first_group)) | |
) | |
) | |
), | |
std::make_tuple(std::forward<Groups>(groups)...) | |
); | |
} | |
template <class T, class Tuple, size_t... Is, | |
class Key = typename T::key_type, | |
class M = map<typename std::tuple_element_t<Is, Tuple>...>, | |
std::enable_if_t<M::template contains_key<Key>::value, int> = 0 > | |
constexpr auto update_table(T&& transition, Tuple&& table, std::index_sequence<Is...>) { | |
return update_matching_group( | |
std::forward<T>(transition), | |
std::get<Is>(std::forward<Tuple>(table))... | |
); | |
} | |
template <class T, class Tuple, size_t... Is, | |
class Key = typename T::key_type, | |
class M = map<typename std::tuple_element_t<Is, Tuple>...>, | |
std::enable_if_t<!M::template contains_key<Key>::value, int> = 0 > | |
constexpr auto update_table(T&& transition, Tuple&& table, std::index_sequence<Is...>) { | |
return std::tuple_cat( | |
std::make_tuple( | |
std::make_pair( | |
Key{}, | |
std::make_tuple(std::forward<T>(transition)) | |
) | |
), | |
std::forward<Tuple>(table) | |
); | |
} | |
template <class A1, class A2, class A3, class A4, class A5> | |
constexpr auto transition_table_builder(A1&& a1, A2&& a2, A3&& a3, A4&& a4, A5&& a5) { | |
auto transition = make_transition( | |
std::forward<A1>(a1), | |
std::forward<A2>(a2), | |
std::forward<A3>(a3), | |
std::forward<A4>(a4), | |
std::forward<A5>(a5) | |
); | |
auto group = std::make_pair( | |
typename decltype(transition)::key_type{}, | |
std::make_tuple(std::move(transition)) | |
); | |
return std::make_tuple(std::move(group)); | |
} | |
template <class A1, class A2, class A3, class A4, class A5, class... Ts> | |
constexpr auto transition_table_builder(A1&& a1, A2&& a2, A3&& a3, A4&& a4, A5&& a5, Ts&&... args) { | |
auto table = transition_table_builder(std::forward<Ts>(args)...); | |
auto transition = make_transition( | |
std::forward<A1>(a1), | |
std::forward<A2>(a2), | |
std::forward<A3>(a3), | |
std::forward<A4>(a4), | |
std::forward<A5>(a5) | |
); | |
auto indices = std::make_index_sequence<std::tuple_size<decltype(table)>::value>{}; | |
return update_table( | |
std::move(transition), | |
std::move(table), | |
std::move(indices) | |
); | |
} | |
} // namespace detail | |
namespace detail { | |
template <class...> struct index_map_impl; | |
template <class... Args> | |
struct index_map_impl<list<Args...>> : index_map_impl<list<>, list<Args...>> {}; | |
template <class... Entries, class Arg, class... Args> | |
struct index_map_impl<list<Entries...>, list<Arg, Args...>> | |
: index_map_impl<list<Entries..., | |
std::pair<Arg, std::integral_constant<size_t, sizeof...(Entries)>> | |
>, | |
list<Args...> | |
> {}; | |
template <class... Entries> | |
struct index_map_impl<list<Entries...>, list<>> : map<Entries...> {}; | |
} // namespace detail | |
template <class... Ts> | |
using index_map = typename detail::index_map_impl<Ts...>::type; | |
namespace detail { | |
template <class... > struct keys_impl; | |
template <class... Groups> | |
struct keys_impl<std::tuple<Groups...>> : keys_impl<list<>, Groups...> {}; | |
template <class... Keys, class Group, class... Groups> | |
struct keys_impl<list<Keys...>, Group, Groups...> | |
: keys_impl<list<Keys..., typename Group::first_type>, Groups...> {}; | |
template <class... Keys> struct keys_impl<list<Keys...>> : list<Keys...> {}; | |
template <class TableTuple> using keys = typename keys_impl<TableTuple>::type; | |
} // namespace detail | |
namespace detail { | |
template <template <class> class, class...> | |
struct filter_impl; | |
template <template <class> class Pred, class... Rs, class T, class... Ts> | |
struct filter_impl<Pred, list<Rs...>, T, Ts...> | |
: std::conditional_t<Pred<T>::value, | |
filter_impl<Pred, list<Rs..., T>, Ts...>, | |
filter_impl<Pred, list<Rs...>, Ts...>> {}; | |
template <template <class> class Pred, class... Rs> | |
struct filter_impl<Pred, list<Rs...>> : list<Rs...> {}; | |
} // namespace detail | |
template <template <class> class Pred, class... Ts> | |
using filter = typename detail::filter_impl<Pred, list<>, Ts...>::type; | |
namespace detail { | |
template <class... > struct state_and_event_index_impl; | |
template <class... Args> | |
struct state_and_event_index_impl<list<Args...>> | |
: state_and_event_index_impl<list<>, list<>, Args...> {}; | |
template <class... States, class... Events, class Arg, class... Args> | |
struct state_and_event_index_impl<list<States...>, list<Events...>, Arg, Args...> | |
: std::conditional_t< | |
is_state<unwrap_t<Arg>>::value, | |
state_and_event_index_impl<list<States..., unwrap_t<Arg>>, list<Events...>, Args...>, | |
std::conditional_t< | |
is_event<unwrap_t<Arg>>::value, | |
state_and_event_index_impl<list<States...>, list<Events..., unwrap_t<Arg>>, Args...>, | |
state_and_event_index_impl<list<States...>, list<Events...>, Args...> | |
> | |
> {}; | |
template <class... States, class... Events> | |
struct state_and_event_index_impl<list<States...>, list<Events...>> { | |
using state_index_type = unique<States...>; | |
using event_index_type = unique<Events...>; | |
}; | |
template <class... Args> | |
using state_and_event_index = state_and_event_index_impl<Args...>; | |
} // namespace detail | |
template <class StateIndex, class EventIndex, class KeyIndex, class Tuple> | |
class Table { | |
public: | |
using type = Table<StateIndex, EventIndex, KeyIndex, Tuple>; | |
Table(Tuple table_data) : table_{std::move(table_data)} {} | |
Table(const Table&) = default; | |
Table& operator=(const Table&) = default; | |
Table(Table&&) = default; | |
Table& operator=(Table&& other) = default; | |
~Table() = default; | |
template <class State, class Event, | |
class K = key<std::pair<State, Event>>, | |
std::enable_if_t<KeyIndex::template contains_key<K>::value, int> = 0> | |
constexpr const auto& transition_group(identity<State> s, identity<Event> e) const noexcept { | |
constexpr auto index = KeyIndex::template at_key_t<K>::value; | |
return std::get<1>(std::get<index>(table_)); | |
} | |
private: | |
Tuple table_; | |
}; | |
template <class... Ts> | |
constexpr auto make_transition_table(Ts&&... args) { | |
static_assert(sizeof...(Ts) % 5 == 0, ""); | |
static_assert(sizeof...(Ts) > 0, ""); | |
using Indices = detail::state_and_event_index<filter<is_wrapped, Ts...>>; | |
using StateIndex = index_map<typename Indices::state_index_type>; | |
using EventIndex = index_map<typename Indices::event_index_type>; | |
auto table_data = detail::transition_table_builder(std::forward<Ts>(args)...); | |
using KeyIndex = index_map<detail::keys<decltype(table_data)>>; | |
std::cout << "sizeof(StateIndex): " << sizeof(StateIndex) << '\n'; | |
std::cout << "sizeof(EventIndex): " << sizeof(EventIndex) << '\n'; | |
std::cout << "sizeof(KeyIndex): " << sizeof(KeyIndex) << '\n'; | |
std::cout << "sizeof(table_data): " << sizeof(table_data) << '\n'; | |
return Table<StateIndex, EventIndex, KeyIndex, decltype(table_data)>{std::move(table_data)}; | |
} | |
template <class T> | |
auto state = identity<State<T>> {}; | |
template <class E> | |
auto event = identity<Event<E>> {}; | |
namespace { | |
using placeholder::_; | |
struct s1 {}; | |
struct s2 {}; | |
struct s3 { | |
s3(int i) : i_{i} {}; | |
int i_; | |
}; | |
struct e1 {}; | |
struct e2 {}; | |
struct e3 { | |
e3() { }; | |
e3(int i) : value{i} { }; | |
int value; | |
}; | |
namespace sm { | |
auto state_machine() { | |
return make_transition_table( | |
state<s1>, event<e2>, _, []{ return s2{}; }, state<s2>, | |
state<s1>, event<e1>, _, []{ std::cout << "action3" << std::endl; }, _, | |
state<s1>, event<e2>, _, []{ std::cout << "action4" << std::endl; return s3{3}; }, state<s3>, | |
state<s1>, event<e3>, _, [](const auto& e) noexcept { return s3{e.value}; }, state<s3>, | |
state<s3>, event<e3>, [](const auto& e){ return e.value > 0; }, []{ return s2{}; }, state<s2>, | |
state<s3>, event<e3>, [](const auto& e){ return e.value == 0; }, []{ return s3{0}; }, state<s3> | |
); | |
} | |
} // namespace sm | |
struct S { | |
S() : table_{sm::state_machine()} {}; | |
decltype(sm::state_machine()) table_; | |
}; | |
} // namespace | |
int main() { | |
using placeholder::_; | |
struct s1 {}; | |
struct s2 {}; | |
struct s3 { | |
s3(int i) : i_{i} {}; | |
int i_; | |
}; | |
struct e1 {}; | |
struct e2 {}; | |
struct e3 { | |
e3() { }; | |
e3(int i) : value{i} { }; | |
int value; | |
}; | |
auto transition_table = make_transition_table( | |
state<s1>, event<e2>, _, []{ return s2{}; }, state<s2>, | |
state<s1>, event<e1>, _, []{ std::cout << "action3" << std::endl; }, _, | |
state<s1>, event<e2>, _, []{ std::cout << "action4" << std::endl; return s3{3}; }, state<s3>, | |
state<s1>, event<e3>, _, [](const auto& e) noexcept { return s3{e.value}; }, state<s3>, | |
state<s3>, event<e3>, [](const auto& e){ return e.value > 0; }, []{ return s2{}; }, state<s2>, | |
state<s3>, event<e3>, [](const auto& e){ return e.value == 0; }, []{ return s3{0}; }, state<s3> | |
); | |
using boost::typeindex::type_id_with_cvr; | |
{ | |
std::string s = type_id_with_cvr<decltype(transition_table)>().pretty_name(); | |
boost::replace_all(s, "map<", "\nmap<\n"); | |
boost::replace_all(s, "ul> >,", "ul> >,\n"); | |
boost::replace_all(s, "ul> > >,", "ul> > >,\n"); | |
boost::replace_all(s, "std::tuple<Transition", "\n std::tuple<Transition"); | |
boost::replace_all(s, "Transition", "\n Transition"); | |
std::cout << "decltype(transitions_table):\n" << s << '\n'; | |
std::cout << "sizeof(transition_table): " << sizeof(transition_table) << "\n\n"; | |
} | |
{ | |
auto group = transition_table.transition_group(state<s1>, event<e1>); | |
std::string s = type_id_with_cvr<decltype(group)>().pretty_name(); | |
std::cout << "decltype(group): " << s << '\n'; | |
std::get<0>(group).action(); | |
std::cout << "guard evals to: " << std::boolalpha << std::get<0>(group).guard() << '\n'; | |
} | |
{ | |
auto sm = S{}; | |
auto group = sm.table_.transition_group(state<::s1>, event<::e1>); | |
std::string s = type_id_with_cvr<decltype(group)>().pretty_name(); | |
std::cout << "decltype(group): " << s << '\n'; | |
std::get<0>(group).action(); | |
std::cout << "guard evals to: " << std::boolalpha << std::get<0>(group).guard() << '\n'; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TODO:
put "always" guards at the end of a transition groupgroup should be a single transition if "always" is useddetect duplicate "always" guards