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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

## `<fn/monadic.hpp>` becomes `<fn/traits.hpp>`; `monadic_invocable` rejects an incomplete functor — 27 September 2026
Comment thread
Bronek marked this conversation as resolved.

Replace includes of `<fn/monadic.hpp>` with `<fn/traits.hpp>`; no compatibility header is provided. `some_in_place_type` moves to `<fn/traits.hpp>` and remains available through `<fn/copack.hpp>`.

`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 `<fn/pack.hpp>` or `<fn/copack.hpp>` before querying. `typelist_applicable` still works from template arguments alone.
Expand Down
6 changes: 3 additions & 3 deletions docs/reference/concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ concept below names the header that declares it. `<fn/concepts.hpp>` carries the

## What a type is {style: "api"}

### fn::some_monadic_type {style: "api", badge: "#include <fn/monadic.hpp>"}
### fn::some_monadic_type {style: "api", badge: "#include <fn/traits.hpp>"}
:include-doxygen-doc: fn::some_monadic_type

### fn::some_expected {style: "api", badge: "#include <fn/expected.hpp>"}
Expand Down Expand Up @@ -52,7 +52,7 @@ concept below names the header that declares it. `<fn/concepts.hpp>` carries the
### fn::some_empty_value {style: "api", badge: "#include <fn/concepts.hpp>"}
:include-doxygen-doc: fn::some_empty_value

### fn::some_in_place_type {style: "api", badge: "#include <fn/copack.hpp>"}
### fn::some_in_place_type {style: "api", badge: "#include <fn/traits.hpp>"}
:include-doxygen-doc: fn::some_in_place_type

---
Expand Down Expand Up @@ -94,7 +94,7 @@ concept below names the header that declares it. `<fn/concepts.hpp>` 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.hpp>"}
### fn::monadic_invocable {style: "api", badge: "#include <fn/traits.hpp>"}
:include-doxygen-doc: fn::monadic_invocable

### fn::applicable_and_then {style: "api", badge: "#include <fn/and_then.hpp>"}
Expand Down
3 changes: 1 addition & 2 deletions include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion include/fn/algebra.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
#include <fn/copack.hpp>
#include <fn/detail/functional.hpp>
#include <fn/detail/traits.hpp>
#include <fn/monadic.hpp>
#include <fn/pack.hpp>
#include <fn/traits.hpp>
#include <libfn_version.hpp>

#include <type_traits>
Expand Down
2 changes: 1 addition & 1 deletion include/fn/concepts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
#include <fn/copack.hpp>
#include <fn/expected.hpp>
#include <fn/just.hpp>
#include <fn/monadic.hpp>
#include <fn/optional.hpp>
#include <fn/traits.hpp>
#include <libfn_version.hpp>

#include <concepts>
Expand Down
9 changes: 1 addition & 8 deletions include/fn/copack.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <fn/detail/variadic_union.hpp>
#include <fn/functional.hpp>
#include <fn/pack.hpp>
#include <fn/traits.hpp>
#include <libfn_version.hpp>
#include <pfn/utility.hpp>

Expand Down Expand Up @@ -43,14 +44,6 @@ concept some_copack = detail::_some_copack<T>;
template <typename T>
concept empty_copack = some_copack<T> && (::std::remove_cvref_t<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 <typename T>
concept some_in_place_type = detail::_some_in_place_type<T>;

namespace detail {
template <typename T>
static constexpr bool _is_valid_copack_subtype //
Expand Down
3 changes: 2 additions & 1 deletion include/fn/detail/functional.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <fn/detail/fwd.hpp>
#include <fn/detail/meta.hpp>
#include <fn/detail/traits.hpp>
#include <libfn_version.hpp>
#include <pfn/functional.hpp>
#include <pfn/tuple.hpp>
Expand Down Expand Up @@ -304,7 +305,7 @@ template <typename Ret, typename Fn, typename Arg, typename Arg0, typename... Ar
template <typename A> constexpr bool _complete_operand() noexcept
{
if constexpr (_some_pack<A> || _some_copack<A>)
return sizeof(::std::remove_cvref_t<A>) > 0; // incomplete: include <fn/pack.hpp> or <fn/copack.hpp> first
return _complete_class<::std::remove_cvref_t<A>>();
else
return true;
}
Expand Down
24 changes: 0 additions & 24 deletions include/fn/detail/monadic.hpp

This file was deleted.

19 changes: 19 additions & 0 deletions include/fn/detail/traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
#ifndef INCLUDE_FN_DETAIL_TRAITS
#define INCLUDE_FN_DETAIL_TRAITS

#include <fn/detail/fwd.hpp>
#include <libfn_version.hpp>

#include <concepts>
#include <type_traits>
#include <utility>

Expand All @@ -19,6 +21,23 @@ template <typename T> constexpr bool _is_in_place_type<::std::in_place_type_t<T>
template <typename T>
concept _some_in_place_type = _is_in_place_type<T &>;

// Reject incomplete types before trait results or concept satisfaction can depend
// on whether the definition has been seen.
template <typename T> constexpr bool _complete_class() noexcept
{
if constexpr (::std::is_class_v<T> || ::std::is_union_v<T>)
return sizeof(T) > 0; // incomplete: include the header defining this type first
else
return true;
}

template <typename T>
concept _some_monadic_type = _some_expected<T> || _some_optional<T> || _some_just<T>;

template <typename Functor, typename V, typename... Args>
concept _monadic_invocable = _complete_class<Functor>() && _complete_class<typename Functor::apply>()
&& _some_monadic_type<V> && ::std::invocable<typename Functor::apply, V, Args...>;

// 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<int, 3>` is not "constructible" from 3 ints) and permits
Expand Down
18 changes: 14 additions & 4 deletions include/fn/monadic.hpp → include/fn/traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
// 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 <fn/detail/monadic.hpp>
#include <fn/detail/traits.hpp>
#include <libfn_version.hpp>

