Pull to refresh

Comments 115

UFO just landed and posted this here
UFO just landed and posted this here
Мне в C-шных циклах нравится их потрясающая гибкость! То что шаг цикла может быть любой, в том числе и динамически меняющийся и может менять свой знак!
А ещё можно запихнуть несколько переменных, например: for ( int i, j; i*j<200; i++, j*=-1.5)
UFO just landed and posted this here
В «негибких» никто, ну просто вообще никто не мешает делать то же самое на while циклах (+-). А цикл for, как именно цикл со счетчиком, для людей «из внешнего мира» выглядит не гибко, а дико. Технопрон.
А кто сказал, что слово «for» должно всегда обозначать именно цикл со счётчиком? В Си так решили обозначать любой цикл, в котором можно выделить, как составные части: выражение для инициализации, выражение для проверки условия продолжения цикла и выражение для перехода к следующей итерации — причём, любое из этих выражений может быть пустым.
Только проблемы в этом, по-факту, никакой нет — люди «из внешнего мира» на C/C++ всё равно не смогут писать. Пока не освоятся, но тогда они уже перестанут быть людьми «из внешнего мира».
Это жонглирование словами. «люди из внешнего мира» = «люди, пришедшие из внешнего мира». И я сказал как раз о процессе перехода. Не говоря уже о том, что человек может писать на десятке языков, включая С++. Если он — не основной, то тоже можно говорить о «внешнем мире».
А для тёти Кати из колхоза «Заветы Моисея» вообще всё программирование выглядит дико, на любом языке, ага. Если уж оценивать «технопрон», то вопрос, который надо задать — насколько часто в применении данной языковой фишки продоложают ошибаться люди с опытом, использующие данный язык на постоянной основе. А не насколько сильно конфузятся абсолютные новички, у которых в Паскале было не так.
А для тёти Кати из колхоза «Заветы Моисея» вообще всё программирование выглядит дико, на любом языке, ага.

Это глупое передергивание, потому что «тетя Катя» не разбирается в вопросе совершенно, а мы говорим о разбирающихся людях.

Если уж оценивать «технопрон», то вопрос, который надо задать — насколько часто в применении данной языковой фишки продоложают ошибаться люди с опытом, использующие данный язык на постоянной основе

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

Это глупое передергивание, потому что «тетя Катя» не разбирается в вопросе совершенно, а мы говорим о разбирающихся людях.

Вам выше пишут разбирающиеся люди, что циклы гибкие и универсальные. Тоже глупо передёргивают? Ваше настоящее имя, случайно, не д'Артаньян?

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

