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
13 changes: 9 additions & 4 deletions include/fn/expected.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,12 @@ template <typename T, typename E> struct _expected_base : ::pfn::detail::_expect
return type(::fn::unexpect, _pfn_base::_error(FWD(self)));
}

// transform, value type is a sum (delegates to sum::transform)
// transform, value type is a sum (delegates to sum::transform). The callback is constrained here,
// in the immediate context, for the reason given on optional's sum-case _transform.
template <typename Self, typename Fn>
static constexpr auto _transform(Self &&self, Fn &&fn)
requires some_sum<T> && ::std::is_constructible_v<E, decltype(_pfn_base::_error(FWD(self)))>
requires some_sum<T> && ::fn::detail::_typelist_invocable<Fn, decltype(_pfn_base::_value(FWD(self)))>
&& ::std::is_constructible_v<E, decltype(_pfn_base::_error(FWD(self)))>
{
using new_value_type = decltype(_pfn_base::_value(FWD(self)).transform(FWD(fn)));
using type = ::fn::expected<new_value_type, E>;
Expand Down Expand Up @@ -282,6 +284,7 @@ template <typename T, typename E> struct _expected_base : ::pfn::detail::_expect
T, ::fn::apply_const_lvalue_t<Self, typename _pfn_base::_value_t &&>>)) // extension
requires(not some_sum<E>)
&& ::fn::detail::_is_invocable_if<not some_sum<E>, Fn, decltype(_pfn_base::_error(FWD(self)))>::value
&& (::std::is_void_v<T> || ::std::is_constructible_v<T, decltype(_pfn_base::_value(FWD(self)))>)
{
using new_error_type = typename ::fn::detail::_invoke_result<Fn, decltype(_pfn_base::_error(FWD(self)))>::type;
using type = ::fn::expected<T, new_error_type>;
Expand All @@ -296,10 +299,12 @@ template <typename T, typename E> struct _expected_base : ::pfn::detail::_expect
});
}

// transform_error, error type is a sum (delegates to sum::transform)
// transform_error, error type is a sum (delegates to sum::transform). The callback is constrained
// here, in the immediate context, for the reason given on optional's sum-case _transform.
template <typename Self, typename Fn>
static constexpr auto _transform_error(Self &&self, Fn &&fn)
requires some_sum<E>
requires some_sum<E> && ::fn::detail::_typelist_invocable<Fn, decltype(_pfn_base::_error(FWD(self)))>
&& (::std::is_void_v<T> || ::std::is_constructible_v<T, decltype(_pfn_base::_value(FWD(self)))>)
{
using new_error_type = decltype(_pfn_base::_error(FWD(self)).transform(FWD(fn)));
using type = ::fn::expected<T, new_error_type>;
Expand Down
8 changes: 6 additions & 2 deletions include/fn/optional.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,14 @@ template <typename T> struct _optional_base : ::pfn::detail::_optional_base<T, o
return type(::std::nullopt);
}

// transform, value type is a sum (delegates to sum::transform)
// transform, value type is a sum (delegates to sum::transform). The callback is constrained here,
// in the immediate context: the deduced return type instantiates the body, so leaving it to
// sum::transform's own constraint would make a bad callback a hard error instead of dropping the
// candidate - and would poison overload resolution, since the losing candidates form their
// signatures too.
template <typename Self, typename Fn>
static constexpr auto _transform(Self &&self, Fn &&fn)
requires some_sum<T>
requires some_sum<T> && ::fn::detail::_typelist_invocable<Fn, decltype(*FWD(self))>
{
using new_value_type = decltype((*FWD(self)).transform(FWD(fn)));
using type = ::fn::optional<new_value_type>;
Expand Down
65 changes: 65 additions & 0 deletions tests/fn/expected.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <catch2/catch_all.hpp>

#include <memory>
#include <utility>
#include <variant>

Expand Down Expand Up @@ -2576,6 +2577,26 @@ TEST_CASE("expected sum support transform", "[expected][sum][transform]")
// TODO Switch bool to std::monostate or similar user-defined type
static_assert(a.transform(fn).value().has_value<bool>());
}

WHEN("constraints")
{
using T = fn::expected<fn::sum_for<int, std::string_view>, Error>;
constexpr auto can_transform_lval = [](auto &&f) { return requires { std::declval<T &>().transform(f); }; };
constexpr auto can_transform_clval = [](auto &&f) { return requires { std::declval<T const &>().transform(f); }; };

// a callback no alternative can take drops the candidate, rather than failing inside the body
static_assert(not can_transform_lval([](double) -> bool { throw 0; }));
static_assert(not can_transform_lval([](int &) -> bool { throw 0; })); // string_view is unhandled

// a visitor need only serve the value category the call actually selects
constexpr auto lval_only
= fn::overload{[](int &i) -> bool { return i == 12; }, [](std::string_view &) -> bool { throw 0; }};
static_assert(can_transform_lval(lval_only));
static_assert(not can_transform_clval(lval_only));

T s{fn::sum{12}};
CHECK(s.transform(lval_only).value() == fn::sum{true});
}
}

