Skip to content

Instantly share code, notes, and snippets.

@oliverlee
Last active May 10, 2020 20:22
Show Gist options
  • Save oliverlee/fae8135c1c0f3c5d9d8e382f08e2efb3 to your computer and use it in GitHub Desktop.
Save oliverlee/fae8135c1c0f3c5d9d8e382f08e2efb3 to your computer and use it in GitHub Desktop.
Compile-time creation of transitions
#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;
}
#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;
}
@oliverlee
Copy link
Author

oliverlee commented May 6, 2020

TODO:

  • noexcept specifiers
  • on_entry/on_exit class member functions called by the State ctor/dtor.
  • group transitions by key
  • transition table
  • put "always" guards at the end of a transition group group should be a single transition if "always" is used
  • detect duplicate "always" guards
  • tagged union (needs unique states from transition table)
  • state machine (tagged union, unique events from transition table)
  • invoke guard/action only with needed args
  • variant alignment padding

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment