From 8f7f465f50ac51b829bbd0b8b381d04e9ab1022c Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Mon, 28 Sep 2026 20:35:31 +0100 Subject: [PATCH] Reject apply-trait queries on an incomplete `pack` or `copack` Require complete pack and copack operands before querying their apply members. This prevents caching a false result before the defining header is included. Typelist queries still work from template arguments alone. Assisted-by: Claude:claude-opus-5-5 Assisted-by: Codex:gpt-6-astra --- CHANGELOG.md | 4 ++++ CONTRIBUTING.md | 1 + include/fn/detail/functional.hpp | 12 ++++++++++++ tests/fn/detail/functional.cpp | 2 ++ 4 files changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf88c009..2f8e10c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ Design history of libfn, newest first. The living documents — [README.md](README.md), [CONTRIBUTING.md](CONTRIBUTING.md), [docs/](docs/) — describe only the present state of the design; when a decision makes an earlier idea obsolete, this file is where the transition is recorded and explained. +## `apply` traits reject an incomplete `pack` or `copack` operand — 27 September 2026 + +`apply`, `apply_r` and their traits reject incomplete `pack` or `copack` operands. Previously, trait queries could cache `false` (or `void` for `apply_result`) even after the type definition became available. Include `` or `` before querying. `typelist_applicable` still works from template arguments alone. + ## `pack` and `copack` operations move to `` — 27 September 2026 `operator&` over `pack` and `copack`, `conjoin`, and `disjoin` move to ``. Include this header when using these operations without a carrier header. The `expected`, `optional`, and `just` headers include it. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 985092c5..7fedd001 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -158,6 +158,7 @@ Match the assertion to the tested property: * **Conditional Specifications**: Pair negative assertions with their positive converses (e.g., pair `static_assert(not noexcept(expr))` with a witness for which it is `noexcept`). This proves that the specification is conditional rather than unconditionally false. Apply the same positive-control discipline to negative constraint or viability probes. * **Substitution Contexts**: Ensure expressions in negative viability probes are dependent on a template parameter. A `requires`-expression over concrete types is not a substitution context, meaning an invalid requirement triggers a hard error rather than a SFINAE failure. Enclose the dependent probe in a generic lambda or a type-keyed concept to yield `false` on invalid substitution. * **Exception Fixtures**: Select exception-proving fixtures carefully. The library's `helper_t` implements a separate constructor for each value category, and its non-const-lvalue copy constructor is always `noexcept`. A `helper_t` configured to throw is therefore not throwing for every value category, which can hide incorrect `noexcept` specifications. Use a simple, local type if the test requires every relevant constructor to be potentially throwing. +* **Expected Failures**: The suite cannot test runtime crashes or compilation failures. Record these untested cases beside their positive controls. ## Client code diff --git a/include/fn/detail/functional.hpp b/include/fn/detail/functional.hpp index fe57d70b..a0ee1697 100644 --- a/include/fn/detail/functional.hpp +++ b/include/fn/detail/functional.hpp @@ -300,6 +300,15 @@ template constexpr bool _complete_operand() noexcept +{ + if constexpr (_some_pack || _some_copack) + return sizeof(::std::remove_cvref_t) > 0; // incomplete: include or first + else + return true; +} + // apply_result template constexpr auto _apply_result_result(Fn &&, Args &&...) @@ -307,6 +316,7 @@ constexpr auto _apply_result_result(Fn &&, Args &&...) template constexpr auto _apply_result_result(auto &&...) -> ::std::type_identity; template struct _apply_result { + static_assert((_complete_operand() && ...)); using type = decltype(_apply_result_result(::std::declval(), ::std::declval()...))::type; }; @@ -318,6 +328,7 @@ constexpr auto _is_applicable_result(Fn &&, Args &&..., template constexpr auto _is_applicable_result(auto &&...) -> ::std::false_type; template struct _is_applicable { + static_assert((_complete_operand() && ...)); static constexpr bool value = decltype(_is_applicable_result(::std::declval(), ::std::declval()...))::value; }; @@ -338,6 +349,7 @@ constexpr auto _is_applicable_r_result( template constexpr auto _is_applicable_r_result(auto &&...) -> ::std::false_type; template struct _is_applicable_r { + static_assert((_complete_operand() && ...)); static constexpr bool value = decltype(_is_applicable_r_result(::std::declval(), ::std::declval()...))::value; }; diff --git a/tests/fn/detail/functional.cpp b/tests/fn/detail/functional.cpp index 96fcb503..8534fe11 100644 --- a/tests/fn/detail/functional.cpp +++ b/tests/fn/detail/functional.cpp @@ -21,6 +21,8 @@ constexpr auto sum_two = [](int i, double d) { return i + d; }; constexpr auto sum_three = [](int i, double d, char c) { return i + d + c; }; } // namespace +// Incomplete pack/copack operands must fail compilation; the suite cannot test this. + TEST_CASE("_apply_result", "[functional][apply_result]") { using fn::detail::_apply_result;