diff --git a/CMakeLists.txt b/CMakeLists.txt index aecf1d3a..7ae55676 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ endif() # ========== Project ========== project(patternia - VERSION 0.4.1 + VERSION 0.5.0 DESCRIPTION "Header-only pattern matching library for modern C++" LANGUAGES CXX ) @@ -77,36 +77,51 @@ option(PTN_SKIP_COMPILER_CHECK "Skip compiler version check" OFF) PTN_JOIN_HEADERS(PTN_HEADERS include/ptn/config.hpp include/ptn/patternia.hpp - include/ptn/core/match_builder.hpp - include/ptn/core/detail/match_builder_detail.hpp + + # Core Layer + include/ptn/core/fwd.h + include/ptn/core/common/common_traits.hpp + include/ptn/core/common/diagnostics.hpp + include/ptn/core/common/eval.hpp + include/ptn/core/common/optimize.hpp + include/ptn/core/engine/builder.hpp + include/ptn/core/engine/match.hpp + include/ptn/core/engine/detail/builder_impl.hpp + include/ptn/core/engine/detail/match_impl.hpp include/ptn/core/dsl/case_expr.hpp - include/ptn/core/dsl/detail/case_expr_detail.hpp - include/ptn/core/dsl/operators.hpp + include/ptn/core/dsl/ops.hpp + include/ptn/core/dsl/detail/case_expr_impl.hpp # Meta Utils - include/ptn/meta/algorithms.hpp - include/ptn/meta/index.hpp include/ptn/meta/meta.hpp - include/ptn/meta/template_info.hpp - include/ptn/meta/traits.hpp - include/ptn/meta/type_list.hpp + include/ptn/meta/base/traits.hpp + include/ptn/meta/dsa/algorithms.hpp + include/ptn/meta/dsa/type_list.hpp + include/ptn/meta/query/index.hpp + include/ptn/meta/query/template_info.hpp + + # Pattern Base + include/ptn/pattern/base/fwd.h + include/ptn/pattern/base/binding_traits.hpp + include/ptn/pattern/base/pattern_base.hpp + include/ptn/pattern/base/pattern_kind.hpp + include/ptn/pattern/base/pattern_traits.hpp + + # Pattern Module Headers + include/ptn/pattern/type.hpp + include/ptn/pattern/value.hpp + + # Type Pattern Layer + include/ptn/pattern/type/from.hpp + include/ptn/pattern/type/in.hpp + include/ptn/pattern/type/is.hpp + include/ptn/pattern/type/not_in.hpp + include/ptn/pattern/type/type_of.hpp # Value Pattern Layer - include/ptn/pattern/value/relational.hpp - include/ptn/pattern/value/detail/relational_pattern.hpp include/ptn/pattern/value/literal.hpp - include/ptn/pattern/value/detail/literal_pattern.hpp include/ptn/pattern/value/predicate.hpp - include/ptn/pattern/value/detail/predicate_pattern.hpp - - # Core Pattern - include/ptn/pattern/detail/pattern_base.hpp - include/ptn/pattern/detail/pattern_traits.hpp - - # Type Pattern Layer - include/ptn/pattern/type/type_of.hpp - include/ptn/pattern/type/type.hpp - include/ptn/pattern/type/detail/type_pattern.hpp + include/ptn/pattern/value/relational.hpp ) # ========== Public interface target ========== @@ -211,7 +226,6 @@ if(PTN_BUILD_BENCHMARKS) add_executable(bench_patternia benchmarks/bench_main.cpp benchmarks/bench_patternia.cpp - benchmarks/bench_patternia_complex.cpp ) target_link_libraries(bench_patternia PRIVATE patternia @@ -252,7 +266,7 @@ if(PTN_INSTALL) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/patterniaConfig.cmake" - INSTALL_DESTINATION ${PTN_CMAKE_DIR} + INSTALL_DESTINATION "${PTN_CMAKE_DIR}" ) install(FILES @@ -271,6 +285,5 @@ if(PTN_INSTALL) export( EXPORT patterniaTargets FILE "${CMAKE_CURRENT_BINARY_DIR}/patterniaTargets.cmake" - NAMESPACE patternia:: ) endif() diff --git a/README.md b/README.md index 96bc9319..d49edab4 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ +REFACTOR +
**A header-only, zero-overhead, compile-time pattern matching library for modern C++.** diff --git a/benchmarks/bench_patternia.cpp b/benchmarks/bench_patternia.cpp index 2d936fe7..b9f20920 100644 --- a/benchmarks/bench_patternia.cpp +++ b/benchmarks/bench_patternia.cpp @@ -4,7 +4,7 @@ #include using namespace ptn; -using namespace ptn::dsl; +using namespace ptn::pat::value; static std::vector generate_int_data(size_t n) { std::mt19937 rng(123); diff --git a/benchmarks/bench_patternia_complex.cpp b/benchmarks/bench_patternia_complex.cpp deleted file mode 100644 index 2b9ad1d8..00000000 --- a/benchmarks/bench_patternia_complex.cpp +++ /dev/null @@ -1,232 +0,0 @@ -#include -#include -#include -#include -#include - -using namespace ptn; - -// thresholds: 32 cuts, 33 buckets -static constexpr int CUTS[] = { - -1875, -1750, -1625, -1500, -1375, -1250, -1125, -1000, -875, -750, -625, - -500, -375, -250, -125, 0, 125, 250, 375, 500, 625, 750, - 875, 1000, 1125, 1250, 1375, 1500, 1625, 1750, 1875, 2000}; -static constexpr int NB = sizeof(CUTS) / sizeof(CUTS[0]) + 1; - -// if-else chain -inline int classify_ifelse(int x) { -#define IFRET(c, idx) \ - if (x < (c)) \ - return (idx); - IFRET(-1875, 0) - else IFRET(-1750, 1) else IFRET(-1625, 2) else IFRET( - -1500, 3) else IFRET(-1375, 4) else IFRET(-1250, 5) else IFRET(-1125, 6) else IFRET(-1000, 7) else IFRET(-875, 8) else IFRET(-750, 9) else IFRET(-625, 10) else IFRET(-500, 11) else IFRET(-375, 12) else IFRET(-250, 13) else IFRET(-125, 14) else IFRET(0, 15) else IFRET(125, 16) else IFRET(250, 17) else IFRET(375, 18) else IFRET(500, 19) else IFRET(625, 20) else IFRET(750, 21) else IFRET(875, 22) else IFRET(1000, 23) else IFRET(1125, 24) else IFRET(1250, 25) else IFRET(1375, 26) else IFRET(1500, 27) else IFRET(1625, 28) else IFRET(1750, 29) else IFRET(1875, 30) else IFRET(2000, 31) else return 32; -#undef IFRET -} - -// switch-case version (dense jump table) -inline int classify_switch(int x) { - int idx = (x - (-1875)) / 125 + 1; - if (idx < 0) - idx = 0; - if (idx > 32) - idx = 32; - switch (idx) { - case 0: - return 0; - case 1: - return 1; - case 2: - return 2; - case 3: - return 3; - case 4: - return 4; - case 5: - return 5; - case 6: - return 6; - case 7: - return 7; - case 8: - return 8; - case 9: - return 9; - case 10: - return 10; - case 11: - return 11; - case 12: - return 12; - case 13: - return 13; - case 14: - return 14; - case 15: - return 15; - case 16: - return 16; - case 17: - return 17; - case 18: - return 18; - case 19: - return 19; - case 20: - return 20; - case 21: - return 21; - case 22: - return 22; - case 23: - return 23; - case 24: - return 24; - case 25: - return 25; - case 26: - return 26; - case 27: - return 27; - case 28: - return 28; - case 29: - return 29; - case 30: - return 30; - case 31: - return 31; - default: - return 32; - } -} - -// patternia DSL version -inline int classify_patternia(int x) { - return match(x) - .when(lt(-1875) >> 0) - .when(lt(-1750) >> 1) - .when(lt(-1625) >> 2) - .when(lt(-1500) >> 3) - .when(lt(-1375) >> 4) - .when(lt(-1250) >> 5) - .when(lt(-1125) >> 6) - .when(lt(-1000) >> 7) - .when(lt(-875) >> 8) - .when(lt(-750) >> 9) - .when(lt(-625) >> 10) - .when(lt(-500) >> 11) - .when(lt(-375) >> 12) - .when(lt(-250) >> 13) - .when(lt(-125) >> 14) - .when(lt(0) >> 15) - .when(lt(125) >> 16) - .when(lt(250) >> 17) - .when(lt(375) >> 18) - .when(lt(500) >> 19) - .when(lt(625) >> 20) - .when(lt(750) >> 21) - .when(lt(875) >> 22) - .when(lt(1000) >> 23) - .when(lt(1125) >> 24) - .when(lt(1250) >> 25) - .when(lt(1375) >> 26) - .when(lt(1500) >> 27) - .when(lt(1625) >> 28) - .when(lt(1750) >> 29) - .when(lt(1875) >> 30) - .when(lt(2000) >> 31) - .otherwise(32); -} - -// data distributions -enum class Dist { - Uniform, - Adversarial, - Zipf -}; - -static std::vector make_data(size_t N, Dist d) { - std::vector v; - v.reserve(N); - std::mt19937_64 rng(123456); - if (d == Dist::Uniform) { - std::uniform_int_distribution uni(-2500, 2500); - for (size_t i = 0; i < N; i++) - v.push_back(uni(rng)); - } - else if (d == Dist::Adversarial) { - for (size_t rep = 0; rep < N / 64; rep++) - for (int c : CUTS) { - v.push_back(c - 1); - v.push_back(c); - } - while (v.size() < N) - v.push_back(CUTS[0]); - } - else { - std::uniform_real_distribution U(0.0, 1.0); - auto invcdf = [](double u) { - double s = 1.25, HN = 0; - for (int k = 1; k <= NB; k++) - HN += 1.0 / std::pow(k, s); - double acc = 0; - for (int k = 1; k <= NB; k++) { - acc += 1.0 / std::pow(k, s); - if (u * HN <= acc) - return k - 1; - } - return NB - 1; - }; - for (size_t i = 0; i < N; i++) { - int b = invcdf(U(rng)); - int lo = (b == 0) ? CUTS[0] - 1000 : CUTS[b - 1]; - int hi = (b < (int) (sizeof(CUTS) / sizeof(CUTS[0]))) ? CUTS[b] - : CUTS[31] + 1000; - v.push_back(lo + (hi - lo) / 2); - } - } - return v; -} - -// benchmark templates -template -static void BM_IfElse(benchmark::State &st) { - auto data = make_data(st.range(0), D); - int acc = 0; - for (auto _ : st) - for (int x : data) - benchmark::DoNotOptimize(acc += classify_ifelse(x)); - benchmark::ClobberMemory(); -} -template -static void BM_Switch(benchmark::State &st) { - auto data = make_data(st.range(0), D); - int acc = 0; - for (auto _ : st) - for (int x : data) - benchmark::DoNotOptimize(acc += classify_switch(x)); - benchmark::ClobberMemory(); -} -template -static void BM_Patternia(benchmark::State &st) { - auto data = make_data(st.range(0), D); - int acc = 0; - for (auto _ : st) - for (int x : data) - benchmark::DoNotOptimize(acc += classify_patternia(x)); - benchmark::ClobberMemory(); -} - -constexpr int N = 100000; -BENCHMARK_TEMPLATE(BM_IfElse, Dist::Uniform)->Arg(N); -BENCHMARK_TEMPLATE(BM_Switch, Dist::Uniform)->Arg(N); -BENCHMARK_TEMPLATE(BM_Patternia, Dist::Uniform)->Arg(N); - -BENCHMARK_TEMPLATE(BM_IfElse, Dist::Adversarial)->Arg(N); -BENCHMARK_TEMPLATE(BM_Switch, Dist::Adversarial)->Arg(N); -BENCHMARK_TEMPLATE(BM_Patternia, Dist::Adversarial)->Arg(N); - -BENCHMARK_TEMPLATE(BM_IfElse, Dist::Zipf)->Arg(N); -BENCHMARK_TEMPLATE(BM_Switch, Dist::Zipf)->Arg(N); -BENCHMARK_TEMPLATE(BM_Patternia, Dist::Zipf)->Arg(N); diff --git a/include/ptn/core/common/common_traits.hpp b/include/ptn/core/common/common_traits.hpp new file mode 100644 index 00000000..000546df --- /dev/null +++ b/include/ptn/core/common/common_traits.hpp @@ -0,0 +1,190 @@ +#pragma once + +/** + * @file common_traits.hpp + * @brief Core type traits used by the matching engine. + */ + +#include "ptn/config.hpp" +#include "ptn/pattern/base/fwd.h" +#include +#include +#include +#include +#include + +#if PTN_USE_CONCEPTS +#include +#endif + +// Forward declaration +namespace ptn::core::dsl::detail { + template + struct case_expr; +} + +namespace ptn::core::common { + + /** Basic Type Extraction */ + + /// @brief Detects if a type is a `case_expr`. + template + struct is_case_expr : std::false_type {}; + + template + struct is_case_expr> + : std::true_type {}; + + template + inline constexpr bool is_case_expr_v = is_case_expr::value; + + /// @brief Extracts the `Pattern` type from a `case_expr`. + template + struct case_pattern { + using type = void; + }; + + template + struct case_pattern> { + using type = Pattern; + }; + + template + using case_pattern_t = typename case_pattern::type; + + /// @brief Extracts the `Handler` type from a `case_expr`. + template + struct case_handler { + using type = void; + }; + + template + struct case_handler> { + using type = Handler; + }; + + template + using case_handler_t = typename case_handler::type; + + /** Handler Invocability Check */ +#if PTN_USE_CONCEPTS + /// @brief C++20 Concept to check if a handler can be invoked with arguments + /// bound by its pattern. + template + concept handler_invocable_for = requires(Case &&c, Subject &&s) { + // Simulates the call: pattern.bind(s) -> args..., handler(args...) + std::apply( + std::forward(c).handler, + std::tuple_cat( + std::forward_as_tuple(std::forward(s)), + std::forward(c).pattern.bind(std::forward(s)))); + }; + + template + inline constexpr bool is_handler_invocable_v = + handler_invocable_for; +#else + /// @brief C++17 SFINAE implementation for handler invocability. + namespace detail { + template + static constexpr std::true_type is_applicable_impl( + decltype(std::apply(std::declval(), std::declval())) *); + + template + static constexpr std::false_type is_applicable_impl(...); + + /** + * @brief Detects "value-like" types to enable `pattern >> value` syntax + * sugar. + */ + template + struct is_value_like_impl { + using D = std::decay_t; + + static constexpr bool value = std::is_arithmetic_v || + std::is_enum_v || + std::is_same_v || + std::is_same_v || + std::is_convertible_v; + }; + + template + inline constexpr bool is_value_like_v = + is_value_like_impl>::value; + } // namespace detail + + template + struct is_handler_invocable { + private: + using handler_type = case_handler_t; + using pattern_type = case_pattern_t; + using bound_args_tuple = pat::base::binding_args_t; + + using full_invoke_args_tuple = decltype(std::tuple_cat( + std::declval>(), + std::declval())); + + public: + static constexpr bool value = + decltype(detail:: + is_applicable_impl( + nullptr))::value; + }; + + template + inline constexpr bool is_handler_invocable_v = + is_handler_invocable::value; +#endif + + /** Case and Match Result Types */ + + /// @brief Computes the result type of a single case expression. + template + struct case_result { + private: + using handler_type = case_handler_t; + using pattern_type = case_pattern_t; + using bound_args_tuple = pat::base::binding_args_t; + + using full_invoke_args_tuple = decltype(std::tuple_cat( + std::declval>(), + std::declval())); + + public: + using type = decltype(std::apply( + std::declval(), std::declval())); + }; + + template + using case_result_t = typename case_result::type; + + namespace detail { + + /// @brief Helper to deduce the result type of an `otherwise` handler. + template + static constexpr auto get_otherwise_result_impl(int) + -> decltype(std::declval()(std::declval())); + + template + static constexpr auto get_otherwise_result_impl(...) + -> decltype(std::declval()()); + + } // namespace detail + + template + using otherwise_result_t = + decltype(detail::get_otherwise_result_impl(0)); + + /// @brief Computes the common result type of the entire match expression. + template + struct match_result { + using type = std::common_type_t< + case_result_t..., + otherwise_result_t>; + }; + + template + using match_result_t = + typename match_result::type; + +} // namespace ptn::core::common diff --git a/include/ptn/core/common/diagnostics.hpp b/include/ptn/core/common/diagnostics.hpp new file mode 100644 index 00000000..e157a625 --- /dev/null +++ b/include/ptn/core/common/diagnostics.hpp @@ -0,0 +1,93 @@ +#pragma once + +/** + * @file diagnostics.hpp + * @brief Compile-time diagnostics and static assertions for Patternia. + */ + +#include +#include "ptn/core/common/common_traits.hpp" + +namespace ptn::core::common { + + /** + * @brief Validates the entire match expression for consistency. + */ + template + constexpr void static_assert_valid_match() { + // Check: All case handlers must be invocable with their pattern's bindings. + static_assert( + (is_handler_invocable_v && ...), + "[Patternia Error] At least one case's handler cannot be invoked with " + "the arguments bound by its pattern. " + "Please check the handler's signature against the pattern's expected " + "bindings."); + + // Check: The `otherwise` handler must be invocable. + static_assert( + std::is_invocable_v || + std::is_invocable_v, + "[Patternia Error] The `otherwise` handler has an invalid signature. " + "It should be either callable with the subject value or callable with " + "no arguments."); + + // Check: All handlers must have a common return type. + using common_return_type = match_result_t; + (void) sizeof(common_return_type); + } + + /** + * @brief Validates that a handler matches a pattern's bindings. + */ + template + constexpr void static_assert_valid_handler() { + static_assert( + is_handler_invocable_v, +#if PTN_USE_CONCEPTS + "[Patternia Error] Handler cannot be invoked with the arguments bound " + "by the pattern." +#else + "[Patternia Error] Handler signature does not match the pattern's " + "binding result." +#endif + ); + } + + /** + * @brief Validates a single case expression. + */ + template + constexpr void static_assert_valid_case() { + static_assert( + is_case_expr_v, + "[Patternia Error] Argument to `.when()` must be a case expression " + "created with the '>>' operator."); + static_assert_valid_handler(); + } + + /** + * @brief Validates that a type is a valid pattern. + */ + template + constexpr void static_assert_valid_pattern() { +#if PTN_USE_CONCEPTS + static_assert( + ptn::pattern::detail::pattern, + "[Patternia Error] The provided type is not a valid pattern. " + "A pattern must be invocable with a subject and return a boolean."); +#else + static_assert( + ptn::pat::base::is_pattern_v, + "[Patternia Error] The provided type is not a valid pattern. " + "A pattern must be invocable with a subject and return a boolean."); +#endif + } + + template + struct has_unreachable_case : std::false_type {}; + + template + inline constexpr bool has_unreachable_case_v = + has_unreachable_case::value; + +} // namespace ptn::core::common diff --git a/include/ptn/core/common/eval.hpp b/include/ptn/core/common/eval.hpp new file mode 100644 index 00000000..248b491d --- /dev/null +++ b/include/ptn/core/common/eval.hpp @@ -0,0 +1,175 @@ +#pragma once + +/** + * @file eval.hpp + * @brief Common evaluation logic for matching and handler invocation. + */ + +#include +#include +#include +#include + +#include "ptn/config.hpp" +#include "ptn/core/common/common_traits.hpp" + +namespace ptn::core::common { + + // --- SFINAE Helper for C++17 --- + namespace detail { + // A helper to provide a `void` type for SFINAE. + template + struct void_type { + using type = void; + }; + + // Detects if `p.match(s)` is a valid expression. + template + struct has_match_member : std::false_type {}; + + template + struct has_match_member< + P, + S, + typename void_type().match( + std::declval()))>::type> : std::true_type {}; + + template + constexpr bool has_match_member_v = has_match_member::value; + + // Detects if `p.bind(s)` is a valid expression. + template + struct has_bind_member : std::false_type {}; + + template + struct has_bind_member< + P, + S, + typename void_type().bind( + std::declval()))>::type> : std::true_type {}; + + template + constexpr bool has_bind_member_v = has_bind_member::value; + } // namespace detail + // --------------------------------- + + /** Case Matching */ + + /// @brief Checks if a case's pattern matches a subject. + template + struct case_matcher { + using case_type = Case; + using subject_type = Subject; + using pattern_type = case_pattern_t; + + /// @brief Returns true if the pattern matches the subject. + static constexpr bool + matches(const case_type &c, const subject_type &subject) { + // Supports both class-based patterns with a .match() method and + // functional patterns (via std::invoke). +#if PTN_USE_CONCEPTS + if constexpr (requires(const pattern_type &p, const subject_type &s) { + { p.match(s) } -> std::convertible_to; + }) { + return c.pattern.match(subject); + } + else { + return static_cast(std::invoke(c.pattern, subject)); + } +#else + // Use a robust SFINAE check instead of the faulty member function + // pointer check. + if constexpr (detail::has_match_member_v) { + return c.pattern.match(subject); + } + else { + return static_cast(std::invoke(c.pattern, subject)); + } +#endif + } + }; + + /** Handler Invocation Logic */ + + /// @brief Invokes a handler with the subject and values bound by its pattern. + template + constexpr decltype(auto) invoke_handler(const Case &c, Subject &&subject) { + using pattern_type = case_pattern_t; + +// The check is different for C++17 and C++20. +#if PTN_USE_CONCEPTS + static_assert( + requires(const pattern_type &p, Subject &&s) { + { p.bind(std::forward(s)) }; + }, + "Pattern must have a 'bind(subject)' method that returns a tuple of " + "bound values."); +#else + // Use the robust SFINAE check instead of the faulty sizeof trick. + static_assert( + detail::has_bind_member_v, + "Pattern must have a 'bind(subject)' method that returns a tuple of " + "bound values."); +#endif + + // Let the pattern perform the binding. + auto bound_values = c.pattern.bind(std::forward(subject)); + + // Combine the subject and bound values into a single tuple. + auto full_args = std::tuple_cat( + std::forward_as_tuple(std::forward(subject)), + std::move(bound_values)); + + // Apply the handler to the full set of arguments. + return std::apply(c.handler, std::move(full_args)); + } + + /** Case Sequence Evaluation */ + namespace detail { + /// @brief Recursive implementation for evaluating a tuple of cases. + template < + std::size_t I, + typename Subject, + typename CasesTuple, + typename Otherwise, + std::size_t N = std::tuple_size_v>> + constexpr decltype(auto) eval_cases_impl( + Subject &subject, CasesTuple &cases, Otherwise &&otherwise_handler) { + if constexpr (I >= N) { + // Base case: no case matched, invoke the `otherwise` handler. + if constexpr (std::is_invocable_v) { + return std::forward(otherwise_handler)(subject); + } + else { + return std::forward(otherwise_handler)(); + } + } + else { + auto ¤t_case = std::get(cases); + using case_t = std::remove_reference_t; + + if (case_matcher::matches(current_case, subject)) { + // Found a match, invoke its handler. + return invoke_handler(current_case, std::forward(subject)); + } + else { + // Try the next case. + return eval_cases_impl( + subject, cases, std::forward(otherwise_handler)); + } + } + } + } // namespace detail + + /// @brief Public entry point to evaluate a sequence of cases. + template + constexpr decltype(auto) eval_cases( + Subject &subject, CasesTuple &cases, Otherwise &&otherwise_handler) { + using tuple_t = std::remove_reference_t; + constexpr std::size_t N = std::tuple_size_v; + + return detail::eval_cases_impl<0>( + subject, cases, std::forward(otherwise_handler)); + } + +} // namespace ptn::core::common diff --git a/include/ptn/core/common/optimize.hpp b/include/ptn/core/common/optimize.hpp new file mode 100644 index 00000000..6a512d31 --- /dev/null +++ b/include/ptn/core/common/optimize.hpp @@ -0,0 +1,16 @@ +#pragma once + +namespace ptn::core::common { + + namespace detail { + template + struct optimize_case_sequence { + using type = CasesTuple; + }; + } // namespace detail + + template + using optimize_case_sequence_t = + typename detail::optimize_case_sequence::type; + +} // namespace ptn::core::common diff --git a/include/ptn/core/detail/match_builder_detail.hpp b/include/ptn/core/detail/match_builder_detail.hpp deleted file mode 100644 index a3cc038d..00000000 --- a/include/ptn/core/detail/match_builder_detail.hpp +++ /dev/null @@ -1,260 +0,0 @@ -#pragma once - -#include "ptn/config.hpp" -#include -#include -#include -#include -#include - -#if PTN_USE_CONCEPTS -#include -#endif - -/** - * @file match_builder_impl.hpp (detail) - * @brief Internal implementation of the core matching engine. - * - * This is an internal header containing the implementation of: - * - `match_builder`: Core engine storing subject and case list. - * - `run_handler()`: Handler invocation logic. - * - * Users should use the public `match()` function and `.when()/.otherwise()` API - * from `ptn/core/match_builder.hpp` instead of directly instantiating - * match_builder. - * - * Part of the Core Layer (namespace ptn::core::detail) - * - * @internal This is an internal implementation detail. - */ - -namespace ptn::dsl::detail { - template - struct case_expr; -} - -namespace ptn::core::detail { - -#if PTN_USE_CONCEPTS - - template - concept Invocable1 = std::is_invocable_v; - - template - concept Invocable0 = std::is_invocable_v; - - /** - * @brief Invoke handler `h(x)` if available. - * - * @internal This is an internal implementation detail. - */ - template - requires Invocable1 - constexpr decltype(auto) run_handler(H &h, X &x) { - return std::invoke(h, x); - } - - /** - * @brief Invoke handler `h()` if `(x)` overload is not available. - * - * @internal This is an internal implementation detail. - */ - template - requires(!Invocable1 && Invocable0) - constexpr decltype(auto) run_handler(H &h, X &) { - return std::invoke(h); - } - - /** - * @brief Fallback: return handler as value. - * - * @internal This is an internal implementation detail. - */ - template - requires(!Invocable1 && !Invocable0) - constexpr decltype(auto) run_handler(H &h, X &) { - return (h); - } - -#else // C++17 fallback - - template < - typename H, - typename X, - typename = std::enable_if_t>> - constexpr decltype(auto) run_handler(H &h, X &x) { - return std::invoke(h, x); - } - - template < - typename H, - typename X, - typename = void, - typename = std::enable_if_t< - !std::is_invocable_v && std::is_invocable_v>> - constexpr decltype(auto) run_handler(H &h, X &) { - return std::invoke(h); - } - - template < - typename H, - typename X, - typename = void, - typename = void, - typename = std::enable_if_t< - !std::is_invocable_v && !std::is_invocable_v>> - constexpr decltype(auto) run_handler(H &h, X &) { - return (h); - } - -#endif - - /** - * @brief Tag used to disambiguate private constructors. - * - * @internal This is an internal implementation detail. - */ - struct ctor_tag {}; - - /** - * @brief Core engine storing subject and case list, evaluated via - * `.otherwise()`. - * - * @tparam TV Type of stored subject value. - * @tparam Cases Variadic pack of `(Pattern, Handler)` pairs. - * - * This class is immutable: each `.when()` returns a new type with the added - * case. - * - * @internal This is an internal implementation detail. Use `match()` - * function. - */ - template - class match_builder { - TV value_; ///< Subject value - std::tuple cases_; ///< Stored (Pattern, Handler) pairs - using ctor_tag_t = ptn::core::detail::ctor_tag; - - template - friend struct ptn::dsl::detail::case_expr; - - template - friend class match_builder; - - /** - * @brief Private constructor used by `create()`. - */ - template -#if PTN_USE_CONCEPTS - requires std::constructible_from && - std::constructible_from, Tuple> -#endif - explicit constexpr match_builder(TV2 &&v, Tuple &&cs, ctor_tag_t) - : value_(std::forward(v)), cases_(std::forward(cs)) { - } - - // Append a case (lvalue) - template - constexpr auto with(Pattern p, Handler h) & { - using pair_t = std::pair; - auto new_cases = std::tuple_cat( - cases_, std::make_tuple(pair_t{std::move(p), std::move(h)})); - - return match_builder( - value_, std::move(new_cases), ctor_tag_t{}); - } - - // Append a case (rvalue) - template - constexpr auto with(Pattern p, Handler h) && { - using pair_t = std::pair; - auto new_cases = std::tuple_cat( - std::move(cases_), - std::make_tuple(pair_t{std::move(p), std::move(h)})); - - return match_builder( - std::move(value_), std::move(new_cases), ctor_tag_t{}); - } - - // Recursive case matching - /** - * @brief Try each case in order. - * - * Stops when a pattern matches or all cases exhausted. - * - * @tparam I Current tuple index. - * @tparam OutT Output type inferred from handlers. - */ - template - constexpr void try_cases(OutT &out, bool &done) { - if constexpr (I < sizeof...(Cases)) { - auto &c = std::get(cases_); - auto &[p, handler] = c; - - // Unified Pattern Layer API: Pattern must implement match(value) - if (!done && p.match(value_)) { - out = static_cast(run_handler(handler, value_)); - done = true; - } - else { - try_cases(out, done); - } - } - } - - public: - // Builder creation (called from ptn::match()) - template -#if PTN_USE_CONCEPTS - requires std::constructible_from, Tuple> -#endif - static constexpr auto create(VArg &&v, Tuple &&cs) - -> match_builder, Cases...> { - using result_t = match_builder, Cases...>; - return result_t( - std::forward(v), std::forward(cs), ctor_tag{}); - } - - // otherwise() - /** - * @brief Finalize matching. - * - * Computes a common return type from all handlers, applies the first - * matching handler, or the fallback if none match. - */ - template - constexpr auto otherwise(H &&fallback) && { - using R = std::common_type_t< - decltype(run_handler( - std::declval(), - std::declval()))..., - decltype(run_handler( - std::declval &>(), std::declval()))>; - - R out{}; - bool done = false; - - try_cases(out, done); - - if (!done) - out = static_cast(run_handler(fallback, value_)); - - return out; - } - - // when() - /** - * @brief Add a pattern-handler case from a `case_expr`. - */ - template - constexpr auto when(ptn::dsl::detail::case_expr &&e) & { - return this->with(std::move(e.pattern), std::move(e.handler)); - } - - template - constexpr auto when(ptn::dsl::detail::case_expr &&e) && { - return std::move(*this).with(std::move(e.pattern), std::move(e.handler)); - } - }; - -} // namespace ptn::core::detail diff --git a/include/ptn/core/dsl/case_expr.hpp b/include/ptn/core/dsl/case_expr.hpp index 74247632..e7c48225 100644 --- a/include/ptn/core/dsl/case_expr.hpp +++ b/include/ptn/core/dsl/case_expr.hpp @@ -3,47 +3,21 @@ /** * @file case_expr.hpp * @brief Public type alias for case expressions. - * - * This module provides the public `case_expr` type (via alias to detail - * implementation). Case expressions are created implicitly when using the `>>` - * operator: - * - * ```cpp - * auto case = pattern >> handler; // Creates a case_expr - * ``` - * - * Users should NOT instantiate `case_expr` directly. Use the `>>` operator - * instead. - * - * Part of the Core Layer (namespace ptn::dsl) */ -#include "ptn/core/dsl/detail/case_expr_detail.hpp" - -namespace ptn::dsl::detail { +namespace ptn::core::dsl::detail { template struct case_expr; } -namespace ptn::dsl { +namespace ptn::core::dsl { /** * @brief Public type alias for case expressions. * * Represents a pattern-handler pair in the match DSL. - * Created via the `>>` operator: `pattern >> handler`. - * - * @tparam Pattern The pattern type. - * @tparam Handler The handler type. - * - * @example - * ```cpp - * using namespace ptn::pattern::value; - * auto case1 = lit(10) >> "ten"; - * auto case2 = lt(5) >> "small"; - * ``` */ template using case_expr = detail::case_expr; -} // namespace ptn::dsl \ No newline at end of file +} // namespace ptn::core::dsl \ No newline at end of file diff --git a/include/ptn/core/dsl/detail/case_expr_detail.hpp b/include/ptn/core/dsl/detail/case_expr_impl.hpp similarity index 64% rename from include/ptn/core/dsl/detail/case_expr_detail.hpp rename to include/ptn/core/dsl/detail/case_expr_impl.hpp index 04f5a835..8a060877 100644 --- a/include/ptn/core/dsl/detail/case_expr_detail.hpp +++ b/include/ptn/core/dsl/detail/case_expr_impl.hpp @@ -4,18 +4,10 @@ * @file case_expr_impl.hpp (detail) * @brief Internal implementation of case expressions. * - * This is an internal header containing the implementation of: - * - `case_expr`: Represents a single case in match() DSL. - * - * Users should create case expressions using the `>>` operator defined in - * `ptn/core/dsl/operators.hpp` instead of directly instantiating this type. - * - * Part of the Core Layer (namespace ptn::dsl::detail) - * * @internal This is an internal implementation detail. */ -namespace ptn::dsl::detail { +namespace ptn::core::dsl::detail { /** * @brief Internal representation of a pattern-handler pair (case). @@ -36,4 +28,4 @@ namespace ptn::dsl::detail { Handler handler; ///< The handler to execute if pattern matches }; -} // namespace ptn::dsl::detail +} // namespace ptn::core::dsl::detail diff --git a/include/ptn/core/dsl/operators.hpp b/include/ptn/core/dsl/operators.hpp deleted file mode 100644 index c7cefbe6..00000000 --- a/include/ptn/core/dsl/operators.hpp +++ /dev/null @@ -1,108 +0,0 @@ -#pragma once - -#include -#include - -namespace ptn::dsl::detail { - template - struct case_expr; -} - -namespace ptn::pattern::value { - - template - struct predicate_pattern; - -} // namespace ptn::pattern::value - -namespace ptn::pattern::value::detail { - - template - struct and_pattern; - - template - struct or_pattern; - - template - struct not_pattern; - -} // namespace ptn::pattern::value::detail - -namespace ptn::pattern::detail { - template - struct is_pattern; - - template - extern const bool is_pattern_v; -} // namespace ptn::pattern::detail - -namespace ptn::dsl::ops { - - // ========== operator>> (pattern >> handler) ========== - template - constexpr auto operator>>(P &&p, H &&h) { - return ptn::dsl::detail::case_expr, std::decay_t>{ - std::forward

