Почему тип диапазона основан на цикле над списком фигурных скобок недопустим C ++ - PullRequest
0 голосов
/ 07 ноября 2019

Это дополнительный вопрос к:

Невинный диапазон, основанный на неработающем цикле

В этом вопросе используется синтаксис:

    int a{},b{},c{},d{};

    for (auto& i : {a, b, c, d}) {
        i = 1;
    }

Это не разрешено c ++. (Чтобы сделать это работой, должен быть изобретен новый тип контейнера, который хранит указатели или ссылки внутри)

Является ли это просто побочным эффектом двух разных концепций или почему он не был разрешен?

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

Я размышляюответы могут быть примерно такими:

Это было проблематично, учитывая, как initializer_list / arrays / brace-init lists / etc. были разработаны (и «исправлены», которые либо были бы слишком сложными для авторов компиляторов, либо выполнялись бы менее оптимально в случае родов).

Это потребовало бы специального правила или могло бы быть потенциально разрешено при некотором фундаментальном изменении языка(например, до initializer_list).

Было бы двусмысленно / неясно читать.

Ответы [ 3 ]

4 голосов
/ 07 ноября 2019

Для этого потребуется специальное правило, или оно может быть потенциально разрешено с некоторым фундаментальным изменением языка (например, на initializer_list).

Изменение действительно должно быть фундаментальным. Проблема не в том, как реализовано initializer_list.

Согласно [dcl.ref] :

Не должно быть ссылок на ссылки, без массивов ссылок и без указателей на ссылки.

Это имеет смысл, поскольку ссылка в строгом смысле не является объектом. Ссылка не обязательна для хранения.

Поскольку массивы ссылок недопустимы, это несколько обходных путей:

Использование массива указателей

for (auto* i : {&a, &b, &c, &d}) {
    *i = 1;
}

Flavorsиспользуя std::reference_wrapper. Обратите внимание, что я использовал int& вместо auto&, чтобы не использовать i.get() = 1:

for (int& i : {std::ref(a), std::ref(b), std::ref(c), std::ref(d)}) {
    i = 1;
}
for (int& i : std::initializer_list<std::reference_wrapper<int>>{a,b,c,d}) {
    i = 1;
}

Если вы часто его используете, создайте помощника:

template<typename T>
using refwrap = std::initializer_list<std::reference_wrapper<T>>;

for (int& i : refwrap<int>{a,b,c,d}) {
    i = 1;
}
4 голосов
/ 07 ноября 2019

Я думаю, что наиболее важной частью о том, почему это неверно, является причина, указанная в cppreference (начиная с C ++ 14, выделено мое):

Базовоемассив - это временный массив типа const T[N], в котором каждый элемент инициализируется копированием (за исключением того, что сужающие преобразования недопустимы) из соответствующего элемента исходного списка инициализатора. Время жизни базового массива такое же, как и у любого другого временного объекта, за исключением того, что инициализация объекта initializer_list из массива продлевает время жизни массива точно так же, как привязка ссылки к временному объекту (с теми же исключениями, например, для инициализации нечлен класса). Базовый массив может быть размещен в постоянной памяти .

Так что указание на неконстантный цикл for для недопустимо. Также, как отмечено в комментарии, массив ссылок недопустим конструкция C ++

Соответствующее стандартное чтение cpp: http://eel.is/c++draft/support.initlist.access

2 голосов
/ 07 ноября 2019

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

Когда C ++ 11 ввел initializer_list, это было сделано только с одной целью: разрешить системегенерировать массив значений из фигурного списка инициализации и передавать их правильно помеченному конструктору (возможно, косвенно), чтобы объект мог инициализировать себя с этой последовательностью значений. «Правильный тег» в этом случае заключается в том, что конструктор принял только что выделенный тип std::initializer_list в качестве первого / единственного параметра. Это и есть цель initializer_list как типа.

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

std::vector<int> v = {1, 2, 3, 4, 5};

Мы хотим дать компилятору свободу сделать этот массив из 5 элементов статическим массивом в скомпилированном двоичном файле, а не массивом стека, который увеличивает размер стека этой функции. Более того, мы логически хотим думать о фигурном списке инициализации как о литерале.

