Почему std :: initializer_list не является встроенным языком?

96

Почему не std::initializer_listвстроен базовый язык?

Мне кажется, что это довольно важная особенность C ++ 11, но у него нет собственного зарезервированного ключевого слова (или чего-то подобного).

Вместо этого initializer_listэто просто класс шаблона из стандартной библиотеки, который имеет специальное неявное сопоставление из нового списка инициализации в фигурных скобках. {...} синтаксиса скобках, который обрабатывается компилятором.

На первый взгляд это решение довольно хакерское .

Так будут ли теперь реализовываться новые дополнения к языку C ++: неявными ролями некоторых классов шаблонов, а не базовым языком?


Пожалуйста, рассмотрите эти примеры:

   widget<int> w = {1,2,3}; //this is how we want to use a class

почему был выбран новый класс:

   widget( std::initializer_list<T> init )

вместо использования чего-то похожего на любую из этих идей:

   widget( T[] init, int length )  // (1)
   widget( T... init )             // (2)
   widget( std::vector<T> init )   // (3)
  1. классический массив, вы, вероятно, могли бы добавить constздесь и там
  2. три точки уже существуют в языке (var-args, теперь вариативные шаблоны), почему бы не использовать синтаксис повторно (и сделать его встроенным )
  3. только существующий контейнер, можно добавить constи&

Все они уже являются частью языка. Я написал всего 3 свои первые идеи, уверен, что есть много других подходов.

emesx
источник
26
Комитет по стандартам ненавидит добавлять новые ключевые слова!
Alex Chamberlain
11
Это я понимаю, но есть много возможностей, как расширить язык ( ключевое слово было всего лишь примером )
emesx
10
std::array<T>не более чем «часть языка» std::initializer_list<T>. И это далеко не единственные библиотечные компоненты, на которых основан язык. См new/ delete, type_infoразличные типы исключений, size_tи т.д.
bames53
6
@Elmes: Я бы предложил это const T(*)[N], потому что это очень похоже на то, как std::initializer_listработает.
Mooing Duck
1
Это ответ на вопрос, почему std::arrayмассив статического размера является менее желательной альтернативой.
boycy 07

Ответы:

48

Уже были примеры «основных» языковых функций, которые возвращали типы, определенные в stdпространстве имен. typeidвозвращается std::type_infoи (возможно, растягивая точку) sizeofвозвращается std::size_t.

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

Теперь для списков инициализаторов случается, что для создания объекта не требуется ключевое слово, синтаксис - контекстно-зависимые фигурные скобки. В остальном это то же самое, что и type_info. Лично я не думаю, что отсутствие ключевого слова делает его «более хакерским». Возможно, это немного более удивительно, но помните, что цель заключалась в том, чтобы разрешить тот же синтаксис в фигурных скобках, который уже был разрешен для агрегатов.

Так что да, вы, вероятно, можете ожидать большего от этого принципа дизайна в будущем:

  • если возникнут другие случаи, когда можно ввести новые функции без новых ключевых слов, комитет их рассмотрит.
  • если для новых функций требуются сложные типы, то эти типы будут помещены stdвнутрь, а не как встроенные.

Отсюда:

  • если новая функция требует сложного типа и может быть введена без новых ключевых слов, тогда вы получите то, что у вас есть здесь, а именно синтаксис "основного языка" без новых ключевых слов и который использует типы библиотеки из std.

Я думаю, что все сводится к тому, что в C ++ нет абсолютного разделения на «основной язык» и стандартные библиотеки. Это разные главы стандарта, но каждая из них ссылается на другую, и так было всегда.

В C ++ 11 есть еще один подход, заключающийся в том, что лямбда-выражения вводят объекты, которые имеют анонимные типы, сгенерированные компилятором. Поскольку у них нет имен, они вообще не находятся в пространстве имен, тем более не в std. Однако это не подходит для списков инициализаторов, потому что вы используете имя типа, когда пишете конструктор, который его принимает.

Стив Джессоп
источник
1
Мне кажется, что это деление невозможно (малины?) Из-за таких неявных ролей типов. type_infoи size_tхорошие аргументы ... ну, size_tэто просто определение типа ... так что давайте пропустим это. Разница между type_infoи initializer_listзаключается в том, что первый является результатом явного оператора, а второй - неявного действия компилятора. Мне также кажется, что это initializer_list можно было бы заменить некоторыми уже существующими контейнерами ... или еще лучше: любой, который пользователь объявляет как тип аргумента!
emesx 04
4
... или это может быть простая причина, по которой, если вы написали конструктор, vectorкоторый принимает, arrayтогда вы можете построить вектор из любого массива правильного типа, а не только из одного, созданного синтаксисом списка инициализаторов. Я не уверен, что было бы плохо конструировать контейнеры из любого array, но это не намерение комитета вводить новый синтаксис.
Стив Джессоп,
2
@Christian: Нет, std::arrayдаже конструкторов нет. std::arrayДело просто агрегатно-инициализации. Кроме того, я приветствую вас присоединиться ко мне в чате Lounge <C ++>, так как это обсуждение становится слишком длинным.
Xeo 04
3
@ChristianRau: Xeo означает, что элементы копируются при создании списка инициализаторов. Копирование списка инициализаторов не копирует содержащиеся элементы.
Mooing Duck
2
@Christian List-initialisation не подразумевает initializer_list. Это может быть много вещей, в том числе хорошая прямая инициализация оле или агрегированная инициализация. Ни один из них не включает initializer_list (а некоторые просто не могут работать таким образом).
Р. Мартиньо Фернандес
42

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