(p), std::forward(h)}; - } - - // ===================================================== - // Logical DSL operators: && || ! - // ===================================================== - -#if defined(PTN_USE_CONCEPTS) && PTN_USE_CONCEPTS - - template - constexpr auto operator&&(L &&l, R &&r) { - return ptn::value::and_pattern, std::decay_t>( - std::forward(l), std::forward(r)); - } - - template - constexpr auto operator||(L &&l, R &&r) { - return ptn::value::or_pattern, std::decay_t>( - std::forward(l), std::forward(r)); - } - - template - constexpr auto operator!(P &&p) { - return ptn::value::not_pattern>(std::forward

(p)); - } - -#else // C++17 fallback (SFINAE) - - template < - typename L, - typename R, - typename = std::enable_if_t< - pattern::detail::is_pattern_v> && - pattern::detail::is_pattern_v>>> - constexpr auto operator&&(L &&l, R &&r) { - return ptn::pattern::value::detail:: - and_pattern, std::decay_t>( - std::forward(l), std::forward(r)); - } - - template < - typename L, - typename R, - typename = std::enable_if_t< - pattern::detail::is_pattern_v> && - pattern::detail::is_pattern_v>>> - constexpr auto operator||(L &&l, R &&r) { - return ptn::pattern::value::detail:: - or_pattern, std::decay_t>( - std::forward(l), std::forward(r)); - } - - template < - typename P, - typename = - std::enable_if_t>>> - constexpr auto operator!(P &&p) { - return ptn::pattern::value::detail::not_pattern>( - std::forward

