On Fri, Dec 15, 2023 at 11:38 AM Jason McKesson via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Fri, Dec 15, 2023 at 11:36 AM Jason McKesson <jmckesson@gmail.com> wrote:
>
> On Fri, Dec 15, 2023 at 1:11 AM Brian Bi <bbi5291@gmail.com> wrote:
> >
> >
> >
> > On Thu, Dec 14, 2023, 9:39 PM Jason McKesson via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
> >>
> >> On Thu, Dec 14, 2023 at 8:03 PM Brian Bi via Std-Proposals
> >> <std-proposals@lists.isocpp.org> 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. This is an unresolved core wording issue, CWG2525.

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@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals


--
Brian Bi