Implement LWG3938 (Cannot use std::expected monadic ops with move-only error_type)
https://wg21.link/LWG3938
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
Thanks for working on this! A few comments.
libcxx/docs/ReleaseNotes.rst | ||
---|---|---|
50 ↗ | (On Diff #535886) | We typically don't mention LWG issues in our release notes, unless they have a huge impact. |
libcxx/include/__expected/expected.h | ||
694 | Can you update this to the Standard wording too? the same for the other places. | |
750 | Please update this one too. | |
libcxx/test/std/utilities/expected/expected.expected/monadic/and_then.pass.cpp | ||
7 | Please update the or_else and transform test too. | |
142 | Can you make a separate MoveOnly class instead. |
Thanks for your review! @Mordante
libcxx/docs/ReleaseNotes.rst | ||
---|---|---|
50 ↗ | (On Diff #535886) | Thanks for your tips, I'll remove that. |
libcxx/include/__expected/expected.h | ||
694 | I tried to keep the same with the std wording, but it compile failed: error: invalid use of 'this' outside of a non-static member function 694 | requires is_constructible_v<_Tp, decltype(**this)> | ^ | |
694 | As we talked in discord:
template <class _Func> requires is_constructible_v<_Err, const _Err&&> _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) const&& { static_assert(std::is_same_v<decltype(*this), const std::expected<_Tp, _Err>&&>); static_assert(std::is_same_v<decltype(**this), const _Tp&&>); using _Up = remove_cvref_t<invoke_result_t<_Func, decltype(**this)>>; static_assert( __is_std_expected<_Up>::value, "The result of f(std::move(value())) must be a specialization of std::expected"); static_assert(is_same_v<typename _Up::error_type, _Err>, "The result of f(std::move(value())) must have the same error_type as this expected"); if (has_value()) { return std::invoke(std::forward<_Func>(__f), std::move(**this)); } return _Up(unexpect, std::move(error())); } the two static_assert fails: static_assert(std::is_same_v<decltype(*this), const std::expected<_Tp, _Err>&&>); static_assert(std::is_same_v<decltype(**this), const _Tp&&>); when and_then called by: #include <expected> #include <cassert> struct UnexpectedCRVal { std::expected<int, int> operator()(int&) = delete; std::expected<int, int> operator()(const int&) = delete; std::expected<int, int> operator()(int&&) = delete; constexpr std::expected<int, int> operator()(const int&&) { return std::expected<int, int>(std::unexpected<int>(5)); } }; int main() { const std::expected<int, int> e{0}; assert(std::move(e).and_then(UnexpectedCRVal{}).error() == 5); return 0; } The reason is that: *this always produces an Lvalue http://eel.is/c++draft/expr.unary.op#1.sentence-3 , so the type of decltype(**this) is const _Tp & but not const _Tp &&
std wording: Constraints: is_constructible_v<E, decltype(std::move(error()))> is true. error: invalid use of 'this' outside of a non-static member function 694 | requires is_constructible_v<_Tp, decltype(**this)> | |
libcxx/include/__expected/expected.h | ||
---|---|---|
694 | Nit:s/discord/Discord |
@Mordante Can we propose to use __union_.__val_, std::as_const(__union_.__val_), std::move(__union_.__val_) and std::move(std::as_const(__union_.__val_)) instead of **this ?
std::as_const is unnecessary because the const on the member functions already makes __union_.__val_ a const lvalue.
I think it's better to use the member access expressions instead of **this, because the latter can be ADL-hijacked. (However, this means the current standard wording is less than ideal and an LWG issue may be needed, see also LWG3969).
libcxx/include/__expected/expected.h | ||
---|---|---|
643 | It might be better to update the messages in static_assert (also occurs below; I originally suggested here.). |
Thanks a lot for your review! I updated to use __union_.__val_. and also update the static_assert message. @Mordante WDYT?
libcxx/include/__expected/expected.h | ||
---|---|---|
643 | fixed | |
libcxx/test/std/utilities/expected/expected.expected/monadic/and_then.pass.cpp | ||
7 | fixed. | |
142 | I have added a new MoveOnlyErrorType class |
libcxx/docs/Status/Cxx2cIssues.csv | ||
---|---|---|
17 | I think this should be 18.0 now. | |
libcxx/test/std/utilities/expected/expected.expected/monadic/and_then.pass.cpp | ||
173 | Here and for the other monadic operations, I think we should add runtime tests that use a move-only error type. Otherwise, if we had something like if (error-is-move-only) { std::abort(); } in the implementation of e.g. and_then, the current tests would pass but out implementation would obviously be incorrect. Generally speaking, static_assert tests are really nice in their negative form, to make sure that something is SFINAE friendly. However, just checking that things compile for a positive test is usually insufficient. |
I think this should be 18.0 now.