Какие примеры жадных списков инициализаторов скрываются в стандартной библиотеке?

Начиная с C++11, контейнеры стандартной библиотеки и std::string имеют конструкторы, принимающие список инициализаторов. Этот конструктор имеет приоритет над другими конструкторами (даже, как указано @JohannesSchaub-litb в комментариях, даже игнорируя другие критерии «наилучшего соответствия»). Это приводит к нескольким хорошо известным ловушкам при преобразовании всех форм конструкторов в скобках () в их версии с фигурными скобками {}

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
#include <string>

void print(std::vector<int> const& v)
{
    std::copy(begin(v), end(v), std::ostream_iterator<int>(std::cout, ","));
    std::cout << "\n";
}

void print(std::string const& s)
{
    std::cout << s << "\n";
}

int main()
{
    // well-known 
    print(std::vector<int>{ 11, 22 });  // 11, 22, not 11 copies of 22
    print(std::vector<int>{ 11 });      // 11,     not 11 copies of 0

    // more surprising
    print(std::string{ 65, 'C' });      // AC,     not 65 copies of 'C'
}

Я не смог найти третий пример на этом сайте, и этот вопрос всплыл в чате Lounge‹C++> (в обсуждении с @rightfold, @Abyx и @JerryCoffin). Несколько удивительно то, что преобразование конструктора std::string принимает count и символ для использования {} вместо () изменяет свое значение с n копий символа на n-й символ (обычно из таблицы ASCII), за которым следует другой символ.

Это не улавливается обычным запретом фигурных скобок на сужающие преобразования, потому что 65 — это постоянное выражение, которое может быть представлено как char и сохранит свое исходное значение при обратном преобразовании в int (§8.5.4/7, маркер 4) (спасибо к @JerryCoffin).

Вопрос: есть ли в стандартной библиотеке еще примеры, в которых преобразование конструктора стиля () в стиль {} жадно соответствует конструктору списка инициализаторов?


person TemplateRex    schedule 07.11.2013    source источник
comment
Вероятно, любой контейнер будет иметь эту проблему   -  person aaronman    schedule 08.11.2013
comment
Быстрый поиск по стандарту показывает только: string, valarray, все контейнеры, min/max/minmax, регулярные выражения, некоторые случайные распределения и seed_seq.   -  person Kerrek SB    schedule 08.11.2013
comment
Извините за педантичность. При прочих равных... это не совсем то, как это работает. Конструктор списка инициализаторов выбирается, даже если он обеспечивает намного худшее совпадение (для определения худшего, которое сравнило бы два конструктора как просто две функции с элементами списка инициализации в качестве аргументов). Это работает так, что другие конструкторы просто игнорируются.   -  person Johannes Schaub - litb    schedule 08.11.2013
comment
@JohannesSchaub-litb tnx, обновлено.   -  person TemplateRex    schedule 08.11.2013
comment
Я даже не знаю, почему initializer_list<T> был добавлен в язык; очень редко вы когда-либо создавали контейнер с известным во время компиляции количеством объектов. Все, что я делаю, это вводит глупые пограничные случаи.   -  person Simple    schedule 08.11.2013
comment
Есть одно исключение: конструкторы по умолчанию имеют более высокий приоритет в списке, чем конструкторы initializer_list. Таким образом, {} вызывает конструктор по умолчанию, а не initializer_list нулевой длины. Если в вашем классе нет конструктора по умолчанию, будет использоваться конструктор initializer_list с нулевыми элементами. (Если есть несколько конструкторов initializer_list, вы получите ошибку неоднозначности.)   -  person CTMacUser    schedule 09.11.2013


Ответы (2)


Я предполагаю, что с вашими примерами для std::vector<int> и std::string вы хотели также охватить другие контейнеры, например, std::list<int>, std::deque<int> и т. д., которые, очевидно, имеют ту же проблему, что и std::vector<int>. Точно так же int не единственный тип, поскольку он также применяется к char, short, long и их версии unsigned (возможно, и к нескольким другим целочисленным типам).

