Default construction of deleted constructor with braced initializer list
c++, c++11, delete-operator
Solution
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.
Problem
Let's say that I want to disable the construction of class, then I can do the following (as per Best style for deleting all constructors (or other function)? ): ``` // This results in Example being CopyConstructible: struct Example { Example() = delete; }; ``` or ``` struct Example { template <typename... Ts> Example(Ts&&...) = delete; }; ``` or ``` struct Example { Example(const Example&) = delete; }; ``` where the first example can still be copied if constructed (which the intention is to disable), but the second two will disable almost all methods for creating `Example`. If I default construct any of the above using an empty braced initializer list, then the instance is successfully constructed. In Meyer's Effective Modern C++ he gives the example (bottom of page 51): ``` Widget w3{}; // calls Widget ctor with no args ``` which I would thus expect to fail for the above `Example` classes i.e: ``` Example e{}; ``` should not construct since it should call the deleted default constructor. However, it does, is usable, and if defined as the first case above, is also copyable. See live demo. My question is: Is this correct, and if so, why? Also, if this is correct, how do I completely disable the destruction of the class?