C++ Logo

std-proposals

Advanced search

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

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Fri, 15 Dec 2023 11:38:47 -0500
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 `{{}}`.

Received on 2023-12-15 16:38:54