diff --git a/docs/api.md b/docs/api.md index 6ec6a538..2e952d2a 100644 --- a/docs/api.md +++ b/docs/api.md @@ -205,8 +205,9 @@ explicit name. ### `PTN_BIND(Type, names...)` -Declares readable placeholders for multi-binding guards. List the names in the -same order as the members passed to `has<>`. +Declares member-anchored placeholders for structural guards. Each name must +designate a non-static data member of `Type`; the macro expands `name` to +`constexpr member_t<&Type::name>`. ```cpp PTN_BIND(Point, x, y); @@ -214,6 +215,19 @@ PTN_BIND(Point, x, y); $(has<&Point::x, &Point::y>)[x * x + y * y == 25] ``` +Inside a guard, names resolve to the position of their member in the `has<>` +member list at compile time, so they follow members, not positions — the order +of member pointers in `has<>` does not matter: + +```cpp +$(has<&Point::y, &Point::x>)[x == 3 && y == 4] // x is still .x +``` + +Misuse is caught at compile time: a misspelled member name fails right at the +`PTN_BIND` line, and a name whose member is not listed in `has<>` fails a +static_assert. Member names are only valid in guards attached to `has<...>`; +using them on a non-structural pattern is a compile-time error. + ### `rng(lo, hi, mode)` Range helper for single-bound-value guards. @@ -227,6 +241,10 @@ $[rng(0, 10, pat::mod::open)] scope. Use callables for domain logic that does not read naturally as `_` or a short named-placeholder expression. +Note: block-scope names cannot be referenced inside `PTN_ON`, because its +caching lambda cannot capture them. Use plain `on(...)` there, or declare the +names at namespace scope. + ### Migration from positional guard APIs The guard surface now uses `_` for one bound value and `PTN_BIND` names for diff --git a/docs/roadmap.md b/docs/roadmap.md index f74e4bb7..75ca071a 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -54,6 +54,23 @@ extensions cost one short macro per level. See PR #44. --- +### Member-anchored `PTN_BIND` placeholders + +`PTN_BIND(Type, ...)` names expand to `member_t<&Type::name>` +instead of positional `arg_t`. Guards resolve each name to the +position of its member in the `has<...>` member list at compile +time: + +- names follow members, so `has<...>` order no longer matters; +- misspelled member names fail at the `PTN_BIND` line; +- a name used with a `has<...>` that lacks its member fails a + static_assert. + +This also settles the previously deferred `Type`-validation item +without static reflection. + +--- + ## NEXT Potential follow-up items after current WIP scope is stabilized. @@ -120,6 +137,6 @@ here so they are not re-proposed without new motivation. - **Chained comparisons** (`1 <= _ <= 10`): breaks predicate semantics and produces unreadable diagnostics. Use `rng(lo, hi)` with explicit range modes. -- **`PTN_BIND` `Type` validation**: deferred. The `Type` argument is - documentary for now; member-name checking becomes feasible with - static reflection (C++26). +- **`PTN_BIND` `Type` validation**: resolved without reflection — + names now expand to `member_t<&Type::name>`, so member checking + happens at declaration time (see WIP above). diff --git a/include/ptn/pattern/base/binding_base.hpp b/include/ptn/pattern/base/binding_base.hpp index 398700de..b2d13e59 100644 --- a/include/ptn/pattern/base/binding_base.hpp +++ b/include/ptn/pattern/base/binding_base.hpp @@ -8,10 +8,25 @@ #include "ptn/pattern/modifiers/fwd.h" #include +#include namespace ptn::pat::base { - // CRTP mixin that provides guard operator (`[]`) for binding patterns. + // Hook that lets a binding pattern rewrite its guard predicate + // before the guarded pattern is built. The default is the + // identity; structural binding patterns specialize this to + // resolve PTN_BIND member placeholders against their member + // list. + template + struct guard_resolver { + template + static constexpr decltype(auto) apply(P &&pred) { + return std::forward

(pred); + } + }; + + // CRTP mixin that provides guard operator (`[]`) for binding + // patterns. // // This class enables the guard syntax `pattern[predicate]` by // providing the operator[] that creates a guarded_pattern. @@ -30,19 +45,23 @@ namespace ptn::pat::base { // Pred: The predicate type (typically a lambda or function). // Parameters: // pred: The predicate function to apply to the bound value. - // Returns: A guarded_pattern that combines the pattern with the predicate. + // Returns: A guarded_pattern that combines the pattern with the + // predicate. template auto operator[](Pred &&pred) const { - using D = std::decay_t; - return mod::guarded_pattern>{ - static_cast(*this), std::forward(pred)}; + using D = std::decay_t; + auto resolved = guard_resolver::apply( + std::forward(pred)); + return mod::guarded_pattern{ + static_cast(*this), std::move(resolved)}; } }; // Base class for patterns that can bind values. // // All binding patterns should inherit from this class to gain - // guard operator functionality and mark themselves as binding patterns. + // guard operator functionality and mark themselves as binding + // patterns. // // Template parameter: // Derived: The derived pattern type (CRTP). diff --git a/include/ptn/pattern/base/pattern_traits.hpp b/include/ptn/pattern/base/pattern_traits.hpp index 09cc8833..8424b333 100644 --- a/include/ptn/pattern/base/pattern_traits.hpp +++ b/include/ptn/pattern/base/pattern_traits.hpp @@ -2,9 +2,9 @@ // Compile-time detection utilities for Patternia patterns. // -// This file provides type traits for detecting and characterizing patterns, -// including pattern identification, binding pattern detection, structural -// pattern traits, and guard predicate traits. +// This file provides type traits for detecting and characterizing +// patterns, including pattern identification, binding pattern +// detection, structural pattern traits, and guard predicate traits. #include #include @@ -25,23 +25,24 @@ namespace ptn::pat::traits { template struct has_match_method< P, - std::void_t(std::declval().match( - std::declval())))>> : std::true_type {}; + std::void_t( + std::declval().match(std::declval())))>> + : std::true_type {}; // Variable template for has_match_method

::value. template - inline constexpr bool has_match_method_v = has_match_method

::value; + inline constexpr bool + has_match_method_v = has_match_method

::value; // Trait: determines whether P acts as a Pattern. // - // A type is considered a pattern if it inherits from base::pattern_tag - // OR has a .match(Subject) method. + // A type is considered a pattern if it inherits from + // base::pattern_tag OR has a .match(Subject) method. template - struct is_pattern - : std::integral_constant< - bool, - std::is_base_of_v || has_match_method_v

