From 23d09b1cc18ea756838ddaa2c7bfab2be0a76c32 Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Fri, 27 Mar 2026 19:08:27 -0400 Subject: [PATCH 1/2] feat(backmp11): improve serializer interface --- .../pages/tutorial/backmp11-back-end.adoc | 19 ++-- doc/modules/ROOT/pages/version-history.adoc | 5 + .../msm/backmp11/detail/history_impl.hpp | 31 +++--- .../backmp11/detail/state_machine_base.hpp | 34 ++++++- include/boost/msm/backmp11/state_machine.hpp | 6 ++ test/Backmp11Adapter.hpp | 98 ++----------------- test/Serialize.cpp | 9 +- 7 files changed, 82 insertions(+), 120 deletions(-) diff --git a/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc b/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc index 007193eb..8f729712 100644 --- a/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc +++ b/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc @@ -303,20 +303,14 @@ If the `fsm_parameter` is set to `root_sm`, then also the `root_sm` must be set. === Generic support for serializers -The `state_machine` allows access to its private members for serialization purposes with a friend: +The `backmp11` namespace contains a `serialize` function for serialization purposes: ```cpp -// Class boost::msm::backmp11::state_machine template -friend void serialize(T&, state_machine&); +void serialize(T&, state_machine&); ``` -A similar friend declaration is available in the `history_impl` classes. - -[IMPORTANT] -==== -This design allows you to provide any serializer implementation, but due to the need to access private members there is no guarantee that your implementation breaks in a new version of the back-end. -==== +Serializers passed to this function need to provide `operator&` overloads for serialization and deserialization. Boost.Serialization supports such overloads out-of-the-box, an example integration of it is shown in the state machine adapter. === Unified event pool for queued and deferred events @@ -471,6 +465,13 @@ class state_machine_adapter this->get_event_pool().events.clear(); } + template + void serialize(Archive& ar, + const unsigned int /*version*/) + { + msm::backmp11::serialize(ar, *this); + } + // No adapter. // Superseded by the visitor API. // void visit_current_states(...) {...} diff --git a/doc/modules/ROOT/pages/version-history.adoc b/doc/modules/ROOT/pages/version-history.adoc index 77451176..d86ff29d 100644 --- a/doc/modules/ROOT/pages/version-history.adoc +++ b/doc/modules/ROOT/pages/version-history.adoc @@ -2,6 +2,11 @@ = Version history +== Boost 1.92 + +* New features (`backmp11`): +** Improve serializer interface (https://github.com/boostorg/msm/issues/197[#197]) + == Boost 1.91 * New features (`backmp11`): diff --git a/include/boost/msm/backmp11/detail/history_impl.hpp b/include/boost/msm/backmp11/detail/history_impl.hpp index 2794fc1b..0fdb107a 100644 --- a/include/boost/msm/backmp11/detail/history_impl.hpp +++ b/include/boost/msm/backmp11/detail/history_impl.hpp @@ -48,11 +48,13 @@ class history_impl return true; } - private: - // Allow access to private members for serialization. - template - friend void serialize(T&, history_impl&); + template + void serialize(T& archive) + { + archive & m_initial_state_ids; + } + private: std::array m_initial_state_ids; }; @@ -83,11 +85,13 @@ class history_impl return false; } -private: - // Allow access to private members for serialization. - template - friend void serialize(T&, history_impl&); + template + void serialize(T& archive) + { + archive & m_last_active_state_ids; + } +private: std::array m_last_active_state_ids; }; @@ -125,11 +129,14 @@ class history_impl, NumberOfRegions> return !mp11::mp_contains::value; } - private: - // Allow access to private members for serialization. - template - friend void serialize(T&, history_impl, N>&); + template + void serialize(T& archive) + { + archive & m_initial_state_ids; + archive & m_last_active_state_ids; + } + private: std::array m_initial_state_ids; std::array m_last_active_state_ids; }; diff --git a/include/boost/msm/backmp11/detail/state_machine_base.hpp b/include/boost/msm/backmp11/detail/state_machine_base.hpp index 452d8884..0e38b645 100644 --- a/include/boost/msm/backmp11/detail/state_machine_base.hpp +++ b/include/boost/msm/backmp11/detail/state_machine_base.hpp @@ -214,9 +214,6 @@ class state_machine_base : public FrontEnd friend class deferred_event; // Allow access to private members for serialization. - // WARNING: - // No guarantee is given on the private member layout. - // Future changes may break existing serializer implementations. template friend void serialize(T&, state_machine_base&); @@ -1144,6 +1141,37 @@ class state_machine_base : public FrontEnd bool m_running{false}; }; +template +struct has_serialize : std::false_type {}; + +template +struct has_serialize().serialize(std::declval()))>> + : std::true_type {}; + +template +void serialize(T& archive, state_machine_base& sm) +{ + using FrontEnd = typename state_machine_base::front_end_t; + if constexpr (has_serialize::value) + { + static_cast(sm).serialize(archive); + } + archive & sm.m_active_state_ids; + sm.m_history.serialize(archive); + archive & sm.m_event_processing; + mp11::tuple_for_each(sm.m_states, + [&archive](auto& state) + { + using State = std::decay_t; + if constexpr (has_serialize::value || + has_state_machine_tag::value) + { + archive & state; + } + }); +} + } // boost::msm::backmp11::detail #endif // BOOST_MSM_BACKMP11_DETAIL_STATE_MACHINE_BASE_HPP diff --git a/include/boost/msm/backmp11/state_machine.hpp b/include/boost/msm/backmp11/state_machine.hpp index b50395b4..e641fd89 100644 --- a/include/boost/msm/backmp11/state_machine.hpp +++ b/include/boost/msm/backmp11/state_machine.hpp @@ -56,6 +56,12 @@ class state_machine using Base::Base; }; +template +void serialize(T& archive, detail::state_machine_base& sm) +{ + detail::serialize(archive, sm); +} + } // boost::msm::backmp11 #endif // BOOST_MSM_BACKMP11_STATE_MACHINE_HPP diff --git a/test/Backmp11Adapter.hpp b/test/Backmp11Adapter.hpp index ae3e8f01..9f116c22 100644 --- a/test/Backmp11Adapter.hpp +++ b/test/Backmp11Adapter.hpp @@ -16,7 +16,6 @@ #include #define BOOST_PARAMETER_CAN_USE_MP11 #include -#include #include #include "Backmp11.hpp" @@ -123,6 +122,13 @@ class state_machine_adapter this->get_event_pool().events.clear(); } + template + void serialize(Archive& ar, + const unsigned int /*version*/) + { + msm::backmp11::serialize(ar, *this); + } + // No adapter. // Superseded by the visitor API. // void visit_current_states(...) {...} @@ -136,94 +142,4 @@ class state_machine_adapter // auto get_state_by_id(int id) {...} }; -template -struct serialize_state -{ - serialize_state(Archive& ar):ar_(ar){} - - template - typename ::boost::enable_if< - typename ::boost::mpl::or_< - typename has_do_serialize::type, - typename detail::has_state_machine_tag::type - >::type - ,void - >::type - operator()(T& t) const - { - ar_ & t; - } - template - typename ::boost::disable_if< - typename ::boost::mpl::or_< - typename has_do_serialize::type, - typename detail::has_state_machine_tag::type - >::type - ,void - >::type - operator()(T&) const - { - // no state to serialize - } - Archive& ar_; -}; - -namespace detail -{ - -template -void serialize(Archive& ar, - state_machine_base& sm) -{ - (serialize_state(ar))(boost::serialization::base_object(sm)); - ar & sm.m_active_state_ids; - ar & sm.m_history; - ar & sm.m_event_processing; - mp11::tuple_for_each(sm.m_states, serialize_state(ar)); -} - -template -void serialize(T& ar, detail::history_impl& history) -{ - ar & history.m_initial_state_ids; -} - -template -void serialize(T& ar, detail::history_impl, N>& history) -{ - ar & history.m_initial_state_ids; - ar & history.m_last_active_state_ids; -} - -} - } // boost::msm::backmp11 - -namespace boost::serialization -{ - -template -void serialize(Archive& ar, - msm::backmp11::state_machine_adapter& sm, - const unsigned int /*version*/) -{ - msm::backmp11::detail::serialize(ar, sm); -} - -template -void serialize(T& ar, - msm::backmp11::detail::history_impl& history, - const unsigned int /*version*/) -{ - msm::backmp11::detail::serialize(ar, history); -} - -template -void serialize(T& ar, - msm::backmp11::detail::history_impl, N>& history, - const unsigned int /*version*/) -{ - msm::backmp11::detail::serialize(ar, history); -} - -} // boost::serialization diff --git a/test/Serialize.cpp b/test/Serialize.cpp index cd1addfe..7eecc91c 100644 --- a/test/Serialize.cpp +++ b/test/Serialize.cpp @@ -69,8 +69,8 @@ namespace // to achieve this, ask for it typedef int do_serialize; // and provide a serialize - template - void serialize(Archive & ar, const unsigned int ) + template + void serialize(Archive& ar, const unsigned int = 0) { ar & front_end_data; } @@ -87,12 +87,11 @@ namespace int some_dummy_data; // we want Empty to be serialized typedef int do_serialize; - template - void serialize(Archive & ar, const unsigned int ) + template + void serialize(Archive& ar, const unsigned int = 0) { ar & some_dummy_data; } - }; struct Open : public msm::front::state<> { From fbcc897c5afa1d45ebe48b93737df9ca54681345 Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Sat, 28 Mar 2026 05:44:47 -0400 Subject: [PATCH 2/2] wip --- .../pages/tutorial/backmp11-back-end.adoc | 2 +- .../backmp11/detail/state_machine_base.hpp | 56 +++++++++++++++--- include/boost/msm/front/common_states.hpp | 1 - .../boost/msm/front/detail/common_states.hpp | 12 +++- test/Backmp11Adapter.hpp | 2 +- test/CMakeLists.txt | 58 +++++++++---------- test/Serialize.cpp | 15 ++++- 7 files changed, 103 insertions(+), 43 deletions(-) diff --git a/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc b/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc index 8f729712..31cc578a 100644 --- a/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc +++ b/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc @@ -469,7 +469,7 @@ class state_machine_adapter void serialize(Archive& ar, const unsigned int /*version*/) { - msm::backmp11::serialize(ar, *this); + backmp11::serialize(ar, *this); } // No adapter. diff --git a/include/boost/msm/backmp11/detail/state_machine_base.hpp b/include/boost/msm/backmp11/detail/state_machine_base.hpp index 0e38b645..9eec107b 100644 --- a/include/boost/msm/backmp11/detail/state_machine_base.hpp +++ b/include/boost/msm/backmp11/detail/state_machine_base.hpp @@ -1142,33 +1142,75 @@ class state_machine_base : public FrontEnd }; template -struct has_serialize : std::false_type {}; - +struct has_serialize_member : std::false_type {}; template -struct has_serialize().serialize(std::declval()))>> : std::true_type {}; -template +template +struct has_serialize_free : std::false_type {}; +template +struct has_serialize_free(), std::declval()))>> + : std::true_type {}; + +// TODOs: +// - Rewrite serialize methods with call operator +// - Try out adapter for Boost.Serialization +// - Try out nlohmann serialization + +// Serialize API for state machine members. +// Can be overloaded to support key-value serialization. +template +void serialize(T& archive, const char* /*key*/, U& member) +{ + archive & member; +} + +// Serialize POD data types. +// Supports serialization without instrumentation for simple states. +// Requires `#define BOOST_MSM_FRONT_ATTRIBUTES_CONTAINER void` +// to make msm::front::state<> trivially copyable. +template >> +void serialize(T& archive, U& value) +{ + // STL type std::array as data carrier should be widely supported by + // serialization libraries. + auto& bytes = reinterpret_cast&>(value); + archive & bytes; +} + +template void serialize(T& archive, state_machine_base& sm) { using FrontEnd = typename state_machine_base::front_end_t; - if constexpr (has_serialize::value) + if constexpr (has_serialize_member::value) { static_cast(sm).serialize(archive); } + else if constexpr (has_serialize_free::value) + { + serialize(archive, static_cast(sm)); + } archive & sm.m_active_state_ids; sm.m_history.serialize(archive); - archive & sm.m_event_processing; + serialize(archive, "event_processing", sm.m_event_processing); + // archive & sm.m_event_processing; mp11::tuple_for_each(sm.m_states, [&archive](auto& state) { using State = std::decay_t; - if constexpr (has_serialize::value || + if constexpr (has_serialize_member::value || has_state_machine_tag::value) { archive & state; } + else if constexpr (has_serialize_free::value) + { + serialize(archive, state); + } }); } diff --git a/include/boost/msm/front/common_states.hpp b/include/boost/msm/front/common_states.hpp index 119891b0..3eaa84c5 100644 --- a/include/boost/msm/front/common_states.hpp +++ b/include/boost/msm/front/common_states.hpp @@ -26,7 +26,6 @@ namespace boost { namespace msm { namespace front // default base: non-polymorphic, not visitable struct default_base_state { - ~default_base_state(){} }; // default polymorphic base state. Derive all states from it to get polymorphic behavior struct polymorphic_state diff --git a/include/boost/msm/front/detail/common_states.hpp b/include/boost/msm/front/detail/common_states.hpp index 8fe65da3..c119d081 100644 --- a/include/boost/msm/front/detail/common_states.hpp +++ b/include/boost/msm/front/detail/common_states.hpp @@ -21,10 +21,14 @@ #include +#ifndef BOOST_MSM_FRONT_ATTRIBUTES_CONTAINER +#define BOOST_MSM_FRONT_ATTRIBUTES_CONTAINER ::boost::fusion::map<> +#endif + namespace boost { namespace msm { namespace front {namespace detail { -template > +template struct inherit_attributes { inherit_attributes():m_attributes(){} @@ -57,8 +61,12 @@ struct inherit_attributes Attributes m_attributes; }; +template <> +struct inherit_attributes {}; + + // the interface for all states. Defines entry and exit functions. Overwrite to implement for any state needing it. -template > +template struct state_base : public inherit_attributes, USERBASE { typedef USERBASE user_state_base; diff --git a/test/Backmp11Adapter.hpp b/test/Backmp11Adapter.hpp index 9f116c22..5da5ab89 100644 --- a/test/Backmp11Adapter.hpp +++ b/test/Backmp11Adapter.hpp @@ -126,7 +126,7 @@ class state_machine_adapter void serialize(Archive& ar, const unsigned int /*version*/) { - msm::backmp11::serialize(ar, *this); + backmp11::serialize(ar, *this); } // No adapter. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 426664a7..e82af337 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,37 +22,37 @@ endif() add_executable(boost_msm_tests EXCLUDE_FROM_ALL - AnonymousAndGuard.cpp - Anonymous.cpp - Back11CompositeMachine.cpp - BigWithFunctors.cpp - CompositeMachine.cpp - Constructor.cpp - Entries.cpp - EventQueue.cpp - History.cpp - KleeneDeferred.cpp - ManyDeferTransitions.cpp - OrthogonalDeferred2.cpp - OrthogonalDeferred3.cpp - OrthogonalDeferred.cpp + # AnonymousAndGuard.cpp + # Anonymous.cpp + # Back11CompositeMachine.cpp + # BigWithFunctors.cpp + # CompositeMachine.cpp + # Constructor.cpp + # Entries.cpp + # EventQueue.cpp + # History.cpp + # KleeneDeferred.cpp + # ManyDeferTransitions.cpp + # OrthogonalDeferred2.cpp + # OrthogonalDeferred3.cpp + # OrthogonalDeferred.cpp Serialize.cpp SerializeWithHistory.cpp - SetStates.cpp - SimpleInternal.cpp - SimpleInternalFunctors.cpp - SimpleKleene.cpp - SimpleMachine.cpp - SimpleWithFunctors.cpp - Test2RegionsAnonymous.cpp - TestConstructor.cpp - TestConstructorMovableOnlyTypes.cpp - TestDeferAndMessageQueue2.cpp - TestDeferAndMessageQueue3.cpp - TestDeferAndMessageQueue.cpp - TestDeferIn2Regions.cpp - Throwing.cpp - TransitionSkipping.cpp + # SetStates.cpp + # SimpleInternal.cpp + # SimpleInternalFunctors.cpp + # SimpleKleene.cpp + # SimpleMachine.cpp + # SimpleWithFunctors.cpp + # Test2RegionsAnonymous.cpp + # TestConstructor.cpp + # TestConstructorMovableOnlyTypes.cpp + # TestDeferAndMessageQueue2.cpp + # TestDeferAndMessageQueue3.cpp + # TestDeferAndMessageQueue.cpp + # TestDeferIn2Regions.cpp + # Throwing.cpp + # TransitionSkipping.cpp main.cpp ) add_test(NAME boost_msm_tests COMMAND boost_msm_tests) diff --git a/test/Serialize.cpp b/test/Serialize.cpp index 7eecc91c..0a06fb5a 100644 --- a/test/Serialize.cpp +++ b/test/Serialize.cpp @@ -8,6 +8,10 @@ // file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) +// Deactivate the attributes container to test +// automatic serialization of POD states with backmp11. +#define BOOST_MSM_FRONT_ATTRIBUTES_CONTAINER void + // back-end #include "BackCommon.hpp" //front-end @@ -76,7 +80,7 @@ namespace } // The list of FSM states - struct Empty : public msm::front::state<> + struct Empty : public msm::front::state<> { template void on_entry(Event const&,FSM& ) {++entry_counter;} @@ -88,9 +92,16 @@ namespace // we want Empty to be serialized typedef int do_serialize; template + + // TODO: + // Trait for has_serialize_free doesn't seem to work right. + // Remove default assignment of second arg to test. void serialize(Archive& ar, const unsigned int = 0) { - ar & some_dummy_data; + // ar & some_dummy_data; + // Current workaround: + // Manual invokation of the free function + boost::msm::backmp11::detail::serialize(ar, *this); } }; struct Open : public msm::front::state<>