Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions include/boost/sml.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,11 @@ template <class T, class U>
struct is_empty_base : T {
U _;
};
template <class T>
template <class T, class = void>
struct is_empty : aux::integral_constant<bool, sizeof(is_empty_base<T, none_type>) == sizeof(none_type)> {};
// Specialisation: final classes cannot be used as base; treat them as non-empty. (#483)
template <class T>
struct is_empty<T, aux::enable_if_t<bool(__is_final(T))>> : aux::false_type {};
template <class>
struct function_traits;
template <class R, class... TArgs>
Expand Down Expand Up @@ -1693,7 +1696,8 @@ struct should_not_instantiate_statemachine_class

template <class Tsm>
struct should_not_subclass_statemachine_class
: integral_constant<bool, is_empty<typename Tsm::sm>::value || should_not_instantiate_statemachine_class<Tsm>::value> {};
: integral_constant<bool, is_empty<typename Tsm::sm>::value || should_not_instantiate_statemachine_class<Tsm>::value ||
bool(__is_final(typename Tsm::sm))> {};
} // namespace aux

namespace back {
Expand Down
46 changes: 46 additions & 0 deletions test/ft/dependencies.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` 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<T>.
// 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<e1> / action = X);
// clang-format on
}
};

final_dep483 dep;
sml::sm<c483dep> 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<e2> = X);
// clang-format on
}
};

sml::sm<final_sm483> sm;
sm.process_event(e2{});
expect(sm.is(sml::X));
};
Loading