Это очевидный критерий, он не мой, я его не придумал, а позаимствовал, кажется, из статьи, написанной создателем языка Ruby в ответ Никлаусу Вирту. Вирт как-то решил ознакомиться с новыми веяниями в дизайне стильных-молодёжных языков, для ознакомления выбрал язык Руби и самую ебанутую книжку по языку Руби («Why's poignant guide»), по результатам чего разродился статьёй в духе «А что это вы говорите, что у Руби простой и понятный синтаксис, я вот, например, ничего не понял...» — «Простой и понятный не для „человека из внешнего мира“, а для человека, освоившего язык!» — ответил ему автор, и был совершенно прав. Вирт вот тоже разбирающийся человек, но в данном случае выступил в роли конкретной «тёти Кати».
Тоже глупо передёргивают?

Нет, это аргумент другого уровня. О том, что этот цикл необычен, но хорош. Ваш — передергивание.

Это очевидный критерий, он не мой, я его не придумал, а позаимствовал, кажется, из статьи, написанной создателем языка Ruby в ответ Никлаусу Вирту

Игра словами — «мой» или «позимствованный мной». Я и не утверждал, что вы его выдумали, когда сказал «ваш аргумент». Ваш — значит, примененный вами. А уж придуман они или заимствован — другое дело.

А что это вы говорите, что у Руби простой и понятный синтаксис, я вот, например, ничего не понял

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

ответил ему автор, и был совершенно прав

Один человек говорит «крокодил зеленый», другой ему «нет, ты неправ, он длинный». И вы говорите, что второй совершенно прав. Ну да, крокодил-то длинный, фиг оспоришь. Только разговор-то был о цвете. Вот и я говорил о сравнении с другими языками, об обучении. Бессмысленно оговаривать, что синтаксис понятен тому, кто его понял («синтаксис может освоить тот, кто освоил синтаксис»).
Обсуждать понятность можно только в контексте обучения, когда человек чего-то не понимал, а потом начинает понимать, и процесс понимания происходит легче или труднее. Вы не можете сказать, что для человека, который понимает синтаксис (обучен), синтаксис является непонятным.
Обсуждать понятность можно в разных контекстах. Например, лежит в колыбельке младенец с соской и гугукает. Показываем ему цикл на Паскале, показываем цикл на C++ с чудовищными итераторами, да хоть монаду на Хаскеле — он на всё одинаково гугукает.

Или вот переходит программист на С с Паскаля. Смотрит на сишный цикл, первая мысль, конечно, «чозанах?» Ему достаточно понять, что в С выражение для цикла более общее, не только инкремент/декремент на единицу, а, как выше написали «инициализация, проверка, переход». Всё. Ментальное усилие максимум на пару минут. Больше проблем с синтаксической конструкцией цикла у него никогда не возникнет (хотя, возможно, по паскалевской привычке он продолжит использовать исключительно один целочисленный счётчик с приращением в единицу). Есть другие всякие языковые конструкты, полно их, таких, что, вроде, уже который год язык используешь, а регулярно надо в справочник лезть, чтобы заново вспомнить делати работы этого извращения — вот они действительно плохо воспринимаются. Но циклы for однозначно не из их числа.

Похожие же непонятки возникнут и у программиста, переходящего с С на Паскаль: чозанах, а как сделать цикл по убыванию? DOWNTO, фигассе, технопрон. А чтобы был счётчик float и умножался на 0.1? Вообще нельзя???
Обсуждать понятность можно в разных контекстах. Например, лежит в колыбельке младенец с соской и гугукает. Показываем ему цикл на Паскале, показываем цикл на C++ с чудовищными итераторами, да хоть монаду на Хаскеле — он на всё одинаково гугукает.

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


Ему достаточно понять, что в С выражение для цикла более общее, не только инкремент/декремент на единицу, а, как выше написали «инициализация, проверка, переход». Всё.

Разумеется, здесь просто накладывается то, что подобные конструкции в других языках (как минимум, времени появления Си и такого синтаксиса) реализовывались подобием while, а также то, что for в громадном количестве случаев используется «традиционно», включая книгу КиР (хотя и с оговоркой, что это только частность).

Похожие же непонятки возникнут и у программиста, переходящего с С на Паскаль:

В принципе, да, но в таком случае, моя фраза, которая вас так припекла, может просто быть просто развернута наоборот — «цикл в Паскале для человек из мира Си — технопрон какой-то», и это будет нормально. Другое дело, что кроме Си и тех, на кого он повлиял (Java и сотоварищи) есть языки, более старые, в основном, которые в свою очередь повлияли на другие и создали другое семейство. Да тот же Паскаль, черт с ним. И то, что поклонники этих «миров» обоюдно друг друга не понимают, неудивительно. Непонятно, отчего вас так возмутила моя фраза. В существоваших на момент появления Си языках циклы for были или циклами со счетчиками, или циклами для работы с чем-то типа range, циклы с пред и постусловием были отделены. Здесь же мы имеем гибкую — бесспорно — конструкцию, при помощи которой можно реализовать циклы вида while. Читаемо ли (легко ли понимается) это? По-моему, это открытый вопрос. Да, мы можем так сделать, но здорово ли заменять специальную управляющую конструкцию, которая читается и понимается однозначно универсальной, построенной как расширение толкования цикла со счетчиком? Это сочетание «можно, но надо ли?» я и назвал технопроном, тут нет ничего обидного.

Мы можем все три базовых вида цикла — с пред, пост условиями и счетчиком выполнить на if'ах и goto. Можно, но нужно ли? В свое время структурное программирование было создано именно для того, чтобы иметь набор базовых конструкций, используемых не рефлексивно (т.е. многоцелевым образом), а по-своему, что облегчает чтение кода. Возможно, мне стоило выразиться более детально и менее экспрессивно.

DOWNTO, фигассе, технопрон. А чтобы был счётчик float и умножался на 0.1? Вообще нельзя???

Это, кстати, да, удивительно. Потому как в даже простом Бейсике можно было в те года выполнить итерации с произвольным шагом, втч дробным.
Стоит добавить, что сама идея сишного цикла for — замечательная, просто удивительно «совмещение». ПМСМ, было бы здорово, если для некоторых задач были бы свои управляющие конструкции — хочешь пройти по всем элементам коллекции — так ннннна for range — ведь сделали же. Хочешь с счетчиком — вот тебе for, хочешь с условием окончания и обязательной операцией в конце итерации, втч срабатывающую при continue — возьми какой-нибудь loop специальный и т.д.

Бритва Оккама здесь не работает — иначе бы все делали на if, goto, адресной и простой арифметике — не нужно по сути ни while, ни do while, ни switch, ни for. Тем не менее, под специфические задачи выделены специфические конструкции. Но не здесь.
UFO just landed and posted this here
Во-вторых, никаких особо катастрофических последствий это решения для программиста не несёт. Ошибок, которые можно по этому поводу совершить я могу придумать целых две: неверно выделить память для строки (забыть место под NULL) и неверно записать строку (забыть NULL). О первой ошибке уже предупреждают компиляторы, избежать второй помогает использование библиотечных функций. Всей-то беды.


1. От неверного выделения памяти компиляторы не спасают. Т.к. зачастую размер строки вычислимый, на стадии компиляции неизвестен.

2. От забыть NULL многие библиотечные функции не спасают. У них нет средств проверить максимальный размер буфера под строку.

3. Имеется третья проблема — переполнение буфера, где финальный NULL затирается. И это как раз проблема дизайна,

В Си мы не можем положится на язык и стандартные библиотечные функции при работе со строками. И дополняем стандартные средства своими проверками и вычислением границ памяти на постоянной основе.

  1. NULL как маркер конца строки — вообще не ошибка, об этом прямо написано по вашей же ссылке.
  2. O(N) при определении длины строки и затирание NULL при переполнении буфера — явно мелочи, не стоящие вашего внимания.
  3. Почему в упрек циклу for родом из C ставится истерическая типизация из C++?
  4. Чем провинились переменные индексы, чьи обозначения абсолютно адекватны, привычны и вообще пришли из математики?
  5. Почему рассматриваются исключительно варианты использования, где проблема не в наличии цикла for, а в отсутствии foreach?
  6. Нельзя было сразу спроектировать получше. Авторам требовалась портабельная замена ассемблеру на машинах семейства PDP — они с блеском решили эту задачу. Не их вина, что последователи принялись забивать новым молотком шурупы.
«Чем провинились переменные индексы, чьи обозначения абсолютно адекватны, привычны и вообще пришли из математики?»

Они пришли из Фотрана. Где имя переменной определяло её тип. a,b,c — символы; i,j,k — целые. Кто в теме, знают.

А в фортран (FORmula TRANslator) они пришли откуда, как вы думаете?

Ну, давайте ещё вспомним древних шумеров. Которые и придумали некоторые из современных символов. И что?

Этот пост для минусов. Я вообще поражаюсь деградации, постигшей хабр в последнее время. Грустное зрелище.
При чем тут деградация? ijk — стандартные математические индексы, которые были примерно всегда. Не во времена шумеров, но до появления ЭВМ. А вы говорите, что их родоначальником был Фортран, что неверно. За это поставили минусы. В чем проблема-то?
За беллетристику пять! Технически статья не актуально аж с С++11.
Ну как-то так
#include <iostream>
#include <vector>

int main(int argc, char const *argv[]) {
  std::vector<int> v{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

  for (auto it = v.begin(); it < v.end(); it += 2)
    std::cout << *it << "\n";
  return 0;
}

А что?

Ну как-то выходит, что большей части проблем, найденных автором, это решение не снимает. И соответственно, технически статья вполне актуальна.


Я думал может есть решение типа for_each с шагом и лямбдой в качестве аргумента, или может принято получать особый итератор, который пройдёт только по каждому второму элементу, а потом писать


for (auto&& value : <источник_особого_итератора>)
Ну как-то выходит, что большей части проблем, найденных автором, это решение не снимает.

Хм, даже интересно стало. А какие проблемы из названных в статье вы видите в этом решении? Давайте разберем.

Проблемы 2, 3, 4, 5, 9, 10 какими были, такими и остались.

Давайте разберем:
  • Я извиняюсь, но второй пункт откровенно высосан из пальца и вообще мало имеет отношения именно к проблемам дизайна цикла for. С таким же успехом можно написать
    while (пока_я_не_устану_смотреть_на_огонь_воду_и_как_работают_другие_люди) {}
    и жаловаться на плохой дизайн while из-за того что получилась слишком длинная строка
  • Третий из той же оперы, если вы знаете как ошибок подобного рода позволит избежать хороший дизайн операторов в языке — напишите, реально интересно.
  • С четвертым в целом согласен, else блок был бы в тему.
  • С пятым пунктом я вообще не понял в чем проблема или скорее, почему это проблема?) Во всех языках обычные циклы for имеют такую же логику происходящую из того, что они являются частным случаем цикла while. Автор предлагает сломать всем мозг?)
  • К чему вы привели девятый пункт честно говоря не понял вообще.
  • Десятый? Вы пробовали в моем примере менять местами 2 и 3 выражение в for и код был скомпилировать? Да ладно), напишите каким копилятором пробовали, это очень интересный случай.

