Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,15 +205,29 @@ 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);

$(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.
Expand All @@ -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
Expand Down
23 changes: 20 additions & 3 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<N>`. 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.
Expand Down Expand Up @@ -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).
31 changes: 25 additions & 6 deletions include/ptn/pattern/base/binding_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,25 @@

#include "ptn/pattern/modifiers/fwd.h"
#include <type_traits>
#include <utility>

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 <typename Derived>
struct guard_resolver {
template <typename P>
static constexpr decltype(auto) apply(P &&pred) {
return std::forward<P>(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.
Expand All @@ -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 <typename Pred>
auto operator[](Pred &&pred) const {
using D = std::decay_t<Derived>;
return mod::guarded_pattern<D, std::decay_t<Pred>>{
static_cast<const D &>(*this), std::forward<Pred>(pred)};
using D = std::decay_t<Derived>;
auto resolved = guard_resolver<D>::apply(
std::forward<Pred>(pred));
return mod::guarded_pattern<D, decltype(resolved)>{
static_cast<const D &>(*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).
Expand Down
108 changes: 62 additions & 46 deletions include/ptn/pattern/base/pattern_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <type_traits>
#include <utility>
Expand All @@ -25,23 +25,24 @@ namespace ptn::pat::traits {
template <typename P>
struct has_match_method<
P,
std::void_t<decltype(static_cast<bool>(std::declval<const P &>().match(
std::declval<int>())))>> : std::true_type {};
std::void_t<decltype(static_cast<bool>(
std::declval<const P &>().match(std::declval<int>())))>>
: std::true_type {};

// Variable template for has_match_method<P>::value.
template <typename P>
inline constexpr bool has_match_method_v = has_match_method<P>::value;
inline constexpr bool
has_match_method_v = has_match_method<P>::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 <typename P>
struct is_pattern
: std::integral_constant<
bool,
std::is_base_of_v<base::pattern_tag, P> || has_match_method_v<P>> {
};
struct is_pattern : std::integral_constant<
bool,
std::is_base_of_v<base::pattern_tag, P>
|| has_match_method_v<P>> {};

// Convenience variable template for is_pattern<P>::value.
template <typename P>
Expand All @@ -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 <typename P, typename = void>
struct is_binding_pattern : std::false_type {};

Expand All @@ -65,57 +66,65 @@ namespace ptn::pat::traits {

// Helper variable template for is_binding_pattern.
template <typename P>
inline constexpr bool is_binding_pattern_v = is_binding_pattern<P>::value;
inline constexpr bool
is_binding_pattern_v = is_binding_pattern<P>::value;

// -----------------------------------------------------------------------
// Structural-pattern traits.
// -----------------------------------------------------------------------

// Checks if M is a non-static data member pointer.
template <auto M>
inline constexpr bool is_data_member_ptr_v =
std::is_member_object_pointer_v<decltype(M)>;
inline constexpr bool is_data_member_ptr_v = std::
is_member_object_pointer_v<decltype(M)>;

// Checks if M is a nullptr placeholder (e.g., _ign).
template <auto M>
inline constexpr bool is_nullptr_placeholder_v =
std::is_same_v<std::decay_t<decltype(M)>, std::nullptr_t>;
inline constexpr bool is_nullptr_placeholder_v = std::
is_same_v<std::decay_t<decltype(M)>, 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 <auto M>
inline constexpr bool is_structural_element_v =
is_data_member_ptr_v<M> || is_nullptr_placeholder_v<M>;
inline constexpr bool
is_structural_element_v = is_data_member_ptr_v<M>
|| is_nullptr_placeholder_v<M>;

// -----------------------------------------------------------------------
// Guard-predicate 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.
//
// A type is considered a guard predicate if it inherits from
// guard_predicate_tag.
template <typename T>
inline constexpr bool is_guard_predicate_v =
std::is_base_of_v<guard_predicate_tag, std::decay_t<T>>;
inline constexpr bool
is_guard_predicate_v = std::is_base_of_v<guard_predicate_tag,
std::decay_t<T>>;

// 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 <typename T>
struct is_arg_expr : std::false_type {};

template <std::size_t I>
struct is_arg_expr<mod::arg_t<I>> : std::true_type {};

template <auto M>
struct is_arg_expr<mod::member_t<M>> : std::true_type {};

template <typename T>
struct is_arg_expr<mod::val_t<T>> : std::true_type {};

Expand All @@ -126,47 +135,54 @@ namespace ptn::pat::traits {
struct is_arg_expr<mod::un_expr<Op, X>> : std::true_type {};

template <typename T>
inline constexpr bool is_arg_expr_v = is_arg_expr<std::decay_t<T>>::value;
inline constexpr bool
is_arg_expr_v = is_arg_expr<std::decay_t<T>>::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 <typename T>
struct is_tuple_predicate : std::false_type {};

template <typename E>
struct is_tuple_predicate<mod::tuple_predicate<E>> : std::true_type {};
struct is_tuple_predicate<mod::tuple_predicate<E>>
: std::true_type {};

template <typename T>
inline constexpr bool is_tuple_predicate_v =
is_tuple_predicate<std::decay_t<T>>::value;
inline constexpr bool is_tuple_predicate_v = is_tuple_predicate<
std::decay_t<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 <typename T>
struct is_tuple_guard_predicate : std::false_type {};

template <typename E>
struct is_tuple_guard_predicate<mod::tuple_predicate<E>> : std::true_type {};
struct is_tuple_guard_predicate<mod::tuple_predicate<E>>
: std::true_type {};

template <typename Fn>
struct is_tuple_guard_predicate<mod::callable_guard<Fn>> : std::true_type {};
struct is_tuple_guard_predicate<mod::callable_guard<Fn>>
: std::true_type {};

template <typename T>
inline constexpr bool is_tuple_guard_predicate_v =
is_tuple_guard_predicate<std::decay_t<T>>::value;
inline constexpr bool
is_tuple_guard_predicate_v = is_tuple_guard_predicate<
std::decay_t<T>>::value;

template <typename L, typename R>
struct is_tuple_guard_predicate<mod::pred_and<L, R>>
: std::bool_constant<
is_tuple_guard_predicate_v<L> || is_tuple_guard_predicate_v<R>> {};
: std::bool_constant<is_tuple_guard_predicate_v<L>
|| is_tuple_guard_predicate_v<R>> {};

template <typename L, typename R>
struct is_tuple_guard_predicate<mod::pred_or<L, R>>
: std::bool_constant<
is_tuple_guard_predicate_v<L> || is_tuple_guard_predicate_v<R>> {};
: std::bool_constant<is_tuple_guard_predicate_v<L>
|| is_tuple_guard_predicate_v<R>> {};

} // namespace ptn::pat::traits
24 changes: 24 additions & 0 deletions include/ptn/pattern/bind.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <tuple>
#include <type_traits>
Expand Down Expand Up @@ -283,6 +284,29 @@ namespace ptn::pat::base {
Subject>::type>()))>;
};

// Guards on as-binding patterns delegate member-placeholder
// resolution to the wrapped subpattern.
template <typename Tag, typename Sub>
struct guard_resolver<pat::detail::binding_as_pattern<Tag, Sub>> {
template <typename P>
static constexpr auto apply(P &&pred) {
return guard_resolver<Sub>::apply(std::forward<P>(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 <auto... Ms>
struct guard_resolver<pat::detail::structural_bind_pattern<
pat::detail::has_pattern<Ms...>>> {
template <typename P>
static constexpr auto apply(P &&pred) {
return pat::mod::resolve_pred(pat::mod::member_list<Ms...>{},
std::forward<P>(pred));
}
};

// Structural-binding pattern binds.
template <auto... Ms, typename Subject>
struct binding_args<pat::detail::structural_bind_pattern<
Expand Down
8 changes: 8 additions & 0 deletions include/ptn/pattern/modifiers/fwd.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ namespace ptn::pat::mod {
template <typename Op, typename X>
struct un_expr;

// Forward declare member-anchored placeholder
template <auto M>
struct member_t;

// Forward declare member pointer list
template <auto... Ms>
struct member_list;

// Forward declare max argument index traits
template <typename E>
struct max_arg_index;
Expand Down
Loading
Loading