Почему Foo ({}) вызывает Foo (0) вместо Foo()?

Исполняемые файлы, созданные clang 3.5.0 и gcc 4.9.1 из кода

#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)
}

вести себя как комментарий.

Из cppreference: инициализация cpp/language/list:

[...]

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

[...]

Эффекты инициализации списка объекта типа T:

Если T является агрегированным типом, выполняется агрегатная инициализация.

В противном случае, если список braced-init пуст и T - это тип класса с конструктором по умолчанию, выполняется инициализация значения.

[...]

Я пришел к выводу, что Foo({}) должен вызывать конструктор по умолчанию.

Где ошибка?

Ответы

Ответ 1

Конструктор по умолчанию применим только в том случае, если вы используете одну пару обеих фигур:

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

Foo({}) вместо этого будет вызывать только конструкторы с пустым списком в качестве аргумента, а именно - list-list - инициализирует параметр любого конструктора. [Dcl.init]/16:

Если тип назначения является (возможно, cv-qualit) типом класса:
- If инициализация - это прямая инициализация [...]. Соответствующие конструкторы перечислены (13.3.1.3), а лучший выбирается путем перегрузки резолюции (13.3). Выбранный таким образом конструктор вызывается инициализировать объект, с помощью выражения инициализатора или выражение-список как его аргумент. Если конструктор не применяется, или разрешение перегрузки неоднозначно, инициализация плохо сформирован.

У вас есть один аргумент: пустой бит-init-list. Существует последовательность инициализации списка, преобразующая {} в int, поэтому конструктор Foo(int) выбирается с помощью разрешения перегрузки. Параметр инициализируется равным нулю, поскольку {} подразумевает value-intialization, который для скаляров подразумевает нулевой инициализации.

В документации cppreferences также нет ошибки: для (7) указано, что

7) в функциональном выражении или другой прямой инициализации, с braced-init-list, используемым как аргумент конструктора

Это явно приводит к такому же результату, что и к приведенной выше цитате: конструктор вызывается с (пустым) скобками-init-list.