Pull to refresh

Comments 64

И слышал, и пользовался. Как по мне более громоздко и менее читаемо. Кроме того не пойму как std::for_each относится к перебору индексов.
но зато это стандарт, а не велосипед.
Как по мне в С++11 for_each абсолютно не нужен с учетом range-based for. Кроме того не могу представить как с помощью for_each сделать проход по индексам.
Для примера:
std::deque<std::string> list;
...
for (auto item : list)
{
  printf("%s\n", items.c_str());
}

Быдлокод, но вполне понятно. Или я не понял сути вопроса?

PS: Черт, ниже уже есть похожий вариант :)
Только лучше
for (auto const & item: list)

чтобы избежать ненужного копирования
Вы как раз написали почему я назвал for_each аттавизмом — Вы написали range-based for-loop. Но бывают случает когда нам нужно перебирать не элементы, а индексы (например, если мы обрабатываем не одну коллекцию в теле цикла). Для того, тобы написать такой цикл по иденксам но в формате range-based for-loop, и был написан код из статьи.
Это я понял. С работы котелок не варил совсем от жары :)
UFO just landed and posted this here
Извините, опечатка из старой версии. Задумывалась проверка по std::is_integral. Исправлю
Зачем из одного языка делать другой?
Почему сразу другой? Просто добавил для себя немного синтаксического сахара.
Это современный C++, привыкайте. Диапазоны могут появится уже в С++17. Ну или по крайней мере в виде TS.
Если итераторы сравнивать не по значению, а по номеру шага, то не будет проблем с вещественными числами.
А ещё мне очень не нравится это: m_value += m_range->step();
Т.к. известны начальное значение, инкремент и номер шага, то лучше сделать через умножение. От плюсования погрешность накапливается неприличными темпами.
Первая реализация и была основана на номере шага. Но тогда диапазон органичен максимальным значением типа size_t. Правда в реальной жизни вряд ли кто-то будет перебирать такие диапазоны.
У вас диапазон в любом случае будет ограничен максимальным значением size_t. В этом же суть этого типа.
Да, сейчас согласен. Сначала думал о double-диапазонах, там можно задать диапазон с количеством эелемнтов больше чем size_t. Но все равно все упирается в размер диапазона, который так или иначе size_t. Так что да, Вы правы.
Да, я читал эти две статьи. Но там понятие интервала очень обобщенное.
В библиотеке range-v3 довольно много предопределённых диапазонов и операций над ними. Смотрите (пока неполный) мануал тут. Для вашего случая интересны view::ints и view::iota. Готовых интервалов с произвольным шагом нету.
Пример:
// prints numbers 1...10
for (auto i: view::ints(1, 11))
    std::cout << i << std::endl;

Ну и ещё эту бублиотеку полезно постмотреть потому, что она пути в стандарт (правда пока не ясно, когда).
Рекомендую посмотреть на LINQ из .NET Framework. Я вроде делал генераторы ренджей в имплементации клона на C++ github.com/k06a/boolinq
Да, суд по по всему мой код решает такую же проблему. Спасибо за ссылку.
А зачем вам вообще индексы в таких высокоуровневых языках как C++ и python?
Разные потребности бывают, иногда и на очень низкий уровень приходится опуститься, до всяких intrinsic'ов и прочего, там и цикл по индексам может пригодиться, но старый-добрый for не кошере, а решение с мета-оберткой и range-based for хорошо оптимизируется и более читаемо. Например, вариант приведенный мною ниже, в GCC/CLang последних версий с -O2/-O3 компилируется в машинный код, эквивалентный коду, получаемому из hand-written for по индексам.
А с каких пор C++ стоит в одном ряду с питоном в качестве высокоуровнего языка? По мне так он ближе к среднему уровню.
А чем C++ не высокоуровневый язык?
Ха, вы правы ) Просто я имел ввиду, что на C++ можно писать и низкоуровневые вещи. Хотя, если честно, я не вижу как уровень языка вообще связан с потребностью в индексах. Тут, скорее, есть связь со сложностью конкретного алгоритма.
Спасибо! Посмотрю на досуге Вашу реализацию
Намного лучше в перле:
for (0..10) {
print
}
Ну вот зачем куча лишних слов — in, range,… Без лишнего мусора элегантней.
Считается, что чем больше код похож на обычное предложение, тем лучше воспринимается человеком
Какое предолжение обычнее, удобнее, проще:
от нуля до десяти делаем…
в диапазоне ноль, десять делаем…

