Почему при инициализации списка C++ также учитываются обычные конструкторы?

В C++ при использовании синтаксиса initializer_list для инициализации объекта обычные конструкторы объекта также участвуют в разрешении перегрузки, когда не применяется никакое другое правило инициализации списка. Насколько я понимаю, следующий код вызывает X::X(int)

class X { int a_; X(int a):a_(a) {} );

void foo() {
   X bar{3};
}

Но я не понимаю, почему обычные конструкторы также рассматриваются в контексте initializer_lists. Я чувствую, что многие программисты теперь пишут X{3} для вызова конструктора вместо X(3) для вызова конструктора. Мне вообще не нравится этот стиль, так как он заставляет меня думать, что у объекта нет обычного конструктора.

По какой причине синтаксис initializer_list также можно использовать для вызова обычного конструктора? Есть ли причина теперь предпочесть этот синтаксис обычным вызовам конструктора?


person Max O    schedule 28.12.2017    source источник
comment
Мне вообще не нравится этот стиль, так как он заставляет меня думать, что у объекта нет обычного конструктора - я бы не назвал это языковой проблемой.   -  person StoryTeller - Unslander Monica    schedule 28.12.2017
comment
Причина в том, что они хотели, чтобы этот синтаксис работал практически для любого типа. Вы обнаружите, что это также работает для int и массивов. Нет никакой реальной причины предпочесть этот синтаксис старому испытанному и проверенному синтаксису, если старый работает для вас.   -  person Johannes Schaub - litb    schedule 28.12.2017
comment
многие программисты сейчас пишут X{3} да, это равномерная инициализация.   -  person    schedule 28.12.2017


Ответы (2)


По сути это бардак. Для C++11 была предпринята попытка создать один единый способ инициализации объектов вместо нескольких подходов, необходимых в противном случае:

  • T v(args...); для обычного случая
  • T d = T(); при использовании конструктора по умолчанию для объектов на основе стека
  • T m((iterator(x)), iterator()); для борьбы с самым неприятным анализом (обратите внимание на дополнительные скобки вокруг первого параметра)
  • T a = { /* some structured values */ }; для агрегатной инициализации

Вместо этого был изобретен унифицированный синтаксис инициализации:

T u{ /* whatever */ };

Намерение состояло в том, что везде будет использоваться единый синтаксис инициализации, а старый стиль выйдет из моды. Все было хорошо, кроме того, что сторонники инициализации из std::initializer_list<S> поняли, что синтаксис будет примерно таким:

std::vector<int> vt({ 1, 2, 3 });
std::vector<int> vu{{ 1, 2, 3 }};

Это было сочтено неприемлемым, и единый синтаксис инициализации был непоправимо скомпрометирован, чтобы обеспечить намного лучшее

std::vector<int> vx{ 1, 2, 3 };

Проблема с этой смесью в том, что теперь иногда неясно, что на самом деле имеется в виду, и единый синтаксис инициализации больше не является единым. Это по-прежнему необходимо в некоторых контекстах (особенно для инициализации объектов на основе стека в универсальном коде), но это не правильный выбор во всех случаях. Например, следующие два обозначения должны были означать одно и то же, но это не так:

std::vector<int> v0(1, 2); // one element with value 2
std::vector<int> v1{1, 2}; // two elements: 1 and 2

Вкратце: список инициализаторов и унифицированный синтаксис инициализации — это две отдельные записи. К сожалению, они конфликтуют.

person Dietmar Kühl    schedule 28.12.2017
comment
vu{{ 1, 2, 3 }}; неприемлемо для c'tor, принимающего std::initializer_list, и в то же время совершенно нормально для агрегата, подобного std::array. Расскажите об исторических ошибках. - person StoryTeller - Unslander Monica; 28.12.2017
comment
Читая это, мне становится грустно. Тем более что {1, 2} не имеет типа, а в auto l = {1, 2}; l есть std::initializer_list<int>. Заставляет меня задуматься, какую проблему решал std::initializer_list - person Passer By; 12.03.2018
comment
Я действительно ненавижу это. Если бы они только сделали что-то вроде требования двойных скобок для списков инициализации, все это было бы намного чище. - person CoffeeTableEspresso; 21.02.2020

Мне вообще не нравится этот стиль, так как он заставляет меня думать, что у объекта нет обычного конструктора.

Если это агрегатный тип, выполняется агрегатная инициализация. В противном случае он рассматривает конструкторы. Если это заставляет вас думать, что класс является агрегатом, это не проблема языка.

Есть ли причина теперь предпочесть этот синтаксис обычным вызовам конструктора?

Если вы сторонник единообразной инициализации, то да. Если нет, то можно придерживаться старого стиля. Обратите внимание, что в другом ответе говорится о std::initializer_list, но это вообще не относится к вашему вопросу, поскольку у вас нет конструктора, который принимает std::initializer_list. Список инициализации в фигурных скобках и std::initializer_list — это разные понятия. Лучше не путать их так рано.

person user9148702    schedule 28.12.2017