C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Specify how `std::nullopt_t` is constructed

From: Brian Bi <bbi5291_at_[hidden]>
Date: Fri, 15 Dec 2023 12:40:13 -0500
On Fri, Dec 15, 2023 at 11:38 AM Jason McKesson via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> On Fri, Dec 15, 2023 at 11:36 AM Jason McKesson <jmckesson_at_[hidden]>
> wrote:
> >
> > On Fri, Dec 15, 2023 at 1:11 AM Brian Bi <bbi5291_at_[hidden]> wrote:
> > >
> > >
> > >
> > > On Thu, Dec 14, 2023, 9:39 PM Jason McKesson via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
> > >>
> > >> On Thu, Dec 14, 2023 at 8:03 PM Brian Bi via Std-Proposals
> > >> <std-proposals_at_[hidden]> wrote:
> > >> >
> > >> > Under the current specification of `std::nullopt_t`, it is
> unspecified whether certain attempts to construct `std::optional<T>` from
> nested braced lists are well-formed, for example:
> > >> >
> > >> > #include <optional>
> > >> > struct S1 {
> > >> > S1() = default;
> > >> > };
> > >> > struct S2 {
> > >> > S2(S1) {}
> > >> > };
> > >> > std::optional<S2> os{{{}}};
> > >> >
> > >> > With libstdc++, this actually selects the `std::nullopt_t` overload
> of the `std::optional<S2>` constructor, which is then ill-formed because
> the constructor of `std::nullopt_t` is explicit.
> > >>
> > >> That a bug in libstdc++. Clang and MSVC don't have a problem:
> > >> https://gcc.godbolt.org/z/Ta1PGzhhT
> > >>
> > >> The C++20 standard states that "Type nullopt_t shall not have a
> > >> default constructor or an initializer-list constructor, and shall not
> > >> be an aggregate." That means that no amount of curly braces should be
> > >> able to create one. It's likely that they didn't quite define
> > >> `nullopt_t` correctly; pre-C++20 aggregate changes, you have to do
> > >> quite a lot of defensive coding to meet those requirements.
> > >
> > >
> > > List-initialization can call a non-initializer-list constructor, so
> nothing in the wording implies that libstdc++ is non-conforming. If you
> look at libstdc++'s definition of `std::nullopt_t` you'll see why it
> behaves the way it does.
> >
> > Yeah, and it's buggy.
> >
> > According to [dcl.init.list/3], list-initialization syntax with an
> > empty list can do exactly and only the following:
> >
> > 1. Call a default constructor.
> > 2. Call an initializer-list constructor with an empty list.
> > 3. Invoke aggregate initialization with an empty sequence of elements.
> >
> > I found this site with some version of libstdc++'s implementation:
> >
> https://gcc.gnu.org/onlinedocs/gcc-5.4.0/libstdc++/api/a01424_source.html
> >
> > That implementation is:
> >
> > ```
> > struct nullopt_t
> > {
> > // Do not user-declare default constructor at all for
> > // optional_value = {} syntax to work.
> > // nullopt_t() = delete;
> >
> > // Used for constructing nullopt.
> > enum class _Construct { _Token };
> >
> > // Must be constexpr for nullopt_t to be literal.
> > explicit constexpr nullopt_t(_Construct) { }
> > };
> > ```
> >
> > The standard explicitly says that `nullopt_t` "shall not be an
> > aggregate". This class is an aggregate. This class definition violates
> > the standard.
> >
> > QED.
>
> Oops, I just realized that the constructor makes it not an aggregate.
>
> That being said, it's still a bad implementation, as `nullopt_t`
> shouldn't be able to be initialized from `{{}}`.
>

The interesting thing is MSVC uses the same idiom:
https://github.com/microsoft/STL/blob/main/stl/inc/optional#L32

A key issue here is whether, during overload resolution, a braced-init-list
is considered to have *no* implicit conversion sequence to a parameter type
if overload resolution selects an explicit constructor, or *an* implicit
conversion sequence that is ill-formed if used. GCC has a different opinion
from the other compilers <https://godbolt.org/z/s5c86vsxr>. This is an
unresolved core wording issue, CWG2525
<https://cplusplus.github.io/CWG/issues/2525.html>.

My opinion is that instead of waiting for CWG2525 to be resolved we should
just specify that `std::nullopt_t` has a constructor template, which
guarantees a deduction failure when the initializer is any
*braced-init-list*.


> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>


-- 
*Brian Bi*

Received on 2023-12-15 17:40:29