4195. expected<int, int> isn't specified to be trivially assignable

Section: 22.8.6.4 [expected.object.assign], 22.8.7.4 [expected.void.assign] Status: New Submitter: Barry Revzin Opened: 2025-01-21 Last modified: 2025-02-07

Priority: 2

View other active issues in [expected.object.assign].

View all other issues in [expected.object.assign].

View all issues with New status.

Discussion:

Currently, we specify that the copy constructor, move constructor, and destructor expected<int, int> are trivial operations. But we do not specify that the copy assignment operator or move assignment operator are. There is implementation divergence — MSVC's implementation is trivially copyable, but libstdc++'s and libc++'s are not (although, they are trivial for the purposes of calls, which is important for being able to return such a type in a register).

I'm not sure there is any reason the assignment operators should not be trivial here. We should add the same kind of remarks in both cases: that the copy assignment operator is trivial if T and E are trivially copy assignable and the move assignment operator is trivial if T and E are trivially move assignable.

[2025-02-07; Reflector poll]

Set priority to 2 after reflector poll.

"Needs to consider trivial constructors as well as assignment."

"This is an ABI break for something that shipped in C++23, NAD."

Proposed resolution:

This wording is relative to N5001.

  1. Modify 22.8.6.4 [expected.object.assign] as indicated:

    constexpr expected& operator=(const expected& rhs);
    

    -2- Effects: […]

    -3- Returns: *this.

    -4- Remarks: This operator is defined as deleted unless:

    1. (4.1) — […]

    2. (4.2) — […]

    3. (4.3) — […]

    4. (4.4) — […]

    -?- This operator is trivial if

    1. (?.1) — is_trivially_copy_assignable_v<T> is true and

    2. (?.2) — is_trivially_copy_assignable_v<E> is true.

    constexpr expected& operator=(expected&& rhs) noexcept(see below);
    

    -5- Constraints: […]

    -6- Effects: […]

    -7- Returns: *this.

    -8- Remarks: The exception specification is equivalent to:

    is_nothrow_move_assignable_v<T> && is_nothrow_move_constructible_v<T> &&
    is_nothrow_move_assignable_v<E> && is_nothrow_move_constructible_v<E>
    

    -?- This operator is trivial if

    1. (?.1) — is_trivially_move_assignable_v<T> is true and

    2. (?.2) — is_trivially_move_assignable_v<E> is true.

  2. Modify 22.8.7.4 [expected.void.assign] as indicated:

    constexpr expected& operator=(const expected& rhs);
    

    -1- Effects: […]

    -2- Returns: *this.

    -3- Remarks: This operator is defined as deleted unless is_copy_assignable_v<E> is true and is_copy_constructible_v<E> is true.

    -?- This operator is trivial if is_trivially_copy_assignable_v<E> is true.

    constexpr expected& operator=(expected&& rhs) noexcept(see below);
    

    -4- Constraints: […]

    -5- Effects: […]

    -6- Returns: *this.

    -7- Remarks: The exception specification is equivalent to is_nothrow_move_constructible_v<E> && is_nothrow_move_assignable_v<E>.

    -?- This operator is trivial if is_trivially_move_assignable_v<E> is true.