Волею судеб мне довелось заняться одной задачей автоматизации при помощи Python-скрипта. Изучая базовые конструкции, наибольший интерес у меня вызвал следующий код:

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(). При чем в нашем случае абсолютная погрешность — это шаг диапазона.

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

Полная версия кода:
range.h
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)


  1. demist
    14.08.2015 17:45
    -3

    видимо, о for_each Вы не слышали…


    1. fareloz
      14.08.2015 17:49
      +4

      И слышал, и пользовался. Как по мне более громоздко и менее читаемо. Кроме того не пойму как std::for_each относится к перебору индексов.


      1. demist
        14.08.2015 17:50
        -9

        но зато это стандарт, а не велосипед.


        1. fareloz
          14.08.2015 17:54
          +4

          Как по мне в С++11 for_each абсолютно не нужен с учетом range-based for. Кроме того не могу представить как с помощью for_each сделать проход по индексам.


          1. andreili
            14.08.2015 21:38
            +1

            Для примера:

            std::deque<std::string> list;
            ...
            for (auto item : list)
            {
              printf("%s\n", items.c_str());
            }
            

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

            PS: Черт, ниже уже есть похожий вариант :)


            1. encyclopedist
              14.08.2015 21:47
              +3

              Только лучше

              for (auto const & item: list)
              

              чтобы избежать ненужного копирования


              1. andreili
                14.08.2015 23:55

                Хм, точно. Это я забыл.


            1. fareloz
              14.08.2015 22:01

              Вы как раз написали почему я назвал for_each аттавизмом — Вы написали range-based for-loop. Но бывают случает когда нам нужно перебирать не элементы, а индексы (например, если мы обрабатываем не одну коллекцию в теле цикла). Для того, тобы написать такой цикл по иденксам но в формате range-based for-loop, и был написан код из статьи.


              1. andreili
                14.08.2015 23:55

                Это я понял. С работы котелок не варил совсем от жары :)


  1. kjam
    14.08.2015 17:58

    А зачем static_assert(std::is_pod::value, «Template type should be a pod-type»);?


    1. fareloz
      14.08.2015 18:00

      Извините, опечатка из старой версии. Задумывалась проверка по std::is_integral. Исправлю


      1. encyclopedist
        14.08.2015 20:24
        +1

        sealed тоже, вероятно


      1. domix32
        14.08.2015 22:04

        Еще return return


        1. fareloz
          15.08.2015 00:18

          Спасибо, исправил


  1. Xelonic
    14.08.2015 17:59
    -4

    Зачем из одного языка делать другой?


    1. fareloz
      14.08.2015 18:05
      +5

      Почему сразу другой? Просто добавил для себя немного синтаксического сахара.


    1. encyclopedist
      14.08.2015 20:34
      +4

      Это современный C++, привыкайте. Диапазоны могут появится уже в С++17. Ну или по крайней мере в виде TS.


  1. maaGames
    14.08.2015 18:43
    +1

    Если итераторы сравнивать не по значению, а по номеру шага, то не будет проблем с вещественными числами.
    А ещё мне очень не нравится это: m_value += m_range->step();
    Т.к. известны начальное значение, инкремент и номер шага, то лучше сделать через умножение. От плюсования погрешность накапливается неприличными темпами.


    1. fareloz
      14.08.2015 19:30

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


      1. stack_trace
        18.08.2015 09:02
        +1

        У вас диапазон в любом случае будет ограничен максимальным значением size_t. В этом же суть этого типа.


        1. fareloz
          18.08.2015 09:41

          Да, сейчас согласен. Сначала думал о double-диапазонах, там можно задать диапазон с количеством эелемнтов больше чем size_t. Но все равно все упирается в размер диапазона, который так или иначе size_t. Так что да, Вы правы.


  1. sermp
    14.08.2015 18:59
    +1

    for-each, мне кажется, здесь иррелевантен. Первичным является как раз понятие интервала. Тут один товарищ как раз работает над разработкой интервалов для следующего стандарта :)


    1. fareloz
      14.08.2015 19:34

      Да, я читал эти две статьи. Но там понятие интервала очень обобщенное.


      1. 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;
        

        Ну и ещё эту бублиотеку полезно постмотреть потому, что она пути в стандарт (правда пока не ясно, когда).


        1. fareloz
          14.08.2015 20:16

          Спасибо!


          1. encyclopedist
            14.08.2015 20:22
            +1

            Ещё можно глянуть обсуждение по теме тут


  1. k06a
    14.08.2015 21:29
    -1

    Рекомендую посмотреть на LINQ из .NET Framework. Я вроде делал генераторы ренджей в имплементации клона на C++ github.com/k06a/boolinq


    1. k06a
      14.08.2015 21:32
      -1

      Да, в самом конце файла github.com/k06a/boolinq/blob/master/include/boolinq/boolinq.h


  1. pravic
    14.08.2015 23:08
    +3

    1. fareloz
      14.08.2015 23:27
      +1

      Да, суд по по всему мой код решает такую же проблему. Спасибо за ссылку.


  1. ivlis
    14.08.2015 23:42
    -3

    А зачем вам вообще индексы в таких высокоуровневых языках как C++ и python?


    1. Hertz
      15.08.2015 00:03
      +1

      Разные потребности бывают, иногда и на очень низкий уровень приходится опуститься, до всяких intrinsic'ов и прочего, там и цикл по индексам может пригодиться, но старый-добрый for не кошере, а решение с мета-оберткой и range-based for хорошо оптимизируется и более читаемо. Например, вариант приведенный мною ниже, в GCC/CLang последних версий с -O2/-O3 компилируется в машинный код, эквивалентный коду, получаемому из hand-written for по индексам.


    1. stack_trace
      18.08.2015 09:06
      -1

      А с каких пор C++ стоит в одном ряду с питоном в качестве высокоуровнего языка? По мне так он ближе к среднему уровню.


      1. ivlis
        19.08.2015 18:10
        +1

        А чем C++ не высокоуровневый язык?


        1. stack_trace
          19.08.2015 19:19

          Ха, вы правы ) Просто я имел ввиду, что на C++ можно писать и низкоуровневые вещи. Хотя, если честно, я не вижу как уровень языка вообще связан с потребностью в индексах. Тут, скорее, есть связь со сложностью конкретного алгоритма.


  1. Hertz
    15.08.2015 00:00

    Я использую нечто такое в своих личных и рабочих проектах (уже неактуальна версия, не library-качества):
    gist.github.com/DieHertz/f83b33ffe33e1c07abfc


    1. fareloz
      15.08.2015 00:16

      Спасибо! Посмотрю на досуге Вашу реализацию


  1. rusec
    15.08.2015 00:14

    Намного лучше в перле:
    for (0..10) {
    print
    }
    Ну вот зачем куча лишних слов — in, range,… Без лишнего мусора элегантней.


    1. fareloz
      15.08.2015 00:17

      Считается, что чем больше код похож на обычное предложение, тем лучше воспринимается человеком


      1. rusec
        15.08.2015 00:21

        Какое предолжение обычнее, удобнее, проще:
        от нуля до десяти делаем…
        в диапазоне ноль, десять делаем…

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


        1. 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 перенял часть, но всё-таки все ему не нужны — идеология «есть только один способ сделать это» неслабо разнится с идеологией «есть более чем один способ сделать это».


        1. fareloz
          15.08.2015 12:19

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

          loop(number_of_times) { do_stuff() }

          для меня выглядел бы лучше.


          1. khim
            16.08.2015 00:17

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


            1. rusec
              16.08.2015 20:58

              Не нравится — дайте индексу имя, никто ж не запрещает.
              Иногда это упрощает, иногда — нет. Зависит.


    1. monolithed
      16.08.2015 20:55

      Ну тогда тоже самое на Bash

      echo {1..10}
      


      1. rusec
        16.08.2015 23:16
        -1

        тут можно вместо echo подставить нетривиальную логику? в перле принт — пример, на его месте мог быть каждый.


  1. segment
    15.08.2015 00:33

    А что насчет производительности по сравнению с использованием оригинального синтаксиса? Какой overhead?


    1. 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   
      


      1. Hertz
        15.08.2015 20:34

        Вариант кода, приведенный мною выше, вообще одинаковый машинный код выдает.


  1. homesoft
    15.08.2015 00:44
    -2

    Куда всё катится! Как можно упростить неупрощаемое? Во сколько раз больше нужно машинных инструкций с ненужными умножениями на исполнение вот таких упрощений? И на сколько такое упрощение дает выйгрышь девелоперу (одна строчка простого кода против одной строчки простого кода)?


    1. encyclopedist
      15.08.2015 15:28
      +1

      В комментарии как раз над вашим приведен машинный код. Он идентичен.


  1. nagato
    15.08.2015 11:51
    +1

    У первого варианта есть ещё один серьёзный недостаток: возможность наличия сайд-эффектов в max


  1. Mercury13
    15.08.2015 13:11

    А я использую примерно такой же макрос FOR_S (var, min, maxplus).
    Буква s означает size_t. Правда, с другой целью — уменьшить вероятность ошибки.


  1. 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 файл.


    1. fareloz
      16.08.2015 11:45
      +1

      Выше уже писали преимущество мета-обертки — очень часто генерируется код без дополнительных расходов, как-будто обычный for и был написан изначально. Ваш код будет так же компилироваться?
      Кроме того отладка кода с макросами — это жесть.
      Ну и читаемость… Лично я сходу не смог разобраться что это за новый оператор $ появился в с++ и с++ ли это вообще.


      1. csmile
        16.08.2015 20:31

        Это тоже macro и компилируется эффективно. Проверенно. На рабочих проектах. В том числе и в моем sciter.


    1. Bronx
      16.08.2015 11:47

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


      1. csmile
        16.08.2015 20:33

        Не понял вопрос. $generator это объявление структуры и одного метода. Что там прятать-то?


        1. Bronx
          16.08.2015 22:01

          int _line; В структурах по умолчанию публичная видимость.


          1. csmile
            16.08.2015 22:29

            Да, имеет смысл. Поправил, спасибо.


  1. 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
    }
    


    1. stack_trace
      19.08.2015 19:30

      А если интервал [0..100000]?


  1. 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; }


    1. encyclopedist
      17.08.2015 15:16

      В математических пакетах (вроде MATLAB, numpy) часто используется форма linspace(начало, конец, количество).