diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f8e10c8..af871fe1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ 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. +## `` becomes ``; `monadic_invocable` rejects an incomplete functor — 27 September 2026 + +Replace includes of `` with ``; no compatibility header is provided. `some_in_place_type` moves to `` and remains available through ``. + +`monadic_invocable` requires a complete functor type and a complete nested `apply`. Querying a forward-declared library or user-defined functor, or one whose `apply` is only declared, now fails to compile. Previously, its answer could change after the definition, making the program ill-formed with no diagnostic required. Include the functor's defining header before querying. + ## `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. diff --git a/docs/reference/concepts.md b/docs/reference/concepts.md index d98ca6a2..1fa3a580 100644 --- a/docs/reference/concepts.md +++ b/docs/reference/concepts.md @@ -13,7 +13,7 @@ concept below names the header that declares it. `` carries the ## What a type is {style: "api"} -### fn::some_monadic_type {style: "api", badge: "#include "} +### fn::some_monadic_type {style: "api", badge: "#include "} :include-doxygen-doc: fn::some_monadic_type ### fn::some_expected {style: "api", badge: "#include "} @@ -52,7 +52,7 @@ concept below names the header that declares it. `` carries the ### fn::some_empty_value {style: "api", badge: "#include "} :include-doxygen-doc: fn::some_empty_value -### fn::some_in_place_type {style: "api", badge: "#include "} +### fn::some_in_place_type {style: "api", badge: "#include "} :include-doxygen-doc: fn::some_in_place_type --- @@ -94,7 +94,7 @@ concept below names the header that declares it. `` carries the `monadic_invocable` is the constraint `operator|` itself carries; the rest are the per-verb constraints it dispatches to, and the ones a verb of your own would join. -### fn::monadic_invocable {style: "api", badge: "#include "} +### fn::monadic_invocable {style: "api", badge: "#include "} :include-doxygen-doc: fn::monadic_invocable ### fn::applicable_and_then {style: "api", badge: "#include "} diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 13278643..b7585784 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -108,7 +108,6 @@ set(INCLUDE_FN_HEADERS fn/detail/macro_begin.hpp fn/detail/macro_end.hpp fn/detail/meta.hpp - fn/detail/monadic.hpp fn/detail/pack_impl.hpp fn/detail/traits.hpp fn/detail/variadic_union.hpp @@ -126,11 +125,11 @@ set(INCLUDE_FN_HEADERS fn/inspect_error.hpp fn/inspect.hpp fn/just.hpp - fn/monadic.hpp fn/optional.hpp fn/or_else.hpp fn/pack.hpp fn/recover.hpp + fn/traits.hpp fn/transform_error.hpp fn/transform.hpp fn/utility.hpp diff --git a/include/fn/algebra.hpp b/include/fn/algebra.hpp index 4002db75..5f559080 100644 --- a/include/fn/algebra.hpp +++ b/include/fn/algebra.hpp @@ -9,8 +9,8 @@ #include #include #include -#include #include +#include #include #include diff --git a/include/fn/concepts.hpp b/include/fn/concepts.hpp index 4ba02367..f82ee3fb 100644 --- a/include/fn/concepts.hpp +++ b/include/fn/concepts.hpp @@ -9,8 +9,8 @@ #include #include #include -#include #include +#include #include #include diff --git a/include/fn/copack.hpp b/include/fn/copack.hpp index 2bf88e05..2721112b 100644 --- a/include/fn/copack.hpp +++ b/include/fn/copack.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -43,14 +44,6 @@ concept some_copack = detail::_some_copack; template concept empty_copack = some_copack && (::std::remove_cvref_t::size == 0); -/** - * @brief Checks if a type is a `std::in_place_type_t` tag - * - * @tparam T Type to check, possibly cv-ref qualified - */ -template -concept some_in_place_type = detail::_some_in_place_type; - namespace detail { template static constexpr bool _is_valid_copack_subtype // diff --git a/include/fn/detail/functional.hpp b/include/fn/detail/functional.hpp index a0ee1697..cc423761 100644 --- a/include/fn/detail/functional.hpp +++ b/include/fn/detail/functional.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -304,7 +305,7 @@ template constexpr bool _complete_operand() noexcept { if constexpr (_some_pack || _some_copack) - return sizeof(::std::remove_cvref_t) > 0; // incomplete: include or first + return _complete_class<::std::remove_cvref_t>(); else return true; } diff --git a/include/fn/detail/monadic.hpp b/include/fn/detail/monadic.hpp deleted file mode 100644 index f8d21686..00000000 --- a/include/fn/detail/monadic.hpp +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2026 Bronek Kozicki -// -// Distributed under the ISC License. See accompanying file LICENSE.md -// or copy at https://opensource.org/licenses/ISC - -#ifndef INCLUDE_FN_DETAIL_MONADIC -#define INCLUDE_FN_DETAIL_MONADIC - -#include -#include - -#include - -namespace fn::inline LIBFN_VERSION::detail { - -template -concept _some_monadic_type = _some_expected || _some_optional || _some_just; - -template -concept _monadic_invocable = _some_monadic_type && ::std::invocable; - -} // namespace fn::inline LIBFN_VERSION::detail - -#endif // INCLUDE_FN_DETAIL_MONADIC diff --git a/include/fn/detail/traits.hpp b/include/fn/detail/traits.hpp index d8147228..c032876b 100644 --- a/include/fn/detail/traits.hpp +++ b/include/fn/detail/traits.hpp @@ -6,8 +6,10 @@ #ifndef INCLUDE_FN_DETAIL_TRAITS #define INCLUDE_FN_DETAIL_TRAITS +#include #include +#include #include #include @@ -19,6 +21,23 @@ template constexpr bool _is_in_place_type<::std::in_place_type_t template concept _some_in_place_type = _is_in_place_type; +// Reject incomplete types before trait results or concept satisfaction can depend +// on whether the definition has been seen. +template constexpr bool _complete_class() noexcept +{ + if constexpr (::std::is_class_v || ::std::is_union_v) + return sizeof(T) > 0; // incomplete: include the header defining this type first + else + return true; +} + +template +concept _some_monadic_type = _some_expected || _some_optional || _some_just; + +template +concept _monadic_invocable = _complete_class() && _complete_class() + && _some_monadic_type && ::std::invocable; + // The storage initializes an element as `T{args...}`, so a constraint on it must ask the same // question: `is_constructible_v` spells parenthesized initialization, which for an aggregate // performs no brace elision (`std::array` is not "constructible" from 3 ints) and permits diff --git a/include/fn/monadic.hpp b/include/fn/traits.hpp similarity index 78% rename from include/fn/monadic.hpp rename to include/fn/traits.hpp index 7d689c46..aac4bf30 100644 --- a/include/fn/monadic.hpp +++ b/include/fn/traits.hpp @@ -3,10 +3,10 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FN_MONADIC -#define INCLUDE_FN_MONADIC +#ifndef INCLUDE_FN_TRAITS +#define INCLUDE_FN_TRAITS -#include +#include #include namespace fn { @@ -23,6 +23,14 @@ inline namespace LIBFN_VERSION { template concept some_monadic_type = detail::_some_monadic_type; +/** + * @brief Checks if a type is a `std::in_place_type_t` tag + * + * @tparam T Type to check, possibly cv-ref qualified + */ +template +concept some_in_place_type = detail::_some_in_place_type; + /** * @brief Checks if a verb applies to a carrier - the constraint `operator|` itself carries * @@ -31,6 +39,8 @@ concept some_monadic_type = detail::_some_monadic_type; * whether the callback is used to serve the operand: an operation over an uninhabited side consults * no callback at all, and so applies while no callback is applicable to it. * + * User-defined verbs provide a nested `apply`. The verb and its `apply` must be complete at the query. + * * @tparam Functor The verb, such as `fn::transform_t` * @tparam V The carrier, possibly cv-ref qualified * @tparam Args The verb's arguments as its functor holds them, typically the callback @@ -41,4 +51,4 @@ concept monadic_invocable = detail::_monadic_invocable; } // namespace LIBFN_VERSION } // namespace fn -#endif // INCLUDE_FN_MONADIC +#endif // INCLUDE_FN_TRAITS diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c1be3e55..da9bd341 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -132,6 +132,7 @@ set(TESTS_FN_SOURCES fn/or_else.cpp fn/pack.cpp fn/recover.cpp + fn/traits.cpp fn/transform_error.cpp fn/transform.cpp fn/utility.cpp diff --git a/tests/fn/concepts.cpp b/tests/fn/concepts.cpp index 9c207b47..3df361f3 100644 --- a/tests/fn/concepts.cpp +++ b/tests/fn/concepts.cpp @@ -54,19 +54,6 @@ static_assert(some_optional const &>); static_assert(some_optional &&>); static_assert(some_optional const &&>); -static_assert(some_monadic_type>); -static_assert(some_monadic_type const>); -static_assert(some_monadic_type &>); -static_assert(some_monadic_type const &>); -static_assert(some_monadic_type &&>); -static_assert(some_monadic_type const &&>); -static_assert(some_monadic_type>); -static_assert(some_monadic_type const>); -static_assert(some_monadic_type &>); -static_assert(some_monadic_type const &>); -static_assert(some_monadic_type &&>); -static_assert(some_monadic_type const &&>); - // clang-format off static_assert(same_kind, optional>); static_assert(not same_kind, expected>); diff --git a/tests/fn/detail/functional.cpp b/tests/fn/detail/functional.cpp index 8534fe11..96fcb503 100644 --- a/tests/fn/detail/functional.cpp +++ b/tests/fn/detail/functional.cpp @@ -21,8 +21,6 @@ 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; diff --git a/tests/fn/detail/traits.cpp b/tests/fn/detail/traits.cpp index 192440c3..518543cf 100644 --- a/tests/fn/detail/traits.cpp +++ b/tests/fn/detail/traits.cpp @@ -113,3 +113,23 @@ TEST_CASE("apply_const_lvalue_t", "[traits][apply_const][apply_lvalue][apply_con SUCCEED(); } } + +// Incomplete classes and unions must fail compilation; the suite cannot test this. + +TEST_CASE("_complete_class", "[traits][complete_class]") +{ + using fn::detail::_complete_class; + union complete_union_t { + int i; + }; + + static_assert(_complete_class()); + static_assert(_complete_class()); + static_assert(_complete_class()); + static_assert(_complete_class()); + static_assert(_complete_class()); + static_assert(_complete_class()); + // a runtime call too, so coverage sees both branches of the body + CHECK(_complete_class()); + CHECK(_complete_class()); +} diff --git a/tests/fn/traits.cpp b/tests/fn/traits.cpp new file mode 100644 index 00000000..b1b39779 --- /dev/null +++ b/tests/fn/traits.cpp @@ -0,0 +1,152 @@ +// Copyright (c) 2026 Bronek Kozicki +// +// Distributed under the ISC License. See accompanying file LICENSE.md +// or copy at https://opensource.org/licenses/ISC + +#include + +#include +#include +#include +#include + +#include + +#include +#include + +// Incomplete functors, or functors whose `apply` is incomplete, must fail compilation for any operand; +// the suite cannot test this. + +namespace { +struct Error {}; + +struct callback_t final { + constexpr auto operator()(int i) const noexcept -> int { return i + 1; } +}; + +struct class_verb final { + struct apply final { + constexpr auto operator()(fn::some_optional auto &&v, std::invocable auto &&) const noexcept -> decltype(v) + { + return std::forward(v); + } + }; +}; + +union union_verb { + struct apply final { + constexpr auto operator()(fn::some_optional auto &&v, std::invocable auto &&) const noexcept -> decltype(v) + { + return std::forward(v); + } + }; +}; + +struct no_apply_verb final {}; + +struct out_of_line_verb final { + struct apply; +}; +struct out_of_line_verb::apply final { + constexpr auto operator()(fn::some_optional auto &&v, std::invocable auto &&) const noexcept -> decltype(v) + { + return std::forward(v); + } +}; +} // namespace + +TEST_CASE("some_in_place_type", "[traits][some_in_place_type]") +{ + using fn::some_in_place_type; + using type = std::in_place_type_t; + + static_assert(some_in_place_type); + static_assert(some_in_place_type); + static_assert(some_in_place_type); + static_assert(some_in_place_type); + static_assert(some_in_place_type); + static_assert(some_in_place_type); + static_assert(not some_in_place_type); + static_assert(not some_in_place_type); + SUCCEED(); +} + +TEST_CASE("some_monadic_type", "[traits][some_monadic_type]") +{ + using fn::some_monadic_type; + + static_assert(some_monadic_type>); + static_assert(some_monadic_type const>); + static_assert(some_monadic_type &>); + static_assert(some_monadic_type const &>); + static_assert(some_monadic_type &&>); + static_assert(some_monadic_type const &&>); + static_assert(some_monadic_type>); + static_assert(some_monadic_type const>); + static_assert(some_monadic_type &>); + static_assert(some_monadic_type const &>); + static_assert(some_monadic_type &&>); + static_assert(some_monadic_type const &&>); + SUCCEED(); +} + +TEST_CASE("monadic_invocable", "[traits][monadic_invocable]") +{ + using fn::monadic_invocable; + using operand_t = fn::optional; + + SECTION("library verb") + { + static_assert(monadic_invocable); + static_assert(monadic_invocable const &, callback_t>); + static_assert(not monadic_invocable); + static_assert(not monadic_invocable, callback_t>); + SUCCEED(); + } + + SECTION("user-defined verb") + { + SECTION("class") + { + static_assert(monadic_invocable); + static_assert(monadic_invocable); + static_assert(monadic_invocable); + static_assert(monadic_invocable); + static_assert(monadic_invocable); + static_assert(not monadic_invocable, callback_t>); + static_assert(not monadic_invocable); + SUCCEED(); + } + + SECTION("union") + { + static_assert(monadic_invocable); + static_assert(monadic_invocable); + static_assert(not monadic_invocable, callback_t>); + static_assert(not monadic_invocable); + SUCCEED(); + } + + SECTION("apply defined out of line") + { + static_assert(monadic_invocable); + static_assert(not monadic_invocable); + SUCCEED(); + } + + SECTION("without apply") + { + static_assert(not monadic_invocable); + SUCCEED(); + } + } + + SECTION("not a class") + { + static_assert(not monadic_invocable); + static_assert(not monadic_invocable); + static_assert(not monadic_invocable); + SUCCEED(); + } +}