> { - }; + struct is_pattern : std::integral_constant< + bool, + std::is_base_of_v + || has_match_method_v

> {}; // Convenience variable template for is_pattern

::value. template @@ -53,8 +54,8 @@ namespace ptn::pat::traits { // Detects if a pattern is a binding pattern. // - // Binding patterns are those that inherit from binding_pattern_base and - // have a static is_binding member. + // Binding patterns are those that inherit from + // binding_pattern_base and have a static is_binding member. template struct is_binding_pattern : std::false_type {}; @@ -65,7 +66,8 @@ namespace ptn::pat::traits { // Helper variable template for is_binding_pattern. template - inline constexpr bool is_binding_pattern_v = is_binding_pattern

::value; + inline constexpr bool + is_binding_pattern_v = is_binding_pattern

::value; // ----------------------------------------------------------------------- // Structural-pattern traits. @@ -73,21 +75,22 @@ namespace ptn::pat::traits { // Checks if M is a non-static data member pointer. template - inline constexpr bool is_data_member_ptr_v = - std::is_member_object_pointer_v; + inline constexpr bool is_data_member_ptr_v = std:: + is_member_object_pointer_v; // Checks if M is a nullptr placeholder (e.g., _ign). template - inline constexpr bool is_nullptr_placeholder_v = - std::is_same_v, std::nullptr_t>; + inline constexpr bool is_nullptr_placeholder_v = std:: + is_same_v, std::nullptr_t>; // Unified notion: M is a structural element. // - // Structural elements are either data member pointers or nullptr placeholders - // used in has<> patterns. + // Structural elements are either data member pointers or nullptr + // placeholders used in has<> patterns. template - inline constexpr bool is_structural_element_v = - is_data_member_ptr_v || is_nullptr_placeholder_v; + inline constexpr bool + is_structural_element_v = is_data_member_ptr_v + || is_nullptr_placeholder_v; // ----------------------------------------------------------------------- // Guard-predicate traits. @@ -95,7 +98,8 @@ namespace ptn::pat::traits { // Marker tag for guard predicates. // - // Guard predicates should inherit from this tag to enable detection. + // Guard predicates should inherit from this tag to enable + // detection. struct guard_predicate_tag {}; // Detects if a type is a guard predicate. @@ -103,19 +107,24 @@ namespace ptn::pat::traits { // A type is considered a guard predicate if it inherits from // guard_predicate_tag. template - inline constexpr bool is_guard_predicate_v = - std::is_base_of_v>; + inline constexpr bool + is_guard_predicate_v = std::is_base_of_v>; // Trait to detect argument expression nodes. // - // Argument expressions include placeholders (arg_t), value wrappers (val_t), - // binary expressions (bin_expr), and unary expressions (un_expr). + // Argument expressions include placeholders (arg_t), value + // wrappers (val_t), binary expressions (bin_expr), and unary + // expressions (un_expr). template struct is_arg_expr : std::false_type {}; template struct is_arg_expr> : std::true_type {}; + template + struct is_arg_expr> : std::true_type {}; + template struct is_arg_expr> : std::true_type {}; @@ -126,47 +135,54 @@ namespace ptn::pat::traits { struct is_arg_expr> : std::true_type {}; template - inline constexpr bool is_arg_expr_v = is_arg_expr>::value; + inline constexpr bool + is_arg_expr_v = is_arg_expr>::value; // Trait to detect tuple predicates. // - // Tuple predicates wrap expression templates and operate on bound tuples. + // Tuple predicates wrap expression templates and operate on bound + // tuples. template struct is_tuple_predicate : std::false_type {}; template - struct is_tuple_predicate> : std::true_type {}; + struct is_tuple_predicate> + : std::true_type {}; template - inline constexpr bool is_tuple_predicate_v = - is_tuple_predicate>::value; + inline constexpr bool is_tuple_predicate_v = is_tuple_predicate< + std::decay_t>::value; - // Trait to detect tuple guard predicates (including && / || compositions). + // Trait to detect tuple guard predicates (including && / || + // compositions). // - // Tuple guard predicates are predicates that can be called with a tuple - // of bound values. This includes tuple_predicate and logical compositions - // (pred_and, pred_or) of tuple guard predicates. + // Tuple guard predicates are predicates that can be called with a + // tuple of bound values. This includes tuple_predicate and logical + // compositions (pred_and, pred_or) of tuple guard predicates. template struct is_tuple_guard_predicate : std::false_type {}; template - struct is_tuple_guard_predicate> : std::true_type {}; + struct is_tuple_guard_predicate> + : std::true_type {}; template - struct is_tuple_guard_predicate> : std::true_type {}; + struct is_tuple_guard_predicate> + : std::true_type {}; template - inline constexpr bool is_tuple_guard_predicate_v = - is_tuple_guard_predicate>::value; + inline constexpr bool + is_tuple_guard_predicate_v = is_tuple_guard_predicate< + std::decay_t>::value; template struct is_tuple_guard_predicate> - : std::bool_constant< - is_tuple_guard_predicate_v || is_tuple_guard_predicate_v> {}; + : std::bool_constant + || is_tuple_guard_predicate_v> {}; template struct is_tuple_guard_predicate> - : std::bool_constant< - is_tuple_guard_predicate_v || is_tuple_guard_predicate_v> {}; + : std::bool_constant + || is_tuple_guard_predicate_v> {}; } // namespace ptn::pat::traits diff --git a/include/ptn/pattern/bind.hpp b/include/ptn/pattern/bind.hpp index 97025766..71e70c13 100644 --- a/include/ptn/pattern/bind.hpp +++ b/include/ptn/pattern/bind.hpp @@ -12,6 +12,7 @@ #include "ptn/pattern/base/binding_base.hpp" #include "ptn/pattern/base/pattern_base.hpp" #include "ptn/pattern/structural.hpp" +#include "ptn/pattern/modifiers/guard.hpp" #include #include @@ -283,6 +284,29 @@ namespace ptn::pat::base { Subject>::type>()))>; }; + // Guards on as-binding patterns delegate member-placeholder + // resolution to the wrapped subpattern. + template + struct guard_resolver> { + template + static constexpr auto apply(P &&pred) { + return guard_resolver::apply(std::forward

(pred)); + } + }; + + // Guard resolver for structural binding patterns: PTN_BIND + // member placeholders in the guard are anchored to their + // member's position in the has<...> member list. + template + struct guard_resolver>> { + template + static constexpr auto apply(P &&pred) { + return pat::mod::resolve_pred(pat::mod::member_list{}, + std::forward

