Почему 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 является агрегатным типом, выполняется агрегатная инициализация.

В противном случае, если список инициализации в фигурных скобках пуст и T является типом класса с конструктором по умолчанию, выполняется инициализация значения.

[...]

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

Где ошибка?


person precarious    schedule 06.11.2014    source источник
comment
Ошибка в cppreference.   -  person Casey    schedule 06.11.2014
comment
Исправлено cppreference для чтения Эффекты инициализации списка объекта типа T из списка-инициализации без скобок... было бы яснее?   -  person Casey    schedule 06.11.2014
comment
@ Кейси, я бы сказал нет. И сказал бы, что T({...}) не является инициализацией списка объекта типа T, вместо того, чтобы добавить расплывчатую формулировку, которая может быть неверно истолкована. Cpprefreference уже не утверждает, что это так. Но с предложенным вами исправлением будет читаться, что T({...}) — это инициализация списка объекта типа T из заключенного в скобки списка инициализации, что не так. Это инициализация списка любого параметра, который имеет выбранный конструктор.   -  person Johannes Schaub - litb    schedule 07.11.2014
comment
@Casey, в качестве альтернативы, возможно, стоит показать несколько списков инициализаторов, T({...}, {...}, ...) (а также в случае функции и везде, где имеет смысл несколько списков, как это делается с arg1, arg2 ...). Тогда, я думаю, его уже нельзя спутать с инициализацией списка объекта типа T.   -  person Johannes Schaub - litb    schedule 07.11.2014
comment
@JohannesSchaub-litb Наоборот, Cppreference делал фактически утверждал, что T({args...}) - это инициализация списка. Он был указан (каламбур) в списке-инициализации страницу в качестве альтернативы 7 ситуаций, в которых выполняется инициализация списка. Я думаю, что чтение страницы ОП было точным. правильное исправление может заключаться в полном удалении элементов 5, 6 и 7 со страницы.   -  person Casey    schedule 07.11.2014
comment
@JohannesSchaub-litb Это лучше? Другие варианты синтаксиса обсуждаются на странице разрешение перегрузки. Казалось бы, тот, кто сделал страницу инициализации списка, в первую очередь перечислял все случаи, когда в грамматике может встречаться список-инициализации в фигурных скобках, независимо от того, включают ли эти случаи на самом деле инициализацию списка.   -  person Casey    schedule 07.11.2014
comment
@Casey Кейси, хм, это, безусловно, является инициализацией списка, и он по-прежнему (IMO) правильно перечисляет его как таковой. Но не список инициализации объекта типа T. И действительно страница не утверждает, что это так. Так же, как не утверждается, что альтернатива 5 инициализирует функцию (возможно, здесь это более очевидно).   -  person Johannes Schaub - litb    schedule 07.11.2014
comment
Возможно, было бы неплохо заменить T({...}) на U({...}), чтобы избежать проблемы с текстом, относящимся к T, что здесь неверно.   -  person Johannes Schaub - litb    schedule 07.11.2014
comment
@Casey Кстати, я только что добавил комментарий на страницу разрешения перегрузки.   -  person Johannes Schaub - litb    schedule 07.11.2014
comment
@Casey и litb, я восстановил и переставил пункты списка list-initialization и сделал формулировку более конкретной о том, что и где инициализируется.   -  person Cubbi    schedule 07.11.2014
comment
@Cubbi, JohannesSchaub-litb и Кейси Спасибо всем за уточнение формулировки на cppreference.com. Теперь обращение к параметру конструктора U в случае (4) кажется неточным, поскольку Foo({}) вызовет конструктор по умолчанию, если Foo(int) будет удален из приведенного выше примера.   -  person precarious    schedule 07.11.2014
comment
@Cubbi намного лучше. Но случай 4 — это вызов функции конструктора, и copy list инициализирует параметр. Прямая инициализация объекта U осуществляется не по списку.   -  person Johannes Schaub - litb    schedule 07.11.2014
comment
@precarious это все еще точно. если конструктор int удален, вы вызовете конструктор копирования/перемещения.   -  person Johannes Schaub - litb    schedule 07.11.2014
comment
@litb справа, перемещен (4) в блок copy-list-init.   -  person Cubbi    schedule 07.11.2014
comment
@JohannesSchaub-litb Спасибо за разъяснения! :-)   -  person precarious    schedule 07.11.2014
comment
@JohannesSchaub-litb Как насчет точности в отношении более чем одного аргумента? Кажется, что Foo({2, 3}) вызывает Foo(int, int).   -  person precarious    schedule 07.11.2014
comment
@JohannesSchaub-litb Я вижу это сейчас, неважно. :-)   -  person precarious    schedule 07.11.2014
comment
@Cubbi Это намного лучше - спасибо за это исправление и за все усилия, которые вы вложили в cppreference.   -  person Casey    schedule 07.11.2014
comment
@Casey, кстати, было бы полезно, если бы ссылка на этот пост SO сопровождала первые правки. Откуда возникла путаница, было непонятно.   -  person Cubbi    schedule 07.11.2014


