diff --git a/include/boost/sml.hpp b/include/boost/sml.hpp index 20f24bb2..b9d71fdf 100644 --- a/include/boost/sml.hpp +++ b/include/boost/sml.hpp @@ -181,8 +181,11 @@ template struct is_empty_base : T { U _; }; -template +template struct is_empty : aux::integral_constant) == sizeof(none_type)> {}; +// Specialisation: final classes cannot be used as base; treat them as non-empty. (#483) +template +struct is_empty> : aux::false_type {}; template struct function_traits; template @@ -1693,7 +1696,8 @@ struct should_not_instantiate_statemachine_class template struct should_not_subclass_statemachine_class - : integral_constant::value || should_not_instantiate_statemachine_class::value> {}; + : integral_constant::value || should_not_instantiate_statemachine_class::value || + bool(__is_final(typename Tsm::sm))> {}; } // namespace aux namespace back { diff --git a/test/ft/dependencies.cpp b/test/ft/dependencies.cpp index c19029b1..17d93277 100644 --- a/test/ft/dependencies.cpp +++ b/test/ft/dependencies.cpp @@ -477,3 +477,49 @@ test const_pointer_dep_lvalue_not_null = [] { sm.process_event(e1{}); expect(sm.is(sml::X)); }; + +// Issue #483: dependencies (and SM classes themselves) declared `final` failed +// to compile because `is_empty_base` tried to inherit from T, and `sm_impl` +// tried to subclass the SM class. +// Fix (is_empty): add a partial specialisation guarded by __is_final(T) that +// reports false without ever instantiating is_empty_base. +// Fix (sm_impl): extend should_not_subclass_statemachine_class to also return +// true for final SM classes, routing them through the composition path. + +struct final_dep483 final { + int val = 99; +}; + +test final_dependency_type_compiles = [] { + // A dependency type declared `final` must be usable as an SM dependency. + struct c483dep { + auto operator()() noexcept { + using namespace sml; + auto action = [](final_dep483& d) { expect(99 == d.val); }; + // clang-format off + return make_transition_table(*idle + event / action = X); + // clang-format on + } + }; + + final_dep483 dep; + sml::sm sm{dep}; + sm.process_event(e1{}); + expect(sm.is(sml::X)); +}; + +test final_sm_class_compiles = [] { + // An SM class declared `final` must work via the composition path. + struct final_sm483 final { + auto operator()() noexcept { + using namespace sml; + // clang-format off + return make_transition_table(*idle + event = X); + // clang-format on + } + }; + + sml::sm sm; + sm.process_event(e2{}); + expect(sm.is(sml::X)); +};