TEST_CASE("expected sum support transform_error", "[expected][sum][transform_error]")
Expand Down Expand Up @@ -2741,6 +2762,50 @@ TEST_CASE("expected sum support transform_error", "[expected][sum][transform_err
static_assert(a.transform_error(fn).error() == fn::sum{42});
}
}

WHEN("constraints")
{
using T = fn::expected<double, fn::sum_for<int, std::string_view>>;
constexpr auto can_lval = [](auto &&f) { return requires { std::declval<T &>().transform_error(f); }; };
constexpr auto can_clval = [](auto &&f) { return requires { std::declval<T const &>().transform_error(f); }; };

// a callback no alternative can take drops the candidate, rather than failing inside the body
static_assert(not can_lval([](double) -> bool { throw 0; }));
static_assert(not can_lval([](int &) -> bool { throw 0; })); // string_view is unhandled

// a visitor need only serve the value category the call actually selects
constexpr auto lval_only
= fn::overload{[](int &i) -> bool { return i == 12; }, [](std::string_view &) -> bool { throw 0; }};
static_assert(can_lval(lval_only));
static_assert(not can_clval(lval_only));

T s{::fn::unexpect, fn::sum{12}};
CHECK(s.transform_error(lval_only).error() == fn::sum{true});
}

WHEN("untouched value is relocated, so a move-only value type drops the copying overloads")
{
// the conjunct or_else carries and pfn's transform_error requires: the untouched value goes
// into the result, so only the overloads whose self can be moved from survive
constexpr auto generic = [](auto &&) -> bool { throw 0; };

using M = fn::expected<std::unique_ptr<int>, fn::sum<int>>; // sum-case overload
constexpr auto can_M_lval = [](auto &&f) { return requires { std::declval<M &>().transform_error(f); }; };
constexpr auto can_M_rval = [](auto &&f) { return requires { std::declval<M &&>().transform_error(f); }; };
static_assert(not can_M_lval(generic)); // would copy the value
static_assert(can_M_rval(generic)); // moves it

using N = fn::expected<std::unique_ptr<int>, Error>; // non-sum overload
constexpr auto can_N_lval = [](auto &&f) { return requires { std::declval<N &>().transform_error(f); }; };
constexpr auto can_N_rval = [](auto &&f) { return requires { std::declval<N &&>().transform_error(f); }; };
static_assert(not can_N_lval(generic));
static_assert(can_N_rval(generic));

N n{std::make_unique<int>(7)};
auto r = std::move(n).transform_error([](Error const &) -> bool { throw 0; });
static_assert(std::is_same_v<decltype(r), fn::expected<std::unique_ptr<int>, bool>>);
CHECK(*r.value() == 7);
}
}

TEST_CASE("expected pack support or_else", "[expected][or_else][pack]")
Expand Down
21 changes: 21 additions & 0 deletions tests/fn/optional.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -619,4 +619,25 @@ TEST_CASE("optional transform sum", "[optional][sum][transform]")
static_assert(std::is_same_v<decltype(a.transform(fn)), fn::optional<fn::sum<bool, int>>>);
static_assert(a.transform(fn).value() == fn::sum{true});
}

WHEN("constraints")
{
using T = fn::optional<fn::sum_for<Xint, int>>;
constexpr auto can_transform_lval = [](auto &&f) { return requires { std::declval<T &>().transform(f); }; };
constexpr auto can_transform_clval = [](auto &&f) { return requires { std::declval<T const &>().transform(f); }; };

// a callback no alternative can take drops the candidate, rather than failing inside the body
static_assert(not can_transform_lval([](std::string_view) -> bool { throw 0; }));
static_assert(not can_transform_lval([](int &) -> bool { throw 0; })); // Xint is unhandled

// a visitor need only serve the value category the call actually selects: the losing const&
// candidate is dropped by its own constraint rather than forming its signature and poisoning
// the call. The four-category visitors above are a spelling choice, not a requirement.
constexpr auto lval_only = fn::overload{[](int &i) -> bool { return i == 12; }, [](Xint &) -> bool { throw 0; }};
static_assert(can_transform_lval(lval_only));
static_assert(not can_transform_clval(lval_only));

T s{12};
CHECK(s.transform(lval_only).value() == fn::sum{true});
}
}