Десятый пункт и правда не компилируется, проверил.


Что касается остальных — вы явно не считатете ничего, кроме четвёртого пункта проблемами. А в коментарии своём написали, что они технически неактуальны начиная с С++11. Считаю, что это не проблема и считаю, что эта проблема уже неактуальна, а раньше была актуальна — это же принципиально разные утверждения.

На самом деле я с самого начала считал, что проблемы 2,3,5,9 высосаны из пальца) и не брал их в расчет. И не я один так решил судя по комментариям)

Ну, если бы вы об этом написали, то детали вашего мнения были бы понятны не только вам :)

Полагая если вы начали придераться к деталям, то с основными моментами вы согласны?)

Обратите внимание, вы написали, что за беллетристику статье надо поставить пять, но технически она не актуальна, начиная с С++11. Когда выяснилось, что значительной части проблем, описанных автором, С++11 не снимает вы сказали, что эти проблемы высосаны из пальца.


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


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

Еще раз вам пишу, перечисленные выше пункты я не считаю проблемами относящимися к реализации цикла for. Как может быть проблемой цикла for невнимательность программиста? Или проблема с (длинными или короткими) названиями переменных? В беллетристики описание таких проблем наряду с техническими допустимо для красочности сюжета, в технической нет.
Поэтому говоря о С++11 я имел ввиду только то из списка, что хоть как-то относится к тиме цикла for. Теперь понятна моя точка зрения?

Ваша точка зрения стала понятна как только вы сказали, что вы считаете проблемы 2,3,5,9 высосанными из пальца :).

4ый пункт — не знаю, как в плюсах, а в Java можно цикл выбросить в отдельную функцию и сделать return из нужного места цикла.
В плюсах все так же. Единственное «но» — цикл может использовать очень много переменных из текущего контекста, передавать их будет не очень удобно.
Можно использовать лямду с захватом переменных по ссылке.
или может принято получать особый итератор

Так можно, но зачем? Это не даст ничего к читабельности кода. Только лишние затраты на создание особого range для особого итератора.

Если такой итератор с вашей точки зрения ничего не даёт к читабельности кода, то получается, что и обычный


for(auto&& item : vector) {
    cout << item;
}

ничего не даёт к читабельности по сравнению с


for (auto it = vector.begin(); it < vector.end(); it++) {
    cout << *it;
}
А теперь попробуйте выполнить ваше же условие — «пробежать по каждому второму элементу вектора», то есть сделать необычную итерацию с помощью for range цикла, который позволит пробежать по каждому второму элементу вектора? И сравним.

Ну как-то вот так это могло бы выглядеть


for(auto&& item : for_each(vector, 2)) {
    cout << item;
}

думаете это ничего не даёт к читабельности по сравнению с


for (auto it = vector.begin(); it < vector.end(); it += 2) {
    cout << *it;
}
хорошо, положим, у вас есть метод for_each, возвращающий диапазон и в качестве второго аргумента принимающий шаг. Теперь усложняем пример: надо итерироваться от второго эл-та вектора до предпоследнего.

Добавим ещё 2 аргумента в перегруженную версию метода. Или, лучше введём абстракцию среза коллекции. Как-то вот так.


for(auto&& item : vector.slice(1, -1).for_each(2)) {
    cout << item;
}
а теперь скажем надо итерироваться в обратном направлении

Ну, не будем долго размышлять, сделаем отдельный reverse_for_each


for(auto&& item : vector.slice(1, -1).reverse_for_each(2)) {
    cout << item;
}

хотя можно и так


for(auto&& item : vector.slice(1, -1).reverse().for_each(2)) {
    cout << item;
}
… и вот теперь эта концепция генерирует более сложный и многословный код чем через итераторы. А если нам в цикле нужен еще и индекс элемента…

На всякий случай приведу оба примера кода вместе ещё раз. Вы утверждаете, что


for(auto&& item : vector.slice(1, -1).reverse_for_each(2)) {
    cout << item;
}

более сложно и многословно, чем


for (auto it = vector.rbegin() + 1; it < vector.rend() - 1; it += 2) {
    cout << *it;
}

Мне кажется, что второй вариант неслабо так более читаемый и в нём сложнее сделать ошибку.


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


for (auto it = vector.begin() + 1; it < vector.end() -1; it += 2) {
    cout << *it;
}

не проще чем