(p)); - } - -#endif - -} // namespace ptn::dsl::ops \ No newline at end of file diff --git a/include/ptn/core/dsl/ops.hpp b/include/ptn/core/dsl/ops.hpp new file mode 100644 index 00000000..30cdbbc5 --- /dev/null +++ b/include/ptn/core/dsl/ops.hpp @@ -0,0 +1,96 @@ +#pragma once + +/** + * @file ops.hpp + * @brief DSL operators for building case expressions and composing patterns. + */ + +#include +#include + +#include "ptn/core/common/common_traits.hpp" +#include "ptn/pattern/value/predicate.hpp" +#include "ptn/core/dsl/detail/case_expr_impl.hpp" +#include "ptn/pattern/base/pattern_traits.hpp" + +namespace ptn::core::dsl::ops { + + // --- Operator Overloads --- + + /** + * @brief `operator>>`: Pattern >> Handler / Pattern >> Value + * + * This operator supports two modes: + * 1. If Handler is a "value type" (int, enum, string, etc.), it constructs + * a value-handler that ignores all arguments and always returns that + * value. + * 2. Otherwise, it is treated as a proper handler that can be invoked + * according to the rules in case_result_t. + * + * This prevents lambdas with parameters from being misidentified as values, + * avoiding closure types being passed into std::common_type_t and resolving + * common_type errors. + */ + template + constexpr auto operator>>(Pattern &&pattern, Handler &&handler) { + using P = std::decay_t; + using H = std::decay_t; + + if constexpr (ptn::core::common::detail::is_value_like_v) { + // Pattern >> Value syntax sugar + auto value_handler = [val = std::forward(handler)]( + auto &&...) -> H { return val; }; + + return core::dsl::detail::case_expr{ + std::forward(pattern), std::move(value_handler)}; + } + else { + // Pattern >> Handler + return core::dsl::detail::case_expr{ + std::forward(pattern), std::forward(handler)}; + } + } + /** + * @brief Pattern logical composition: &&, ||, ! + * + * These operators enable logical composition of patterns using + * `pat::detail::is_pattern_v` from pattern/base/pattern_traits.hpp. + * They are only enabled for actual pattern types. + */ + + /// @brief AND composition: p1 && p2 + template < + typename L, + typename R, + typename = std::enable_if_t< + pat::detail::is_pattern_v> && + pat::detail::is_pattern_v>>> + constexpr auto operator&&(L &&l, R &&r) { + return ptn::pat::value::detail:: + and_pattern, std::decay_t>( + std::forward(l), std::forward(r)); + } + + /// @brief OR composition: p1 || p2 + template < + typename L, + typename R, + typename = std::enable_if_t< + pat::detail::is_pattern_v> && + pat::detail::is_pattern_v>>> + constexpr auto operator||(L &&l, R &&r) { + return ptn::pat::value::detail:: + or_pattern, std::decay_t>( + std::forward(l), std::forward(r)); + } + + /// @brief NOT composition: !p + template < + typename P, + typename = std::enable_if_t>>> + constexpr auto operator!(P &&p) { + return ptn::pat::value::detail::not_pattern>( + std::forward

