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 | ||
| 705 | Can you update this to the Standard wording too? the same for the other places. | |
| 761 | 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 | ||
| 705 | 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)>
| ^ | |
| 705 | 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 | ||
|---|---|---|
| 705 | 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 | ||
|---|---|---|
| 654 | 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 | ||
|---|---|---|
| 654 | 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.