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.
The result of the assignment depends on whether
this->has_value()is true. We can assign an expected holding aTto an expected holding anE. 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:
Since this is all determined at runtime, both conditions must hold for the member to be well-defined on all code paths.