Remote default constructor. Objects can still be created ... sometimes

Naive, optimistic and oh .. so a wrong look at C ++ 11 syntax syntax

I thought that since C ++ 11 custom type objects should be built using the new syntax {...} instead of the old syntax (...) (with the exception of the overloaded constructor for std::initializer_list and similar parameters (for example, std::vector : size ctor vs 1 elem init_list ctor)).

Advantages: lack of narrow implicit transformations, lack of problems with the most unpleasant analysis, consistency (?). I did not see the problems, because I thought they were the same (except for the above example).

But this is not so.

The Story of Crazy Madness

{} calls the default constructor.

... Except when:

  • the default constructor is removed and
  • other constructors are not defined.

Then it looks like this rather value initializes the object? ... Even if the object deleted the default constructor, {} can create the object. Didn't that exceed the whole purpose of the remote constructor?

... Except when:

  • the object has a remote default constructor and
  • other constructors are defined.

Then it fails with a call to deleted constructor .

... Except when:

  • the object has a remote constructor and
  • no other constructor is defined and
  • at least a non-static data element.

Then it does not work with missing field initializers.

But then you can use {value} to create the object.

Well, maybe this is the same as the first exception (the value of an init object)

... Except when:

  • the class has a remote constructor
  • and at least one data item in the class is initialized by default.

Then neither {} nor {value} can create an object.

I'm sure I missed a few. The irony is that it is called uniform initialization syntax. I say again: UNIFORM initialization syntax .

What is this madness?

Scenario A

Remote default constructor:

 struct foo { foo() = delete; }; // All bellow OK (no errors, no warnings) foo f = foo{}; foo f = {}; foo f{}; // will use only this from now on. 

Scenario b

Remote default constructor, other constructors removed

 struct foo { foo() = delete; foo(int) = delete; }; foo f{}; // OK 

Script c

Remote default constructor, other constructors defined

 struct foo { foo() = delete; foo(int) {}; }; foo f{}; // error call to deleted constructor 

Scenario D

Remote default constructor, other constructors not defined, data member

 struct foo { int a; foo() = delete; }; foo f{}; // error use of deleted function foo::foo() foo f{3}; // OK 

Scenario E

Remote default constructor, remote constructor T, data member T

 struct foo { int a; foo() = delete; foo(int) = delete; }; foo f{}; // ERROR: missing initializer foo f{3}; // OK 

Scenario f

Remote default constructor, class data initializers

 struct foo { int a = 3; foo() = delete; }; /* Fa */ foo f{}; // ERROR: use of deleted function `foo::foo()` /* Fb */ foo f{3}; // ERROR: no matching function to call `foo::foo(init list)` 

+26
c ++ list-initialization c ++ 14
Nov 29 '15 at
source share
2 answers

When viewing things in this way, it is easy to say that there is complete and complete chaos in the way the object is initialized.

The big difference comes from the foo type: if it is an aggregate type or not.

This is an aggregate if it has:

  • no user-created constructors (remote or default function is not considered to be provided by the user),
  • no private or protected non-static data members,
  • there are no elementary elements for non-static data (since C ++ 11 before (returned to) C ++ 14)
  • no base classes
  • no virtual member functions.

So:

  • in ABDE scripts: foo is a collection
  • in C scripts: foo not aggregated
  • script F:
    • in C ++ 11, this is not an aggregate.
    • in C ++ 14, this is a collection.
    • g ++ did not implement this and still considers it as a non-aggregate even in C ++ 14.
      • 4.9 does not implement this.
      • 5.2.0 does
      • 5.2.1 ubuntu not (possibly regression)

Effects of initializing a list of an object of type T:

  • ...
  • If T is an aggregate type, aggregate initialization is performed. This applies to ABDE scripts (and F in C ++ 14)
  • Otherwise, the constructors T are considered in two phases:
    • All constructors that accept std :: initializer_list ...
    • otherwise [...] all T constructors are involved in overload resolution [...] This applies to C (and F in C ++ 11)
  • ...

:

Aggregate initialization of an object of type T (ABDE scripts (F C ++ 14)):

  • Each non-static member of a class, when an appearance appears in the class definition, is initialized with a copy of the list of initializers from the corresponding section. (array reference omitted)



TL; DR

All of these rules can still seem very complex and cause a headache. I personally oversimplify this for myself (if I thereby shoot myself in the leg, then so: I think I will spend 2 days in the hospital, and not for several tens of days of headaches):

  • for aggregate, each data item is initialized from list initializer items
  • else call constructor



Didn't that exceed the whole purpose of the remote constructor?

Well, I don't know about this, but the solution is to make foo not a collection. The most common form, which does not add unnecessary costs and does not change the syntax used by the object, is to inherit it from an empty structure:

 struct dummy_t {}; struct foo : dummy_t { foo() = delete; }; foo f{}; // ERROR call to deleted constructor 

In some situations (I believe there are no non-static elements), an alternative would be to remove the destructor (this will make the object incompatible in any context):

 struct foo { ~foo() = delete; }; foo f{}; // ERROR use of deleted function `foo::~foo()` 



This answer uses information derived from:

Many thanks to @MM who helped to fix and improve this post.

+20
Nov 29 '15 at
source share

What makes you run aggregate initialization.

As you say, there are advantages and disadvantages to using list initialization. (The term "uniform initialization" is not used by the C ++ standard).

One of the drawbacks is that list initialization behaves differently for aggregates than non-aggregates. In addition, the definition of population varies slightly with each standard.




Aggregates are not created using the constructor. (Technically, they may actually be, but this is a good way to think about it). Instead, when creating an aggregate, memory is allocated, and then each element is initialized in the order that corresponds to what is in the list initializer.

Non-aggregators are created through constructors, in which case the constructor arguments are members of the list initializer.

Actually, there is a design flaw: if we have T t1; T t2{t1}; T t1; T t2{t1}; , then the goal is to perform copy-construction. However (before C ++ 14), if T is an aggregate, then the aggregate is initialized instead, and t2 first element is initialized with t1 .

This flaw was recorded in the defect report, which changed with C ++ 14, therefore, from now on, copying is checked before we proceed to aggregate initialization.




Aggregate definition from C ++ 14:

An aggregate is an array or class (section 9) without constructors provided by the user (12.1), without private or protected non-static data elements (section 11), without base classes (section 10) and without virtual functions (10.3).

In C ++ 11, the default value for a non-static element means that the class is not an aggregate; however, this has been changed for C ++ 14. User means means declared by the user, but not = default or = delete .




If you want your constructor call to never perform aggregate initialization, you should use ( ) rather than { } and avoid MVP in other ways.

+6
Nov 29 '15 at 22:41
source share



All Articles