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
16 changes: 9 additions & 7 deletions docs/design_rationale.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ class value_or_t {
using Traits = bms::get_box_traits<Box>;
if (Traits::has_value(box))
return Traits::value(std::forward<Box>(box));
return std::forward<Op>(op).callable(key);
return std::forward<Op>(op).identity(key);
}
};

Expand All @@ -263,15 +263,17 @@ static_assert(v == 42);
`pipe_adaptor<value_or_t>` is the callable object users invoke (e.g. `value_or(42)`). Calling
it produces a `closure` that derives from `value_or_t` and stores the argument. The `friend
operator|` is found via the derived type and retrieves the stored argument via
`op.callable(key)`, where `key` is an `access_key<value_or_t>` that only `value_or_t` can
construct — preventing external code from bypassing the access protocol.
`op.identity(key)` — named after the identity function `id(x) = x`, because the accessor
simply returns whatever was stored unchanged, whether that is a callable or a plain value.
`key` is an `access_key<value_or_t>` that only `value_or_t` can construct, preventing
external code from bypassing the access protocol.

> **Note — why `access_key` instead of `op.callable({})`?**
> **Note — why `access_key` instead of `op.identity({})`?**
> On GCC and Clang the key can be replaced by a default-constructed sentinel:
> `return std::forward<Op>(op).callable({});`.
> This works because `closure::callable` is only accessible from within `value_or_t` anyway
> `return std::forward<Op>(op).identity({});`.
> This works because `closure::identity` is only accessible from within `value_or_t` anyway
> (the `friend operator|` is a member of `value_or_t`). However, MSVC incorrectly accepts
> `op.callable({})` from outside the class in some contexts, breaking the access restriction.
> `op.identity({})` from outside the class in some contexts, breaking the access restriction.
> `access_key<value_or_t>` is used to work around this MSVC bug: its constructor is private
> and only `value_or_t` can name the type, so the access protocol is enforced on all three
> compilers.
Expand Down
6 changes: 3 additions & 3 deletions include/beman/monadics/detail/and_then.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ class and_then_t {

template<box Box, std::derived_from<and_then_t> Op>
[[nodiscard]] friend constexpr decltype(auto) operator|(Box&& box, Op&& op)
requires and_thenable_impl<decltype(box), decltype(std::forward<Op>(op).callable(key))>
requires and_thenable_impl<decltype(box), decltype(std::forward<Op>(op).identity(key))>
{
using Traits = get_box_traits<Box>;
using NewBox = decltype(invoke_with_value(std::forward<Op>(op).callable(key), std::forward<Box>(box)));
using NewBox = decltype(invoke_with_value(std::forward<Op>(op).identity(key), std::forward<Box>(box)));

if (Traits::has_value(box)) {
return invoke_with_value(std::forward<Op>(op).callable(key), std::forward<Box>(box));
return invoke_with_value(std::forward<Op>(op).identity(key), std::forward<Box>(box));
}

return propagate_error<NewBox>(std::forward<Box>(box));
Expand Down
6 changes: 3 additions & 3 deletions include/beman/monadics/detail/or_else.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ class or_else_t {

template<box Box, std::derived_from<or_else_t> Op>
[[nodiscard]] friend constexpr decltype(auto) operator|(Box&& box, Op&& op)
requires or_elseable_impl<decltype(box), decltype(std::forward<Op>(op).callable(key))>
requires or_elseable_impl<decltype(box), decltype(std::forward<Op>(op).identity(key))>
{
using Traits = get_box_traits<Box>;
using NewBox = decltype(invoke_with_error(std::forward<Op>(op).callable(key), std::forward<Box>(box)));
using NewBox = decltype(invoke_with_error(std::forward<Op>(op).identity(key), std::forward<Box>(box)));

if (!Traits::has_value(box)) {
return invoke_with_error(std::forward<Op>(op).callable(key), std::forward<Box>(box));
return invoke_with_error(std::forward<Op>(op).identity(key), std::forward<Box>(box));
}

return propagate_value<NewBox>(std::forward<Box>(box));
Expand Down
12 changes: 6 additions & 6 deletions include/beman/monadics/detail/pipe_adaptor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ struct pipe_adaptor {

// msvc bug does not allow to use Callable in noexcept
constexpr explicit closure(Fn&& f) noexcept(std::is_nothrow_constructible_v<std::decay_t<Fn>, Fn>)
: callable_(std::forward<Fn>(f)) {}
: identity_(std::forward<Fn>(f)) {}

constexpr Callable& callable(access_key<T>) & noexcept { return callable_; }
constexpr const Callable& callable(access_key<T>) const& noexcept { return callable_; }
constexpr Callable&& callable(access_key<T>) && noexcept { return std::move(callable_); }
constexpr const Callable&& callable(access_key<T>) const&& noexcept { return std::move(callable_); }
constexpr Callable& identity(access_key<T>) & noexcept { return identity_; }
constexpr const Callable& identity(access_key<T>) const& noexcept { return identity_; }
constexpr Callable&& identity(access_key<T>) && noexcept { return std::move(identity_); }
constexpr const Callable&& identity(access_key<T>) const&& noexcept { return std::move(identity_); }

private:
Callable callable_;
Callable identity_;
};

return closure{std::forward<Fn>(fn)};
Expand Down
8 changes: 4 additions & 4 deletions include/beman/monadics/detail/transform.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@ class transform_t {

template<box Box, std::derived_from<transform_t> Op, typename Traits = get_box_traits<Box>>
[[nodiscard]] friend constexpr decltype(auto) operator|(Box&& box, Op&& op)
requires transform_impl<decltype(box), decltype(std::forward<Op>(op).callable(key))>
requires transform_impl<decltype(box), decltype(std::forward<Op>(op).identity(key))>
{
using NewValue = decltype(invoke_with_value(std::forward<Op>(op).callable(key), std::forward<Box>(box)));
using NewValue = decltype(invoke_with_value(std::forward<Op>(op).identity(key), std::forward<Box>(box)));
using NewBox = typename Traits::template rebind<NewValue>;
using NewBoxTraits = get_box_traits<NewBox>;

if (Traits::has_value(box)) {
if constexpr (std::is_void_v<NewValue>) {
invoke_with_value(std::forward<Op>(op).callable(key), std::forward<Box>(box));
invoke_with_value(std::forward<Op>(op).identity(key), std::forward<Box>(box));
return NewBoxTraits::make();
} else {
return NewBoxTraits::make(invoke_with_value(std::forward<Op>(op).callable(key),
return NewBoxTraits::make(invoke_with_value(std::forward<Op>(op).identity(key),
std::forward<Box>(box)));
}
}
Expand Down
8 changes: 4 additions & 4 deletions include/beman/monadics/detail/transform_error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,22 @@ class transform_error_t {

template<box Box, std::derived_from<transform_error_t> Op>
[[nodiscard]] friend constexpr decltype(auto) operator|(Box&& box, Op&& op)
requires transform_errorable_impl<decltype(box), decltype(std::forward<Op>(op).callable(key))>
requires transform_errorable_impl<decltype(box), decltype(std::forward<Op>(op).identity(key))>
{
using Traits = get_box_traits<Box>;
using NewError = decltype(invoke_with_error(std::forward<Op>(op).callable(key), std::forward<Box>(box)));
using NewError = decltype(invoke_with_error(std::forward<Op>(op).identity(key), std::forward<Box>(box)));
using NewBox = typename Traits::template rebind_error<NewError>;
using NewBoxTraits = get_box_traits<NewBox>;

if (!Traits::has_value(box)) {
return NewBoxTraits::make_error(invoke_with_error(std::forward<Op>(op).callable(key),
return NewBoxTraits::make_error(invoke_with_error(std::forward<Op>(op).identity(key),
std::forward<Box>(box)));
}

return propagate_value<NewBox>(std::forward<Box>(box));

// gcc11/12 internal crash with pipe_adaptor_t
// return std::forward<Box>(box) | or_else([f = std::forward<Op>(op).callable(key)](auto&& e) {
// return std::forward<Box>(box) | or_else([f = std::forward<Op>(op).identity(key)](auto&& e) {
// return NewBoxTraits::make_error(f(std::forward<decltype(e)>(e)));
// });
}
Expand Down
10 changes: 5 additions & 5 deletions tests/beman/monadics/detail/pipe_adaptor.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ struct test_op_t {

template<typename Box, std::derived_from<test_op_t> Op>
[[nodiscard]] friend constexpr auto operator|(Box&& box, Op&& op) {
return std::forward<Op>(op).callable(key)(std::forward<Box>(box));
return std::forward<Op>(op).identity(key)(std::forward<Box>(box));
}
};

Expand All @@ -37,8 +37,8 @@ TEST_CASE("lvalue-callable-is-copied") {
return test_op(fn);
}();

STATIC_REQUIRE(closure.callable(test_op_t::key).tracker.copies == 1);
STATIC_REQUIRE(closure.callable(test_op_t::key).tracker.moves == 0);
STATIC_REQUIRE(closure.identity(test_op_t::key).tracker.copies == 1);
STATIC_REQUIRE(closure.identity(test_op_t::key).tracker.moves == 0);
}

TEST_CASE("vvalue-callable-is-copied") {
Expand All @@ -53,8 +53,8 @@ TEST_CASE("vvalue-callable-is-copied") {
return test_op(std::move(fn));
}();

STATIC_REQUIRE(closure.callable(test_op_t::key).tracker.copies == 0);
STATIC_REQUIRE(closure.callable(test_op_t::key).tracker.moves == 1);
STATIC_REQUIRE(closure.identity(test_op_t::key).tracker.copies == 0);
STATIC_REQUIRE(closure.identity(test_op_t::key).tracker.moves == 1);
}

TEST_CASE("fn-stored-by-value-no-dangling") {
Expand Down
Loading