неговоря уж о часто ненужном индексе, который в перле можно опустить, а в питоне — вынь да полож.
в обычных предложениях ненужные вещи опускают.
неговоря уж о часто ненужном индексе, который в перле можно опустить, а в питоне — вынь да полож.
В 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 перенял часть, но всё-таки все ему не нужны — идеология «есть только один способ сделать это» неслабо разнится с идеологией «есть более чем один способ сделать это».
В статье я рассматривал случай когда индекс необходим внутри тела цикла.
Если же индекс ненужен (я так понимаю именно это Вы имели под вохможностью его опустить), то вариант:

loop(number_of_times) { do_stuff() }

для меня выглядел бы лучше.
В perl'е индекс доступен через <a href="http://perldoc.perl.org/perlvar.html#General-Variables">$_</a> — и это одна из вещей, которая превращает программу в ребус. Где эта переменная возникла? Когда исчезнет? Как меняется? Какая у неё области видимости и как это зависит от версии perl'а? Это ж просто пьесня — без работы не останетесь!
Не нравится — дайте индексу имя, никто ж не запрещает.
Иногда это упрощает, иногда — нет. Зависит.
Ну тогда тоже самое на Bash

echo {1..10}
тут можно вместо echo подставить нетривиальную логику? в перле принт — пример, на его месте мог быть каждый.
А что насчет производительности по сравнению с использованием оригинального синтаксиса? Какой overhead?
Для простого случая 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   


Насчет сложных случаев не уверен, можно ли сочинить такой, что gcc решит это всё не инлайнить.
Вариант кода, приведенный мною выше, вообще одинаковый машинный код выдает.
Куда всё катится! Как можно упростить неупрощаемое? Во сколько раз больше нужно машинных инструкций с ненужными умножениями на исполнение вот таких упрощений? И на сколько такое упрощение дает выйгрышь девелоперу (одна строчка простого кода против одной строчки простого кода)?
В комментарии как раз над вашим приведен машинный код. Он идентичен.
У первого варианта есть ещё один серьёзный недостаток: возможность наличия сайд-эффектов в max
А я использую примерно такой же макрос FOR_S (var, min, maxplus).
Буква s означает size_t. Правда, с другой целью — уменьшить вероятность ошибки.
То же самое пишется куда как лаконичнее с использованием моего $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 файл.
Выше уже писали преимущество мета-обертки — очень часто генерируется код без дополнительных расходов, как-будто обычный for и был написан изначально. Ваш код будет так же компилироваться?
Кроме того отладка кода с макросами — это жесть.
Ну и читаемость… Лично я сходу не смог разобраться что это за новый оператор $ появился в с++ и с++ ли это вообще.
Это тоже macro и компилируется эффективно. Проверенно. На рабочих проектах. В том числе и в моем sciter.
Прикольная штука. Только зачем у генератора все его «кишки» сделаны публичными?
Не понял вопрос. $generator это объявление структуры и одного метода. Что там прятать-то?
Да, имеет смысл. Поправил, спасибо.
Я в свое время не стал сильно заморачиваться, сделал примерно так:

	 /*!
	  * 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
}
Для вещественных диапазонов есть две альтернативы итераторов
— (значение, шаг) и приблизительное сравнение по значению
— (начало, шаг, индекс) и точное сравнение по индексу — то есть, фактически, 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; }
В математических пакетах (вроде MATLAB, numpy) часто используется форма linspace(начало, конец, количество).
Sign up to leave a comment.

Articles