You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Actions can not use concepts / constrained auto parameters #629
args_t<F, E> (sml.hpp ~2448) probes callable argument types in two steps:
// 1. instantiate template op() with the event typeautoargs_(int) -> function_traits_t<decltype(&T::operator()<get_event_t<E>>)>;
// 2. take address of non-template op()autoargs1_(int) -> function_traits_t<decltype(&T::operator())>;
// fallback: action_base
aux::type_list<action_base> args1_(...);
For [](Foolike auto f){}: step 1 SFINAE-fails (constraint rejects EventType), step 2 SFINAE-fails (template operator() → can't take address without explicit args), falls to action_base. Then call<TEvent, type_list<action_base>>::execute (sml.hpp:2587) fires:
Compile error: no match for call to (lambda(auto:5)) (const release&, sm_impl&, pool<>&, pool<sm_impl>&). The lambda expects one Foolike-constrained arg, not 4 internal SML context objects.
Important:action_base is not a "no-arg" fallback — it is the dispatch path for SML internals (defer, process_impl) that derive from action_base and deliberately consume the full (event, sm, deps, subs) context. Any call-site fix must gate on !is_base_of<action_base, decay_t<T>> to avoid breaking those.
The same limitation hits C++14 generic lambdas
[](Concept auto x){} is C++20 syntax, but the analogous C++14 problem is [](auto x){} where x is meant to be a dep (not the event).
Step 1 of the probe succeeds for [](auto&& x){} — &T::operator()<trigger> instantiates cleanly, returning type_list<trigger&&>. SML concludes the callable wants the event, not a dep. At call time it executes get<trigger&&>(pool<MyDep>) → invalid cast, compile error. Different symptom, same root cause: the probe always runs with the event type, so any template operator() that intends to accept a dep is misrouted.
Fix paths
Path A — explicit per-action annotation (C++14, additive, no behavior change)
A wrapper type that exposes a concrete operator()(Deps...) so function_traits can decompose it:
constauto send_fin = sml::action<Foo>([](Foolike auto f){});
// or for event + dep:constauto send_fin2 = sml::action<const release&, Foo>([](const release& e, Foolike auto f){});
The wrapper has a non-template operator()(Dep&... deps) that forwards to the inner lambda. No changes to the probe chain or call<> specializations. ~25 lines, zero risk to existing code.
Con: user must annotate every constrained/generic-dep action. Boilerplate for a common pattern.
Path B — dep probe at call site (C++14+, automatic)
In call<TEvent, type_list<action_base>>::execute, gate on !is_base_of<action_base, decay_t<T>> and probe the dep pool types with a C++14-compatible SFINAE trick:
// detect: is F callable with a single Dep?template <classF, classD>
autotry_call(F& f, D& d, int) -> decltype(f(d), void()) { f(d); }
template <classF, classD>
voidtry_call(F& f, D& d, ...) { /* no match */ }
Iterate over pool type list; invoke the first match. Must also handle event-as-first-arg combinations and guard (bool-return) variants.
Con: O(N) probe instantiations per action per dep type. Ambiguity if multiple deps satisfy the callable. More complex to implement; careful handling needed for multi-arg constrained lambdas. The guard on !is_base_of<action_base> is load-bearing — omitting it silently breaks defer/process.
Path C — sml::deps<> hint propagated to args_ (architectural)
The SM-level sml::deps<Ts...> policy already feeds explicit dep types into the pool. If those types were threaded into the args_ probe as additional candidates to try before falling back to action_base, the constrained lambda could be matched at introspection time.
Con:args_t<F, E> is a pure type alias from callable + event; adding dep types as a third parameter threads through the entire transition/mapping machinery. Significant refactor, high risk.
Workaround today: use a concrete typed lambda [](Foo f){} or the existing sml::deps<Foo> SM policy with an unconstrained [](auto f){} (though the latter still hits the generic-lambda misrouting described above — concrete type is safest).
Activity
Schoppenglas commentedon Jan 13, 2025
The SML lib targets C++14.
PavelGuzenfeld commentedon May 29, 2026
Root cause analysis (verified by compilation)
args_t<F, E>(sml.hpp ~2448) probes callable argument types in two steps:For
[](Foolike auto f){}: step 1 SFINAE-fails (constraint rejects EventType), step 2 SFINAE-fails (templateoperator()→ can't take address without explicit args), falls toaction_base. Thencall<TEvent, type_list<action_base>>::execute(sml.hpp:2587) fires:Compile error:
no match for call to (lambda(auto:5)) (const release&, sm_impl&, pool<>&, pool<sm_impl>&). The lambda expects oneFoolike-constrained arg, not 4 internal SML context objects.Important:
action_baseis not a "no-arg" fallback — it is the dispatch path for SML internals (defer,process_impl) that derive fromaction_baseand deliberately consume the full(event, sm, deps, subs)context. Any call-site fix must gate on!is_base_of<action_base, decay_t<T>>to avoid breaking those.The same limitation hits C++14 generic lambdas
[](Concept auto x){}is C++20 syntax, but the analogous C++14 problem is[](auto x){}wherexis meant to be a dep (not the event).Verified by compilation:
Step 1 of the probe succeeds for
[](auto&& x){}—&T::operator()<trigger>instantiates cleanly, returningtype_list<trigger&&>. SML concludes the callable wants the event, not a dep. At call time it executesget<trigger&&>(pool<MyDep>)→ invalid cast, compile error. Different symptom, same root cause: the probe always runs with the event type, so any templateoperator()that intends to accept a dep is misrouted.Fix paths
Path A — explicit per-action annotation (C++14, additive, no behavior change)
A wrapper type that exposes a concrete
operator()(Deps...)sofunction_traitscan decompose it:The wrapper has a non-template
operator()(Dep&... deps)that forwards to the inner lambda. No changes to the probe chain orcall<>specializations. ~25 lines, zero risk to existing code.Con: user must annotate every constrained/generic-dep action. Boilerplate for a common pattern.
Path B — dep probe at call site (C++14+, automatic)
In
call<TEvent, type_list<action_base>>::execute, gate on!is_base_of<action_base, decay_t<T>>and probe the dep pool types with a C++14-compatible SFINAE trick:Iterate over pool type list; invoke the first match. Must also handle event-as-first-arg combinations and guard (bool-return) variants.
Con: O(N) probe instantiations per action per dep type. Ambiguity if multiple deps satisfy the callable. More complex to implement; careful handling needed for multi-arg constrained lambdas. The guard on
!is_base_of<action_base>is load-bearing — omitting it silently breaksdefer/process.Path C —
sml::deps<>hint propagated toargs_(architectural)The SM-level
sml::deps<Ts...>policy already feeds explicit dep types into the pool. If those types were threaded into theargs_probe as additional candidates to try before falling back toaction_base, the constrained lambda could be matched at introspection time.Con:
args_t<F, E>is a pure type alias from callable + event; adding dep types as a third parameter threads through the entire transition/mapping machinery. Significant refactor, high risk.Workaround today: use a concrete typed lambda
[](Foo f){}or the existingsml::deps<Foo>SM policy with an unconstrained[](auto f){}(though the latter still hits the generic-lambda misrouting described above — concrete type is safest).feat(#629): add sml::make_action<Deps...>(f) to support template/cons…
docs: document API and behavior changes since v1.1.13
docs: document sml::make_action and regenerate Godbolt links for v1.2.0
docs(sml.hpp): comment the new public API (polish, no functional change)