This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

3688. Exception specifications of copy/move member functions of std::bad_expected_access

Section: 22.8.4 [expected.bad], 22.8.5 [expected.bad.void], 22.8.6.6 [expected.object.obs], 22.8.7.6 [expected.void.obs] Status: New Submitter: Jiang An Opened: 2022-03-24 Last modified: 2024-07-24

Priority: 2

View all issues with New status.

Discussion:

The move constructor and the move assignment operator of standard exception types are not covered by 17.9.3 [exception]/2, and thus it is currently effectively unspecified whether these move functions of std::bad_expected_access<void> are noexcept. Furthermore, std::bad_expected_access<void> has protected special member functions, which overrides (or conflicts with?) the general rule in 17.9.3 [exception]/2.

The primary template std::bad_expected_access<E> stores an E object, and copying the stored object may throw an exception. Is it intended that the copy functions of std::bad_expected_access<E> are noexcept while those of E are not? When the copy happens because a std::bad_expected_access<E> is caught by value, if the copy constructor of E throws, std::terminate is called no matter whether that of std::bad_expected_access<E> is noexcept. But std::bad_expected_access<E> may be copied/moved in other circumstances.

I think the move constructor and the move assignment operator of a standard exception type should be specified to be public and noexcept when they exist, although sometimes whether they exist may be unspecified. And the move functions should also propagate the result of what() when the source and target have the same dynamic type, except that they can leave the result of what() from the source valid but unspecified.

Related to this, std::expected<T, E>::value overloads are specified to throw std::bad_expected_access<std::decay_t<E>> when an E is contained. However, it seems that the copy constructor of std::bad_expected_access is implicitly deleted if E is move-only, so the throw-expression is ill-formed.

Is it intended that std::expected<T, E>::value is ill-formed in such cases?

[2022-05-17; Reflector poll]

Set priority to 2 after reflector poll.

[2023-05-25; Jonathan comments]

The last part was clarified by LWG 3843(i), confirming that value() is ill-formed for move-only E.

[2024-07-24; Jonathan comments]

LWG 4031(i) made the move (and copy) operations of bad_expected_access<void> non-throwing.

Proposed resolution: