3

I have the following class definition. I included a private x to make sure it is not an aggregate.

class A {
 private:
  int x;

 public:
  A() = delete;
  A(std::initializer_list<int>) { printf("init\n"); }
};

Now if I initialize this object with A a = A{}, it will say A::A() is deleted. I guess it is trying to call A::A() but it is deleted. If I comment out that line, so A::A() is automatically generated. Then if I run this code, I could see that it is calling A::A(std::initializer_list<int>)!

And more confusing, if I define A() = default, the initialization calls A::A() again.

Can anyone point me to the right direction of understading this behavior? Thanks!

I'm using G++ 6.3.0 with flag -std=c++17.

Hot.PxL
  • 1,902
  • 1
  • 17
  • 30

3 Answers3

2

The behaviour is correct.

First of all:

// Calling
A a = A{}; // equals A a = A();, 

which was a convenience uniformization of the list-initialization idiom. Refer to: https://stackoverflow.com/a/9020606/3754223

Case A:

class A {
     private:
         int x;

     public:
         **A() = delete;**
         A(std::initializer_list<int>) { printf("init\n"); }
};

As said above, A a = A{} will be ... = A(), which is deleted.

Case B:

class A {
     private:
         int x;

     public:
         A(std::initializer_list<int>) { printf("init\n"); }
};

In this case, there's no default constructor provided, since you have your initializer-list constructor be defined. Consequently {} is converted to an empty initializer list implicitely!

Case C:

class A {
     private:
         int x;

     public:
         **A() = default;**
         A(std::initializer_list<int>) { printf("init\n"); }
};

In this case the empty default constructor is available and A{} is converted back to A(), causing it to be called.

Please refer to the below pages and get a thorough read into it. http://en.cppreference.com/w/cpp/utility/initializer_list http://en.cppreference.com/w/cpp/language/value_initialization

Finally:

Using:

A a = {{}};

Would cause that ALWAYS the initializer-list constructor is called, as the outer-curly-brackets denoted the init-list, and the inner a zero-constructed element. -> Non-empty initializer-list... (See stackoverflow link above again!)

And btw, I cannot understand why the question is downvoted, considering that this is a really tricky part...

Community
  • 1
  • 1
MABVT
  • 1,350
  • 10
  • 17
  • I read value initialization on cppreference before I came here with the question. But thanks to your explanation I have a new understanding. On cppreference, it says "If T is a class type that has no default constructor...". So I guess, an implicit default constructor satisfies the case that T has no default constructor. Is that what it meant? – Hot.PxL Mar 08 '17 at 05:32
  • I guess ppl downvote just because the question is too tricky for them to notice at a first glance. :( – Hot.PxL Mar 08 '17 at 05:33
  • @Hot.PxL "If T is a class type that has no default constructor but has a constructor taking std::initializer_list, list-initialization is performed." -> list-initialization. BUT: "if T is a class type with no default constructor or with a user-provided or deleted default constructor, the object is default-initialized;", see: http://stackoverflow.com/questions/33988297/deleted-default-constructor-objects-can-still-be-created-sometimes – MABVT Mar 08 '17 at 06:03
2

You have lots of cases listed, so let us go over them one by one.

A() = delete;
A(std::initializer_list<int>) { ... }

Writing A a = A{}; will indeed try to call the deleted default constructor, and your code fails to compile.

What you have above is list initialization, and because the braced-init-list is empty, value initialization will be performed. This selects the deleted default constructor, which makes your code ill-formed.


If I comment out that line, so A::A() is automatically generated

No, this is not the case. There is no implicitly declared default constructor due to the presence of the user provided constructor that takes an initializer_list. Now, A a = A{}; will call the initializer_list constructor, and in this case the initializer_list will be empty.


if I define A() = default, the initialization calls A::A() again

This behaves the same as the first case, except the default constructor is explicitly defaulted instead of deleted, so your code compiles.


Finally,

I included a private x to make sure it is not an aggregate

There's no need for this, defining a constructor makes A a non-aggregate.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Oh! You are right that there is "no implicitly declared default constructor" if I defined any constructor. My C++ knowledge must have gotten rusty :( – Hot.PxL Mar 08 '17 at 05:45
1

List initialization follows a very specific ordering, as laid out in [dcl.init.list]/3. The two relevant bullet points and their relative ordering are highlighted:

List-initialization of an object or reference of type T is defined as follows:
— If T is an aggregate class and [...]
— Otherwise, if T is a character array and [...]
— Otherwise, if T is an aggregate, [...]
Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
— Otherwise, if T is a specialization of std::initializer_list<E>, [...]
Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7).
— Otherwise, if the initializer list has a single element of type E and [...]
— Otherwise, if T is a reference type, [...]
— Otherwise, if T is an enumeration with a fixed underlying type (7.2), [...]
— Otherwise, if the initializer list has no elements, the object is value-initialized.
— Otherwise, the program is ill-formed.

An empty initializer list for class types with default constructors is a special case that precedes normal constructor resolution. Your class isn't an aggregate (since it has a user-provided constructor), so A{} will attempt to value-initialize A if A has a default constructor. It doesn't matter if that default constructor is accessible, it only matters if it exists. If it exists and is inaccessible (your first case), that's ill-formed. If it exists and is accessible (your third case), then it's invoked. Only if your class does not have a default constructor will the constructors be enumerated, in which case the A(initializer_list<int> ) constructor will be invoked with an empty initializer list (your second case).

Barry
  • 286,269
  • 29
  • 621
  • 977