Я думаю, что есть также std::valarray<T>, но я не уверен, разрешено ли T быть целочисленным типом. На самом деле, я думаю, что у них разная семантика:

std::valarray<double>(0.0, 3);
std::valarray<double>{0.0, 3};

Есть несколько других стандартных шаблонов классов C++, которые принимают std::initializer_list<T> в качестве аргумента, но я не думаю, что какой-либо из них имеет перегруженный конструктор, который использовался бы при использовании круглых скобок вместо фигурных скобок.

person Dietmar Kühl    schedule 07.11.2013
comment
@ааронман: спасибо. исправлено. Это будет выглядеть почти так, как если бы я скопировал и вставил код! Конечно, это был не тот случай, я считаю дублирование кода оскорбительным ;-) - person Dietmar Kühl; 08.11.2013
comment
о, отлично, std::valarray имеет (val, count) вместо (count, val), как и все контейнеры последовательности, еще более подверженные ошибкам - person TemplateRex; 08.11.2013
comment
@TemplateRex: Насколько я могу судить, вы делаете ошибку, когда вводите std::valarray в свой источник. - person Dietmar Kühl; 08.11.2013
comment
Re: использование valarray, некоторые люди не согласны. - person TemplateRex; 08.11.2013
comment
Этот ответ говорит просто для удовольствия ... Я был бы удивлен, если бы он действительно использовал valarray в серьезном коде, а не только в 12-строчном ответе на SO. Не путайте код на SO с реальным миром! ;-) - person Jonathan Wakely; 08.11.2013
comment
Я был бы рад использовать std::valarray, если бы их реализации были более надежными. К сожалению, мне приходится прибегать к сторонним библиотекам метапрограммирования шаблонов. Было бы гораздо лучше иметь стандартное решение, которое является пуленепробиваемым. - person Ali; 08.11.2013
comment
@DietmarKühl кстати, std::set не имеет такого конструктора, только контейнеры последовательности - person TemplateRex; 08.11.2013
comment
@JonathanWakely, независимо от внутренних достоинств valarray, существует также эффект курицы и яйца: valarray неясен, поэтому его использование может вызвать недопонимание, которое увековечивает неясность. Это напоминает мне о плохой работе битовых полей, которые никто не исправляет, потому что никто не использует их из-за их плохой работы... - person TemplateRex; 08.11.2013
comment
@TemplateRex: замените std::set<int> на std::deque<int>: я не проверял его конструкторы. Относительно std::valarray<T>: Они вошли в стандарт довольно поздно, и в тот момент, когда за первоначальную версию проголосовали, люди, настаивающие на них, исчезли. В результате их интерфейс так и не был исправлен, хотя, например, Дэвид Вандевурд пытался это сделать. Blitz++ показал, что действительно требовалось. Однако ни у кого, работавшего над стандартом, не было времени и сил на его исправление. std::valarray<T> достаточно полезен, чтобы его можно было использовать, но не то, что действительно нужно. - person Dietmar Kühl; 08.11.2013

Просто ищу вхождение initializer_list.

  • Все последовательности имеют конструкторы, подобные вектору:

    • deque
    • динамический массив
    • список_вперед
    • список
    • вектор
  • валаррай

  • основная_строка

  • Для неупорядоченных коллекций существует конструктор, который принимает целое число для определения начального количества сегментов.

    • unordered_set
    • unordered_multiset

Я думаю, что это все.

#include <unordered_set>
#include <iostream>

int main() {
    std::unordered_set<int> f (3);
    std::unordered_set<int> g {3};
    std::cout << f.size() << "/" << g.size() << std::endl; // prints 0/1.
}
person kennytm    schedule 07.11.2013
comment
спасибо, это довольно систематично. если бы вы могли расширить это до условий для типов и значений (чтобы им не мешал сужающий запрет), это сделало бы его принятым ответом. - person TemplateRex; 08.11.2013