for(auto&& item : vector.slice(1, -1).for_each(2)) {
    cout << item;
}

И вот по поводу того, что будет, если вам понадобится индекс элемента. Если вам понадобится индекс элемента вы же вернётесь к простому циклу for, со всеми его недостатками, так? Все плюсы от использования итераторов будут потеряны. Будет что-то типа такого наверное


for (int i = vector.size() - 1; i >= 0; i -=2) {
    cout << i << " " << vector[i];
}

А если использовать подход, который защищаю я, будет


for(auto&& item : vector.slice(1, -1).reverse_for_each_with_index(2)) {
    cout << item.index << " " << item.value;
}

В первом случае код пришлось переписать и следить, чтобы при этом не сделать новых ошибок. Во втором — только добавить индекс и всё.

Кстати с интересом бы прочитал про юсекейс где при итерации вектора может понадобиться индекс элемента)

Не готов на конкретные примеры кода ссылаться, но бывают случаи когда у нас есть несколько связанных векторов (с одинаковым размером). Итерируемся по одному из них и, в каких-то случаях, по индексу обращаемся ко второму. Ну или ищем конкретное значение, но интересен его индекс.


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


Поискал по нашей кодовой базе boost::adaptors::indexed() — 22 вхождения. И да, мне тоже кажется, что с такими итераторами работать удобнее чем с "обычным" циклом for. Хотя бы потому, что по первой строчке уже понятно как будет происходить итерация.

Возможно в таких ситуациях выгоднее было бы использовать set или map. Но согласитесь, это все таки экзотические кэйсы.
Ну а вообще я хотел услышать ответ от человека написавшего
И вот по поводу того, что будет, если вам понадобится индекс элемента. Если вам понадобится индекс элемента вы же вернётесь к простому циклу for, со всеми его недостатками, так? Все плюсы от использования итераторов будут потеряны.

И не подозревающего про:
auto index = std::distance(v.begin(), it)

С экзотичностью, пожалуй, согласен, но так получается, что цикл for по диапазону покрывает 90% случаев и остаётся как раз экзотика. (:

А какая О у distance для list? Или, скажем, map? Оно же в цикле вызывается.
Эмм, distance между элементами неотсортированного контейнера, зачем это вам?)
Ну ведь вы, как я понял, предложили использовать distance в том случае, если нужен индекс элемента. Ну пусть не для map (это действительно довольно странный случай), но для list это вполне естественное желание.
ну список в этом плане в принципе неудобен: там и distance(), и operator[], и size() выполняются за линейное время. Поэтому придется комбинировать использование итераторов и счетчика
Это было не предложение, а пример того что если сильно надо, то в цикле с итераторами индекс получить можно. А так я просто не вижу применимости этому. Выше посмотрите, обсуждали есть ли юсекейс где при итерации вектора может понадобиться индекс элемента, ну и как бы за редкими экзотическими исключениями…
А дистансе для map это вообще странная хотелка, она не дает ровным счетом ничего. Исходя из этого и то что в этот момент обсуждали тоже самое по вектору я и написал вам такой ответ.
если вы знакомы с stl вы должны понимать, что вот это:
for (auto &&item : vector.slice(1, -1).reverse_for_each_with_index(2))

не вписывается в парадигмы с++. Потому что написание миллиона member функций сделает stl нерасширяемым. Может быть что-то вроде
for (auto &item : step(slice(reverse(vector),1,-1),2))

В общем, ranges proposal примерно в том направлении и движется.

Вариант с итераторами может быть немного многословен, но он очень универсален. Вот сами гляньте, насколько незначительные изменения в коде между:
for (auto it = vector.begin(); it < vector.end(); it++) // A
for (auto it = vector.rbegin() + 1; it < vector.rend() - 1; it += 2) // B

в сравнении с
for (auto &item : vector) // A
for (auto &item : step(slice(reverse(vector),1,-1),2)) // B

И при этом не надо помнить тонну утилитарных функций.

Итераторы мощная штука, но код для тривиальных случаев получается хуже. Оставить бы итераторы и добавить методов для тривиальных случаев, но действительно в stl не впишется.


Я слышал был какой-то proposal для extension methods, мне кажется тут это было бы полезно. Можно было бы добавить методов к объектам, просто подключив библиотеку.

UFO just landed and posted this here
Потому что написание миллиона member функций сделает stl нерасширяемым.

Дело постепенно движется в сторону принятия UCS и тогда это перестанет быть проблемой.


И при этом не надо помнить тонну утилитарных функций.

Опять же, с UCS IDE будет подсказывать доступные варианты. Плюс названия у таких функций, как правило, говорящие, ну или гугл подскажет по описанию того, что сделать хочется.


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

В python можно так:
for item in vector[::-1]:
   pass

На всякий случай хочу уточнить. Это же итерация от первого до предпоследнего?

Нет. Это отрицательный шаг. В общем синтаксис такой vector[from:to:step]. Любую часть можно опустить.

А ничё что при этом у вас создастся дополнительный объект с отфильтрованным набором?
Кстати внизу есть пост в примером использования библиотеки ranges v3. Так что вы изобретаете велосипед) А я вам показал просто способ с использованием голого С++.

Дополнительный объект конечно создаётся, но он не будет хранить отфильтрованный набор, он будет уметь отдавать итераторы на этот набор, а внутри будет ссылка на вектор.


Про библиотеку видел, даже прокоментировал, что она мне нравится.

но он не будет хранить отфильтрованный набор, он будет уметь отдавать итераторы на этот набор

Пример осилите привести, или все таки признаете что мой цикл для подобных целей попроще будет?)

Пример чего вы хотите увидеть. Чем примеры использования написанные моим гипотетическим псевдокодом и пример использования библиотеки ranges v3 вам не подошли?

Понятно)
Просто я думал что мы какие-то реальные вещи обсуждаем, а не гипотетические.
И как следствие было интересно посмотреть как вы планировали в С++ реализовать генератор с состоянием, полагая что о нем идет речь в «он не будет хранить отфильтрованный набор, он будет уметь отдавать итераторы на этот набор».
Ну и как его собственно задействовать его потом в for.
Ну как говорится на нет и суда нет)