namespace fn {

Check warning on line 12 in include/fn/traits.hpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Concatenate this namespace with the nested one.

See more on https://sonarcloud.io/project/issues?id=libfn_functional&issues=AaDpwf8j5cE7jEMbiIWX&open=AaDpwf8j5cE7jEMbiIWX&pullRequest=444
inline namespace LIBFN_VERSION {

/**
Expand All @@ -23,6 +23,14 @@
template <typename T>
concept some_monadic_type = detail::_some_monadic_type<T>;

/**
* @brief Checks if a type is a `std::in_place_type_t` tag
*
* @tparam T Type to check, possibly cv-ref qualified
*/
template <typename T>
concept some_in_place_type = detail::_some_in_place_type<T>;

/**
* @brief Checks if a verb applies to a carrier - the constraint `operator|` itself carries
*
Expand All @@ -31,6 +39,8 @@
* 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
Expand All @@ -41,4 +51,4 @@
} // namespace LIBFN_VERSION
} // namespace fn

#endif // INCLUDE_FN_MONADIC
#endif // INCLUDE_FN_TRAITS
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 0 additions & 13 deletions tests/fn/concepts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,6 @@ static_assert(some_optional<optional<int> const &>);
static_assert(some_optional<optional<int> &&>);
static_assert(some_optional<optional<int> const &&>);

static_assert(some_monadic_type<expected<int, bool>>);
static_assert(some_monadic_type<expected<int, bool> const>);
static_assert(some_monadic_type<expected<int, bool> &>);
static_assert(some_monadic_type<expected<int, bool> const &>);
static_assert(some_monadic_type<expected<int, bool> &&>);
static_assert(some_monadic_type<expected<int, bool> const &&>);
static_assert(some_monadic_type<optional<int>>);
static_assert(some_monadic_type<optional<int> const>);
static_assert(some_monadic_type<optional<int> &>);
static_assert(some_monadic_type<optional<int> const &>);
static_assert(some_monadic_type<optional<int> &&>);
static_assert(some_monadic_type<optional<int> const &&>);

// clang-format off
static_assert(same_kind<optional<bool>, optional<Value>>);
static_assert(not same_kind<optional<bool>, expected<void, bool>>);
Expand Down
2 changes: 0 additions & 2 deletions tests/fn/detail/functional.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
20 changes: 20 additions & 0 deletions tests/fn/detail/traits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<empty_t>());
static_assert(_complete_class<complete_union_t>());
static_assert(_complete_class<void>());
static_assert(_complete_class<int>());
static_assert(_complete_class<int &>());
static_assert(_complete_class<int[]>());
// a runtime call too, so coverage sees both branches of the body
CHECK(_complete_class<empty_t>());
CHECK(_complete_class<int>());
}
Loading
Loading