(Consider this question for C++17 and forward)
LWG issue 3562(+), whether nullopt_t's requirement to not be DefaultConstructible could be superseded with the explicit explicitly-defaulted default-constructor of other tag types:
struct nullopt_t { explicit nullopt_t() = default; };
was closed as not a defect (NAD), with the following rationale
2021-06-14 Reflector poll:
Current wording prevents an implicit conversion sequence from
{}.
(+) Note that this issue mentions both LWG issue 2510 and CWG issues 1518 and 1630, whose resolutions (changed/superseded) were tightly coupled.
Consider the following example:
struct my_nullopt_t {
explicit constexpr my_nullopt_t() = default;
};
struct S {
constexpr S() {} // user-provided: S is not an aggregate
constexpr S(S const&) = default;
S& operator=(S const&) = default; // #1
S& operator=(my_nullopt_t) = delete; // #2
};
int main() {
S s{};
s = {}; // #3
}
which I would expect is well-formed as the overload #2 is not viable for the assignment at #3, as the default constructor of my_nullopt_t is explicit (meaning my_nullopt_t is moreover not an aggregate in C++17). I particularly thought this was governed by [over.match.ctor]/1:
When objects of class type are direct-initialized, copy-initialized from an expression of the same or a derived class type ([dcl.init]), or default-initialized, overload resolution selects the constructor. For direct-initialization or default-initialization that is not in the context of copy-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization (including default initialization in the context of copy-initialization), the candidate functions are all the converting constructors ([class.conv.ctor]) of that class. [...]
[class.conv.ctor]/1 A constructor that is not explicit ([dcl.fct.spec]) specifies a conversion from the types of its parameters (if any) to the type of its class. Such a constructor is called a converting constructor.
But given the reflector's comment above I'm probably mistaken. Is possibly [over.match.list]/1
In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed. [ Note: This differs from other situations ([over.match.ctor], [over.match.copy]), where only converting constructors are considered for copy-initialization. This restriction only applies if this initialization is part of the final result of overload resolution. — end note ]
governing this case via entry from [over.ics.list]/7?
We may finally note that we also see compiler variance here, where Clang accepts the program whereas GCC rejects it (for C++11 through C++20) with an ambiguity error. GCC particularly seems to consider #2 a viable overload, whereas Clang does not.
GCC error: ambiguous overload for 'operator='
Question
- What standardese passages support's the reflector's rationale above, e.g. applied to the example above?
- Is this then in support of GCC's rejection of the program above, such that Clang is wrong to accept it?
{}?{}is not an expression at all.S& operator=(my_nullopt_t)(if it is to be a viable candidate)? As I mention I'm likely missing something here, and I would be really happy for an enlightening answer :){}is (somehow viable) to construct anullopttemporary object, which is used as an argument toS& operator=(my_nullopt_t), where the parameter type is a non-reference type that would arguably require to be copy-constructed from the temporarynulloptobject. Is this not a copy-initialization context? But again, I'm obviously misunderstanding this, and would be grateful for an explanation of how there's an implicit conversion sequence from{}to an overload to a tag type with explicit default constructor.