15
votes

Executables produced by clang 3.5.0 and gcc 4.9.1 from the code

#include <iostream>

struct Foo
{
   Foo() { std::cout << "Foo()" << std::endl; }
   Foo(int x) { std::cout << "Foo(int = " << x << ")" << std::endl; }
   Foo(int x, int y) { std::cout << "Foo(int = " << x << ", int = " << y << ")" << std::endl; }
};

int main()                 // Output
{                          // ---------------------
   auto a = Foo();         // Foo()
   auto b = Foo(1);        // Foo(int = 1)
   auto c = Foo(2, 3);     // Foo(int = 2, int = 3)
   auto d = Foo{};         // Foo()
   auto e = Foo{1};        // Foo(int = 1)
   auto f = Foo{2, 3};     // Foo(int = 2, int = 3)
   auto g = Foo({});       // Foo(int = 0)          <<< Why?
   auto h = Foo({1});      // Foo(int = 1)
   auto i = Foo({2, 3});   // Foo(int = 2, int = 3)
}

behave as commented.

From cppreference: cpp/language/list initialization:

[...]

T( { arg1, arg2, ... } )    (7)

[...]

The effects of list initialization of an object of type T are:

If T is an aggregate type, aggregate initialization is performed.

Otherwise, If the braced-init-list is empty and T is a class type with a default constructor, value-initialization is performed.

[...]

I concluded that Foo({}) should call the default constructor.

Where's the bug?

1
The bug is in cppreference. - Casey
Fixed cppreference to read "The effects of list initialization of an object of type T from a non-parenthesized braced-init-list are..." would that have been clearer? - Casey
Perhaps it might be a good idea to replace T({...}) by U({...}) to avoid the problem with the text referring to T, which here is not right. - Johannes Schaub - litb
@Casey and litb, I restored and rearranged the list-initialization bullet points and made the wording more specific about what's getting initialized where. - Cubbi
@precarious it still is precise. if the int constructor is removed, you are going to call the copy/move constructor. - Johannes Schaub - litb

1 Answers

18
votes

The default constructor is only applicable if you use one single pair of either braces:

auto a = Foo();         // Foo()
auto b = Foo{};         // Foo()

Foo({}) instead will only call constructors with the empty list as the argument, copy-list-initializing the parameter of whatever constructor is chosen. [dcl.init]/16:

If the destination type is a (possibly cv-qualified) class type:
— If the initialization is direct-initialization […] constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.

You have one argument: The empty braced-init-list. There is a list-initialization sequence converting {} to int so the constructor Foo(int) is chosen by overload resolution. The parameter is initialized to zero as {} implies a value-intialization which, for scalars, implies a zero-initialization.

There is no bug in cppreferences documentation either: For (7) it is stated that

7) in a functional cast expression or other direct-initialization, with braced-init-list used as the constructor argument

Which clearly leads to the same result as with the above quote: The constructor is called with the (empty) braced-init-list.