Why is nullopt_t
required to be DefaultConstructible
in the first place?
The spec requirement that nullopt_t
shall not be DefaultConstructible
is arguably, in retrospect, a mistake based on some LWG and CWG confusion around tag types, and the resolution of this confusion which came only after std::optional
was brought in from the Library Fundamentals TS Components.
First of all, the current (C++17, C++20) spec of nullopt_t
, [optional.nullopt]/2, requires [emphasis mine]:
Type nullopt_t
shall not have a default constructor or an initializer-list constructor, and shall not be an aggregate.
and its main use is described in the previous section, [optional.nullopt]/1:
[...] In particular, optional<T>
has a constructor with nullopt_t
as a single argument; this indicates that an optional object not containing a value shall be constructed.
Now, P0032R3 (Homogeneous interface for variant
, any
and optional
), one of the papers which was part of introducing std::optional
, has a discussion around nullopt_t
, tag types in general, and the DefaultConstructible
requirement [emphasis mine]:
No default constructible
While adapting optional<T>
to the new in_place_t
type we found
that we cannot anymore use in_place_t{}
. The authors don't consider
this a big limitation as the user can use in_place
instead. It needs
to be noted that this is in line with the behavior of nullopt_t
as
nullopt_t{}
fails as no default constructible. However nullptr_t{}
seems to be well formed.
Not assignable from {}
After a deeper analysis we found also that the old in_place_t
supported in_place_t t = {};
. The authors don't consider this a big limitation as we don't expect that a lot of users could use this and the user can use
in_place
instead.
in_place_t t;
t = in_place;
It needs to be noted that this is in line with the behavior of
nullopt_t
as the following compile fails.
nullopt_t t = {}; // compile fails
However nullptr_t
seems to be support it.
nullptr_t t = {}; // compile pass
To re-enforce this design, there is an pending issue 2510-Tag types should not be DefaultConstructible
Core issue 2510.
And indeed, the initial proposed resolution of LWG Core Issue 2510 was to require all tag types to not be DefaultConstructible
[emphasis mine]:
(LWG) 2510. Tag types should not be DefaultConstructible
[...]
Previous resolution [SUPERSEDED]:
[...] Add a new paragraph after 20.2 [utility]/2 (following the header synopsis):
- -?- Type
piecewise_construct_t
shall not have a default constructor. It shall be a literal type. Constant piecewise_construct
shall be initialized with an argument of literal type.
This resolution was superseded, however, as there were overlap with CWG Core Issue 1518, which was eventually resolved in a way that did not require tag types to not be DefaultConstructible
, as explicit
would suffice [emphasis mine]:
(CWG) 1518. Explicit default constructors and copy-list-initialization
[...]
Additional note, October, 2015:
It has been suggested that the resolution of issue 1630 went too far in allowing use of explicit constructors for default initialization, and that default initialization should be considered to model copy initialization instead. The resolution of this issue would provide an opportunity to adjust that.
Proposed resolution (October, 2015):
Change 12.2.2.4 [over.match.ctor] paragraph 1 as follows:
[...] For direct-initialization or default-initialization, the candidate functions are all the constructors of the class of the object being initialized. [...]
as long as explicit
also implied that the type was not an aggregate, which in turn was the final resolution of LWG Core Issue 2510 (based on the final resolution of CWG Core Issue 1518)
(LWG) 2510. Tag types should not be DefaultConstructible
[...]
Proposed resolution:
[...] In 20.2 [utility]/2, change the header synopsis:
[...]
These latter changes, however, were not brought into the proposal for std::optional
, arguably an oversight, and I would like to claim that nullopt_t
need not be required to not be DefaultConstructible
, only, like other tag types, that it should have a user-declared explicit
constructor, which bans it from a candidate for empty-braces copy-list-init both by it not being an aggregate and by the only candidate constructor being explicit
.
Which compiler is right and wrong here?
Given the LWG 2510, CWG 1518 (and other) confusion, let's focus on C++17 and beyond. In this case, GCC is arguably wrong to reject the program, whereas Clang and MSVC are correct to accept it.
Why?
Because the S& operator=(nullopt_t)
assignment operator is not viable for the assignment s = {};
, as the empty braces {}
would require either aggregate initialization or copy-list-initialization to create a nullopt_t
(temporary) object. nullopt_t
, however (by the idiomatic tag implementation: my implementation above), as per as per P0398R0 (which resolves CWG Core Issue 1518), is neither an aggregate nor does its default constructor participate in copy-list-initialization (from empty braces).
This likely falls under the following GCC bug report:
which was listed as SUSPENDED
in 2015-06-15, before the change in the resolution of CWG Core Issue 1630 ("resolution of issue 1630 went too far"). The ticket is now re-opened based on a ping from this Q&A.