Empty initializer list as argument doesn't call default constructor
Asked Answered
C

2

6

The following code

class A {
public:
    A() {} // default constructor
    A(int i) {} // second constructor
};
int main() {
    A obj({});
}

calls the second constructor. Probably the empty initializer_list is treated as one argument and is converted to int. But when you remove the second constructor from the class, it calls the default constructor. Why?

Also, I understand why A obj { {} } will always call a constructor with one argument as there we are passing one argument which is an empty initializer_list.

Comedy answered 18/3, 2018 at 17:24 Comment(15)
Please provide a Minimal, Complete, and Verifiable example.Disengagement
There's more constructors in your class than you imagineKruter
@n.caillou: Are you complaining about the missing semicolon between the class definition and the variable?Ogilvie
No, I am asking why A obj ( {} ) may call the default constructor? Please feel free to correct the syntax.Comedy
@Comedy "feel free to correct the syntax", no, you need to provide a minimal reproducible exampleClient
"The following code calls the second constructor" I'm pretty sure it does not, but instead calls an implicitly-defined copy- or move-constructor (print something in your constructors to confirm). A obj ( {} ); is equivalent to A obj ( A() );, move-constructing obj from a default-constructed temporary.Alcoholize
I don't know why, but your assertion seems to be correct, even when deleting the other potential implicitly generated constructors.I don't think I missed any of them. ideone.com/jeP0LtFeathering
okay, here is complete code that runs.#include <iostream> class A { public: A () { std::cout << "default"; } A (int i) { std::cout << "A(int i)" << i;} }; int main (void) { A obj({}); }Comedy
Ah. It calls A(int{}), where int{} is just an elaborate way to write 0.Alcoholize
If you mark the single argument constructor as explicit then Visual Studio gives this error: "copy-list-initialization of 'A' cannot use an explicit constructor"Feathering
@RetiredNinja, that too is weird as parentheses are supposed to be direct initialization and not copy-list initialization. copy list initialization is when we use A obj = 0;Comedy
This actually calls a default constructor followed by a move constructor in case all the constructors are explicitly defined.Yard
@Ron. Yes you are right.Comedy
Well, the answer below explains it but it leaves me wondering why copy/move constructor is not called in case of A obj { {} }; when there is no A (int i) {} constructor there.Comedy
@IgorTandetnik, +1.Comedy
G
5

The presence of the parentheses surrounding the braces in A obj({}); indicates the single argument constructor will be called, if possible. In this case it is possible because an empty initializer list, or braced-init-list, can be used to value initialize an int, so the single argument constructor is called with i=0.

When you remove the single argument constructor, A obj({}); can no longer call the default constructor. However, the {} can be used to default construct an A and then the copy constructor can be called to initialize obj. You can confirm this by adding A(const A&) = delete;, and the code will fail to compile.

Glassworks answered 18/3, 2018 at 17:57 Comment(4)
why it doesn't decide to call the copy constructor in case of A obj { {} }; when the constructor from int is not there?Comedy
@Comedy With A obj{{}}, you reach over.match.list/1.2. Then you check over.best.ics to find a viable constructor. 4.1 and 4.5 match your case, and that disallows the conversion from {} -> A. When the int constructor is present, over.ics.list/10.2 applies and that constructor is selected.Glassworks
Well, fair enough. Though I could go through the references you included and make some sense of the standard clauses, but you don't expect an average to fair c++ programmer to go into the standard and be able to understand it. To them you need to through some consistent way they all behave in easy to follow terms. Not sure if we are making C++ more confusing for the sake of extracting performance?Comedy
@Comedy Yes, the behavior is intuitive in this case, but it's very easy to avoid. Write A obj{}; to default initialize, and don't use nested parentheses or braces.Glassworks
D
0

It's because the {} in A obj({}); ends up being interpreted as of type int. So the code ends up being similar to A obj(0);.

Disengagement answered 18/3, 2018 at 17:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.