Ответы (1)


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

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

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

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

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

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

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

Что явно приводит к тому же результату, что и в приведенной выше цитате: конструктор вызывается с (пустым) списком инициализации в фигурных скобках.

person Columbo    schedule 06.11.2014
comment
+1 Чтобы уточнить для ОП, в данном случае код эквивалентен auto g = Foo(int{}); и int{} == 0. - person cdhowie; 06.11.2014
comment
говоря о нестандартных подходах, в Visual Studio / msvc, когда список содержит 1 литерал, который можно интерпретировать как интегральный тип, например. {42}, это не список, каким он должен быть по стандарту, а настоящее целое число с точки зрения компилятора - msvc; пример auto a = {1}, a это целое число, если вы используете msvc . - person user2485710; 06.11.2014
comment
@ user2485710: Вы уверены, что это нестандартно? Хотя я не обращал особого внимания, я действительно читал, что auto a = {1} предполагается для вывода int как типа. - person ; 07.11.2014
comment
@Hurkyl ожидаемый результат в этом конкретном случае является ошибкой, потому что {} на самом деле не имеет типа, поэтому в соответствии со стандартом компилятор должен видеть auto как запрос на вывод типа, a как метку, = как присваивание/ скопировать ctor, {1} как что-то без типа с литералом внутри; поэтому компилятор не должен быть в состоянии вывести какой-либо тип для a и поэтому он должен генерировать ошибку вместо экземпляра целочисленного типа. Microsoft добавила это исключение в правило, и я не думаю, что это исключение улучшит использование {}. - person user2485710; 07.11.2014
comment
@ user2485710 Списки инициализаторов не имеют типа, потому что они не являются выражениями. Но auto a = {1} имеет правильный формат и не должен вызывать ошибки. Тип должен выводиться как std::initializer_list‹int› (частный случай, о котором много спорят). Если Microsoft выводит int, это ошибка. Но вы не должны ожидать ошибки. - person gigabytes; 07.11.2014
comment
Post-N3922, auto a = {1}; должен выводить std::initializer_list, auto a{1}; должно выводить int, а auto a{1,2}; должно быть неправильно сформировано. - person T.C.; 07.11.2014
comment
@Т.С. и auto a{(1,2)} будет int. :п - person Yakk - Adam Nevraumont; 07.11.2014
comment
Примечание. Аналогичная ловушка возникает с оператором присваивания; a = {}; предпочтет operator=(int) копированию-назначению временного Foo() - person M.M; 18.03.2016
comment
Итак, если список параметров ({}) интерпретируется как список с одним аргументом, то вопрос переходит к последней инициализации в примере OP: Foo({2, 3}). Согласно OP, он вызывает конструктор Foo::Foo(int x, int y). Почему? - person AnT; 18.03.2016
comment
О, понял. Концептуально он интерпретируется как Foo(Foo{2 , 3}), означающий, что конструктор копирования является конструктором с одним аргументом, который в этом случае выигрывает при разрешении перегрузки. - person AnT; 18.03.2016
comment
@AnT На самом деле конструктор перемещения. Вы можете заметить, что с -fno-elide-constructors. - person Columbo; 18.03.2016