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