(pred)); + } + }; + // Structural-binding pattern binds. template struct binding_args struct un_expr; + // Forward declare member-anchored placeholder + template + struct member_t; + + // Forward declare member pointer list + template + struct member_list; + // Forward declare max argument index traits template struct max_arg_index; diff --git a/include/ptn/pattern/modifiers/guard.hpp b/include/ptn/pattern/modifiers/guard.hpp index 01be9671..a3781c70 100644 --- a/include/ptn/pattern/modifiers/guard.hpp +++ b/include/ptn/pattern/modifiers/guard.hpp @@ -98,6 +98,19 @@ namespace ptn::pat::mod { return Op{}(eval(e.x, std::forward(t))); } + // Trap: member placeholders must be resolved to positional + // placeholders by the structural pattern owning the guard. This + // overload is only reached when a PTN_BIND name is used in a + // guard on a non-structural pattern. + template + constexpr decltype(auto) eval(const member_t &, Tuple &&) { + static_assert(dependent_false::value, + "[Patternia.guard] A PTN_BIND member placeholder " + "was used in a guard on a non-structural pattern. " + "Member names are only valid in guards attached " + "to has<...>."); + } + // Makes expression callable as predicate on bound tuples. template struct tuple_predicate : traits::guard_predicate_tag { @@ -140,6 +153,18 @@ namespace ptn::pat::mod { : std::integral_constant::value> {}; + // Trap with a clear message when a member placeholder leaks into + // the positional bounds check (non-structural guard). + template + struct max_arg_index> { + static_assert(dependent_false>::value, + "[Patternia.guard] A PTN_BIND member placeholder " + "was used in a guard on a non-structural pattern. " + "Member names are only valid in guards attached " + "to has<...>."); + static constexpr std::size_t value = 0; + }; + template inline constexpr std::size_t max_arg_index_v = max_arg_index>::value; @@ -459,6 +484,126 @@ namespace ptn::pat::mod { std::forward(l), std::forward(r)}; } + // --- Member placeholder resolution (structural guards) --- + + namespace detail { + + // Type-safe member pointer equality: comparing member pointers + // of different types is ill-formed, so guard the comparison. + template + struct member_eq_impl : std::false_type {}; + + template + struct member_eq_impl : std::bool_constant { + }; + + template + struct member_eq + : member_eq_impl< + M, + N, + (std::is_same_v)> {}; + + } // namespace detail + + // Position of member M among the non-_ign members of Ms.... + // _ign (nullptr) slots do not occupy binding positions. + template + struct member_position; + + template + struct member_position { + static constexpr bool found = false; + static constexpr std::size_t value = 0; + }; + + template + struct member_position { + private: + static constexpr bool + is_ign = std::is_null_pointer_v; + static constexpr bool + hit = !is_ign && detail::member_eq::value; + using next = member_position; + + public: + static constexpr bool found = hit || next::found; + static constexpr std::size_t value = hit ? 0 + : (is_ign ? next::value + : next::value + + 1); + }; + + // Rewrites a guard expression tree, replacing every member_t + // leaf with arg_t. All other nodes are + // preserved. Evaluation machinery is untouched. + // + // Fallback: non-expression nodes pass through unchanged (by + // value, so stored types stay decayed). + template + constexpr auto resolve_expr(member_list, E &&e) { + return std::forward(e); + } + + template + constexpr auto resolve_expr(member_list, member_t) { + using pos = member_position; + static_assert(pos::found, + "[Patternia.guard] A PTN_BIND name does not match " + "any member listed in has<...>. Check the member " + "pointers in the pattern."); + return arg_t{}; + } + + template + constexpr auto resolve_expr(member_list ml, + bin_expr e) { + auto l = resolve_expr(ml, e.l); + auto r = resolve_expr(ml, e.r); + return bin_expr{std::move(l), + std::move(r)}; + } + + template + constexpr auto resolve_expr(member_list ml, + un_expr e) { + auto x = resolve_expr(ml, e.x); + return un_expr{std::move(x)}; + } + + // Rewrites a full guard predicate. Expression predicates + // (tuple_predicate) and logical compositions (pred_and/pred_or) + // are rewritten; callables and other predicates pass through. + template + constexpr auto resolve_pred(member_list, P &&p) { + return std::forward

(p); + } + + template + constexpr auto resolve_pred(member_list ml, + tuple_predicate p) { + auto e = resolve_expr(ml, std::move(p.expr)); + return tuple_predicate{std::move(e)}; + } + + template + constexpr auto resolve_pred(member_list ml, + pred_and p) { + auto l = resolve_pred(ml, std::move(p.lhs)); + auto r = resolve_pred(ml, std::move(p.rhs)); + return pred_and{std::move(l), + std::move(r)}; + } + + template + constexpr auto resolve_pred(member_list ml, + pred_or p) { + auto l = resolve_pred(ml, std::move(p.lhs)); + auto r = resolve_pred(ml, std::move(p.rhs)); + return pred_or{std::move(l), + std::move(r)}; + } + // Range modes for interval predicates. enum class range_mode { closed, @@ -598,6 +743,16 @@ namespace ptn::pat::mod { namespace ptn::pat::base { + // Guards on already-guarded patterns delegate member-placeholder + // resolution to the inner pattern (chained guard support). + template + struct guard_resolver> { + template + static constexpr auto apply(P &&pred) { + return guard_resolver::apply(std::forward