(p)); + } + +} // namespace ptn::core::dsl::ops diff --git a/include/ptn/core/engine/builder.hpp b/include/ptn/core/engine/builder.hpp new file mode 100644 index 00000000..5a0b754a --- /dev/null +++ b/include/ptn/core/engine/builder.hpp @@ -0,0 +1,36 @@ +#pragma once + +/** + * @file builder.hpp + * @brief Public-facing builder type for the Core matching engine. + * + * Internally, it is an alias to `ptn::core::detail::match_builder`. + */ + +#include "ptn/config.hpp" + +// Pull in the implementation +#include "ptn/core/engine/detail/builder_impl.hpp" + +namespace ptn::core { + + namespace detail { + + /** + * @brief Forward declaration of the concrete builder implementation. + * + * TV : subject type + * Cases : sequence of case_expr-like types + */ + template + class match_builder; + + } // namespace detail + + /** + * @brief Public alias forwarding to the actual implementation. + */ + template + using match_builder = engine::detail::match_builder; + +} // namespace ptn::core diff --git a/include/ptn/core/engine/detail/builder_impl.hpp b/include/ptn/core/engine/detail/builder_impl.hpp new file mode 100644 index 00000000..a982a05e --- /dev/null +++ b/include/ptn/core/engine/detail/builder_impl.hpp @@ -0,0 +1,142 @@ +#pragma once +/** + * @file builder_impl.hpp + * @brief Implementation of the Core match_builder. + */ + +#include +#include +#include + +#include "ptn/config.hpp" +#include "ptn/core/common/diagnostics.hpp" +#include "ptn/core/engine/detail/match_impl.hpp" + +namespace ptn::core::engine::detail { + + template + class match_builder { + public: + using subject_type = TV; + using cases_type = std::tuple; + + private: + subject_type subject_; + cases_type cases_; + + public: + /** @brief Static factory with an empty case list. */ + static constexpr auto create(subject_type subject) { + return match_builder{std::move(subject), cases_type{}}; + } + + /** @brief Static factory with an explicit case tuple (internal use). */ + static constexpr auto create(subject_type subject, cases_type cases) { + return match_builder{std::move(subject), std::move(cases)}; + } + + /** @brief Construct from subject and case tuple. */ + constexpr match_builder(subject_type subject, cases_type cases) + : subject_(std::move(subject)), cases_(std::move(cases)) { + } + + // ----------------- chaining API: .when(...) ----------------- + + /** + * @brief Add a new case (rvalue-qualified). + * + * Accepts any CaseExpr-like object; typically `dsl::case_expr`. + */ + template + constexpr auto when(CaseExpr &&expr) && { + using new_case_type = std::decay_t; + + // Compile-time validation of the new case. + static_assert( + ptn::core::common::is_case_expr_v, + "Argument to .when() must be a case expression created with the '>>' " + "operator."); + static_assert( + ptn::core::common:: + is_handler_invocable_v, +#if PTN_USE_CONCEPTS + "Handler cannot be invoked with the arguments bound by the pattern." +#else + "Handler signature does not match the pattern's binding result." +#endif + ); + + auto new_cases = std::tuple_cat( + std::move(cases_), + std::tuple(std::forward(expr))); + + using builder_t = match_builder; + + return builder_t{std::move(subject_), std::move(new_cases)}; + } + + /** + * @brief Add a new case (lvalue-qualified). + */ + + template + constexpr auto when(CaseExpr &&expr) const & { + using new_case_type = std::decay_t; + + static_assert( + ptn::core::common::is_case_expr_v, + "Argument to .when() must be a case expression created with the '>>' " + "operator."); + static_assert( + ptn::core::common:: + is_handler_invocable_v, +#if PTN_USE_CONCEPTS + "Handler cannot be invoked with the arguments bound by the pattern." +#else + "Handler signature does not match the pattern's binding result." +#endif + ); + + auto new_cases = std::tuple_cat( + cases_, std::tuple(std::forward(expr))); + + using builder_t = match_builder; + + return builder_t{subject_, std::move(new_cases)}; + } + + // ----------------- terminal API: .otherwise(...) ------------ + + /** + * @brief Terminal step: evaluate all cases; if none matches, + * call the provided fallback handler. + */ + template + constexpr decltype(auto) otherwise(Otherwise &&otherwise_handler) && { + + using OtherwiseDecayed = std::decay_t; + + auto final_handler = [&]() { + if constexpr (ptn::core::common::detail::is_value_like_v< + OtherwiseDecayed>) { + // Case 1: Pass a value + return [val = std::forward(otherwise_handler)]( + auto &&...) -> OtherwiseDecayed { return val; }; + } + else { + // Case 2: Pass a "handler" + return std::forward(otherwise_handler); + } + }(); + + ptn::core::common::static_assert_valid_match< + subject_type, + decltype(final_handler), + Cases...>(); + + return match_impl::eval(subject_, cases_, std::move(final_handler)); + } + }; + +} // namespace ptn::core::engine::detail diff --git a/include/ptn/core/engine/detail/match_impl.hpp b/include/ptn/core/engine/detail/match_impl.hpp new file mode 100644 index 00000000..7da14be3 --- /dev/null +++ b/include/ptn/core/engine/detail/match_impl.hpp @@ -0,0 +1,25 @@ +#pragma once +/** + * @file match_impl.hpp + * @brief Core evaluation logic for a sequence of cases. + */ + +#include + +#include "ptn/core/common/eval.hpp" + +namespace ptn::core::engine::detail { + + struct match_impl { + + /** + * @brief Evaluate the tuple of cases against a subject. + */ + template + static constexpr decltype(auto) + eval(TV &subject, CasesTuple &cases, Otherwise &&otherwise_handler) { + return ptn::core::common::eval_cases( + subject, cases, std::forward(otherwise_handler)); + } + }; +} // namespace ptn::core::engine::detail diff --git a/include/ptn/core/engine/match.hpp b/include/ptn/core/engine/match.hpp new file mode 100644 index 00000000..93a861bd --- /dev/null +++ b/include/ptn/core/engine/match.hpp @@ -0,0 +1,51 @@ +#pragma once + +/** + * @file match.hpp + * @brief Entry function for the Core matching engine. + */ + +#include + +#include "ptn/core/engine/detail/builder_impl.hpp" +#include "ptn/config.hpp" + +namespace ptn { + + /** + * @brief Primary entry for pattern matching. + * + * Subject type is deduced as std::decay_t. + */ + + template + constexpr auto match(T &&value) { + using V = std::decay_t; + // Forward into the builder with an empty case list + return core::engine::detail::match_builder::create( + V(std::forward(value))); + } + + /** + * @brief Explicit-typed entry for pattern matching: match(value) + * This overload is SFINAE-disabled if U and T are the same after decay, + * to avoid ambiguous calls with the primary overload. + */ + + template + constexpr auto match(T &&value) -> std::enable_if_t< + !std::is_same_v, std::decay_t>, + core::engine::detail::match_builder> { + constexpr bool subject_constructible = + std::is_constructible_v || std::is_constructible_v; + + static_assert( + subject_constructible, + "[ptn::match(value)]: explicit subject type U cannot be " + "constructed from the given value."); + + U subject = static_cast(std::forward(value)); + return core::engine::detail::match_builder::create(std::move(subject)); + } + +} // namespace ptn \ No newline at end of file diff --git a/include/ptn/core/fwd.h b/include/ptn/core/fwd.h new file mode 100644 index 00000000..bf719571 --- /dev/null +++ b/include/ptn/core/fwd.h @@ -0,0 +1,69 @@ +#pragma once + +/** + * @file fwd.hpp + * @brief Forward declarations for Patternia Core Layer. + */ + +#include "ptn/config.hpp" // For PTN_USE_CONCEPTS + +#if PTN_USE_CONCEPTS +#include +#endif + +namespace ptn { + + /* --- Public API --- */ + + template + constexpr auto match(T &&); + + template + constexpr auto match(T &&); + + namespace core { + template + class match_builder; // Public-facing alias + } + + /* --- Core Internals --- */ + + namespace core::engine::detail { + template + class match_builder; // Internal implementation + } + + namespace core::dsl::detail { + template + struct case_expr; + } + + /* --- Common Traits (Forward Declarations) --- */ + + namespace core::common { + +#if PTN_USE_CONCEPTS + /// @brief Concept that defines a valid pattern. + template + concept pattern_like = requires(const P &p, auto &&subj) { + { p.match(subj) } -> std::convertible_to; + }; +#else + /// @brief C++17 SFINAE fallback for `pattern_like`. + template + struct is_pattern; + + template + inline constexpr bool is_pattern_v = is_pattern