И мы не разрешаем модифицировать литералы.

Ваша попытка сделать {a, b, c, d} в диапазон изменяемых ссылок, по сути, пытается взять конструкцию, которая уже существует для одной цели, и превратить ее в конструкцию для другой цели. Вы ничего не инициализируете;вы просто используете, казалось бы, удобный синтаксис, который создает итеративные списки значений. Но этот синтаксис называется «braced- init -list», и он генерирует список initializer ;эта терминология не случайна.

Если вы возьмете инструмент, предназначенный для X, и попытаетесь угнать его, сделав Y, вы, вероятно, встретите неровные края где-нибудь в будущем. Поэтому причина, по которой это не работает, заключается в том, что это не предназначено для ;это не то, для чего предназначены braced-init-lists и initializer_list s.

Вы могли бы затем сказать "если это так, почему for(auto i: {1, 2, 3, 4, 5}) вообще работает, если только braced-init-listsдля инициализации? "

Когда-то, это не ;в C ++ 11 это было бы неправильно. Только в C ++ 14 auto l = {1, 2, 3, 4}; стал легальным синтаксисом;переменной auto было разрешено автоматически выводить список фигурных скобок инициализации как initializer_list.

На основе диапазона for использует вычет auto для типа диапазона, поэтому она унаследовала эту способность. Это, естественно, заставило людей поверить, что фигурные скобки-списки инициализации - это создание диапазонов значений, а не инициализация вещей.

Короче говоря, чья-то удобная функция привела вас к мысли, что конструкция, предназначенная для инициализации объектов, являетсябыстрый способ создать массив. Этого никогда не было.


Установив, что braced-init-lists не предполагают , чтобы делать то, что вы от них хотите, что нужно, чтобы заставить их делать то, чтоВы хотите?

Ну, есть два основных способа сделать это: маленький и большой. Крупномасштабная версия должна была бы изменить работу auto i = {a, b, c, d};, чтобы она могла (на основе чего-либо) создавать изменяемый диапазон ссылок на выражения. Основанный на диапазоне for просто использовал бы существующую auto -средуктивную машину, чтобы поймать его.

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

Мелкомасштабным изменением было бы взломать основанный на диапазоне for, чтобы сделать какой-то причудливый вывод, основанный на том, является ли выражение диапазона скобочным списком инициализации или нет. Теперь, поскольку такие диапазоны и их итераторы скрыты сгенерированным компилятором кодом для основанного на диапазонах for, у вас не будет столько проблем обратной совместимости. Таким образом, вы могли бы создать правило, в котором, если ваш оператор диапазона определяет не-const ссылочную переменную, а выражение диапазона является фигурным списком инициализации, тогда у вас есть несколько различных механизмов вывода.

Самая большая проблема здесь в том, что это полный взлом. Если это полезно сделать for(auto &i : {a, b, c d}), то, вероятно, полезно иметь возможность получить такой же диапазон вне цикла for на основе диапазона. В настоящее время правила о auto выводе фигурных скобок init-списков повсеместны. Основанный на диапазоне for получает свои возможности просто потому, что использует auto вычет.

Последнее, что нужно C ++, это больше несоответствий .

Это вдвойне важно вlight of C ++ 20 добавление оператора init в диапазон- for. Эти две вещи должны быть эквивалентны:

for(auto &i : {a, b, c, d})
for(auto &&rng = {a, b, c, d}; auto &i: rng)

Но если вы измените правила, основываясь только на выражении диапазона и выражении диапазона, они не будут. rng будет выведено в соответствии с существующими правилами auto, что сделает auto &i неработоспособным. И наличие правила супер-специального случая, которое меняет поведение оператора init диапазона for, отличного от того же оператора в других местах, было бы еще более противоречивым.

Кроме того, оно неслишком сложно написать обобщенную функцию reference_range, которая принимает не const переменные ссылочные параметры (того же типа) и возвращает некоторый диапазон произвольного доступа по ним. Это будет работать везде одинаково и без проблем с совместимостью.

Так что давайте просто не будем пытаться превратить синтаксическую конструкцию, предназначенную для инициализации объектов, в общий инструмент «list of stuff».

...