From ground up
We will first clarify what it means to initialize an object and how/when/if a constructor is invoked.
The following is my laymen's interpretation of the standard, for simplicity's sake some irrelevant details have been omitted or mangled.
Initializer
An initializer is one of the following
() // parentheses
// nothing
{} // braced initializer list
= expr // assignment expression
The parentheses and braced initializer list may contain further expressions.
They are used like this, given struct S
new S() // empty parentheses
S s(1, 2) // parentheses with expression list as (1, 2)
S s // nothing
S s{} // empty braced initializer list
S s{{1}, {2}} // braced initializer list with sublists
S s = 1 // assignment
S s = {1, 2} // assignment with braced initializer list
Note that we have not yet mentioned constructors
Initialization
Initialization is performed according to what initializers are used.
new S() // value-initialize
S s(1, 2) // direct-initialize
S s // default-initialize
S s{} // list-initialize
S s{{1}, {2}} // list-initialize
S s = 1 // copy-initialize
S s = {1, 2} // list-initialize
Once initialization is performed, the object is considered initialized.
Note that, again, constructors have not been mentioned
List initialize
We will be primarily explaining what it means to list initialize something, as this is the question at hand.
When list initialization occurs, the following is considered in order
- If the object is an aggregate type and the list has a single element that is the object's type or is derived from the object's type, the object is initialized with that element
- If the object is an aggregate type, the object is aggregate initialized
- If the list is empty, and the object has a default constructor, the object is value-initialized (ends up calling default constructor)
- If the object is a class type, the constructors are considered, performing overload resolution with the elements of the list
Aggregate
An aggregate type is defined as [dcl.init.aggr]
An aggregate is an array or a class with
-- no user-provided, explicit, or inherited constructors
-- no private or protected non-static data members
-- no virtual functions, and no virtual, private, or protected base classes
Having a deleted constructor does not count towards providing a constructor.
Elements of an aggregate is defined as
The elements of an aggregate are:
-- for an array, the array elements in increasing subscript order, or
-- for a class, the direct base classes in declaration order, followed by the direct non-static data members that are not members of an anonymous union, in declaration order.
Aggregate-initialization is defined as
[...] the elements of the initializer list are taken as initializers for the elements of the aggregate, in order.
Example e{}
Following the rules above the question why Example e{}
is legal is because
the initializer is a braced initializer list
uses list initialization
since Example is an aggregate type
uses aggregate initialization
and therefore does not invoke any constructor
When you write Example e{}
, it is not default constructed. It is aggregate initialized. So, yes it is perfectly fine.
In fact, the following compiles
struct S
{
S() = delete;
S(const S&) = delete;
S(S&&) = delete;
S& operator=(const S&) = delete;
};
S s{}; //perfectly legal
Turn off construction
Make sure that Example
is not an aggregate type to stop aggregate initialization and delete its constructors.
This is often trivial as most classes have private or protected data members. As such, it is often forgotten that aggregate initialization exists in C++.
The simplest way to make a class non-aggregate would be
struct S
{
explicit S() = delete;
};
S s{}; //illegal, calls deleted default constructor
However, as of 2017 May 30, only gcc 6.1 and above and clang 4.0.0 will reject this, all versions of CL and icc will incorrectly accept this.
Other initializations
This is one of the craziest corners in C++, and it was hellish informative to look through the standard to understand what exactly happened. There have been lots of references already written and I will not attempt to explain them.