On Thu, Dec 7, 2023 at 12:02 PM Ville Voutilainen via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Thu, 7 Dec 2023 at 17:29, Edward Catmur <ecatmur@googlemail.com> wrote:
>> It was an intentional design decision to give the T in optional<T> a
>> chance to convert directly
>> from any U, including U=optional<Z>, if it can, instead of wrapping
>> into an optional<Z> first
>> and then converting T from Z.
>>
>> You may think that makes optional not play nice with templated
>> conversion operators, but
>> if we make the change you suggest, then someone will come in and say
>> that optional doesn't
>> play nice with templated conversion constructors.
>
>
> We're not talking about U -> optional<Z> -> optional<T> though

Nobody said we are. optional<Z> is the U incoming to the optional(U&&)
constructor. That's the constructor chosen if T
constructs from optional, and that's the constructor chosen if T just
converts from U in general and U isn't an optional.
For both of those cases, it's intentional that T gets the chance to
construct from the incoming U, like the Proxy mentioned.
It's intentional that it optional doesn't try to convert the Proxy to
an optional<Whatever> as a preference overy just
converting the Proxy to U.

> we're talking about U -> optional<T> directly. Intuitively it seems that that should be preferred over U -> T -> optional<T>.

optional<T> converts like a T, as far as possible. Because optional<T>
behaves like a T, as far as possible.

Yes, but surely optional<T> should behave first and foremost like an optional<T>!
https://godbolt.org/z/qTa9cMn4b

struct MaybeInt {
bool has_value_ = false;
int value_ = 0;
operator int() const {
if (!has_value_) throw "oops";
return value_;
}
operator std::optional<int>() const {
if (!has_value_) return std::nullopt;
return value_;
}
};

MaybeInt m2 = {false, 0};
std::optional<int> o2 = m2; 

currently throws an exception, rather than simply calling the conversion function `MaybeInt::optional<int>`. That's pretty weird — and I'm sure one can contrive even weirder scenarios by changing `int` to a more promiscuous type such as `void*` or `std::any`.

But this issue is not unique to C++17 `optional`. It seems to affect several modern types that possess converting constructor templates.
It certainly affects C++17 `variant`:
https://godbolt.org/z/neshfE4PT
and C++23 `expected`:
https://godbolt.org/z/eeWx9q7Ma

The issue does not affect type-erasure types like `function` and `any` (Godbolt), because they don't have a single "privileged" type T to try the conversion to.
Does it affect any other standard-library type(s)?
I think this deserves a paper, but the author should make sure they treat the whole library consistently.

–Arthur