Я на всякий случай повторюсь. Чем вас не устраивают исходники негипотетической библиотеки range-v3? :)

Хотя сам ее не использую, хватает стандартных средств, но и что range-v3 не устраивает я как бы нигде не писал) Какая моя цитата навела вас на эту мысль?)

Я по моему даже указал на то что городить свои классы нет необходимости, потому что это уже реализовано для случаев если вдруг не хватает или не устраивают варианты на голом С++ (учитывая что stl уже в стандарте начиная с С++11)

Ну как же, вы захотели примеров использования.


Пример осилите привести, или все таки признаете что мой цикл для подобных целей попроще будет?)

Я ответил, что есть мой псевдокод и есть примеры с range-v3.


Вы захотели посмотреть как можно реализовать всё это не храня отфильтрованного набора данных.


И как следствие было интересно посмотреть как вы планировали в С++ реализовать генератор с состоянием, полагая что о нем идет речь в «он не будет хранить отфильтрованный набор, он будет уметь отдавать итераторы на этот набор».

Очевидно, что реализация есть в ranges-v3, поэтому я и спросил чем она вас не устраивает. Ну, то есть, почему не посмотреть, как это реализовано там?

it < v.end();

Есть ли технически разница с it != v.end()?

Есть, не все типы итераторов можно сравнивать с помощью <. Так что предпочтительным в общем случае должно быть !=.

Решение данной вводной «пробежать по каждому второму элементу вектора» без зацикливания возможно только с it < v.end().
Это, кстати, вполне себе design flaw. Если использовать контейнер с непосредственным доступом и ограничением по номеру элемента вида " < v.count()", это будет работать одинаково для любых проходов.

Мне не совсем понятня механика в данном случае: насколько я понимаю STL, .end() — особый вид итератора, который невалиден и располагается за последним элементом. Мы предполагаем здесь, что при двойной инкрементации мы можем попасть в зацикливание. Что происходит при инкрементации итератора .end()?
calling this function might result in undefined behavior because calling operator ++ for the end of a sequence is not defined

Ага, ну тогда ясно. Непонятно, почему не сделать так, что инкремент .end()'а давал бы тот же .end().
Ага, ну тогда ясно. Непонятно, почему не сделать так, что инкремент .end()'а давал бы тот же .end().

Можно код глянуть, но влом чета) Думаю просто не стали заморачиваться, итераторы на стандартных контейнерах сводятся к чистой арифметики указателей. А почему it < v.end() design flaw совсем не понял, по моему все логично — меньше последнего адреса, значит не вышло за границу range.
Можно код глянуть, но влом чета) Думаю просто не стали заморачиваться, итераторы на стандартных контейнерах сводятся к чистой арифметики указателей.

Нет, просто по стандарту, насколько я понял, итератор должен позволять делать dereference, чтобы выполнять операцию инкремента. Это понятно — если у нас, скажем, линейный список, нам нужно, чтобы элемент дереференсился, чтобы найти адрес следующего. Но .end специальный вид итератора, для которого можно было (по идее, я могу ошибаться, мне было бы интересно, почему) определить операцию инкремента как всегда выдающую сам .end, потому что нет разницы, мы шагнули за пределы контейнера на 1 элемент, или на 100 — все равно выдаем .end, как признак выхода за пределы.
А почему it < v.end() design flaw совсем не понял

Не "<" design flaw, а то, что двойной инкремент может привести к UB.
по моему все логично — меньше последнего адреса

Так .end не последний адрес, а заведомо невалидный, после последнего.
Не "<" design flaw, а то, что двойной инкремент может привести к UB.

А ну это да, здесь полностью согласен, можно было бы и прикрутить проверку выхода за границыи оставлять указатель на end.
Для таких «изотерических» вариантов и задумывалась старая версия for. А для обычного прохода по всем элементам используйте range-based for.
Вопрос просто зашибись :) интересный и не корректный слегка. порассуждаю. язык не есть доказуемая теорема, а всего лишь система символов, причём с недоказуемыми (и всегда истинными, если язык содержит арифметику, согласно теоремам Гёделя, утверждениями). касательно теории катастроф. основа теории катастроф это всё-таки моделирование (мягкое, жесткое, не суть). языки, оперирующие моделями есть (я когда-то писал про на то время сыроватую OpenModelica, есть Wolfram ещё). С другой стороны, задумайтесь, а не есть ли нейросети реализация моделей теории катастроф. я сейчас с мобильника пока пишу не могу вот прикинуть, какого класса дифуры решаются функциями нейросетей, но поведение (те же бифуркации в обывательском понимании) имеют место. а в целом у меня ощущение что императивные языки, унаследованные от машины Тьюринга, ввиду своей частичной рекурсивности (проблемы остановки), не эквивалентны моделям ни нейросетей, ни моделям теории катастроф. последние ближе к практике, но очевидно это более узкий класс алгоритмов (ибо конечны). и самый большой вопрос, это user input, собственно. слишком далеки мы даже с нечёткой логикой и другими ИИ-алгоритмами, и железной их реализацией от природных и физических процессов: наши машины символьные, и отражают физику насколько символы их отражают. да, ваннами с ДНК пытаются взламывать хэши… но это тоже символьный хак. подытожу — язык, который вам нужен — не символьный. и ради любопытства загляните на runthemodel.com
Извините, промахнулся с мобилы. Отвечал Вячеславу.

Или, с помощью библиотеки, вот так:


#include <range/v3/view.hpp>
using namespace ranges;

std::vector<...> v;

for (auto &element: view::stride(v, 2)) {
    do_smth(element);
}

И это, на мой взгляд, значительно лучше, чем предыдущий пример.

Одна проблема: это будет не каждый второй элемент, а первый и дальше уже каждый второй.

for (auto &element: v | view::drop(1) | view::stride(2)) {
    do_smth(element);
}