::value; +#endif + + /// @brief Forward declaration of the core binding trait. + /// + /// This is the primary template. Specializations for concrete patterns + /// should be provided in the same header as the pattern definition. + template + struct binding_args; + + } // namespace core::common + +} // namespace ptn diff --git a/include/ptn/core/match_builder.hpp b/include/ptn/core/match_builder.hpp deleted file mode 100644 index 7338b933..00000000 --- a/include/ptn/core/match_builder.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "ptn/config.hpp" -#include "ptn/core/detail/match_builder_detail.hpp" - -/** - * @file match_builder.hpp - * @brief Public API for the Core Layer matching engine. - * - * This header provides the public interface to Patternia's *Core Layer*. - * The actual implementation is in `ptn/core/detail/match_builder_impl.hpp`. - * - * `match_builder` owns: - * - a subject value - * - a compile-time tuple of (Pattern, Handler) pairs - * - * The builder supports: - * - `.when(Pattern >> Handler)` to append a new case - * - `.otherwise(handler)` to perform final evaluation - * - * @ingroup core - */ - -namespace ptn { - - /** - * @brief Forward declaration of free-function `match()`. - */ - template - constexpr auto - match(T &&) noexcept(std::is_nothrow_constructible_v, T &&>); - -} // namespace ptn - -namespace ptn::core { - - // Forward declare the detail namespace - namespace detail { - template - class match_builder; - } - - /** - * @brief Public type alias forwarding to the detail implementation. - * - * Users see `ptn::core::match_builder` which is actually - * `ptn::core::detail::match_builder` internally. - */ - template - using match_builder = detail::match_builder; - -} // namespace ptn::core diff --git a/include/ptn/meta/traits.hpp b/include/ptn/meta/base/traits.hpp similarity index 78% rename from include/ptn/meta/traits.hpp rename to include/ptn/meta/base/traits.hpp index ff21992d..35ef3033 100644 --- a/include/ptn/meta/traits.hpp +++ b/include/ptn/meta/base/traits.hpp @@ -9,20 +9,14 @@ namespace ptn::meta { - //==================================================================== // remove_cvref_t - //==================================================================== - /** * @brief Remove const/volatile qualifiers and references from T. */ template using remove_cvref_t = std::remove_cv_t>; - //==================================================================== // is_spec_of & is_spec_of_v - //==================================================================== - /** * @brief Check whether T is a specialization of a given template. * @@ -41,10 +35,7 @@ namespace ptn::meta { template