(pred)); + } + }; + // Binding contract specialization for guarded_pattern. template struct binding_args, Subject> { diff --git a/include/ptn/pattern/modifiers/placeholder.hpp b/include/ptn/pattern/modifiers/placeholder.hpp index ae0e9662..c3f05d75 100644 --- a/include/ptn/pattern/modifiers/placeholder.hpp +++ b/include/ptn/pattern/modifiers/placeholder.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include namespace ptn::pat::mod { @@ -10,6 +11,27 @@ namespace ptn::pat::mod { static constexpr std::size_t index = I; }; + // Member-anchored placeholder for structural guards. + // + // A PTN_BIND name expands to member_t<&Type::member>. Inside a + // guard attached to has<...>, the structural pattern resolves + // the member pointer to the position of that member in its + // member list at compile time, so guard names follow members, + // not the order of pointers in has<...>. + template + struct member_t { + static constexpr auto member = M; + }; + + // Type-level list of member pointers (including _ign slots) + // describing the member order of a structural pattern. + template + struct member_list {}; + + // Dependent false for static_assert inside templates. + template + struct dependent_false : std::false_type {}; + // Computes the largest binding position referenced by an // expression. template diff --git a/include/ptn/pattern/structural.hpp b/include/ptn/pattern/structural.hpp index 4edb2f6d..bcf3ed0f 100644 --- a/include/ptn/pattern/structural.hpp +++ b/include/ptn/pattern/structural.hpp @@ -11,6 +11,7 @@ #include "ptn/pattern/base/fwd.h" #include "ptn/pattern/base/pattern_base.hpp" #include "ptn/pattern/base/pattern_traits.hpp" +#include "ptn/pattern/modifiers/guard.hpp" #include "ptn/core/common/diagnostics.hpp" namespace ptn::pat { @@ -130,13 +131,18 @@ namespace ptn::pat { } // Deferred definition of has_pattern::operator[]. + // PTN_BIND member placeholders in the guard are resolved + // against this pattern's member list. template template constexpr auto has_pattern::operator[](Pred &&pred) const { + auto resolved = ptn::pat::mod::resolve_pred( + ptn::pat::mod::member_list{}, + std::forward(pred)); return has_guarded_pattern, - std::decay_t>{ - std::forward(pred)}; + decltype(resolved)>{ + std::move(resolved)}; } } // namespace detail diff --git a/include/ptn/pattern/type.hpp b/include/ptn/pattern/type.hpp index bd84d2cc..6957fa32 100644 --- a/include/ptn/pattern/type.hpp +++ b/include/ptn/pattern/type.hpp @@ -289,3 +289,26 @@ namespace ptn::pat::base { }; } // namespace ptn::pat::base + +namespace ptn::pat::base { + + // Guards on type patterns delegate member-placeholder + // resolution to the wrapped subpattern, so PTN_BIND names work + // through is(...) / alt(...) wrappers. + template + struct guard_resolver> { + template + static constexpr auto apply(P &&pred) { + return guard_resolver::apply(std::forward

