From 449dbd7de20b7214774c77daa15d0119123e02fa Mon Sep 17 00:00:00 2001 From: Pavel Guzenfeld Date: Mon, 25 May 2026 00:00:51 +0300 Subject: [PATCH] fix non-copyable derived dependency passed as base-class reference (issue #467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an action or guard takes Base& but the sm<> ctor receives Derived& (e.g. NiceMock, any type with a deleted copy constructor), the pool init copy-constructor called try_get() which found nothing in the source pool (which held Derived&) and fell back to missing_ctor_parameter. missing_ctor_parameter tried to default-construct Base and bind it to Base& — which fails (can't bind a reference to a temporary). Root cause: try_get had no covariant overloads; lookup was exact-type only. Fix: add two new try_get overloads constrained on is_base_of: try_get(pool_type*) -> T& try_get(pool_type*) -> const T& Both return the stored D& / const D& value, which implicitly converts to the base-class reference T& / const T&. is_same is excluded to prevent ambiguity with the existing exact-match overloads. Regression test: non_copyable_derived_dep_as_base_ref in test/ft/dependencies.cpp — passes derived467 (inherits base467, deleted copy ctor) to sm whose action takes base467&. --- include/boost/sml.hpp | 18 ++++++++++++++++++ test/ft/dependencies.cpp | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) 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