Skip to content

fix(dsl): classify callable handlers without instantiating them - #61

Merged
sentomk merged 1 commit into
mainfrom
fix/handler-classification
Oct 16, 2026
Merged

sentomk merged 1 commit into
mainfrom
fix/handler-classification

Conversation

@sentomk

@sentomk sentomk commented Oct 16, 2026

Copy link
Copy Markdown
Owner

Summary

Handler classification decided whether a handler is callable by instantiating its operator() with int arguments. That broke generic lambdas whose body only compiles for their real argument type, treated pointers to members as plain values, and the invocability check rejected any handler that returns a reference. This change fixes all three, which together make pattern >> &Circle::area and $ >> &Point::y work.

Changes

  • common_traits.hpp: detect a call operator without instantiating any body, by deriving from the handler type and from a class with a known operator(); an ambiguous operator() means the handler type has one. This works for plain, overloaded and generic call operators.
  • A final class cannot be derived from, so for it only a single non-template operator() is recognised, which keeps non-template final function objects working.
  • Pointers to members are handlers now. This is a behaviour change: pattern >> &T::member used to return the member pointer itself as a value, and now calls the member on the bound value.
  • is_applicable_impl takes the result type through remove_reference_t, so handlers returning a reference (a lambda returning const T &, a pointer to data member) are no longer rejected with "Handler signature does not match".
  • New tests/tests_handler_forms.cpp with 8 cases, including static_asserts on the classification trait, registered in tests/CMakeLists.txt.

Testing

  • Built in Debug on Apple clang 17 and ran the full suite: 224 of 224 tests pass (216 existing plus 8 new).
  • Against the old header the new test file does not compile (18 errors, among them the failing classification static_asserts and the "Handler signature does not match" diagnostic), so the tests do exercise the change.
  • Both changed files are clang-format clean. The reformatting of two older declarations in common_traits.hpp (get_case_result_impl, get_otherwise_result_impl) comes from running clang-format on the whole file, which the CI format check requires once a file is touched.
  • Not run on GCC or MSVC; the CI matrix covers them. The detection relies on an ambiguous member lookup being a substitution failure, which those compilers support, but I have not checked it there.
  • Known limits: a final class with a template or overloaded operator() is still classified as a value, and std::mem_fn(&T::m) as a handler still fails with libc++ (not investigated); use the member pointer directly.

@codecov

codecov Bot commented Oct 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Detect a call operator by deriving from the handler type and a class
with a known operator(), instead of taking the address of operator()
with explicit template arguments. The old probe instantiated the body
of generic lambdas with int arguments, so a generic lambda that
accesses members of its argument failed to compile. Pointers to
members are handlers as well now, so `>> &Circle::area` calls the
member instead of returning the pointer. Handlers that return a
reference (a lambda returning `const T &`, a pointer to data member)
were rejected by the invocability check and are accepted now.
@sentomk
sentomk force-pushed the fix/handler-classification branch from f6a097a to ebaf51a Compare October 16, 2026 14:42
@github-actions

Copy link
Copy Markdown
Contributor

Benchmark comparison (informational)

Base c5d9e3c vs PR ebaf51a, run one after the other on the same GitHub-hosted runner. This does not block merging.

Noise estimate: the 20 control rows below do not run Patternia code, so they should not change between base and PR. As a group they moved by a median of +0.0% (run order and machine drift); that offset is divided out of the Patternia deltas (Adjusted). The controls then scatter by a median of 1.4%. Adjusted differences under 5.0% are treated as noise.

Patternia: 8 benchmarks, 0 slower, 0 faster, 8 within noise.

Benchmark Base (ns) PR (ns) Delta Adjusted Verdict
PatterniaPipe CommandParser 1.640 1.636 -0.3% -0.3% within noise
PatterniaPipe PacketMixed 1.411 1.412 +0.1% +0.0% within noise
PatterniaPipe PacketMixedHeavyBind 1.520 1.521 +0.1% +0.0% within noise
PatterniaPipe ProtocolRouter 1.338 1.316 -1.7% -1.7% within noise
PatterniaPipe VariantAltHot 1.335 1.364 +2.1% +2.1% within noise
PatterniaPipe VariantAltHotGuarded 1.497 1.500 +0.2% +0.2% within noise
PatterniaPipe VariantMixed 1.655 1.661 +0.4% +0.3% within noise
PatterniaPipe VariantMixedGuarded 1.529 1.524 -0.3% -0.4% within noise
Control rows (not Patternia code)
Benchmark Base (ns) PR (ns) Delta
IfElse CommandParser 1.454 1.487 +2.3%
IfElse ProtocolRouter 1.749 1.717 -1.8%
Sequential VariantAltHot 2.320 1.909 -17.7%
Sequential VariantAltHotGuarded 2.326 2.319 -0.3%
Sequential VariantMixed 0.905 0.906 +0.1%
Sequential VariantMixedGuarded 0.960 0.945 -1.5%
StdVisit CommandParser 1.109 1.137 +2.5%
StdVisit ProtocolRouter 1.318 1.311 -0.5%
StdVisit VariantAltHot 1.777 2.187 +23.1%
StdVisit VariantAltHotGuarded 2.320 1.734 -25.3%
StdVisit VariantMixed 2.459 2.459 +0.0%
StdVisit VariantMixedGuarded 1.792 1.796 +0.2%
SwitchIndex VariantAltHot 1.690 2.319 +37.2%
SwitchIndex VariantAltHotGuarded 2.204 2.458 +11.5%
SwitchIndex VariantMixed 0.831 0.830 -0.1%
SwitchIndex VariantMixedGuarded 0.933 0.916 -1.9%
Switch CommandParser 1.405 1.401 -0.3%
Switch PacketMixed 1.217 1.235 +1.4%
Switch PacketMixedHeavyBind 1.251 1.252 +0.1%
Switch ProtocolRouter 1.346 1.358 +0.9%

Raw results and the comparison chart are attached to this workflow run as an artifact. Shared runners are noisy, so use this as a trend, not a measurement.

@sentomk
sentomk merged commit f9f77d9 into main Oct 16, 2026
24 checks passed
@sentomk
sentomk deleted the fix/handler-classification branch October 16, 2026 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant