From fe94f8b7e022b7e154f6c47cc292d4463bddac5e Mon Sep 17 00:00:00 2001 From: Jonathan Wakely <jwakely@redhat.com> Date: Thu, 1 Jun 2023 11:16:49 +0100 Subject: [PATCH] libstdc++: Do not use std::expected::value() in monadic ops (LWG 3938) The monadic operations in std::expected always check has_value() so we can avoid the execptional path in value() and the assertions in error() by accessing _M_val and _M_unex directly. This means that the monadic operations no longer require _M_unex to be copyable so that it can be thrown from value(), as modified by LWG 3938. This also fixes two incorrect uses of std::move in transform(F&&)& and transform(F&&) const& which I found while making these changes. Now that move-only error types are supported, it's possible to properly test the constraints that LWG 3877 added to and_then and transform. The lwg3877.cc test now does that. libstdc++-v3/ChangeLog: * include/std/expected (expected::and_then, expected::or_else) (expected::transform_error): Use _M_val and _M_unex instead of calling value() and error(), as per LWG 3938. (expected::transform): Likewise. Remove incorrect std::move calls from lvalue overloads. (expected<void, E>::and_then, expected<void, E>::or_else) (expected<void, E>::transform): Use _M_unex instead of calling error(). * testsuite/20_util/expected/lwg3877.cc: Add checks for and_then and transform, and for std::expected<void, E>. * testsuite/20_util/expected/lwg3938.cc: New test. --- libstdc++-v3/include/std/expected | 78 +++++----- .../testsuite/20_util/expected/lwg3877.cc | 145 ++++++++++++++---- .../testsuite/20_util/expected/lwg3938.cc | 142 +++++++++++++++++ 3 files changed, 298 insertions(+), 67 deletions(-) create mode 100644 libstdc++-v3/testsuite/20_util/expected/lwg3938.cc diff --git a/libstdc++-v3/include/std/expected b/libstdc++-v3/include/std/expected index 5ea0d6a7cb94..a63557448f76 100644 --- a/libstdc++-v3/include/std/expected +++ b/libstdc++-v3/include/std/expected @@ -745,8 +745,7 @@ namespace __expected { if (_M_has_value) [[likely]] return std::move(_M_val); - _GLIBCXX_THROW_OR_ABORT(bad_expected_access<_Er>( - std::move(_M_unex))); + _GLIBCXX_THROW_OR_ABORT(bad_expected_access<_Er>(std::move(_M_unex))); } constexpr _Tp&& @@ -754,8 +753,7 @@ namespace __expected { if (_M_has_value) [[likely]] return std::move(_M_val); - _GLIBCXX_THROW_OR_ABORT(bad_expected_access<_Er>( - std::move(_M_unex))); + _GLIBCXX_THROW_OR_ABORT(bad_expected_access<_Er>(std::move(_M_unex))); } constexpr const _Er& @@ -849,9 +847,9 @@ namespace __expected static_assert(is_same_v<typename _Up::error_type, _Er>); if (has_value()) - return std::__invoke(std::forward<_Fn>(__f), value()); + return std::__invoke(std::forward<_Fn>(__f), _M_val); else - return _Up(unexpect, error()); + return _Up(unexpect, _M_unex); } template<typename _Fn> requires is_constructible_v<_Er, const _Er&> @@ -863,9 +861,9 @@ namespace __expected static_assert(is_same_v<typename _Up::error_type, _Er>); if (has_value()) - return std::__invoke(std::forward<_Fn>(__f), value()); + return std::__invoke(std::forward<_Fn>(__f), _M_val); else - return _Up(unexpect, error()); + return _Up(unexpect, _M_unex); } template<typename _Fn> requires is_constructible_v<_Er, _Er> @@ -877,9 +875,9 @@ namespace __expected static_assert(is_same_v<typename _Up::error_type, _Er>); if (has_value()) - return std::__invoke(std::forward<_Fn>(__f), std::move(value())); + return std::__invoke(std::forward<_Fn>(__f), std::move(_M_val)); else - return _Up(unexpect, std::move(error())); + return _Up(unexpect, std::move(_M_unex)); } @@ -892,9 +890,9 @@ namespace __expected static_assert(is_same_v<typename _Up::error_type, _Er>); if (has_value()) - return std::__invoke(std::forward<_Fn>(__f), std::move(value())); + return std::__invoke(std::forward<_Fn>(__f), std::move(_M_val)); else - return _Up(unexpect, std::move(error())); + return _Up(unexpect, std::move(_M_unex)); } template<typename _Fn> requires is_constructible_v<_Tp, _Tp&> @@ -906,9 +904,9 @@ namespace __expected static_assert(is_same_v<typename _Gr::value_type, _Tp>); if (has_value()) - return _Gr(in_place, value()); + return _Gr(in_place, _M_val); else - return std::__invoke(std::forward<_Fn>(__f), error()); + return std::__invoke(std::forward<_Fn>(__f), _M_unex); } template<typename _Fn> requires is_constructible_v<_Tp, const _Tp&> @@ -920,9 +918,9 @@ namespace __expected static_assert(is_same_v<typename _Gr::value_type, _Tp>); if (has_value()) - return _Gr(in_place, value()); + return _Gr(in_place, _M_val); else - return std::__invoke(std::forward<_Fn>(__f), error()); + return std::__invoke(std::forward<_Fn>(__f), _M_unex); } @@ -935,9 +933,9 @@ namespace __expected static_assert(is_same_v<typename _Gr::value_type, _Tp>); if (has_value()) - return _Gr(in_place, std::move(value())); + return _Gr(in_place, std::move(_M_val)); else - return std::__invoke(std::forward<_Fn>(__f), std::move(error())); + return std::__invoke(std::forward<_Fn>(__f), std::move(_M_unex)); } template<typename _Fn> requires is_constructible_v<_Tp, const _Tp> @@ -949,9 +947,9 @@ namespace __expected static_assert(is_same_v<typename _Gr::value_type, _Tp>); if (has_value()) - return _Gr(in_place, std::move(value())); + return _Gr(in_place, std::move(_M_val)); else - return std::__invoke(std::forward<_Fn>(__f), std::move(error())); + return std::__invoke(std::forward<_Fn>(__f), std::move(_M_unex)); } template<typename _Fn> requires is_constructible_v<_Er, _Er&> @@ -967,7 +965,7 @@ namespace __expected _M_val); }); else - return _Res(unexpect, std::move(error())); + return _Res(unexpect, _M_unex); } template<typename _Fn> requires is_constructible_v<_Er, const _Er&> @@ -983,7 +981,7 @@ namespace __expected _M_val); }); else - return _Res(unexpect, std::move(error())); + return _Res(unexpect, _M_unex); } template<typename _Fn> requires is_constructible_v<_Er, _Er> @@ -999,7 +997,7 @@ namespace __expected std::move(_M_val)); }); else - return _Res(unexpect, std::move(error())); + return _Res(unexpect, std::move(_M_unex)); } template<typename _Fn> requires is_constructible_v<_Er, const _Er> @@ -1015,7 +1013,7 @@ namespace __expected std::move(_M_val)); }); else - return _Res(unexpect, std::move(error())); + return _Res(unexpect, std::move(_M_unex)); } template<typename _Fn> requires is_constructible_v<_Tp, _Tp&> @@ -1026,7 +1024,7 @@ namespace __expected using _Res = expected<_Tp, _Gr>; if (has_value()) - return _Res(in_place, value()); + return _Res(in_place, _M_val); else return _Res(__unexpect_inv{}, [&]() { return std::__invoke(std::forward<_Fn>(__f), @@ -1042,7 +1040,7 @@ namespace __expected using _Res = expected<_Tp, _Gr>; if (has_value()) - return _Res(in_place, value()); + return _Res(in_place, _M_val); else return _Res(__unexpect_inv{}, [&]() { return std::__invoke(std::forward<_Fn>(__f), @@ -1058,7 +1056,7 @@ namespace __expected using _Res = expected<_Tp, _Gr>; if (has_value()) - return _Res(in_place, std::move(value())); + return _Res(in_place, std::move(_M_val)); else return _Res(__unexpect_inv{}, [&]() { return std::__invoke(std::forward<_Fn>(__f), @@ -1074,7 +1072,7 @@ namespace __expected using _Res = expected<_Tp, _Gr>; if (has_value()) - return _Res(in_place, std::move(value())); + return _Res(in_place, std::move(_M_val)); else return _Res(__unexpect_inv{}, [&]() { return std::__invoke(std::forward<_Fn>(__f), @@ -1530,7 +1528,7 @@ namespace __expected if (has_value()) return std::__invoke(std::forward<_Fn>(__f)); else - return _Up(unexpect, error()); + return _Up(unexpect, _M_unex); } template<typename _Fn> requires is_constructible_v<_Er, const _Er&> @@ -1544,7 +1542,7 @@ namespace __expected if (has_value()) return std::__invoke(std::forward<_Fn>(__f)); else - return _Up(unexpect, error()); + return _Up(unexpect, _M_unex); } template<typename _Fn> requires is_constructible_v<_Er, _Er> @@ -1558,7 +1556,7 @@ namespace __expected if (has_value()) return std::__invoke(std::forward<_Fn>(__f)); else - return _Up(unexpect, std::move(error())); + return _Up(unexpect, std::move(_M_unex)); } template<typename _Fn> requires is_constructible_v<_Er, const _Er> @@ -1572,7 +1570,7 @@ namespace __expected if (has_value()) return std::__invoke(std::forward<_Fn>(__f)); else - return _Up(unexpect, std::move(error())); + return _Up(unexpect, std::move(_M_unex)); } template<typename _Fn> @@ -1586,7 +1584,7 @@ namespace __expected if (has_value()) return _Gr(); else - return std::__invoke(std::forward<_Fn>(__f), error()); + return std::__invoke(std::forward<_Fn>(__f), _M_unex); } template<typename _Fn> @@ -1600,7 +1598,7 @@ namespace __expected if (has_value()) return _Gr(); else - return std::__invoke(std::forward<_Fn>(__f), error()); + return std::__invoke(std::forward<_Fn>(__f), _M_unex); } template<typename _Fn> @@ -1614,7 +1612,7 @@ namespace __expected if (has_value()) return _Gr(); else - return std::__invoke(std::forward<_Fn>(__f), std::move(error())); + return std::__invoke(std::forward<_Fn>(__f), std::move(_M_unex)); } template<typename _Fn> @@ -1628,7 +1626,7 @@ namespace __expected if (has_value()) return _Gr(); else - return std::__invoke(std::forward<_Fn>(__f), std::move(error())); + return std::__invoke(std::forward<_Fn>(__f), std::move(_M_unex)); } template<typename _Fn> requires is_constructible_v<_Er, _Er&> @@ -1641,7 +1639,7 @@ namespace __expected if (has_value()) return _Res(__in_place_inv{}, std::forward<_Fn>(__f)); else - return _Res(unexpect, error()); + return _Res(unexpect, _M_unex); } template<typename _Fn> requires is_constructible_v<_Er, const _Er&> @@ -1654,7 +1652,7 @@ namespace __expected if (has_value()) return _Res(__in_place_inv{}, std::forward<_Fn>(__f)); else - return _Res(unexpect, error()); + return _Res(unexpect, _M_unex); } template<typename _Fn> requires is_constructible_v<_Er, _Er> @@ -1667,7 +1665,7 @@ namespace __expected if (has_value()) return _Res(__in_place_inv{}, std::forward<_Fn>(__f)); else - return _Res(unexpect, std::move(error())); + return _Res(unexpect, std::move(_M_unex)); } template<typename _Fn> requires is_constructible_v<_Er, const _Er> @@ -1680,7 +1678,7 @@ namespace __expected if (has_value()) return _Res(__in_place_inv{}, std::forward<_Fn>(__f)); else - return _Res(unexpect, std::move(error())); + return _Res(unexpect, std::move(_M_unex)); } template<typename _Fn> diff --git a/libstdc++-v3/testsuite/20_util/expected/lwg3877.cc b/libstdc++-v3/testsuite/20_util/expected/lwg3877.cc index 556d8d5dc9ef..876275bfdb06 100644 --- a/libstdc++-v3/testsuite/20_util/expected/lwg3877.cc +++ b/libstdc++-v3/testsuite/20_util/expected/lwg3877.cc @@ -1,6 +1,8 @@ // { dg-options "-std=gnu++23" } // { dg-do compile { target c++23 } } +// LWG 3877. Incorrect constraints on const-qualified monadic overloads + #include <expected> struct T1 @@ -20,45 +22,134 @@ struct T3 T3(const T3&&) { } }; +template<typename Exp, typename F> +concept Has_and_then = requires(Exp&& exp, F f) { + std::forward<Exp>(exp).and_then(f); +}; + +using ExpiT1 = std::expected<int, T1>; +static_assert( Has_and_then<ExpiT1&, ExpiT1(int)> ); +static_assert( Has_and_then<const ExpiT1&, ExpiT1(int)> ); +static_assert( Has_and_then<ExpiT1&&, ExpiT1(int)> ); +static_assert( Has_and_then<const ExpiT1&&, ExpiT1(int)> ); + +using ExpiT2 = std::expected<int, T2>; +static_assert( !Has_and_then<ExpiT2&, ExpiT2(int)> ); +static_assert( !Has_and_then<const ExpiT2&, ExpiT2(int)> ); +static_assert( !Has_and_then<ExpiT2&&, ExpiT2(int)> ); +static_assert( !Has_and_then<const ExpiT2&&, ExpiT2(int)> ); + +using ExpiT3 = std::expected<int, T3>; +static_assert( Has_and_then<ExpiT3&, ExpiT3(int)> ); +static_assert( !Has_and_then<const ExpiT3&, ExpiT3(int)> ); +static_assert( Has_and_then<ExpiT3&&, ExpiT3(int)> ); // uses and_then(F) const && +static_assert( Has_and_then<const ExpiT3&&, ExpiT3(int)> ); + template<typename Exp, typename F> concept Has_or_else = requires(Exp&& exp, F f) { std::forward<Exp>(exp).or_else(f); }; -using E1 = std::expected<T1, int>; -static_assert( Has_or_else<E1&, E1(int)> ); -static_assert( Has_or_else<const E1&, E1(int)> ); -static_assert( Has_or_else<E1&&, E1(int)> ); -static_assert( Has_or_else<const E1&&, E1(int)> ); +using ExpT1i = std::expected<T1, int>; +static_assert( Has_or_else<ExpT1i&, ExpT1i(int)> ); +static_assert( Has_or_else<const ExpT1i&, ExpT1i(int)> ); +static_assert( Has_or_else<ExpT1i&&, ExpT1i(int)> ); +static_assert( Has_or_else<const ExpT1i&&, ExpT1i(int)> ); + +using ExpT2i = std::expected<T2, int>; +static_assert( !Has_or_else<ExpT2i&, ExpT2i(int)> ); +static_assert( !Has_or_else<const ExpT2i&, ExpT2i(int)> ); +static_assert( !Has_or_else<ExpT2i&&, ExpT2i(int)> ); +static_assert( !Has_or_else<const ExpT2i&&, ExpT2i(int)> ); + +using ExpT3i = std::expected<T3, int>; +static_assert( Has_or_else<ExpT3i&, ExpT3i(int)> ); +static_assert( !Has_or_else<const ExpT3i&, ExpT3i(int)> ); +static_assert( Has_or_else<ExpT3i&&, ExpT3i(int)> ); // uses or_else(F) const && +static_assert( Has_or_else<const ExpT3i&&, ExpT3i(int)> ); + +template<typename Exp, typename F> +concept Has_transform = requires(Exp&& exp, F f) { + std::forward<Exp>(exp).transform(f); +}; + +static_assert( Has_transform<ExpiT1&, int(int)> ); +static_assert( Has_transform<const ExpiT1&, int(int)> ); +static_assert( Has_transform<ExpiT1&&, int(int)> ); +static_assert( Has_transform<const ExpiT1&&, int(int)> ); -using E2 = std::expected<T2, int>; -static_assert( !Has_or_else<E2&, E2(int)> ); -static_assert( !Has_or_else<const E2&, E2(int)> ); -static_assert( !Has_or_else<E2&&, E2(int)> ); -static_assert( !Has_or_else<const E2&&, E2(int)> ); +static_assert( !Has_transform<ExpiT2&, int(int)> ); +static_assert( !Has_transform<const ExpiT2&, int(int)> ); +static_assert( !Has_transform<ExpiT2&&, int(int)> ); +static_assert( !Has_transform<const ExpiT2&&, int(int)> ); -using E3 = std::expected<T3, int>; -static_assert( Has_or_else<E3&, E3(int)> ); -static_assert( !Has_or_else<const E3&, E3(int)> ); -static_assert( Has_or_else<E3&&, E3(int)> ); // uses or_else(F) const && -static_assert( Has_or_else<const E3&&, E3(int)> ); +static_assert( Has_transform<ExpiT3&, int(int)> ); +static_assert( !Has_transform<const ExpiT3&, int(int)> ); +static_assert( Has_transform<ExpiT3&&, int(int)> ); // uses transform(F) const && +static_assert( Has_transform<const ExpiT3&&, int(int)> ); template<typename Exp, typename F> concept Has_transform_error = requires(Exp&& exp, F f) { std::forward<Exp>(exp).transform_error(f); }; -static_assert( Has_transform_error<E1&, int(int)> ); -static_assert( Has_transform_error<const E1&, int(int)> ); -static_assert( Has_transform_error<E1&&, int(int)> ); -static_assert( Has_transform_error<const E1&&, int(int)> ); +static_assert( Has_transform_error<ExpT1i&, int(int)> ); +static_assert( Has_transform_error<const ExpT1i&, int(int)> ); +static_assert( Has_transform_error<ExpT1i&&, int(int)> ); +static_assert( Has_transform_error<const ExpT1i&&, int(int)> ); + +static_assert( !Has_transform_error<ExpT2i&, int(int)> ); +static_assert( !Has_transform_error<const ExpT2i&, int(int)> ); +static_assert( !Has_transform_error<ExpT2i&&, int(int)> ); +static_assert( !Has_transform_error<const ExpT2i&&, int(int)> ); + +static_assert( Has_transform_error<ExpT3i&, int(int)> ); +static_assert( !Has_transform_error<const ExpT3i&, int(int)> ); +static_assert( Has_transform_error<ExpT3i&&, int(int)> ); // uses transform_error(F) const && +static_assert( Has_transform_error<const ExpT3i&&, int(int)> ); + +// std::expected<cv void, E> + +using ExpvT1 = std::expected<void, T1>; +static_assert( Has_and_then<ExpvT1&, ExpvT1()> ); +static_assert( Has_and_then<const ExpvT1&, ExpvT1()> ); +static_assert( Has_and_then<ExpvT1&&, ExpvT1()> ); +static_assert( Has_and_then<const ExpvT1&&, ExpvT1()> ); + +using ExpvT2 = std::expected<void, T2>; +static_assert( !Has_and_then<ExpvT2&, ExpvT2()> ); +static_assert( !Has_and_then<const ExpvT2&, ExpvT2()> ); +static_assert( !Has_and_then<ExpvT2&&, ExpvT2()> ); +static_assert( !Has_and_then<const ExpvT2&&, ExpvT2()> ); + +using ExpvT3 = std::expected<void, T3>; +static_assert( Has_and_then<ExpvT3&, ExpvT3()> ); +static_assert( !Has_and_then<const ExpvT3&, ExpvT3()> ); +static_assert( Has_and_then<ExpvT3&&, ExpvT3()> ); // uses and_then(F) const && +static_assert( Has_and_then<const ExpvT3&&, ExpvT3()> ); + +using Expvi = std::expected<void, int>; +static_assert( Has_or_else<Expvi&, Expvi(int)> ); +static_assert( Has_or_else<const Expvi&, Expvi(int)> ); +static_assert( Has_or_else<Expvi&&, Expvi(int)> ); +static_assert( Has_or_else<const Expvi&&, Expvi(int)> ); + +static_assert( Has_transform<ExpvT1&, int()> ); +static_assert( Has_transform<const ExpvT1&, int()> ); +static_assert( Has_transform<ExpvT1&&, int()> ); +static_assert( Has_transform<const ExpvT1&&, int()> ); + +static_assert( !Has_transform<ExpvT2&, int()> ); +static_assert( !Has_transform<const ExpvT2&, int()> ); +static_assert( !Has_transform<ExpvT2&&, int()> ); +static_assert( !Has_transform<const ExpvT2&&, int()> ); -static_assert( !Has_transform_error<E2&, int(int)> ); -static_assert( !Has_transform_error<const E2&, int(int)> ); -static_assert( !Has_transform_error<E2&&, int(int)> ); -static_assert( !Has_transform_error<const E2&&, int(int)> ); +static_assert( Has_transform<ExpvT3&, int()> ); +static_assert( !Has_transform<const ExpvT3&, int()> ); +static_assert( Has_transform<ExpvT3&&, int()> ); // uses transform(F) const && +static_assert( Has_transform<const ExpvT3&&, int()> ); -static_assert( Has_transform_error<E3&, int(int)> ); -static_assert( !Has_transform_error<const E3&, int(int)> ); -static_assert( Has_transform_error<E3&&, int(int)> ); // uses transform_error(F) const && -static_assert( Has_transform_error<const E3&&, int(int)> ); +static_assert( Has_transform_error<Expvi&, int(int)> ); +static_assert( Has_transform_error<const Expvi&, int(int)> ); +static_assert( Has_transform_error<Expvi&&, int(int)> ); +static_assert( Has_transform_error<const Expvi&&, int(int)> ); diff --git a/libstdc++-v3/testsuite/20_util/expected/lwg3938.cc b/libstdc++-v3/testsuite/20_util/expected/lwg3938.cc new file mode 100644 index 000000000000..c7e3758a9020 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/expected/lwg3938.cc @@ -0,0 +1,142 @@ +// { dg-options "-std=gnu++23" } +// { dg-do compile { target c++23 } } + +// LWG 3938. Cannot use std::expected monadic ops with move-only error_type + +#include <expected> +#include <testsuite_hooks.h> + +struct MoveOnly { + constexpr MoveOnly(int i) : i(i) { } + constexpr MoveOnly(MoveOnly&&) = default; + constexpr MoveOnly(const MoveOnly&& mo) : i(mo.i) { } + constexpr bool operator==(const MoveOnly&) const = default; + int i; +}; + +constexpr bool +test_and_then() +{ + auto fun = [](int i) { return std::expected<long, MoveOnly>(i); }; + + std::expected<int, MoveOnly> good(9); + std::expected<long, MoveOnly> e1 = std::move(good).and_then(fun); + VERIFY( e1 == good ); + const auto& gooder = good; + std::expected<long, MoveOnly> e2 = std::move(gooder).and_then(fun); + VERIFY( e2 == gooder ); + + std::expected<int, MoveOnly> bad(std::unexpect, 99); + std::expected<long, MoveOnly> e3 = std::move(bad).and_then(fun); + VERIFY( e3 == bad ); + const auto& badder = bad; + std::expected<long, MoveOnly> e4 = std::move(badder).and_then(fun); + VERIFY( e4 == badder ); + + auto vun = [] { return std::expected<long, MoveOnly>(1); }; + std::expected<void, MoveOnly> vud; + std::expected<long, MoveOnly> e5 = std::move(vud).and_then(vun); + VERIFY( *e5 == 1 ); + const auto& vudder = vud; + std::expected<long, MoveOnly> e6 = std::move(vudder).and_then(vun); + VERIFY( *e6 == 1 ); + + return true; +} + +static_assert( test_and_then() ); + +constexpr bool +test_or_else() +{ + auto fun = [](const MoveOnly&& mo) { return std::expected<int, long>(mo.i); }; + + std::expected<int, MoveOnly> good(9); + std::expected<int, long> e1 = std::move(good).or_else(fun); + VERIFY( e1 == good ); + const auto& gooder = good; + std::expected<int, long> e2 = std::move(gooder).or_else(fun); + VERIFY( e2 == gooder ); + + std::expected<int, MoveOnly> bad(std::unexpect, 99); + std::expected<int, long> e3 = std::move(bad).or_else(fun); + VERIFY( *e3 == 99 ); + const auto& badder = bad; + std::expected<int, long> e4 = std::move(badder).or_else(fun); + VERIFY( *e4 == 99 ); + + auto vun = [](const MoveOnly&& mo) { return std::expected<void, long>{}; }; + std::expected<void, MoveOnly> vud; + std::expected<void, long> e5 = std::move(vud).or_else(vun); + VERIFY( e5.has_value() ); + const auto& vudder = vud; + std::expected<void, long> e6 = std::move(vudder).or_else(vun); + VERIFY( e6.has_value() ); + + return true; +} + +static_assert( test_or_else() ); + +constexpr bool +test_transform() +{ + auto fun = [](int i) { return (long)i; }; + + std::expected<int, MoveOnly> good(9); + std::expected<long, MoveOnly> e1 = std::move(good).transform(fun); + VERIFY( e1 == good ); + const auto& gooder = good; + std::expected<long, MoveOnly> e2 = std::move(gooder).transform(fun); + VERIFY( e2 == gooder ); + + std::expected<int, MoveOnly> bad(std::unexpect, 99); + std::expected<long, MoveOnly> e3 = std::move(bad).transform(fun); + VERIFY( e3 == bad ); + const auto& badder = bad; + std::expected<long, MoveOnly> e4 = std::move(badder).transform(fun); + VERIFY( e4 == badder ); + + auto vun = []() { return 1L; }; + std::expected<void, MoveOnly> vud; + std::expected<long, MoveOnly> e5 = std::move(vud).transform(vun); + VERIFY( *e5 == 1 ); + const auto& vudder = vud; + std::expected<long, MoveOnly> e6 = std::move(vudder).transform(vun); + VERIFY( *e6 == 1 ); + + return true; +} + +static_assert( test_transform() ); + +constexpr bool +test_transform_error() +{ + auto fun = [](const MoveOnly&& mo) { return (long)mo.i; }; + + std::expected<int, MoveOnly> good(9); + std::expected<int, long> e1 = std::move(good).transform_error(fun); + VERIFY( e1 == good ); + const auto& gooder = good; + std::expected<int, long> e2 = std::move(gooder).transform_error(fun); + VERIFY( e2 == gooder ); + + std::expected<int, MoveOnly> bad(std::unexpect, 99); + std::expected<int, long> e3 = std::move(bad).transform_error(fun); + VERIFY( e3.error() == 99 ); + const auto& badder = bad; + std::expected<int, long> e4 = std::move(badder).transform_error(fun); + VERIFY( e4.error() == 99 ); + + std::expected<void, MoveOnly> vud(std::unexpect, 1); + std::expected<void, long> e5 = std::move(vud).transform_error(fun); + VERIFY( e5.error() == 1 ); + const auto& vudder = vud; + std::expected<void, long> e6 = std::move(vudder).transform_error(fun); + VERIFY( e6.error() == 1 ); + + return true; +} + +static_assert( test_transform_error() ); -- GitLab