C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Making the converting constructor of std::optional less greedy

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Fri, 8 Dec 2023 10:50:37 -0500
On Thu, Dec 7, 2023 at 12:02 PM Ville Voutilainen via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> On Thu, 7 Dec 2023 at 17:29, Edward Catmur <ecatmur_at_[hidden]> 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 <https://godbolt.org/z/rnjM5rx1q>), 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

Received on 2023-12-08 15:50:52