Reputation: 1990
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
.
Upvotes: 3
Views: 1157
Reputation: 304122
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:
— IfT
is an aggregate class and [...]
— Otherwise, ifT
is a character array and [...]
— Otherwise, ifT
is an aggregate, [...]
— Otherwise, if the initializer list has no elements andT
is a class type with a default constructor, the object is value-initialized.
— Otherwise, ifT
is a specialization ofstd::initializer_list<E>
, [...]
— Otherwise, ifT
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 typeE
and [...]
— Otherwise, ifT
is a reference type, [...]
— Otherwise, ifT
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).
Upvotes: 1
Reputation: 109289
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 callsA::A()
again
This behaves the same as the first case, except the default constructor is explicitly default
ed instead of delete
d, 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.
Upvotes: 2
Reputation: 1360
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
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.
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!
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
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...
Upvotes: 2