Вопрос — я знаю о стремлении к конструированию надёжных языков программирования, например исходя из представлений о языке как о математической доказуемой теореме а имеются ли языки, построенные исходя из математических теории хаоса и теории катастроф — т. е. не рассчитывающие на надёжность отдельных языковых элементов и пытающиеся построить из ненадёжного строительного материала — надёжные языковые конструкции-программы?

Интересная статья, люблю такие обсуждения, хоть и не могу с большей частью доводов согласиться. Основные проблемы дизайна С++ — это инклуды и препроцессор, а также использование шаблонов для метапрограммирования. Еще можно отметить то, что имя массива эквивалентно адресу первого элемента массива, что делает массивы «не-объектами» из-за экономии лишнего символа & (унифицированное взятие адреса).
А что касается цикла for, то тут интересно вот что. Существует два «фундаментальных» способа доступа к коллекциям — последовательный и произвольный. Для произвольного существует общепринятая операция — квадратные скобки (индексация массивов и позже доступ к элементам ассоциативного массива). А для последовательного доступа такой операции нет. Обычно делают какой-то интерфейс итератора с методами типа First(), Next(), IsEnd() и т.п. (причем во всех языках они называются по-разному и логика работы немного разная). А хотелось бы придумать что-то общепринятое и в виде операции, а не методов интерфейса. И вообще максимально упростить этот аспект языка.
Отсутствие модульности, которую пытались ввести в C++17 но так и не ввели.
Инклуды это древнее и крайне примитивное средство имитации модульности. Каждый инклуд полностью включается в файл перед компиляцией. Таким образом, время сборки проекта резко увеличивается по сравнению с другими языками, в которых модульность есть.
Всякие precompiled headers это по сути костыли.
Шаблоны также существуют только в инклудах, их невозможно экспортировать в бинарные библиотеки.
А кроме for each ничего больше не придумывали толкового для перебора по нескольким индексам одим оператором?
Функциональные языки конечно позволяют заменить цикл рекурсией, но обычно они смотрят уровнем выше и выкидывают цикл/рекурсию вообще. Вместо них вводят такие примитивы как map, fold, filter и т.д.
Потому что чаще всего программисту не надо обходить что-то в цикле. Ему надо преобразовать одни элементы контейнера в другие, найти агрегат, отфильтровать и т.д. Мы же не пишем каждый раз реализацию сортировки когда нам нужно отсортировать массив, правда? Так зачем мы каждый раз пишем реализацию фильтрации, например?
1. Не знаю, кто первый начал и под кого подстроился, но если значение нашего индекса-счётчика будет участвовать в операциях, где важен знак, то int как раз будет верным выбором. Иначе приведение типов.
2. Я не программист и высказываю своё личное мнение, но неужели при виде однобуквенных имён переменных, особенно i, j, k, нет первой мысли, что это счётчик цикла? Я бы назвал дурным тоном использовать эти имена где-либо, кроме цикла.
3. Это невнимательность программиста.
4. Надуманная проблема. Кто мешает объявлять переменную вне цикла, если нужно вынести значение счётчика за его пределы? О каких неоправданных затратах речь — не понимаю.
5. Что не так с порядком блоков? Объявляем переменную счётчик, задаём ей стартовое значение, проверяем с контрольным значением. Если всё хорошо — выполняем тело цикла и в конце прохода по телу цикла изменение счётчика. Не в начале следующего шага, а в конце текущего.
Это не дизайн плохой, а восприятие цикла неправильное.
6. Вот из-за неверного понимания структуры цикла и появляются такие пункты.
7. Эм… Жалко сколько-то там байт на переменную счётчик, в которую загоняем нужное контрольное значение до начала цикла? Вместо этого просим компилятор самому оптимизировать этот момент, чтобы программа на каждом шаге не занималась этими вычислениями? Оооок, мистер_это_цикл_плохой_а_я_хороший.
7. В случае смены метода, логично использовать Поиск&Замену. Я не представляю себе редактора, в котором можно писать код и нельзя делать поиск с заменой.

Кстати, я не опечатался. Да и форматирование какое-то странное — я не вижу выделения. Или у меня монитор такой, яркость и системные шрифты?
чтобы понять боль автора топика, нужно персонально написать (и отладить) ну хотя-бы пару тысяч операторов цикла…
Это же каким должен быть проект, если там столько циклов, которые отлаживает один человек?
C++ — это язык, который одну и ту же задачу позволяет решать десятком разных способов. Это вынос мозга для новичков и киллер-фича для гуру. Реплики типа «смотрите, в пайтоне даже отступы стандартизированы» в мире плюсов должны вызывать лишь снисходительную улыбку. Ровно как и статья выше.

Вы можете использовать for с индексами, если знаете зачем (ну или если не знаете больше ничего другого). А можете пользоваться range-based for, и все вопросы из статьи отпадают. А можете пользоваться std::for_each, и все вопросы также отпадают. А можете пользоваться while(it != vec.end()). А можете for(const auto& it = vec.begin(); it != vec.end(); ++it). Легко еще несколько проходов по коллекции написать, и в этом вся прелесть.

Язык развивается, и это прекрасно.
2.
. «Ну, если это счётчик в цикле, то можно просто i или j». Бр-р-р-р. Стоп! То есть давать корректные имена переменным нужно во всех случаях… кроме вот этих случаев, когда переменным по каким-то причинам понятные имена не требуются и можно написать одну непонятную букву?

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

3.
Набитая рука программиста привычно копипастит пишет = 0, а дальше требуется отладка и вспоминание кузькиной матери, чтобы исправить такое привычное = 0 на нужный вариант.

Никогда такого не было за годы программирования. Если вы не осознаете, что пишете, у вас проблемы.

4.
Это могло бы выглядеть как какой-нибудь пост-блок (вроде else для цикла while), в котором было бы доступно последнее значение счётчика итераций.

Скажите, что бы вы вернули, например, отсюда: «for (int i = 0, j = 5; i < 3 && j > 2; i++, j--)»?

5.
Проверка условия во втором блоке оператора for не выглядит логичной, поскольку на каждой итерации цикла (кроме первой) сначала будет выполняться инкремент счётчика (третий блок) затем проверка условия (второй блок)

