Search code examples
c++c++23std-expected

Why does std::expected<T, E> require T to be copy-constructible if it is copy-assignable?


If we have a type T that is not copy-constructible but copy-assignable:

struct T {
    T() = default;
    ~T() = default;
    T(const T&) = delete;
    T(T&&) = default;
    T& operator=(const T&) = default;
    T& operator=(T&&) = default;
};

then std::is_copy_assignable_v<T> is obviously true, but std::is_copy_assignable_v<std::expected<T, int>> is false.

This behavior is described on cppreference: std::expected<T,E>::operator=

What is the rationale behind this? Why couldn't we allow std::expected<T, E> to be copy-assignable if T is copy-assignable, even if it is not copy-constructible? The same question also applies to move assignability.


Solution

  • The result of the assignment depends on whether this->has_value() is true. We can assign an expected holding a T to an expected holding an E. In the case of holding an error, there is no "target T" to assign to, and we must copy construct to obtain a value.

    Simple illustration:

    std::expected<char, int> e1 = std::unexpected(2),
                             e2 = {'1'};
    
    e1 = e2; // This copy initializes a char in e1 from the char in e2
    

    Since this is all determined at runtime, both conditions must hold for the member to be well-defined on all code paths.