Default-constructed objects often represent an "initialized, but no value stored" state. For example, a default-constructed std::unique_ptr
does not point to anything (and in practice represents this by storing a the exceptional value nullptr
).
On the other hand, default initialization is often done by assigning {}
. This would suggest that one might want to test whether an object is default-initialized by comparing against {}
, but the following code does not compile:
struct S
{
S() = default;
bool operator== (const S&) const { return false; }
};
int main ()
{
S s = {}; // Default initialization
s = {}; // Assigning a default-initialized object
if (s == {}) // Check if 's' contains a value
;
}
The error one gets is this:
x.cc:12:12: error: expected primary-expression before ‘{’ token
12 | if (s == {}) // Check if 's' contains a value
| ^
(One might be tempted to add bool operator== (const std::initializer_list<int>());
to the class, but that too does not help.)
My questions then are:
s = {}
, the right hand side is correctly converted to a default-constructed object, but in s == {}
it is not? (Clearly, using s == S{}
works -- but that's not the question.)bool operator== (const std::initializer_list<int>());
not help?S s = {}; // Default initialization
That's not default-initialization. That's copy-list-initialization with empty initializer list. For a non-aggregate class with default constructor (as here is the case), that resolves to value-initialization, which is again a different form of initialization than default-initialization, although both will call the default constructor (in this case). Initialization rules of C++ are very complex and without using correct terminology trying to follow them is a lost cause.
s = {}; // Assigning a default-initialized object
That's not what the syntax means. The syntax just means that overload resolution for the =
operator is done with {}
as argument.
So it is (almost) equivalent to
s.operator=({});
Depending on what kind of operator=
overloads s
has, this can have any kind of meaning. For example there may only be a single overload with signature operator=(int)
, in which case this would be equivalent to s = 0;
.
However, in most cases a class has only copy/move assignment operators. For example S
has an implicit move constructor
operator=(S&&)
Overload resolution will choose this one (the copy assignment is a worse match). When initializing a S&&
reference from empty braces {}
, this is equivalent to creating a temporary object of type S
and initializing it as if by = {}
(i.e. copy-list-initialization with empty initializer list as above). The reference in the assignment operator then binds to this temporary object.
The {}
in s = {}
is a bit strange, because (in contrast to = {}
in initialization) s = {}
is an expression and normally the operands of an expression are also expressions. But {}
is not an expression. It is its own syntax construct without type and value category which expressions should have.
Braced-init-lists such as {}
are permitted as operands instead of expressions only for the right-hand side of =
and for function call arguments. In either case, they do not behave like normal expressions would in their place and require special rules to handle.
It would be possible to add similar syntax constructs for other operators so that they also do overload resolution with the braced-init-list as argument, but that was simply never added to the language. Either it wasn't proposed by anyone to the standard committee or it was proposed and didn't pass through the process to approval and inclusion in the standard.
The behavior was added with C++11 for =
and function call arguments only so that braces could be used universally to "initialize" or set the value of objects. (Although that never really turned out to work as intended.) Prior to C++11 braces could only initialize aggregate types and non-aggregate classes had to be initialized with parentheses instead.
Whether to allow braced-init-lists as more expression operands has been discussed in the contexts of introduction of these C++11 additions. Some rationale against them is referenced in this answer.