Первый цикл — присвоение, проверка, инкремент. Последующие — проверка, инкремент. Надо просто понимать, что инкремент происходит как бы в конце блока. Так что тут просто дело вкуса.

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

Опять же, у цикла for полно применений, и простая итерация по контейнеру на данный момент является НЕстандарнтым вариантом.

7.
Да, STL со своим size() достаточно консистентен, но другие библиотеки используеют

Стоп, мы о дизайне языка или о самодеятельности программистов на нем?

8.
но неужели же надо было таким местом делать третий блок оператора for, используемого тысячами в каждом первом проекте?

К сожалению, такова плата за гибкость. Третья часть блока for — просто выражение, поэтому должно следовать синтаксису языка.

9.
Здесь мы имеем тоже классический спор «Да это самый эффективный способ организации бесконечного цикла!» с «Выглядит мерзко, while(true) значительно выразительнее». Больше холиваров богу холиваров!

Еще можно label: goto label; И что?

10.
Этот код компилируется. Некоторые компиляторы выдают warning, но никто не выдаёт ошибку.

Потому что он корректен, не?

Я понимаю, что вам не нравится, что for слишком гибок, но это весьма хороший дизайн оператора. foreach безусловно тоже нужен для тривиальной задачи итерации по контейнерам. Но в тысячах других ситуаций сложно придумать нормальную альтернативу. Написание нестандартных циклов на том же Python выливается в очень корявый код. Я уж лучше запомню синтаксис одного базового оператора.
для интов вроде как не перегрузится

Да, оказывается для примитивных типов запятая не перегружается.


Но бывают случаи, когда возможность перегрузки нужно учитывать. Недавно наткнулся на вопрос: зачем нужен void() в (foo, void(), bar) в какой-то библиотеке шаблонов. Оказывается, что согласно пункту 5.2.2/7 стандарта С++ void() никогда не является валидным аргументом функции, поэтому, согласно пункту 13.3.1.2/9 стандарта С++, в этом случае используется стандартная запятая.


http://stackoverflow.com/questions/39514765/the-void-the-comma-operator-operator-and-the-impossible-overloading


Правильный способ записать такой цикл в общем случае: for (...; ... ; i++, (void)j--)


(В процессе захотел посмотреть упомянутые пункты стандарта, посмотрел, посмотрел, и решил поверить автору ответа на слово)

Объявление i до цикла ломает стройность кода — первая секция for остаётся пустой, что заставляет читателя вдумываться в код выше, пытаясь понять то ли это ошибка, то ли так и было надо.

Что-что?
Кто мешает сделать
void abc() {
  int i;
..
..
..
  for (i = 0; i <= 10; i++){
}
}


Проверка условия находится во втором блоке, чтобы подчеркнуть тот факт, что она будет выполняться при первой итерации цикла сразу после инициализации счётчика i — только при этом объяснении всё выглядит более-менее логично

Нет, не только. Это стандартное объявление в самых разных ЯП аж с Фортрана: начало, конец и приращение.
Странный подход.
Как мне представляется, не стоит искать что-то сакральное в цикле for.
Конечно, данный инструмент, как и многое в C/C++, дает много возможностей «отстрелить себе ногу», но это обычная цена за беспрецедентную гибкость языка.
Чтобы не возникало большинства проблем, описанных в статье, достаточно не бездумно шлепать привычный шаблон цикла с индексом, а понимать, как оно устроено.
И устроено оно не просто, а очень просто.
Первая секция — инициализация. И это не обязательно присвоение начального значения индекса. Это может быть создание экземпляров классов, вызов инициализирующих функций, открытие файлов и т.п.
Вторая секция — условие продолжения, которое проверяется до цикла. Тут может проверяться совершенно любое условие, и совсем не обязательно это проверка индекса на достижение границы перебора.
Третья секция — это финализация очередной итерации цикла. Тот тоже может быть что угодно, как и в первой секции. И, опять же, совсем не обязательно это будет увеличение или уменьшение счетчика цикла.
Пожалуй, за недоработку авторам языка можно поставить только отсутствие еще одной секции — финализации всего цикла, где можно было бы выполнить соответствующие действия, закрывающие открытое и убивающие созданное в секции инициализации. Причем исключительно ради полноты картины, т.е. для уравновешивания секции иницализации со столь широкими возможностями.
Ну еще можно было бы докопаться, что финализацию итерации цикла можно было сделать после тела цикла, но в именно такой компоновке тоже есть сермяжная правда, ибо она позволяет сразу увидеть, как устроен данный цикл, абстрагировавшисть от кода в теле цикла.

Что же до переменных с «неправильными» однобуквенными именами, так это же индексы! Они же в математических выражениях, как правило, записываются именно этими самыми буквами, и вводятся именно в такой последовательности — i, j, k. Чем вам программирование так сильно отличается, что там должно быть не так?
все ошибки в циклах от невнимательности (даже в цикле от 1 до 10 у вас пункт 7 встречается 2 раза, поправьте)…
Каждый попытался как-то исправить ситуацию — а ведь можно было сразу спроектировать получше


И как, например?
UFO just landed and posted this here
вот насчет выделения «лишних» байт под размер строки автор точно погорячился: во-первых, если хранить размер, то его считать каждый раз не надо (а проверка на i < size быстрее чем проверка на str[i] != '\0'), а во-вторых, теперь STL с++ вынуждена хранить и размер, и терминирующий ноль.

Что до i — использование его в качестве счетчика было принято как отраслевая best-practice еще задолго до формирования гайдлайнов к именованию переменных. Переменные с длинными именами тоже не везде легко воспринимать.

А не смущает, что вилька бутылька пишется без мягкого знака, а сол фасол с мягким?
А в английском языке вообще полно неправильных глаголов.
И все это невозможно понять (если не лезть в историю), а нужно просто запомнить.

Мне кажется, что не стоит так неосторожно использовать слово NULL. Правильно: null-terminated (прописными буквами), NUL (как название символа ASCII), '\0'.

