This is an archive of the discontinued LLVM Phabricator instance.

[libc++] Implement LWG3938 (Cannot use std::expected monadic ops with move-only error_type)
ClosedPublic

Authored by yronglin on Jun 29 2023, 10:35 AM.

Details

Summary

Implement LWG3938 (Cannot use std::expected monadic ops with move-only error_type)
https://wg21.link/LWG3938

Diff Detail

Event Timeline

yronglin created this revision.Jun 29 2023, 10:35 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 29 2023, 10:35 AM
yronglin requested review of this revision.Jun 29 2023, 10:35 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 29 2023, 10:35 AM
Herald added a reviewer: Restricted Project. · View Herald Transcript
yronglin edited the summary of this revision. (Show Details)Jun 29 2023, 10:37 AM

Thanks for working on this! A few comments.

libcxx/docs/ReleaseNotes.rst
50

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.

yronglin marked 2 inline comments as done.Jul 5 2023, 10:06 AM

Thanks for your review! @Mordante

libcxx/docs/ReleaseNotes.rst
50

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:

  1. *this always produces an LValue
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 &&

  1. we can use no-static member function in constraints

std wording: Constraints: is_constructible_v<E, decltype(std​::​move(error()))> is true.
we can't use decltype(std​::​move(error())) outside a non-static member function, and we use const _Err&& as a workaround before.
Also is_constructible_v<decltype(**this)> in constraints has the same issue:

error: invalid use of 'this' outside of a non-static member function
  694 |     requires is_constructible_v<_Tp, decltype(**this)>
          |
yronglin updated this revision to Diff 537414.EditedJul 5 2023, 10:19 AM
yronglin marked 2 inline comments as done.

Address comments:

  1. Add more tests.
  2. Undo the change of ReleaseNotes.rst
yronglin added inline comments.Jul 5 2023, 10:38 AM
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 ?

This comment was removed by yronglin.

@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.).

yronglin updated this revision to Diff 553100.Aug 24 2023, 6:20 AM

Address comments

yronglin marked 4 inline comments as done.Aug 24 2023, 6:24 AM

@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).

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

ldionne added inline comments.Aug 25 2023, 11:13 AM
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
178

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.

yronglin updated this revision to Diff 554415.Aug 29 2023, 10:14 AM
yronglin marked 4 inline comments as done.

Address comments

yronglin marked an inline comment as done.Aug 29 2023, 10:16 AM

Thanks for your review @ldionne

libcxx/docs/Status/Cxx2cIssues.csv
17

fixed

libcxx/test/std/utilities/expected/expected.expected/monadic/and_then.pass.cpp
178

Thanks, I have add runtime test for each monadic operations.

ldionne accepted this revision.Sep 5 2023, 2:28 PM
This revision is now accepted and ready to land.Sep 5 2023, 2:28 PM
yronglin marked an inline comment as done.Sep 6 2023, 7:36 AM

Thanks for the review! @ldionne

This revision was landed with ongoing or failed builds.Sep 6 2023, 7:39 AM
This revision was automatically updated to reflect the committed changes.