From 4bbf2b40642fb5f7dd91c01c59b2ea2586d6b964 Mon Sep 17 00:00:00 2001 From: Thomas Kemmer Date: Thu, 22 Jul 2021 14:03:41 +0200 Subject: [PATCH] Fix #30: Require C++17 support. --- README.md | 76 +---------------------- configure.ac | 2 +- src/fsm.h | 125 ++++---------------------------------- tests/Makefile.am | 2 - tests/test_basic_row.cpp | 82 ------------------------- tests/test_mem_fn_row.cpp | 67 -------------------- tests/test_player.cpp | 124 ------------------------------------- tests/test_row.cpp | 7 +-- 8 files changed, 17 insertions(+), 468 deletions(-) delete mode 100644 tests/test_basic_row.cpp delete mode 100644 tests/test_mem_fn_row.cpp diff --git a/README.md b/README.md index f14210b..d9a65a0 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,12 @@ # fsmlite [![Build Status](https://travis-ci.com/tkem/fsmlite.svg?branch=master)](https://travis-ci.com/tkem/fsmlite/) [![Coverage Status](https://coveralls.io/repos/github/tkem/fsmlite/badge.svg?branch=master)](https://coveralls.io/github/tkem/fsmlite?branch=master) [![Documentation Status](https://readthedocs.org/projects/fsmlite/badge/?version=latest&style=flat)](http://fsmlite.readthedocs.io/en/latest/) -**fsmlite** is a lightweight finite state machine framework for C++11. +**fsmlite** is a lightweight finite state machine framework for C++17. It is based on concepts first presented by David Abrahams and Aleksey Gurtovoy in [C++ Template Metaprogramming][1], with additional ideas taken liberally from Boost's [Meta State Machine][2] (MSM). The canonical CD player example (with CD-Text and auto-play support!) -therefore looks somewhat like this: - -```C++ -#include "fsm.h" - -#include - -class player: public fsmlite::fsm { - // grant base class access to private transition_table - friend class fsmlite::fsm; - - std::string cd_title; - bool autoplay = false; - -public: - enum states { Stopped, Open, Empty, Playing, Paused }; - - player(state_type init_state = Empty) : fsm(init_state) { } - - void set_autoplay(bool f) { autoplay = f; } - - bool is_autoplay() const { return autoplay; } - - const std::string& get_cd_title() const { return cd_title; } - - struct play {}; - struct open_close {}; - struct cd_detected { std::string title; }; - struct stop {}; - struct pause {}; - -private: - // guards - bool is_autoplay(const cd_detected&) const { return autoplay; } - bool is_bad_cd(const cd_detected& cd) const { return cd.title.empty(); } - - // actions - void start_playback(const play&); - void start_autoplay(const cd_detected& cd); - void open_drawer(const open_close&); - void open_drawer(const cd_detected& cd); - void close_drawer(const open_close&); - void store_cd_info(const cd_detected& cd); - void stop_playback(const stop&); - void pause_playback(const pause&); - void stop_and_open(const open_close&); - void resume_playback(const play&); - -private: - using m = player; // for brevity - - using transition_table = table< -// Start Event Target Action Guard (optional) -// -----------+--------+------------+--------+-------------------+---------------+- - mem_fn_row< Stopped, play, Playing, &m::start_playback >, - mem_fn_row< Stopped, open_close, Open, &m::open_drawer >, - mem_fn_row< Open, open_close, Empty, &m::close_drawer >, - mem_fn_row< Empty, open_close, Open, &m::open_drawer >, - mem_fn_row< Empty, cd_detected, Open, &m::open_drawer, &m::is_bad_cd >, - mem_fn_row< Empty, cd_detected, Playing, &m::start_autoplay, &m::is_autoplay >, - mem_fn_row< Empty, cd_detected, Stopped, &m::store_cd_info /* fallback */ >, - mem_fn_row< Playing, stop, Stopped, &m::stop_playback >, - mem_fn_row< Playing, pause, Paused, &m::pause_playback >, - mem_fn_row< Playing, open_close, Open, &m::stop_and_open >, - mem_fn_row< Paused, play, Playing, &m::resume_playback >, - mem_fn_row< Paused, stop, Stopped, &m::stop_playback >, - mem_fn_row< Paused, open_close, Open, &m::stop_and_open > -// -----------+--------+------------+--------+-------------------+---------------+- - >; -}; -``` - -C++17 will give you a little more flexibility: +looks somewhat like this: ```C++ class player: public fsmlite::fsm { diff --git a/configure.ac b/configure.ac index b5a8d13..ef541bf 100644 --- a/configure.ac +++ b/configure.ac @@ -15,7 +15,7 @@ AC_PROG_INSTALL AC_CHECK_PROGS([DOXYGEN], [doxygen], [false]) AC_CHECK_PROGS([SPHINXBUILD], [sphinx-build], [false]) -AX_CXX_COMPILE_STDCXX([11]) +AX_CXX_COMPILE_STDCXX([17]) AX_CXXFLAGS_WARN_ALL AC_ARG_ENABLE([coverage], diff --git a/src/fsm.h b/src/fsm.h index 8fa0e32..e4efc8a 100644 --- a/src/fsm.h +++ b/src/fsm.h @@ -34,33 +34,8 @@ namespace fsmlite { namespace detail { -#if __cplusplus >= 201703L - template - using invoke_result_t = std::invoke_result_t; - - template - using is_invocable = std::is_invocable; -#elif __cplusplus >= 201103L - template - using invoke_result_t = typename std::result_of::type; - - struct is_invocable_test { - struct no_type { int a; int b; }; - - template> - static char test(int); - - template - static no_type test(...); - }; - - template - using is_invocable = typename std::integral_constant< - bool, - sizeof(is_invocable_test::test(0)) == 1 - >::type; -#else -#error "fsmlite requires C++11 support." +#if __cplusplus < 201703L +#error "fsmlite requires C++17 support." #endif // C++11 std::forward() is in , which may not be // present on freestanding implementations @@ -77,27 +52,27 @@ namespace fsmlite { // C++17 std::invoke() is in , which may not be // present on freestanding implementations template - invoke_result_t invoke(F&& f, Args&&... args) { + std::invoke_result_t invoke(F&& f, Args&&... args) { return f(args...); } template - invoke_result_t invoke(M T::* f, T1&& obj, Args&&... args) { + std::invoke_result_t invoke(M T::* f, T1&& obj, Args&&... args) { return (obj.*f)(args...); } // use any of F(), F(Arg1), F(Arg2), F(Arg1, Arg2) template< class F, class Arg1, class Arg2, - bool f1 = is_invocable::value, - bool f2 = is_invocable::value, - bool f3 = is_invocable::value, - bool f4 = is_invocable::value + bool f1 = std::is_invocable::value, + bool f2 = std::is_invocable::value, + bool f3 = std::is_invocable::value, + bool f4 = std::is_invocable::value > struct binary_fn_helper; template struct binary_fn_helper { - using result_type = invoke_result_t; + using result_type = std::invoke_result_t; static result_type invoke(F&& f, Arg1&&, Arg2&&) { return detail::invoke(f); @@ -106,7 +81,7 @@ namespace fsmlite { template struct binary_fn_helper { - using result_type = invoke_result_t; + using result_type = std::invoke_result_t; static result_type invoke(F&& f, Arg1&& a, Arg2&&) { return detail::invoke(f, a); @@ -115,7 +90,7 @@ namespace fsmlite { template struct binary_fn_helper { - using result_type = invoke_result_t; + using result_type = std::invoke_result_t; static result_type invoke(F&& f, Arg1&&, Arg2&& b) { return detail::invoke(f, b); @@ -124,7 +99,7 @@ namespace fsmlite { template struct binary_fn_helper { - using result_type = invoke_result_t; + using result_type = std::invoke_result_t; static result_type invoke(F&& f, Arg1&& a, Arg2&& b) { return detail::invoke(f, a, b); @@ -286,80 +261,7 @@ namespace fsmlite { template using table = detail::list; /** - * Basic transition class template. - * - * @tparam start the start state of the transition - * - * @tparam Event the event type triggering the transition - * - * @tparam target the target state of the transition - * - * @tparam Action an action function type, or `std::nullptr_t` - * - * @tparam action a static `Action` instance - * - * @tparam Guard a guard function type, or `std::nullptr_t` - * - * @tparam guard a static `Guard` instance - */ - template< - State start, - class Event, - State target, - class Action = std::nullptr_t, - Action action = nullptr, - class Guard = std::nullptr_t, - Guard guard = nullptr - > - struct basic_row : public row_base { - static void process_event(Derived& self, const Event& event) { - row_base::process_event(action, self, event); - } - - static bool check_guard(const Derived& self, const Event& event) { - return row_base::check_guard(guard, self, event); - } - }; - - /** - * Member function transition class template. - * - * @tparam start the start state of the transition - * - * @tparam Event the event type triggering the transition - * - * @tparam target the target state of the transition - * - * @tparam action an action member function, or `nullptr` - * - * @tparam guard a guard member function, or `nullptr` - */ - template< - State start, - class Event, - State target, - void (Derived::*action)(const Event&) = nullptr, - bool (Derived::*guard)(const Event&) const = nullptr - > - struct mem_fn_row : public row_base { - static void process_event(Derived& self, const Event& event) { - if (action != nullptr) { - row_base::process_event(action, self, event); - } - } - - static bool check_guard(const Derived& self, const Event& event) { - if (guard != nullptr) { - return row_base::check_guard(guard, self, event); - } else { - return true; - } - } - }; - -#if __cplusplus >= 201703L - /** - * Generic transition class template (requires C++17). + * Transition class template. * * @tparam start the start state of the transition * @@ -387,7 +289,6 @@ namespace fsmlite { return row_base::check_guard(guard, self, event); } }; -#endif private: template struct by_event_type; diff --git a/tests/Makefile.am b/tests/Makefile.am index fb4191f..aaebd35 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -3,9 +3,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src AM_DEFAULT_SOURCE_EXT = .cpp check_PROGRAMS = \ - test_basic_row \ test_player \ - test_mem_fn_row \ test_notrans \ test_recursive \ test_row \ diff --git a/tests/test_basic_row.cpp b/tests/test_basic_row.cpp deleted file mode 100644 index 8274fb0..0000000 --- a/tests/test_basic_row.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include - -#include "fsm.h" - -int value = 0; - -// global actions - -void store(int i) { value = i; } -void clear() { value = 0; } - -// global guards - -bool is1(int i) { return i == 1; } - -// fsm - -class state_machine: public fsmlite::fsm { - friend class fsmlite::fsm; // base class needs access to transition_table - -public: - enum states { Init, Running, Exit }; - - using event = int; - -public: - - static void store2() { value = 2; } - - static bool is2(int i) { return i == 2; } - - void store3() { value = 3; } - - bool is3(int i) const { return i == 3; } - -private: - using m = state_machine; - - using transition_table = table< -// Row-Type Start Event Target Action-Type Action Guard-Type Guard -// ----------+--------+------+--------+---------------------+-----------+------------------+-------+- - basic_row< Init, event, Running, decltype(&store), &store >, - basic_row< Running, event, Running, decltype(&store), &store, decltype(&is1), &is1 >, - basic_row< Running, event, Running, decltype(&m::store2), &m::store2, decltype(&m::is2), &m::is2 >, - basic_row< Running, event, Running, decltype(&m::store3), &m::store3, decltype(&m::is3), &m::is3 >, - basic_row< Running, event, Exit, decltype(&clear), &clear /* fallback */ >, - basic_row< Exit, event, Exit > -// ----------+--------+------+--------+---------------------+-----------+------------------+-------+- - >; -}; - -int main() -{ - state_machine m; - assert(m.current_state() == state_machine::Init); - assert(value == 0); - - m.process_event(42); - assert(m.current_state() == state_machine::Running); - assert(value == 42); - - m.process_event(1); - assert(m.current_state() == state_machine::Running); - assert(value == 1); - - m.process_event(2); - assert(m.current_state() == state_machine::Running); - assert(value == 2); - - m.process_event(3); - assert(m.current_state() == state_machine::Running); - assert(value == 3); - - m.process_event(42); - assert(m.current_state() == state_machine::Exit); - assert(value == 0); - - m.process_event(42); - assert(m.current_state() == state_machine::Exit); - assert(value == 0); - return 0; -} diff --git a/tests/test_mem_fn_row.cpp b/tests/test_mem_fn_row.cpp deleted file mode 100644 index 2419ee6..0000000 --- a/tests/test_mem_fn_row.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include - -#include "fsm.h" - -class state_machine: public fsmlite::fsm { - friend class fsmlite::fsm; // base class needs access to transition_table -public: - enum states { Init, Even, Odd }; - - using event = int; - -private: - - bool is_even(const event& e) const { - return e % 2 == 0; - } - - bool is_odd(const event& e) const { - return e % 2 != 0; - } - -private: - using m = state_machine; - - using transition_table = table< -// Start Event Target Action Guard -// -----------+-----+------+------+--------+--------------+- - mem_fn_row< Init, event, Even, nullptr, &m::is_even >, - mem_fn_row< Init, event, Odd, nullptr, &m::is_odd >, - mem_fn_row< Even, event, Odd, nullptr, &m::is_odd >, - mem_fn_row< Even, event, Even, nullptr, &m::is_even >, - mem_fn_row< Odd, event, Even, nullptr, &m::is_even >, - mem_fn_row< Odd, event, Odd, nullptr /* fallback */ > -// -----------+-----+------+------+--------+--------------+- - >; -}; - -void test_even() -{ - state_machine m; - assert(m.current_state() == state_machine::Init); - m.process_event(0); - assert(m.current_state() == state_machine::Even); - m.process_event(0); - assert(m.current_state() == state_machine::Even); - m.process_event(1); - assert(m.current_state() == state_machine::Odd); -} - -void test_odd() -{ - state_machine m; - assert(m.current_state() == state_machine::Init); - m.process_event(1); - assert(m.current_state() == state_machine::Odd); - m.process_event(1); - assert(m.current_state() == state_machine::Odd); - m.process_event(0); - assert(m.current_state() == state_machine::Even); -} - -int main() -{ - test_even(); - test_odd(); - return 0; -} diff --git a/tests/test_player.cpp b/tests/test_player.cpp index f26c6c2..7b0b521 100644 --- a/tests/test_player.cpp +++ b/tests/test_player.cpp @@ -4,8 +4,6 @@ #include #include -#if __cplusplus >= 201703L - class player: public fsmlite::fsm { friend class fsmlite::fsm; // base class needs access to transition_table @@ -115,128 +113,6 @@ void player::resume_playback() std::cout << "Resuming playback\n"; } -#else - -class player: public fsmlite::fsm { - friend class fsmlite::fsm; // base class needs access to transition_table - - std::string cd_title; - bool autoplay = false; - -public: - enum states { Stopped, Open, Empty, Playing, Paused }; - - player(state_type init_state = Empty) : fsm(init_state) { } - - void set_autoplay(bool f) { autoplay = f; } - - bool is_autoplay() const { return autoplay; } - - const std::string& get_cd_title() const { return cd_title; } - - struct play {}; - struct open_close {}; - struct cd_detected { std::string title; }; - struct stop {}; - struct pause {}; - -private: - // guards - bool is_autoplay(const cd_detected&) const { return autoplay; } - bool is_bad_cd(const cd_detected& cd) const { return cd.title.empty(); } - - // actions - void start_playback(const play&); - void start_autoplay(const cd_detected& cd); - void open_drawer(const open_close&); - void open_drawer(const cd_detected& cd); - void close_drawer(const open_close&); - void store_cd_info(const cd_detected& cd); - void stop_playback(const stop&); - void pause_playback(const pause&); - void stop_and_open(const open_close&); - void resume_playback(const play&); - -private: - using m = player; // for brevity - - using transition_table = table< -// Start Event Target Action Guard (optional) -// -----------+--------+------------+--------+-------------------+---------------+- - mem_fn_row< Stopped, play, Playing, &m::start_playback >, - mem_fn_row< Stopped, open_close, Open, &m::open_drawer >, - mem_fn_row< Open, open_close, Empty, &m::close_drawer >, - mem_fn_row< Empty, open_close, Open, &m::open_drawer >, - mem_fn_row< Empty, cd_detected, Open, &m::open_drawer, &m::is_bad_cd >, - mem_fn_row< Empty, cd_detected, Playing, &m::start_autoplay, &m::is_autoplay >, - mem_fn_row< Empty, cd_detected, Stopped, &m::store_cd_info /* fallback */ >, - mem_fn_row< Playing, stop, Stopped, &m::stop_playback >, - mem_fn_row< Playing, pause, Paused, &m::pause_playback >, - mem_fn_row< Playing, open_close, Open, &m::stop_and_open >, - mem_fn_row< Paused, play, Playing, &m::resume_playback >, - mem_fn_row< Paused, stop, Stopped, &m::stop_playback >, - mem_fn_row< Paused, open_close, Open, &m::stop_and_open > -// -----------+--------+------------+--------+-------------------+---------------+- - >; -}; - -void player::start_playback(const play&) -{ - std::cout << "Starting playback\n"; -} - -void player::start_autoplay(const cd_detected& cd) -{ - std::cout << "Starting playback of '" << cd.title << "'\n"; - cd_title = cd.title; -} - -void player::open_drawer(const open_close&) -{ - std::cout << "Opening drawer\n"; - cd_title.clear(); -} - -void player::open_drawer(const cd_detected&) -{ - std::cout << "Ejecting bad CD\n"; - cd_title.clear(); -} - -void player::close_drawer(const open_close&) -{ - std::cout << "Closing drawer\n"; -} - -void player::store_cd_info(const cd_detected& cd) -{ - std::cout << "Detected CD '" << cd.title << "'\n"; - cd_title = cd.title; -} - -void player::stop_playback(const stop&) -{ - std::cout << "Stopping playback\n"; -} - -void player::pause_playback(const pause&) -{ - std::cout << "Pausing playback\n"; -} - -void player::stop_and_open(const open_close&) -{ - std::cout << "Stopping and opening drawer\n"; - cd_title.clear(); -} - -void player::resume_playback(const play&) -{ - std::cout << "Resuming playback\n"; -} - -#endif - void test_player() { player p; diff --git a/tests/test_row.cpp b/tests/test_row.cpp index abe2a5f..e9c848f 100644 --- a/tests/test_row.cpp +++ b/tests/test_row.cpp @@ -2,8 +2,6 @@ #include "fsm.h" -#if __cplusplus >= 201703L - int value = 0; // global actions @@ -51,11 +49,8 @@ class state_machine: public fsmlite::fsm { >; }; -#endif - int main() { -#if __cplusplus >= 201703L state_machine m; assert(m.current_state() == state_machine::Init); assert(value == 0); @@ -83,6 +78,6 @@ int main() m.process_event(42); assert(m.current_state() == state_machine::Exit); assert(value == 0); -#endif + return 0; }