А NULL — это константа, введённая для обозначения нулевого указателя. Она обычно определяется как #define NULL 0, но предназначена для замены ключевого слова null, которое есть во многих других языках. Иногда её определяют как #define NULL (void *)0.

В C и C++ с этим вообще небольшая путаница. Дело в том, что литерал «0» при использовании в случае с указателями означает не числовое значение «0», а значение нулевого указателя на данной конкретной платформе. Это может быть и адрес 0xFFFFFF или ещё какой-нибудь другой. В C++11 есть ключевое слово nullptr, лучше использовать его вместо NULL.
>Возьмите напишите какое-нибудь перемножение матриц, с первого раза не перепутав нигде переменные i и j, каждая из которых в одном месте кода означает столбик, а в другом — строку матрицы.

Вы в институте переменным тоже для матриц давали осмысленные имена при письме в тетради? :)

>Если того же программиста попросить написать тот же код с помощью do\while или while — он его напишет с первого раза без ошибки.

Если не нужен цикл for, то не нужно его и использовать. Для простых циклов / других случаев как раз и есть другие циклы.

>Удобная особенность цикла for состоит в том, что переменная i создаётся в области видимости цикла и уничтожается при выходе из неё.

В PHP не уничтожается.
В PHP переменную можно объявить в любой момент.
Да и просто можно сделать return i;

5 пункт вообще бред. :)

>«Меньше». Или «меньше равно»?

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

>Здесь у нас, конечно же, любымый холивар о префиксной и постфиксной форме инкремента.

Этот холивар относится не к только к циклу.

>for (int i = 0; i++; i < vec.size())

А можно так:
for (int i = vec.size(); i--;)

>Функциональные языки предложили заменить циклы рекурсией.

Хм, а у них хватить памяти раскрутить длинный цикл? :)
Хм, а у них хватить памяти раскрутить длинный цикл? :)

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

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


  1. и согласен и не согласен. Правило — для индексов массива — только unsigned типы, считаю не совсем верной практикой:


    ptr[-50] = ...;

    легальный код, так как раскрывается в


    *(ptr - 50) = ...;

    Ведь этот самый ptr может уже быть смещён относительно реального начала блока памяти. Тут может выстрелить сам факт наличия арифметики указателей, но по природе языка — это неизбежный факт.

    Да в случае с std::vector это не совсем верно, а vector<>::size_type как по мне — так явный перебор, тем паче, что поголовно это std::size_t.

    При любом раскладе: ошибки дазайна языка тут нет. А вот к STL прикопаться можно. К примеру, можно не использовать std::vector, а остановиться на какой-то другой реализации.


  2. Аналогично. В данном случае — вкусовщина. Ещё ни разу не было проблем, когда изучал чужой код и там использовались i, j как счётчики циклов, тут даже так: видишь, сразу понимаешь, что это счётчик. Проблем ноль.

    А вот длинная запись с длинными именами: кто мешает использовать волшебную новую строку?


    for (int currentRowIndex = 0;
     currentRowIndex < vec.size();
     currentRowIndex++)

    Возьмите напишите какое-нибудь перемножение матриц, с первого раза не перепутав нигде переменные i и j, каждая из которых в одном месте кода означает столбик, а в другом — строку матрицы.

    А вот, что забавно, видел, и каюсь, сам грешен, и перепутанные column и row, так что тоже не показатель.

  3. Если у программиста появится устоявшийся шаблон с do/while, его это так же не убережёт от ошибки как и инициализация нулём в for


  4. Реально придирка. Лично стараюсь минимизировать область видимости переменных. А если хочется чего-то инициализацией индексом, ну сделайте что-то вроде:


    auto idx = [&vec]() {
    for (int i = 0; i < vec.size(); ++i) {
        ...
        if (...)
            return i;
        ...
    }
    return -1; // а как вам сообщить о том, что нужное - не найдено?
    }();

    или воспользуйтесь алгоритмами, типа std::search_n (вкупе с лямбдой), которые вернут итератор, а по нему можно:


    • проверить, а найден ли элемент вообще
    • получить индекс, хотя бы так: it - vec.begin(), но более правильно с std::distance(vec.begin(), it)

  5. Чего это не логична? Если что, цикл не будет выполнен ни одного раза, если при входе в него проверка не выполнилась. Что произошло? А произошло то, что проверка выполнилась РАНЬШЕ блока выражений. Так что всё тут логично, а прикопаться можно и к столбу. Вообще, если представить цикл for через while, то логики ещё прибавляется:


    {
    int i = 0;
    while (i < vec.size()) {
        ...
        i++;
    }
    }

  6. Бррррррр… Да это вообще посыл в духе: индексация с нуля или единицы. Ни. о. чём.


  7. Так используйте алгоритмы или вариант for-each из C++11! Или ограничивайте области видимости переменных или… Короче, написать ерунду можно на чём угодно и как угодно.


  8. А при чём тут for и язык программирования вообще!? И смотрите предыдущий пункт.


  9. И снова: при чём тут for? Там может быть любое выражение. А если холивар на эту тему возник — остановите его профилировкой. Я ни разу не видел заметной разницы между i++ и ++i в части примитивных типов.


  10. Оптимизация делает их идентичными. Более того, в современных системах бесконечный цикл, обычно, ждёт какого-то события: эвента, условной переменной, данных в очереди, тем самым отдавая процессорное время другим частям системы. В таких условиях даже наличие различий в проверке условий занимает ничтожно малое количество времени. Может быть критичным для всяких спин-локов, но… смотрим первое предложение этого пункта.


  11. Не знаю как вам, а мне бросаются. И снова: при чём тут оператор for? Да, язык позволяет неявно кастить int в bool, временами это бьёт (enum class, к примеру, уже не имеют неявного каста в bool), но такое можно написать и в while и в if. Косяк языка? Не думаю, скорее — функциональная особенность.

Собственно, видно, что если какие-то нарекания и были, то они были устранены эволюционно (алгоритмы STL) или частично-революционно (C++11 и последующие). Причём, по большей части решалась проблема удобства и унификации, а не какие-то надуманные проблемы for.

Only those users with full accounts are able to leave comments. Log in, please.