diff --git a/include/boost/sml.hpp b/include/boost/sml.hpp index 989f86d1..ff2728e5 100644 --- a/include/boost/sml.hpp +++ b/include/boost/sml.hpp @@ -662,6 +662,14 @@ template constexpr const T *try_get(const pool_type *); template constexpr T *try_get(const pool_type *); +// Covariant overloads: when the pool holds D& (or const D&) where D derives +// from T, return T& (or const T&) via implicit base-class conversion. (#467) +// remove_const_t in the is_same guard prevents matching when T is const D +// (i.e. the same underlying type, just cv-qualified differently). +template , D>::value && aux::is_base_of::value)> +constexpr T &try_get(const pool_type *); +template , D>::value && aux::is_base_of::value)> +constexpr const T &try_get(const pool_type *); #if defined(BOOST_SML_CREATE_DEFAULT_CONSTRUCTIBLE_DEPS) template struct pool_type_impl::value && aux::is_constructible::value>> @@ -736,6 +744,16 @@ template constexpr const T *try_get(const pool_type *object) { return object->value; } +// Covariant overloads: pool holds D& or const D& where D is a subclass of T. +// Allows passing NiceMock (or any derived, non-copyable type) as a T& dep. (#467) +template , D>::value && aux::is_base_of::value, int>::type> +constexpr T &try_get(const pool_type *object) { + return object->value; +} +template , D>::value && aux::is_base_of::value, int>::type> +constexpr const T &try_get(const pool_type *object) { + return object->value; +} template constexpr bool would_instantiate_missing_ctor_parameter() { return is_same, decltype(try_get(aux::declval()))>::value; diff --git a/test/ft/dependencies.cpp b/test/ft/dependencies.cpp index f7bfd645..c19029b1 100644 --- a/test/ft/dependencies.cpp +++ b/test/ft/dependencies.cpp @@ -397,6 +397,39 @@ test ref_dep_copy_from_pool_not_dangling = [] { sm.process_event(e2{}); }; +// Issue #467: passing a derived (non-copyable) type as a base-class reference +// dependency failed to compile after v1.1.3. The pool init copy-constructor +// called try_get() which found nothing when the pool held Derived&, fell +// back to missing_ctor_parameter, tried to default-construct Base, and +// then could not bind the temporary to Base&. +// Fix: add covariant try_get overloads for pool_type and pool_type +// constrained on is_base_of — returns T& via implicit base-class conversion. +struct base467 { + base467() = default; + base467(const base467 &) = delete; // non-copyable, like NiceMock + int val = 7; +}; + +struct derived467 : base467 {}; + +test non_copyable_derived_dep_as_base_ref = [] { + struct c467 { + auto operator()() noexcept { + using namespace sml; + auto check = [](base467 &d) { expect(7 == d.val); }; + // clang-format off + return make_transition_table(*idle + event / check = X); + // clang-format on + } + }; + + derived467 dep; + // Action takes base467& but we pass derived467 — must compile and work. + sml::sm sm{dep}; + sm.process_event(e1{}); + expect(sm.is(sml::X)); +}; + // Issue #485: passing a pointer dependency as an lvalue caused the SM to store // nullptr instead of the actual pointer. Root cause: forwarding reference // deduction wraps a T* lvalue as T*&; the pool init constructor's try_get