(pred)); + } + }; + + template + struct guard_resolver> { + template + static constexpr auto apply(P &&pred) { + return guard_resolver::apply(std::forward

(pred)); + } + }; + +} // namespace ptn::pat::base diff --git a/include/ptn/patternia.hpp b/include/ptn/patternia.hpp index 1120a2ea..6fd4c875 100644 --- a/include/ptn/patternia.hpp +++ b/include/ptn/patternia.hpp @@ -89,11 +89,16 @@ namespace ptn { // PTN_BIND(Type, member0, member1, ...) // -// Declares named placeholder objects for use in guard expressions. -// Each name is a constexpr arg_t where N is the zero-based -// position of the member in the argument list. Declarations are -// valid at namespace or block scope, so names can stay close to a -// match. +// Declares member-anchored named placeholders for use in guard +// expressions attached to has<...>. Each name must designate a +// non-static data member of Type: the macro expands name to +// constexpr member_t<&Type::name>. Declarations are valid at +// namespace or block scope. +// +// Inside a guard, names resolve to the position of their member +// in the has<...> member list at compile time. They follow +// members, not positions, so the order of member pointers in +// has<...> does not matter. // // Example: // struct Point { int x; int y; }; @@ -104,11 +109,9 @@ namespace ptn { // >> [](auto& p) { return dist(p); } // ); // -// NOTE: The Type argument is currently unused by the generated code. -// It serves as documentation: the names are expected to correspond -// to the members of Type, listed in the same order as in has<...>. -// A future reflection-based variant may validate this at compile -// time. +// A misspelled member name fails at the PTN_BIND line, and a name +// used with a has<...> that does not list its member fails a +// static_assert. // // Supports 1 to 10 member names. // @@ -117,47 +120,47 @@ namespace ptn { // - PTN_BIND_N is defined by chaining: it expands to // PTN_BIND_{N-1} plus one more declaration, so adding a new // arity only costs one short macro instead of a full rewrite. -#define PTN_BIND_DECL(Index, name) \ - constexpr ::ptn::pat::mod::arg_t name{}; +#define PTN_BIND_DECL(Type, name) \ + constexpr ::ptn::pat::mod::member_t<&Type::name> name{}; -#define PTN_BIND_1(Type, m0) PTN_BIND_DECL(0, m0) +#define PTN_BIND_1(Type, m0) PTN_BIND_DECL(Type, m0) #define PTN_BIND_2(Type, m0, m1) \ PTN_BIND_EXPAND(PTN_BIND_1(Type, m0)) \ - PTN_BIND_DECL(1, m1) + PTN_BIND_DECL(Type, m1) #define PTN_BIND_3(Type, m0, m1, m2) \ PTN_BIND_EXPAND(PTN_BIND_2(Type, m0, m1)) \ - PTN_BIND_DECL(2, m2) + PTN_BIND_DECL(Type, m2) #define PTN_BIND_4(Type, m0, m1, m2, m3) \ PTN_BIND_EXPAND(PTN_BIND_3(Type, m0, m1, m2)) \ - PTN_BIND_DECL(3, m3) + PTN_BIND_DECL(Type, m3) #define PTN_BIND_5(Type, m0, m1, m2, m3, m4) \ PTN_BIND_EXPAND(PTN_BIND_4(Type, m0, m1, m2, m3)) \ - PTN_BIND_DECL(4, m4) + PTN_BIND_DECL(Type, m4) #define PTN_BIND_6(Type, m0, m1, m2, m3, m4, m5) \ PTN_BIND_EXPAND(PTN_BIND_5(Type, m0, m1, m2, m3, m4)) \ - PTN_BIND_DECL(5, m5) + PTN_BIND_DECL(Type, m5) #define PTN_BIND_7(Type, m0, m1, m2, m3, m4, m5, m6) \ PTN_BIND_EXPAND(PTN_BIND_6(Type, m0, m1, m2, m3, m4, m5)) \ - PTN_BIND_DECL(6, m6) + PTN_BIND_DECL(Type, m6) #define PTN_BIND_8(Type, m0, m1, m2, m3, m4, m5, m6, m7) \ PTN_BIND_EXPAND(PTN_BIND_7(Type, m0, m1, m2, m3, m4, m5, m6)) \ - PTN_BIND_DECL(7, m7) + PTN_BIND_DECL(Type, m7) #define PTN_BIND_9(Type, m0, m1, m2, m3, m4, m5, m6, m7, m8) \ PTN_BIND_EXPAND(PTN_BIND_8(Type, m0, m1, m2, m3, m4, m5, m6, m7)) \ - PTN_BIND_DECL(8, m8) + PTN_BIND_DECL(Type, m8) #define PTN_BIND_10(Type, m0, m1, m2, m3, m4, m5, m6, m7, m8, m9) \ PTN_BIND_EXPAND( \ PTN_BIND_9(Type, m0, m1, m2, m3, m4, m5, m6, m7, m8)) \ - PTN_BIND_DECL(9, m9) + PTN_BIND_DECL(Type, m9) #define PTN_BIND_PICK( \ _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, NAME, ...) \ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 93f22754..8553886e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -60,6 +60,9 @@ set(PTN_COMPILE_FAIL_CASES compile_fail/static_on_with_capture.cpp compile_fail/on_pipeline_without_wildcard.cpp compile_fail/val_requires_compile_time_constant.cpp + compile_fail/member_placeholder_not_in_pattern.cpp + compile_fail/member_placeholder_non_structural.cpp + compile_fail/ptn_bind_unknown_member.cpp ) if(CMAKE_CXX_STANDARD LESS 20) diff --git a/tests/compile_fail/member_placeholder_non_structural.cpp b/tests/compile_fail/member_placeholder_non_structural.cpp new file mode 100644 index 00000000..e69d1bd5 --- /dev/null +++ b/tests/compile_fail/member_placeholder_non_structural.cpp @@ -0,0 +1,16 @@ +#include + +struct Point { + int x; + int y; +}; + +PTN_BIND(Point, x, y); + +int main() { + int v = 3; + // Member placeholders are only valid in guards attached to + // has<...>. Using one on a non-structural pattern must fail. + auto r = ptn::match(v) | ptn::on(ptn::$[x > 0] >> 1, ptn::_ >> 0); + return r; +} diff --git a/tests/compile_fail/member_placeholder_not_in_pattern.cpp b/tests/compile_fail/member_placeholder_not_in_pattern.cpp new file mode 100644 index 00000000..7a972454 --- /dev/null +++ b/tests/compile_fail/member_placeholder_not_in_pattern.cpp @@ -0,0 +1,18 @@ +#include + +struct Point { + int x; + int y; +}; + +PTN_BIND(Point, x, y); + +int main() { + Point p{3, 4}; + // has<...> does not list &Point::y, so the member placeholder + // y cannot be resolved: static_assert must fire. + auto r = ptn::match(p) + | ptn::on(ptn::$(ptn::has<&Point::x>)[y > 0] >> 1, + ptn::_ >> 0); + return r; +} diff --git a/tests/compile_fail/ptn_bind_unknown_member.cpp b/tests/compile_fail/ptn_bind_unknown_member.cpp new file mode 100644 index 00000000..0206ed96 --- /dev/null +++ b/tests/compile_fail/ptn_bind_unknown_member.cpp @@ -0,0 +1,14 @@ +#include + +struct Point { + int x; + int y; +}; + +// PTN_BIND names must be members of the given type; a misspelled +// member name must fail right at the declaration. +PTN_BIND(Point, xx); + +int main() { + return 0; +} diff --git a/tests/tests_destructure.cpp b/tests/tests_destructure.cpp index af52b27e..b3eac7a0 100644 --- a/tests/tests_destructure.cpp +++ b/tests/tests_destructure.cpp @@ -18,8 +18,8 @@ struct Packet { std::string payload; }; -PTN_BIND(Point, point_x, point_y); -PTN_BIND(Packet, packet_type, packet_length); +PTN_BIND(Point, x, y); +PTN_BIND(Packet, type, length); // -- $(has<>) destructure binding -- @@ -40,9 +40,8 @@ TEST(Destructure, WithGuard) { int result = match(p) | on( - $(has<&Point::x, &Point::y>)[point_x > 0 - && point_y > 0] - >> [](int x, int y) { return x * y; }, + $(has<&Point::x, &Point::y>)[x > 0 && y > 0] >> + [](int x, int y) { return x * y; }, _ >> -1); EXPECT_EQ(result, 50); @@ -53,9 +52,8 @@ TEST(Destructure, GuardRejects) { int result = match(p) | on( - $(has<&Point::x, &Point::y>)[point_x > 0 - && point_y > 0] - >> [](int x, int y) { return x * y; }, + $(has<&Point::x, &Point::y>)[x > 0 && y > 0] >> + [](int x, int y) { return x * y; }, _ >> -1); EXPECT_EQ(result, -1); @@ -137,8 +135,7 @@ TEST(HasGuard, MultiMemberGuard) { int result = match(pkt) | on( has<&Packet::type, - &Packet::length>[packet_type == 0x01 - && packet_length == 0] + &Packet::length>[type == 0x01 && length == 0] >> [] { return 1; }, _ >> 0); diff --git a/tests/tests_named_placeholder.cpp b/tests/tests_named_placeholder.cpp index 9703d41a..08a0076d 100644 --- a/tests/tests_named_placeholder.cpp +++ b/tests/tests_named_placeholder.cpp @@ -1,13 +1,15 @@ // Tests for PTN_BIND named placeholder macro. // -// Verifies that PTN_BIND declares readable names for bound tuple -// positions. All tests exercise the full match pipeline -// (match | on($[guard] >> handler)) end to end. +// Verifies that PTN_BIND declares member-anchored names for use in +// structural guard expressions. All tests exercise the full match +// pipeline (match | on(pattern[guard] >> handler)) end to end. #include #include "ptn/patternia.hpp" +#include + // Test fixture structs. namespace { @@ -59,15 +61,15 @@ namespace { int j; }; - // Declare named placeholders for each struct. - // These are constexpr arg_t objects. + // Member-anchored placeholders declared at namespace scope. + // Names must be real members of the given struct, and names in + // one scope must be unique, so structs with overlapping member + // names (Triple/Quad/Penta/Deca) bind at block scope inside the + // tests that need them. PTN_BIND(Point, x, y); PTN_BIND(Packet, type, len); PTN_BIND(Triple, a, b, c); - PTN_BIND(Single, sv); - PTN_BIND(Quad, qa, qb, qc, qd); - PTN_BIND(Penta, pa, pb, pc, pd, pe); - PTN_BIND(Deca, da, db, dc, dd, de, df, dg, dh, di, dj); + PTN_BIND(Single, value); } // namespace @@ -91,29 +93,29 @@ TEST(NamedPlaceholder, SupportsBlockScopeDeclarations) { } // ========================================================================= -// Type correctness: PTN_BIND names must be arg_t of the right -// index. +// Type correctness: PTN_BIND names must be +// member_t<&Struct::member>. // ========================================================================= -TEST(NamedPlaceholder, SingleArgTypeMatchesArg0) { +TEST(NamedPlaceholder, SingleArgTypeMatchesMember) { static_assert(std::is_same_v, - ptn::pat::mod::arg_t<0>>); + ptn::pat::mod::member_t<&Point::x>>); } -TEST(NamedPlaceholder, TwoArgTypesMatchPositions) { +TEST(NamedPlaceholder, TwoArgTypesMatchMembers) { static_assert(std::is_same_v, - ptn::pat::mod::arg_t<0>>); + ptn::pat::mod::member_t<&Point::x>>); static_assert(std::is_same_v, - ptn::pat::mod::arg_t<1>>); + ptn::pat::mod::member_t<&Point::y>>); } -TEST(NamedPlaceholder, ThreeArgTypesMatchPositions) { +TEST(NamedPlaceholder, ThreeArgTypesMatchMembers) { static_assert(std::is_same_v, - ptn::pat::mod::arg_t<0>>); + ptn::pat::mod::member_t<&Triple::a>>); static_assert(std::is_same_v, - ptn::pat::mod::arg_t<1>>); + ptn::pat::mod::member_t<&Triple::b>>); static_assert(std::is_same_v, - ptn::pat::mod::arg_t<2>>); + ptn::pat::mod::member_t<&Triple::c>>); } // ========================================================================= @@ -123,7 +125,7 @@ TEST(NamedPlaceholder, ThreeArgTypesMatchPositions) { TEST(NamedPlaceholder, SingleValueUsesWildcardPlaceholder) { int val = 10; auto result = ptn::match(val) - | PTN_ON( + | ptn::on( ptn::$[ptn::_ > 5] >> [](int v) { return v * 2; }, ptn::_ >> 0); @@ -133,7 +135,7 @@ TEST(NamedPlaceholder, SingleValueUsesWildcardPlaceholder) { TEST(NamedPlaceholder, SingleValueWildcardGuardFails) { int val = 3; auto result = ptn::match(val) - | PTN_ON( + | ptn::on( ptn::$[ptn::_ > 5] >> [](int v) { return v * 2; }, ptn::_ >> 0); @@ -143,7 +145,7 @@ TEST(NamedPlaceholder, SingleValueWildcardGuardFails) { TEST(NamedPlaceholder, StructuralGuardTwoMembers) { Point p{3, 4}; auto result = ptn::match(p) - | PTN_ON( + | ptn::on( ptn::$(ptn::has<&Point::x, &Point::y>)[x * x + y * y == 25] >> 1, @@ -154,7 +156,7 @@ TEST(NamedPlaceholder, StructuralGuardTwoMembers) { TEST(NamedPlaceholder, StructuralGuardFails) { Point p{1, 1}; auto result = ptn::match(p) - | PTN_ON( + | ptn::on( ptn::$(ptn::has<&Point::x, &Point::y>)[x * x + y * y == 25] >> 1, @@ -166,7 +168,7 @@ TEST(NamedPlaceholder, StructuralGuardLessThan) { // x < y Point p{3, 7}; auto result = ptn::match(p) - | PTN_ON( + | ptn::on( ptn::$(ptn::has<&Point::x, &Point::y>)[x < y] >> 1, ptn::_ >> 0); @@ -177,7 +179,7 @@ TEST(NamedPlaceholder, StructuralGuardEquality) { // type == 0x01 Packet pkt{0x01, 42}; auto result = ptn::match(pkt) - | PTN_ON( + | ptn::on( ptn::$(ptn::has<&Packet::type>)[type == 1] >> 1, ptn::_ >> 0); EXPECT_EQ(result, 1); @@ -187,7 +189,7 @@ TEST(NamedPlaceholder, StructuralGuardCompound) { // type == 0x01 && len > 0 Packet pkt{0x01, 42}; auto result = ptn::match(pkt) - | PTN_ON( + | ptn::on( ptn::$( ptn::has<&Packet::type, &Packet::len>)[type == 1 && len > 0] @@ -200,22 +202,133 @@ TEST(NamedPlaceholder, ThreeMemberGuard) { // a + b == c Triple t{2, 3, 5}; auto result = ptn::match(t) - | PTN_ON(ptn::$(ptn::has<&Triple::a, - &Triple::b, - &Triple::c>)[a + b == c] - >> 1, - ptn::_ >> 0); + | ptn::on(ptn::$(ptn::has<&Triple::a, + &Triple::b, + &Triple::c>)[a + b == c] + >> 1, + ptn::_ >> 0); EXPECT_EQ(result, 1); } TEST(NamedPlaceholder, ThreeMemberGuardFails) { Triple t{2, 3, 6}; auto result = ptn::match(t) - | PTN_ON(ptn::$(ptn::has<&Triple::a, - &Triple::b, - &Triple::c>)[a + b == c] - >> 1, - ptn::_ >> 0); + | ptn::on(ptn::$(ptn::has<&Triple::a, + &Triple::b, + &Triple::c>)[a + b == c] + >> 1, + ptn::_ >> 0); + EXPECT_EQ(result, 0); +} + +// ========================================================================= +// Member anchoring: names follow members, not positions. +// ========================================================================= + +TEST(NamedPlaceholder, NamesFollowMembersNotPositions) { + // has<> lists members in reverse order; names still resolve to + // their own member. Positionally this guard would read + // (.y == 3 && .x == 4) and fail. + Point p{3, 4}; + auto result = ptn::match(p) + | ptn::on( + ptn::$(ptn::has<&Point::y, &Point::x>)[x == 3 + && y == 4] + >> 1, + ptn::_ >> 0); + EXPECT_EQ(result, 1); +} + +TEST(NamedPlaceholder, PositionalReadingWouldDiffer) { + // Same reversed pattern with the positionally-read guard: it + // must NOT match, proving resolution is by member. + Point p{3, 4}; + auto result = ptn::match(p) + | ptn::on( + ptn::$(ptn::has<&Point::y, &Point::x>)[x == 4 + && y == 3] + >> 1, + ptn::_ >> 0); + EXPECT_EQ(result, 0); +} + +TEST(NamedPlaceholder, MemberNamesSkipIgnoredSlots) { + // _ign occupies a pattern slot but no binding position, so + // c resolves to position 1 of the extracted tuple. + Triple t{2, 99, 5}; + auto result = ptn::match(t) + | ptn::on(ptn::$(ptn::has<&Triple::a, + ptn::_ign, + &Triple::c>)[a + c == 7] + >> 1, + ptn::_ >> 0); + EXPECT_EQ(result, 1); +} + +TEST(NamedPlaceholder, MixedWildcardAndMemberNames) { + // `_` keeps its positional meaning (position 0 == .x) and can be + // combined with member-anchored names. + Point p{3, 4}; + auto result = ptn::match(p) + | ptn::on( + ptn::$( + ptn::has<&Point::x, &Point::y>)[ptn::_ == 3 + && y == 4] + >> 1, + ptn::_ >> 0); + EXPECT_EQ(result, 1); +} + +TEST(NamedPlaceholder, NonBindingGuardWithMemberNames) { + // has<>[guard] without binding: handler is nullary, names still + // anchor to members. + Packet pkt{0x01, 42}; + auto result = ptn::match(pkt) + | ptn::on( + ptn::has<&Packet::type, &Packet::len>[type == 1 + && len > 0] + >> 1, + ptn::_ >> 0); + EXPECT_EQ(result, 1); +} + +TEST(NamedPlaceholder, NonBindingGuardRejects) { + Packet pkt{0x02, 42}; + auto result = ptn::match(pkt) + | ptn::on( + ptn::has<&Packet::type, &Packet::len>[type == 1 + && len > 0] + >> 1, + ptn::_ >> 0); + EXPECT_EQ(result, 0); +} + +TEST(NamedPlaceholder, GuardOnTypePatternWithStructuralSub) { + // Member names resolve through is(...) wrappers: the guard + // attaches to the type pattern but anchors to the members of + // the nested has<...>. + using V = std::variant; + V v = Point{3, 4}; + auto result = ptn::match(v) + | ptn::on( + ptn::is(ptn::$( + ptn::has<&Point::x, &Point::y>))[x == 3 + && y == 4] + >> [](int px, int py) { return px + py; }, + ptn::_ >> [] { return 0; }); + EXPECT_EQ(result, 7); +} + +TEST(NamedPlaceholder, GuardOnTypePatternRejects) { + using V = std::variant; + V v = Point{3, -4}; + auto result = ptn::match(v) + | ptn::on( + ptn::is(ptn::$( + ptn::has<&Point::x, &Point::y>))[x > 0 + && y > 0] + >> [](int px, int py) { return px + py; }, + ptn::_ >> [] { return 0; }); EXPECT_EQ(result, 0); } @@ -227,7 +340,7 @@ TEST(NamedPlaceholder, ArithmeticThenCompare) { // x + y > 5 Point p{3, 4}; auto result = ptn::match(p) - | PTN_ON( + | ptn::on( ptn::$(ptn::has<&Point::x, &Point::y>)[x + y > 5] >> 1, ptn::_ >> 0); @@ -238,10 +351,10 @@ TEST(NamedPlaceholder, MixedArithmeticAndComparison) { // x*x + y*y < 50 && x < y Point p{3, 4}; auto result = ptn::match(p) - | PTN_ON(ptn::$(ptn::has<&Point::x, &Point::y>) - [x * x + y * y < 50 && x < y] - >> 1, - ptn::_ >> 0); + | ptn::on(ptn::$(ptn::has<&Point::x, &Point::y>) + [x * x + y * y < 50 && x < y] + >> 1, + ptn::_ >> 0); EXPECT_EQ(result, 1); } @@ -250,15 +363,16 @@ TEST(NamedPlaceholder, MixedArithmeticAndComparison) { // ========================================================================= TEST(NamedPlaceholder, OneMemberTypeCorrect) { - static_assert(std::is_same_v, - ptn::pat::mod::arg_t<0>>); + static_assert( + std::is_same_v, + ptn::pat::mod::member_t<&Single::value>>); } TEST(NamedPlaceholder, OneMemberGuard) { Single s{42}; auto result = ptn::match(s) - | PTN_ON( - ptn::$(ptn::has<&Single::value>)[sv > 5] >> 1, + | ptn::on( + ptn::$(ptn::has<&Single::value>)[value > 5] >> 1, ptn::_ >> 0); EXPECT_EQ(result, 1); } @@ -266,79 +380,81 @@ TEST(NamedPlaceholder, OneMemberGuard) { TEST(NamedPlaceholder, OneMemberGuardFails) { Single s{3}; auto result = ptn::match(s) - | PTN_ON( - ptn::$(ptn::has<&Single::value>)[sv > 5] >> 1, + | ptn::on( + ptn::$(ptn::has<&Single::value>)[value > 5] >> 1, ptn::_ >> 0); EXPECT_EQ(result, 0); } TEST(NamedPlaceholder, FourMemberTypeCorrect) { - static_assert(std::is_same_v, - ptn::pat::mod::arg_t<0>>); - static_assert(std::is_same_v, - ptn::pat::mod::arg_t<3>>); + PTN_BIND(Quad, a, b, c, d); + static_assert(std::is_same_v, + ptn::pat::mod::member_t<&Quad::a>>); + static_assert(std::is_same_v, + ptn::pat::mod::member_t<&Quad::d>>); } TEST(NamedPlaceholder, FourMemberGuard) { // a + b + c == d + PTN_BIND(Quad, a, b, c, d); Quad q{1, 2, 3, 6}; auto result = ptn::match(q) - | PTN_ON( - ptn::$(ptn::has<&Quad::a, - &Quad::b, - &Quad::c, - &Quad::d>)[qa + qb + qc == qd] - >> 1, - ptn::_ >> 0); + | ptn::on(ptn::$(ptn::has<&Quad::a, + &Quad::b, + &Quad::c, + &Quad::d>)[a + b + c == d] + >> 1, + ptn::_ >> 0); EXPECT_EQ(result, 1); } TEST(NamedPlaceholder, FourMemberGuardFails) { + PTN_BIND(Quad, a, b, c, d); Quad q{1, 2, 3, 7}; auto result = ptn::match(q) - | PTN_ON( - ptn::$(ptn::has<&Quad::a, - &Quad::b, - &Quad::c, - &Quad::d>)[qa + qb + qc == qd] - >> 1, - ptn::_ >> 0); + | ptn::on(ptn::$(ptn::has<&Quad::a, + &Quad::b, + &Quad::c, + &Quad::d>)[a + b + c == d] + >> 1, + ptn::_ >> 0); EXPECT_EQ(result, 0); } TEST(NamedPlaceholder, FiveMemberTypeCorrect) { - static_assert(std::is_same_v, - ptn::pat::mod::arg_t<0>>); - static_assert(std::is_same_v, - ptn::pat::mod::arg_t<4>>); + PTN_BIND(Penta, a, b, c, d, e); + static_assert(std::is_same_v, + ptn::pat::mod::member_t<&Penta::a>>); + static_assert(std::is_same_v, + ptn::pat::mod::member_t<&Penta::e>>); } TEST(NamedPlaceholder, FiveMemberGuard) { // a * b - c == d + e + PTN_BIND(Penta, a, b, c, d, e); Penta p{6, 2, 5, 4, 3}; auto result = ptn::match(p) - | PTN_ON( - ptn::$( - ptn::has<&Penta::a, - &Penta::b, - &Penta::c, - &Penta::d, - &Penta::e>)[pa * pb - pc == pd + pe] + | ptn::on( + ptn::$(ptn::has<&Penta::a, + &Penta::b, + &Penta::c, + &Penta::d, + &Penta::e>)[a * b - c == d + e] >> 1, ptn::_ >> 0); EXPECT_EQ(result, 1); } TEST(NamedPlaceholder, FiveMemberGuardFails) { + PTN_BIND(Penta, a, b, c, d, e); Penta p{6, 2, 5, 4, 4}; auto result = ptn::match(p) - | PTN_ON( - ptn::$( - ptn::has<&Penta::a, - &Penta::b, - &Penta::c, - &Penta::d, - &Penta::e>)[pa * pb - pc == pd + pe] + | ptn::on( + ptn::$(ptn::has<&Penta::a, + &Penta::b, + &Penta::c, + &Penta::d, + &Penta::e>)[a * b - c == d + e] >> 1, ptn::_ >> 0); EXPECT_EQ(result, 0); @@ -351,48 +467,48 @@ TEST(NamedPlaceholder, FiveMemberGuardFails) { TEST(NamedPlaceholder, BindThreeArgGuard) { Triple t{2, 3, 5}; auto result = ptn::match(t) - | PTN_ON(ptn::$(ptn::has<&Triple::a, - &Triple::b, - &Triple::c>)[a + b == c] - >> 1, - ptn::_ >> 0); + | ptn::on(ptn::$(ptn::has<&Triple::a, + &Triple::b, + &Triple::c>)[a + b == c] + >> 1, + ptn::_ >> 0); EXPECT_EQ(result, 1); } TEST(NamedPlaceholder, BindThreeArgGuardFails) { Triple t{2, 3, 6}; auto result = ptn::match(t) - | PTN_ON(ptn::$(ptn::has<&Triple::a, - &Triple::b, - &Triple::c>)[a + b == c] - >> 1, - ptn::_ >> 0); + | ptn::on(ptn::$(ptn::has<&Triple::a, + &Triple::b, + &Triple::c>)[a + b == c] + >> 1, + ptn::_ >> 0); EXPECT_EQ(result, 0); } TEST(NamedPlaceholder, BindFourArgGuard) { + PTN_BIND(Quad, a, b, c, d); Quad q{1, 2, 3, 6}; auto result = ptn::match(q) - | PTN_ON( - ptn::$(ptn::has<&Quad::a, - &Quad::b, - &Quad::c, - &Quad::d>)[qa + qb + qc == qd] - >> 1, - ptn::_ >> 0); + | ptn::on(ptn::$(ptn::has<&Quad::a, + &Quad::b, + &Quad::c, + &Quad::d>)[a + b + c == d] + >> 1, + ptn::_ >> 0); EXPECT_EQ(result, 1); } TEST(NamedPlaceholder, BindFourArgGuardFails) { + PTN_BIND(Quad, a, b, c, d); Quad q{1, 2, 3, 7}; auto result = ptn::match(q) - | PTN_ON( - ptn::$(ptn::has<&Quad::a, - &Quad::b, - &Quad::c, - &Quad::d>)[qa + qb + qc == qd] - >> 1, - ptn::_ >> 0); + | ptn::on(ptn::$(ptn::has<&Quad::a, + &Quad::b, + &Quad::c, + &Quad::d>)[a + b + c == d] + >> 1, + ptn::_ >> 0); EXPECT_EQ(result, 0); } @@ -401,52 +517,53 @@ TEST(NamedPlaceholder, BindFourArgGuardFails) { // ========================================================================= TEST(NamedPlaceholder, TenMemberTypeCorrect) { - static_assert(std::is_same_v, - ptn::pat::mod::arg_t<0>>); - static_assert(std::is_same_v, - ptn::pat::mod::arg_t<9>>); + PTN_BIND(Deca, a, b, c, d, e, f, g, h, i, j); + static_assert(std::is_same_v, + ptn::pat::mod::member_t<&Deca::a>>); + static_assert(std::is_same_v, + ptn::pat::mod::member_t<&Deca::j>>); } TEST(NamedPlaceholder, TenMemberGuard) { // a + b + ... + i == j - Deca d{1, 1, 1, 1, 1, 1, 1, 1, 1, 9}; - auto result = ptn::match(d) - | PTN_ON( - ptn::$( - ptn::has<&Deca::a, - &Deca::b, - &Deca::c, - &Deca::d, - &Deca::e, - &Deca::f, - &Deca::g, - &Deca::h, - &Deca::i, - &Deca::j>)[da + db + dc + dd + de - + df + dg + dh + di - == dj] + PTN_BIND(Deca, a, b, c, d, e, f, g, h, i, j); + Deca dc{1, 1, 1, 1, 1, 1, 1, 1, 1, 9}; + auto result = ptn::match(dc) + | ptn::on( + ptn::$(ptn::has<&Deca::a, + &Deca::b, + &Deca::c, + &Deca::d, + &Deca::e, + &Deca::f, + &Deca::g, + &Deca::h, + &Deca::i, + &Deca::j>)[a + b + c + d + e + f + + g + h + i + == j] >> 1, ptn::_ >> 0); EXPECT_EQ(result, 1); } TEST(NamedPlaceholder, TenMemberGuardFails) { - Deca d{1, 1, 1, 1, 1, 1, 1, 1, 1, 10}; - auto result = ptn::match(d) - | PTN_ON( - ptn::$( - ptn::has<&Deca::a, - &Deca::b, - &Deca::c, - &Deca::d, - &Deca::e, - &Deca::f, - &Deca::g, - &Deca::h, - &Deca::i, - &Deca::j>)[da + db + dc + dd + de - + df + dg + dh + di - == dj] + PTN_BIND(Deca, a, b, c, d, e, f, g, h, i, j); + Deca dc{1, 1, 1, 1, 1, 1, 1, 1, 1, 10}; + auto result = ptn::match(dc) + | ptn::on( + ptn::$(ptn::has<&Deca::a, + &Deca::b, + &Deca::c, + &Deca::d, + &Deca::e, + &Deca::f, + &Deca::g, + &Deca::h, + &Deca::i, + &Deca::j>)[a + b + c + d + e + f + + g + h + i + == j] >> 1, ptn::_ >> 0); EXPECT_EQ(result, 0);