Более того, мне кажется, что определение std::initializer_list как шаблонный контейнер - довольно элегантный выбор: если бы это было ключевое слово, как бы вы получили доступ к его базовому типу? Как бы вы его повторили? Вам также понадобится множество новых операторов, и это просто заставит вас запомнить больше имен и больше ключевых слов, чтобы делать то же самое, что вы можете делать со стандартными контейнерами.

Лечение std::initializer_list контейнера как любого другого дает вам возможность написать общий код, который работает с любым из этих элементов.

ОБНОВИТЬ:

Тогда зачем вводить новый тип вместо использования комбинации существующих? (из комментариев)

Начнем с того, что все остальные контейнеры имеют методы для добавления, удаления и размещения элементов, что нежелательно для коллекции, созданной компилятором. Единственное исключение - std::array<>это массив фиксированного размера в стиле C, который, следовательно, останется единственным разумным кандидатом.

Однако, как Никол Болас правильно указует в комментариях, другой, принципиальное отличие std::initializer_listи все другие стандартные контейнеры ( в том числе std::array<>) в том , что последние из них имеет семантику значений , в то время как std::initializer_listимеет эталонную семантику . Копирование std::initializer_list, например, не будет вызывать копию элементов , которые он содержит.

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

Энди Проул
источник
4
Тогда зачем вводить новый тип вместо использования комбинации существующих?
emesx 04
3
@elmes: Вообще-то это больше похоже std::array. Но std::arrayвыделяет память при std::initializaer_listобертывании массива времени компиляции. Думайте об этом как о разнице между char s[] = "array";и char *s = "initializer_list";.
Rodrigo 04
2
А наличие нормального типа делает перегрузку, специализацию шаблона, оформление имени и тому подобное.
Rodrigo 04
2
@rodrigo: std::arrayне выделяет никакой памяти, это обычная T arr[N];, та же самая вещь, которая поддерживает std::initializer_list.
Xeo 04
6
@Xeo: T arr[N] выделяет память, возможно, не в динамической куче, а где-то еще ... То же самое std::array. Однако непустой объект не initializer_listможет быть создан пользователем, поэтому он, очевидно, не может выделить память.
Rodrigo 04
6

В этом нет ничего нового. Например, for (i : some_container)полагается на наличие определенных методов или автономных функций в some_containerклассе. C # даже больше полагается на свои библиотеки .NET. На самом деле, я думаю, что это довольно элегантное решение, потому что вы можете сделать свои классы совместимыми с некоторыми языковыми структурами, не усложняя спецификацию языка.

Призрак
источник
2
методы в классе или автономные beginи endметоды. Это немного другое ИМО.
emesx 04
3
Это? Опять же, у вас есть чисто языковая конструкция, основанная на конкретной конструкции вашего кода. Это также могло быть сделано, например, путем введения нового ключевого словаiterable class MyClass { };
Spook,
но вы можете размещать методы где угодно, реализовывать их как хотите ... Есть некоторое сходство, согласен! Этот вопрос о том, initializer_listхотя
emesx
4

В этом действительно нет ничего нового, и, как многие отмечали, такая практика была и в C ++, и, например, в C #.

Андрей Александреску упомянул об этом хороший момент: вы можете думать об этом как о части воображаемого «основного» пространства имен, тогда это будет иметь больше смысла.

Таким образом, это на самом деле что - то вроде: core::initializer_list, core::size_t, core::begin(), core::end()и так далее. Это просто досадное совпадение, что stdпространство имен имеет внутри некоторые конструкции ядра.

Артем Токмаков
источник
2

Он не только может полностью работать в стандартной библиотеке. Включение в стандартную библиотеку не означает, что компилятор не умеет хитрить.

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

Другими словами , int i {5};может быть эквивалентно int i(5);или int i=5;даже intwrapper iw {5};где intwrapperпростой оберткой класса над междунар с тривиальным конструктор принимаетinitializer_list

Поль де Вриз
источник
Есть ли у нас воспроизводимые примеры того, как компиляторы действительно разыгрывают такие «хитрые трюки»? Это как бы разумно , но я хотел бы увидеть обоснование.
underscore_d
Идея оптимизации компиляторов заключается в том, что компилятор может преобразовать код в любой эквивалентный код. C ++, в частности, полагается на оптимизацию для «свободных» абстракций. Идея замены кода из стандартной библиотеки распространена ( см . Встроенный список gcc gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html ).
Поль де Вриз, 01
Фактически, ваша идея, которая int i {5}включает в себя что- либо std::initializer_list, неверна. intне имеет конструктора std::initializer_list, поэтому 5используется непосредственно для его создания. Так что основной пример неуместен; оптимизации просто не нужно делать. Кроме того, поскольку std::initializer_listкомпилятор создает и проксирует `` воображаемый '' массив, я думаю, он может способствовать оптимизации, но это `` волшебная '' часть компилятора, поэтому она не зависит от того, может ли оптимизатор в целом сделать что-нибудь умное с красивым тупой объект, содержащий 2 итератора, результат
underscore_d
1

Он не является частью основного языка, потому что он может быть полностью реализован в библиотеке, только в строке operator newи operator delete. Какое преимущество было бы в том, чтобы сделать компиляторы более сложными для встраивания?

Пит Беккер
источник