From 1abcbd545d137a26ea1ffa7782d56c5fd037441f Mon Sep 17 00:00:00 2001 From: Pavel Guzenfeld Date: Mon, 25 May 2026 10:53:45 +0300 Subject: [PATCH] fix: allow final classes as SM deps and SM classes (#483) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two compilation errors triggered when a type marked final is used either as a dependency or as the SM class itself. Root cause 1 — is_empty: is_empty_base inherits from T. The compiler rejects this when T is final ('cannot derive from final base'). Fix: add a partial specialisation guarded by __is_final(T) that derives from false_type instead, so is_empty_base is never instantiated for final types. Root cause 2 — sm_impl : Tsm::sm: sm_impl inherits from the user's SM class unless should_not_subclass_statemachine_class is true. That predicate previously only checked is_empty (the EBO optimisation path) and the explicit dont_instantiate_statemachine_class policy. Fix: extend the predicate to also return true when __is_final(Tsm::sm) is true, routing final SM classes through the existing composition path (try_get_without_instantiating). __is_final is a compiler builtin available on GCC, Clang and MSVC, used here consistently with __is_base_of already present in the same header. Tests added to test/ft/dependencies.cpp: final_dependency_type_compiles — final dep type used in action final_sm_class_compiles — final SM class itself --- include/boost/sml.hpp | 8 +++++-- test/ft/dependencies.cpp | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/include/boost/sml.hpp b/include/boost/sml.hpp index 989f86d1..dcf154ec 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 @@ -1675,7 +1678,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 f7bfd645..fecae94e 100644 --- a/test/ft/dependencies.cpp +++ b/test/ft/dependencies.cpp @@ -444,3 +444,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)); +};