for index in range(0,10) :
do_stuff()
Удобно, читаемо, лаконично (модно, стильно, молодежно)! Почему бы не организовать такой же цикл в С++? Что из этого вышло — под катом.
Попытка первая — макросы
О недостатках макросов написано много. И главное правило гласит: «Если можно что-то реализовать не используя макросы — так и сделай». Но иногда использование макросов вполне оправданно.
Макросы часто используют для расширения языка нестандартными конструкциями — например, чтобы ввести ключевое слово вечного цикла для большей читаемости кода:
#define infinite_loop while(true)
infinite_loop
{
do_stuff();
}
Кстати, мы ведь тоже задались вопросом реализации нестандартного цикла. Что если попробовать реализовать это дело с помощью макросов. Примерно вот так:
#include <iostream>
#define ranged_for(var, min, max, step) for(auto var = (min); var < (max); var += (step) )
int main()
{
ranged_for(i, 0, 10, 1)
{
std::cout << i << std::endl;
}
return 0;
}
Конечно такой код свою задачу выполняет, но цель более чем не достигнута — вместо того, чтобы сделать код более читаемым и лаконичным, мы скорее еще больше запутали его.
Кроме того есть и ряд других недостатков:
- Нечитаемые имена. Макросы — это автозамена. Если использовать простые имена в названии и аргументах макроса, то велик шанс коллизий с пользовательским кодом. Показательный пример — коллизия макроса min\max из Windows.h с функциями стандартной библиотеки std::min\std::max. Поэтому часто приходится использовать нечитаемые имена во благо избежания описанной проблемы.
- Никакой перегрузки. Макросы — это автозамена. Если написать несколько макросов с одинаковым именем, то доступен будет только один и з них. Поэтому написать несколько версий одного и того же макроса нельзя. А нам бы хотелось чтоб прям совсем как в Python.
Да и что уж тут говорить — это абсолютно не похоже на пример из Python.
Попытка вторая — функция-генератор коллекции
Если внимательно почитать документацию по range() из Python, то можно увидеть, что range() генерирует список сразу всех значений из диапазона. Поступим точно так же и напишем функцию, которая будет возвращать std::vector где каждый элемент — это значение индекса:
template<typename T>
std::vector<T> range(T min, T max, T step)
{
const bool is_unsigned = std::is_unsigned<T>::value;
if (is_unsigned && min > max)
return std::vector<T>(0);
size_t size = size_t((max - min) / step);
if (!is_unsigned && size < 0)
return std::vector<T>();
if (size == 0)
return std::vector<T>(1, min);
std::vector<T> values;
values.reserve(size);
if (step < 0)
{
for (T i = min; i > max; i += step)
{
values.push_back(i);
}
}
else
{
for (T i = min; i < max; i += step)
{
values.push_back(i);
}
}
return values;
}
template<typename T>
std::vector<T> range(T min, T max)
{
return range<T>(min, max, 1);
}
template<typename T>
std::vector<T> range(T max)
{
return range<T>(0, max);
}
Учитывая новый синтаксис для перебора значений коллекции в стандарте С++11, возможно написать следующий код:
int main()
{
std::cout << '[';
for (int i : range<int>(10))
std::cout << i << ' ';
std::cout << ']' << std::endl;
std::cout << '[';
for (int i : range<int>(0, 10))
std::cout << i << ' ';
std::cout << ']' << std::endl;
std::cout << '[';
for (int i : range<int>(0, 10, 2))
std::cout << i << ' ';
std::cout << ']' << std::endl;
std::cout << '[';
for (int i : range<int>(10, 2))
std::cout << i << ' ';
std::cout << ']' << std::endl;
std::cout << '[';
for (int i : range<int>(10, 2, -1))
std::cout << i << ' ';
std::cout << ']' << std::endl;
return 0;
}
Вооот, это уже похоже на то, чего мы хотим достигнуть. Теперь это читается как «По всем i в диапазоне от 0 до 10». Согласитесь, звучит лучше, чем «От i равного 0, пока меньше 10, увеличивать на 1». В итоге вывод программы будет следующим:
[0 1 2 3 4 5 6 7 8 9 ]
[0 1 2 3 4 5 6 7 8 9 ]
[0 2 4 6 8 ]
[]
[10 9 8 7 6 5 4 3 ]
Это решение имеет очевидный недостаток, который следует из определения, — чрезмерное для данной операции потребление ресурсов. И чем больше диапазон значений — тем больше ресурсов потребляет промежуточное звено. В Python для решения данной проблемы существует функция xrange(), которая позволяет генерировать значения на лету.
К сожалению, функции-генераторы нам недоступны, поэтому прийдется искать другое решение.
Попытка третья, финальная — псевдо-коллекция
Чтобы пользовательский класс-коллекция поддерживал проход с помощью range-based циклов необходимо всего нечего — реализовать функции begin() и end(), которые возвращают итераторы на начало и конец коллекции соответственно. Дополнительно необходимо реализовать класс самого итератора. Но что если реализовать класс, который коллекцией будет только на уровне интерфейса, но внутренняя реализация хранить все значения не будет, а сгенерирует их по мере необходимости?
Тогда упрощенная реализация нашего класса может выглядеть следующим образом:
template<typename T>
class range sealed
{
public:
range(T _min, T _max, T _step = T(1))
: m_min(_min), m_max(_max), m_step(_step)
{ }
T operator[](size_t index)
{
return (m_min + index * m_step);
}
size_t size()
{
return static_cast<size_type>((m_max - m_min) / m_step);
}
range_iterator<range<T>> begin()
{
return range_iterator<range<T>>(this, m_min);
}
range_iterator<range<T>> end()
{
return range_iterator<range<T>>(this, m_min + size() * m_step);
}
private:
T m_min;
T m_max;
T m_step;
};
Все, что необходимо хранить — это границы диапазона и шаг. Тогда любой элемент диапазона можно получить с помощью простой арифметики (см. operator[]). Основная же работа возлагается на класс итератора:
template<typename T>
class range_iterator sealed
{
public:
typedef T range_type;
typedef range_iterator<range_type> self_type;
typedef typename range_type::value_type value_type;
range_iterator(const range_type* const range, value_type start_value)
: m_range(range), m_value(start_value)
{ }
operator value_type() const
{
return m_value;
}
value_type& operator*() {
return m_value;
}
self_type& operator++() {
m_value += m_range->step();
return *this;
}
self_type operator++(int) {
self_type tmp(*this);
++(*this);
return tmp;
}
bool operator==(const self_type& other) const {
return ((m_range == other.m_range) &&
(equals<value_type>(m_value, other.m_value, m_range->step())));
}
bool operator!=(const self_type& other) const {
return !((*this) == other);
}
private:
template<typename R> static bool equals(R a, R b, R e) {
return a == b;
}
template<> static bool equals(double a, double b, double e) {
return std::abs(a - b) < std::abs(e);
}
template<> static bool equals(float a, float b, float e) {
return std::abs(a - b) < std::abs(e);
}
const range_type* const m_range;
value_type m_value;
};
Думаю, дополнительно стоит пояснить наличие функции equals(). Предположим у нас диапазон нецелочисленный, а, допустим, от 0 до 10 с шагом 0.1. Сравнение итераторов основано на сравнении текущих значений из диапазона, хранящихся в каждом из них. Но сравнивать числа с плавающей точкой в С++ просто так нельзя. Подробнее почему можно почитать вот здесь. Скажу лишь, что если сравнивать «в лоб», то скорее всего цикл будет бесконечным. Лучший способ — это сравнивать разницу с допустимой абсолютной погрешностью. Это и реализовано в функции equals(). При чем в нашем случае абсолютная погрешность — это шаг диапазона.
Вот теперь действительно можно написать цикл в необходимой нам форме и при этом не сильно тратиться на накладные расходы.
Полная версия кода:
template<typename T>
class range_iterator : std::iterator<std::random_access_iterator_tag, typename T::value_type>
{
public:
typedef T range_type;
typedef range_iterator<range_type> self_type;
typedef std::random_access_iterator_tag iterator_category;
typedef typename range_type::value_type value_type;
typedef typename range_type::size_type size_type;
typedef typename range_type::difference_type difference_type;
typedef typename range_type::pointer pointer;
typedef typename range_type::const_pointer const_pointer;
typedef typename range_type::reference reference;
typedef typename range_type::const_reference const_reference;
range_iterator(const range_type* const range, value_type start_value)
: m_range(range), m_value(start_value)
{ }
range_iterator(const self_type&) = default;
range_iterator(self_type&&) = default;
range_iterator& operator=(const range_iterator&) = default;
~range_iterator() = default;
operator value_type() const {
return m_value;
}
value_type& operator*() {
return m_value;
}
self_type& operator++() {
m_value += m_range->step();
return *this;
}
self_type operator++(int) {
self_type tmp(*this);
++(*this);
return tmp;
}
self_type& operator--() {
m_value -= m_range->step();
return *this;
}
self_type operator--(int) {
self_type tmp(*this);
--(*this);
return tmp;
}
self_type operator+(difference_type n) {
self_type tmp(*this);
tmp.m_value += m_range->step() * n;
return tmp;
}
self_type& operator+=(difference_type n) {
m_value += n * m_range->step();
return (*this);
}
self_type operator-(difference_type n) {
self_type tmp(*this);
tmp.m_value -= n * m_range->step();
return tmp;
}
self_type& operator-=(difference_type n) {
m_value -= n * m_range->step();
return (*this);
}
bool operator==(const self_type& other) const {
return ((m_range == other.m_range) &&
(equals<value_type>(m_value, other.m_value, m_range->step())));
}
bool operator!=(const self_type& other) const {
return !((*this) == other);
}
private:
template<typename T> static bool equals(T a, T b, T e) {
return a == b;
}
template<> static bool equals(double a, double b, double e) {
return std::abs(a - b) < std::abs(e);
}
template<> static bool equals(float a, float b, float e) {
return std::abs(a - b) < std::abs(e);
}
const range_type* const m_range;
value_type m_value;
};
template<typename T>
class range sealed
{
static_assert(std::is_arithmetic<T>::value, "Template type should be a integral-type");
public:
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef range<value_type> self_type;
typedef class range_iterator<self_type> iterator;
typedef std::reverse_iterator<iterator> reverse_iterator;
range(value_type _min, value_type _max, value_type _step = value_type(1))
: m_min(_min), m_max(_max), m_step(_step) {
if (m_step == 0) {
throw std::invalid_argument("Step equals zero");
}
}
range(const self_type&) = default;
range(self_type&&) = default;
range& operator=(const self_type&) = default;
~range() = default;
bool operator==(const self_type& _obj) const {
return (m_max == _obj.max()) &&
(m_min == _obj.min()) &&
(m_step == _obj.step());
}
bool operator!=(const self_type& _obj) const {
return !(this == _obj);
}
value_type operator[](size_type _index) const {
#ifdef _DEBUG
if (_index > size()) {
throw std::out_of_range("Index out-of-range");
}
#endif
return (m_min + (_index * m_step));
}
bool empty() const {
bool is_empty = ((m_max < m_min) && (m_step > 0));
is_empty |= ((m_max > m_min) && (m_step < 0));
return is_empty;
}
size_type size() const {
if (empty()) {
return 0;
}
return static_cast<size_type>((m_max - m_min) / m_step);
}
value_type min() const {
return m_min;
}
value_type max() const {
return m_max;
}
value_type step() const {
return m_step;
}
iterator begin() const {
iterator start_iterator(this, m_min);
return start_iterator;
}
iterator end() const {
iterator end_iterator(this, m_min + size() * m_step);
return end_iterator;
}
reverse_iterator rbegin() const {
reverse_iterator start_iterator(end());
return start_iterator;
}
reverse_iterator rend() const {
reverse_iterator end_iterator(begin());
return end_iterator;
}
private:
value_type m_min;
value_type m_max;
value_type m_step;
};
Комментарии (64)
Xelonic
14.08.2015 17:59-4Зачем из одного языка делать другой?
fareloz
14.08.2015 18:05+5Почему сразу другой? Просто добавил для себя немного синтаксического сахара.
encyclopedist
14.08.2015 20:34+4Это современный C++, привыкайте. Диапазоны могут появится уже в С++17. Ну или по крайней мере в виде TS.
maaGames
14.08.2015 18:43+1Если итераторы сравнивать не по значению, а по номеру шага, то не будет проблем с вещественными числами.
А ещё мне очень не нравится это: m_value += m_range->step();
Т.к. известны начальное значение, инкремент и номер шага, то лучше сделать через умножение. От плюсования погрешность накапливается неприличными темпами.fareloz
14.08.2015 19:30Первая реализация и была основана на номере шага. Но тогда диапазон органичен максимальным значением типа size_t. Правда в реальной жизни вряд ли кто-то будет перебирать такие диапазоны.
stack_trace
18.08.2015 09:02+1У вас диапазон в любом случае будет ограничен максимальным значением size_t. В этом же суть этого типа.
fareloz
18.08.2015 09:41Да, сейчас согласен. Сначала думал о double-диапазонах, там можно задать диапазон с количеством эелемнтов больше чем size_t. Но все равно все упирается в размер диапазона, который так или иначе size_t. Так что да, Вы правы.
sermp
14.08.2015 18:59+1for-each, мне кажется, здесь иррелевантен. Первичным является как раз понятие интервала. Тут один товарищ как раз работает над разработкой интервалов для следующего стандарта :)
fareloz
14.08.2015 19:34Да, я читал эти две статьи. Но там понятие интервала очень обобщенное.
encyclopedist
14.08.2015 20:14+1В библиотеке range-v3 довольно много предопределённых диапазонов и операций над ними. Смотрите (пока неполный) мануал тут. Для вашего случая интересны
view::ints
иview::iota
. Готовых интервалов с произвольным шагом нету.
Пример:
// prints numbers 1...10 for (auto i: view::ints(1, 11)) std::cout << i << std::endl;
Ну и ещё эту бублиотеку полезно постмотреть потому, что она пути в стандарт (правда пока не ясно, когда).
k06a
14.08.2015 21:29-1Рекомендую посмотреть на LINQ из .NET Framework. Я вроде делал генераторы ренджей в имплементации клона на C++ github.com/k06a/boolinq
k06a
14.08.2015 21:32-1Да, в самом конце файла github.com/k06a/boolinq/blob/master/include/boolinq/boolinq.h
ivlis
14.08.2015 23:42-3А зачем вам вообще индексы в таких высокоуровневых языках как C++ и python?
Hertz
15.08.2015 00:03+1Разные потребности бывают, иногда и на очень низкий уровень приходится опуститься, до всяких intrinsic'ов и прочего, там и цикл по индексам может пригодиться, но старый-добрый for не кошере, а решение с мета-оберткой и range-based for хорошо оптимизируется и более читаемо. Например, вариант приведенный мною ниже, в GCC/CLang последних версий с -O2/-O3 компилируется в машинный код, эквивалентный коду, получаемому из hand-written for по индексам.
stack_trace
18.08.2015 09:06-1А с каких пор C++ стоит в одном ряду с питоном в качестве высокоуровнего языка? По мне так он ближе к среднему уровню.
ivlis
19.08.2015 18:10+1А чем C++ не высокоуровневый язык?
stack_trace
19.08.2015 19:19Ха, вы правы ) Просто я имел ввиду, что на C++ можно писать и низкоуровневые вещи. Хотя, если честно, я не вижу как уровень языка вообще связан с потребностью в индексах. Тут, скорее, есть связь со сложностью конкретного алгоритма.
Hertz
15.08.2015 00:00Я использую нечто такое в своих личных и рабочих проектах (уже неактуальна версия, не library-качества):
gist.github.com/DieHertz/f83b33ffe33e1c07abfc
rusec
15.08.2015 00:14Намного лучше в перле:
for (0..10) {
print
}
Ну вот зачем куча лишних слов — in, range,… Без лишнего мусора элегантней.fareloz
15.08.2015 00:17Считается, что чем больше код похож на обычное предложение, тем лучше воспринимается человеком
rusec
15.08.2015 00:21Какое предолжение обычнее, удобнее, проще:
от нуля до десяти делаем…
в диапазоне ноль, десять делаем…
неговоря уж о часто ненужном индексе, который в перле можно опустить, а в питоне — вынь да полож.
в обычных предложениях ненужные вещи опускают.JIghtuse
15.08.2015 09:06неговоря уж о часто ненужном индексе, который в перле можно опустить, а в питоне — вынь да полож.
В Python (и не только) для таких целей служит подчерк:
for _ in range(0, 10): pass
Насчёт формулировок — дело привычки. Мне поначалу вариант на Perl чересур упрощённым и вследствие непонятным, но после некоторых раздумий согласен, так удобнее. Perl вообще очень богат на различные человеко-ориентированные конструкции:
chdir '/usr/spool/news' or die "Can't cd to spool: $!\n"; print 'ok' if chdir '/tmp';
Python перенял часть, но всё-таки все ему не нужны — идеология «есть только один способ сделать это» неслабо разнится с идеологией «есть более чем один способ сделать это».
fareloz
15.08.2015 12:19В статье я рассматривал случай когда индекс необходим внутри тела цикла.
Если же индекс ненужен (я так понимаю именно это Вы имели под вохможностью его опустить), то вариант:
loop(number_of_times) { do_stuff() }
для меня выглядел бы лучше.khim
16.08.2015 00:17В perl'е индекс доступен через
<a href="http://perldoc.perl.org/perlvar.html#General-Variables">$_</a>
— и это одна из вещей, которая превращает программу в ребус. Где эта переменная возникла? Когда исчезнет? Как меняется? Какая у неё области видимости и как это зависит от версии perl'а? Это ж просто пьесня — без работы не останетесь!rusec
16.08.2015 20:58Не нравится — дайте индексу имя, никто ж не запрещает.
Иногда это упрощает, иногда — нет. Зависит.
monolithed
16.08.2015 20:55Ну тогда тоже самое на Bash
echo {1..10}
rusec
16.08.2015 23:16-1тут можно вместо echo подставить нетривиальную логику? в перле принт — пример, на его месте мог быть каждый.
segment
15.08.2015 00:33А что насчет производительности по сравнению с использованием оригинального синтаксиса? Какой overhead?
Balthasar
15.08.2015 13:48+1Для простого случая gcc выдаёт практически идентичный код.#include <boost/range/irange.hpp> void do_the_thing(int); void oldschool(int a, int b) { for (auto i = a; i < b; ++i) do_the_thing(i); } void ranges(int a, int b) { for (auto i : boost::irange(a, b)) do_the_thing(i); }
$ g++-4.9.2 -std=c++14 -O3 -DNDEBUG -c range.cpp $ objdump -dC range.o range.o: формат файла elf64-x86-64 Дизассемблирование раздела .text: 0000000000000000 <oldschool(int, int)>: 0: 55 push %rbp 1: 53 push %rbx 2: 89 f5 mov %esi,%ebp 4: 89 fb mov %edi,%ebx 6: 48 83 ec 08 sub $0x8,%rsp a: 39 f7 cmp %esi,%edi c: 7d 10 jge 1e <oldschool(int, int)+0x1e> e: 66 90 xchg %ax,%ax 10: 89 df mov %ebx,%edi 12: 83 c3 01 add $0x1,%ebx 15: e8 00 00 00 00 callq 1a <oldschool(int, int)+0x1a> 1a: 39 eb cmp %ebp,%ebx 1c: 75 f2 jne 10 <oldschool(int, int)+0x10> 1e: 48 83 c4 08 add $0x8,%rsp 22: 5b pop %rbx 23: 5d pop %rbp 24: c3 retq 25: 66 66 2e 0f 1f 84 00 data32 nopw %cs:0x0(%rax,%rax,1) 2c: 00 00 00 00 0000000000000030 <ranges(int, int)>: 30: 55 push %rbp 31: 53 push %rbx 32: 89 f5 mov %esi,%ebp 34: 89 fb mov %edi,%ebx 36: 48 83 ec 08 sub $0x8,%rsp 3a: 39 f7 cmp %esi,%edi 3c: 74 10 je 4e <ranges(int, int)+0x1e> 3e: 66 90 xchg %ax,%ax 40: 89 df mov %ebx,%edi 42: 83 c3 01 add $0x1,%ebx 45: e8 00 00 00 00 callq 4a <ranges(int, int)+0x1a> 4a: 39 dd cmp %ebx,%ebp 4c: 75 f2 jne 40 <ranges(int, int)+0x10> 4e: 48 83 c4 08 add $0x8,%rsp 52: 5b pop %rbx 53: 5d pop %rbp 54: c3 retq
homesoft
15.08.2015 00:44-2Куда всё катится! Как можно упростить неупрощаемое? Во сколько раз больше нужно машинных инструкций с ненужными умножениями на исполнение вот таких упрощений? И на сколько такое упрощение дает выйгрышь девелоперу (одна строчка простого кода против одной строчки простого кода)?
nagato
15.08.2015 11:51+1У первого варианта есть ещё один серьёзный недостаток: возможность наличия сайд-эффектов в max
Mercury13
15.08.2015 13:11А я использую примерно такой же макрос FOR_S (var, min, maxplus).
Буква s означает size_t. Правда, с другой целью — уменьшить вероятность ошибки.
csmile
16.08.2015 08:24-1То же самое пишется куда как лаконичнее с использованием моего $generator/$yield. И не только на ranges:
include "generator.h" $generator(descent) { // place for all variables used in the generator int i; // our counter // place the constructor of our generator, e.g. // descent(int minv, int maxv) {...} // from $emit to $stop is a body of our generator: $emit(int) // will emit int values. Start of body of the generator. for (i = 10; i > 0; --i) $yield(i); // a.k.a. yield in Python, // returns next number in [1..10], reversed. $stop; // stop, end of sequence. End of body of the generator. };
И использование
int main(int argc, char* argv[]) { descent gen; for(int n; gen(n);) // "get next" generator invocation printf("next number is %d\n", n); return 0; }
Сама статья с имплементацией $generator/$yield — 16 строк .h файл.fareloz
16.08.2015 11:45+1Выше уже писали преимущество мета-обертки — очень часто генерируется код без дополнительных расходов, как-будто обычный for и был написан изначально. Ваш код будет так же компилироваться?
Кроме того отладка кода с макросами — это жесть.
Ну и читаемость… Лично я сходу не смог разобраться что это за новый оператор $ появился в с++ и с++ ли это вообще.csmile
16.08.2015 20:31Это тоже macro и компилируется эффективно. Проверенно. На рабочих проектах. В том числе и в моем sciter.
Bronx
16.08.2015 11:47Прикольная штука. Только зачем у генератора все его «кишки» сделаны публичными?
monolithed
16.08.2015 21:17-1Я в свое время не стал сильно заморачиваться, сделал примерно так:
/*! * range ( container, from, to, [ step ] ); * @returns { container<T> } */ template <typename Container, typename Position, typename Step = uint> static inline Container range ( Container &container, Position from, Position to, Step step = 1) noexcept { typename std::back_insert_iterator<Container> it = std::back_inserter(container); do { *it++ = from; } while ((from += step) <= to); return container; } ;
Пример использования:
std::vector<int> vector; for (auto value: range(vector, 1, 5)) { std::cout << value << std::endl; // 1, 2, 3, 4, 5 }
nickolaym
17.08.2015 14:59Для вещественных диапазонов есть две альтернативы итераторов
— (значение, шаг) и приблизительное сравнение по значению
— (начало, шаг, индекс) и точное сравнение по индексу — то есть, фактически, transform_iterator линейной функции поверх обычного целочисленного range.
Приблизительное сравнение плохо тем, что оно позволяет организовать путаницу между обычным полуоткрытым интервалом и закрытым.
range(0.0, 1.0, 0.2) = { 0.0, 0.2, 0.4, 0.6, 0.8 }
range(0.0, 1.0+EPS, 0.2) = { 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 } — эмулировали закрытый интервал из полуоткрытого… якобы. На всех этапах поимели ошибки округления. Исходя из этого значение EPS нужно будет подобрать достаточно большим, чтобы переплюнуть сумму ошибок, но достаточно маленьким, чтобы не перескочить через следующее за концом значение.
Тогда как сделать закрытый интервал из полуоткрытого по-честному на целочисленной основе — элементарно.
Опять же, диапазон следует внутренне представлять не тройкой (начало, конец, шаг), а (начало, шаг, количество).
Раз уж всё равно end() { return begin() + size(); }
где size() { return (m_max-m_min)/m_step; }encyclopedist
17.08.2015 15:16В математических пакетах (вроде MATLAB, numpy) часто используется форма linspace(начало, конец, количество).
demist
видимо, о for_each Вы не слышали…
fareloz
И слышал, и пользовался. Как по мне более громоздко и менее читаемо. Кроме того не пойму как std::for_each относится к перебору индексов.
demist
но зато это стандарт, а не велосипед.
fareloz
Как по мне в С++11 for_each абсолютно не нужен с учетом range-based for. Кроме того не могу представить как с помощью for_each сделать проход по индексам.
andreili
Для примера:
Быдлокод, но вполне понятно. Или я не понял сути вопроса?
PS: Черт, ниже уже есть похожий вариант :)
encyclopedist
Только лучше
чтобы избежать ненужного копирования
andreili
Хм, точно. Это я забыл.
fareloz
Вы как раз написали почему я назвал for_each аттавизмом — Вы написали range-based for-loop. Но бывают случает когда нам нужно перебирать не элементы, а индексы (например, если мы обрабатываем не одну коллекцию в теле цикла). Для того, тобы написать такой цикл по иденксам но в формате range-based for-loop, и был написан код из статьи.
andreili
Это я понял. С работы котелок не варил совсем от жары :)