4

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?

Community
  • 1
  • 1
RobClucas
  • 815
  • 7
  • 16

1 Answers1

11

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

  1. 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
  2. If the object is an aggregate type, the object is aggregate initialized
  3. If the list is empty, and the object has a default constructor, the object is value-initialized (ends up calling default constructor)
  4. 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.

Community
  • 1
  • 1
Passer By
  • 19,325
  • 6
  • 49
  • 96
  • Okay, so that's what I thought, but by deleting the constructors, all the implicitly declared constructors should be deleted. There should therefore not be a constructor which can be called for the aggregate initialization, and I should still get a compiler error. So why can I still construct the class? Edited question. – RobClucas Apr 29 '17 at 10:51
  • @RobClucas Here you go – Passer By Apr 29 '17 at 16:06
  • Thanks! So to achieve what I intended the pre c++11 disabling of construction by making the constructor private, disables both construction and aggregate initialization, for both aggregate and non-aggregate classes. – RobClucas Apr 29 '17 at 16:24
  • @RobClucas Having private or protected **data** members, not member **functions** turns it into non-aggregate – Passer By Apr 29 '17 at 16:26
  • No, from your own answer, you state (which cppreference agrees with) that for a class to be aggregate it must have "no user-provided, explicit, or inherited constructors" so by putting an empty private constructor i.e ``template Example(Ts&&...) {}`` then I am providing a user-provided constructor, and hence it is not an aggregate class. – RobClucas Apr 29 '17 at 16:32
  • Oh I misunderstood what you said. Yes private constructors disables construction. It is somewhat less understandable than deleted constructors though – Passer By Apr 29 '17 at 16:34
  • Yes, I would prefer to use ``delete``, but since my class is aggregate, I don't have much choice! – RobClucas Apr 29 '17 at 16:36
  • @RobClucas actually you can have an explicit deleted constructor – Passer By Apr 29 '17 at 16:43
  • Go and try that, I added it and ``Example e{}`` still succeeds. – RobClucas Apr 29 '17 at 17:05
  • @RobClucas Take a look at [this](https://godbolt.org/g/jMda7C). I highly suspect that compilers are not entirely conformant in this respect. btw the interpretation that deleted explicit constructors will turn the class into non-aggregate is totally mine. – Passer By Apr 29 '17 at 17:07
  • It seems that the use of explicit delete is only a compiler error in clang >= 4 and gcc >= 6.1 (by the way ``delete`` is c++11, and you didn't have the ``-std=c++11`` flags in the example), before that the use of explicit doesn't result in an error. So yes, with more recent compiler versions it's probably better to use delete and explicit default constructor, but if you need to support (only slightly) older compiler versions, the making it private is the better option. – RobClucas Apr 30 '17 at 10:16