Есть ли в С++ 11 класс диапазона для использования с циклами for на основе диапазона?

Я поймал себя на том, что написал это совсем недавно:

template <long int T_begin, long int T_end>
class range_class {
 public:
   class iterator {
      friend class range_class;
    public:
      long int operator *() const { return i_; }
      const iterator &operator ++() { ++i_; return *this; }
      iterator operator ++(int) { iterator copy(*this); ++i_; return copy; }

      bool operator ==(const iterator &other) const { return i_ == other.i_; }
      bool operator !=(const iterator &other) const { return i_ != other.i_; }

    protected:
      iterator(long int start) : i_ (start) { }

    private:
      unsigned long i_;
   };

   iterator begin() const { return iterator(T_begin); }
   iterator end() const { return iterator(T_end); }
};

template <long int T_begin, long int T_end>
const range_class<T_begin, T_end>
range()
{
   return range_class<T_begin, T_end>();
}

И это позволяет мне писать такие вещи:

for (auto i: range<0, 10>()) {
    // stuff with i
}

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

Так ли это? Была ли добавлена ​​какая-то новая библиотека для итераторов в диапазоне целых чисел или, может быть, общий диапазон вычисляемых скалярных значений?


person Omnifarious    schedule 25.08.2011    source источник
comment
+1. Я хотел бы иметь такие классы в своих утилитах. :-)   -  person Nawaz    schedule 25.08.2011
comment
Кстати, какой смысл писать шаблонную функцию range? Это ничего не добавляет к использованию, в котором используется range_class. Я имею в виду, что range<0,10>() и range_class<0,10>() выглядят совершенно одинаково!   -  person Nawaz    schedule 25.08.2011
comment
@Nawaz: Да, ты прав. У меня было какое-то странное видение, что я могу заставить функцию обрабатывать различие между динамическим и статическим случаем, но я не думаю, что это можно сделать.   -  person Omnifarious    schedule 25.08.2011
comment
@iammilind: это сработает. :-). и я предоставил другое решение. проверить это.   -  person Nawaz    schedule 25.08.2011
comment
@iammilind: Наваз задал тот же вопрос за 35 минут до тебя ;)   -  person Sebastian Mach    schedule 25.08.2011
comment
@фреснель; Не замечайте этого. Удаление моего вопроса.   -  person iammilind    schedule 25.08.2011
comment
В CUDA Thrust есть пара таких итераторов, создающих ценность. Согласен, было бы неплохо иметь их в стандарте! Не забудьте добавить теги итераторов и все такое.   -  person Kerrek SB    schedule 25.08.2011
comment
Я не вижу, что можно получить по сравнению с обычным циклом for.   -  person Gene Bushuyev    schedule 30.08.2011
comment
@Gene Bushuyev: В обычном цикле for вам действительно нужно тщательно проанализировать цикл, чтобы убедиться, что значение индекса не изменяется в цикле, а концы диапазона, который он охватывает, не так явны.   -  person Omnifarious    schedule 30.08.2011
comment
@Omnifarious - я не встречал профессионального разработчика, который бы испытывал трудности или жаловался на такие мелочи. И цикл for, и алгоритм for_each идеально подходят для нужд разработчика. На самом деле я не думаю, что оператор for, основанный на диапазоне, был хорошей идеей привнести в язык, поскольку он создает нечестивую зависимость языковых функций от библиотеки, неудивительно, что сразу же возникли проблемы с файлами заголовков и ADL.   -  person Gene Bushuyev    schedule 31.08.2011
comment
@Gene Bushuyev - Что ж, я думаю, что нынешняя работа циклов - это абсолютный мусор, особенно для классов STL. И вы можете подумать, что их легко понять, но когда я вижу, как люди изменяют индексную переменную внутри цикла, они становятся для меня кошмаром. И такой код встречается чаще, чем вы думаете.   -  person Omnifarious    schedule 31.08.2011
comment
Чтобы быть педантичным, я думаю, что в этой реализации есть ошибка, заключающаяся в том, что вы не можете использовать ее для перебора всего диапазона целых чисел. Если вы подключите INT_MIN и INT_MAX в качестве аргументов шаблона, INT_MAX при увеличении будет переполняться, давая INT_MIN и вызывая бесконечные циклы. конец в STL должен быть на единицу выше конца, который не может поместиться внутри самого целочисленного типа, поэтому я не знаю, может ли это быть эффективно реализовано для самого широкого целочисленного типа на данной платформе. Для меньших целочисленных типов вы всегда можете заставить его использовать более широкий тип внутри...   -  person Joseph Garvin    schedule 20.09.2012


Ответы (8)


Стандартная библиотека C++ не имеет его, но Boost.Range имеет boost::counting_range, что, безусловно, подходит. Вы также можете использовать boost::irange, который немного более сфокусирован по объему.

Библиотека диапазонов C++20 позволит вам сделать это через view::iota(start, end).

person Nicol Bolas    schedule 25.08.2011
comment
Да, это определенно характер того, что я бы искал. Я рад, что Boost сделал это. Мне грустно, что комитет по стандартам не включил его по какой-то причине. Это было бы отличным дополнением к функции range-base-for. - person Omnifarious; 25.08.2011
comment
Этот ответ лучше отвечает на мой прямой вопрос, поэтому я выберу его, хотя ответ Наваза очень хорош. - person Omnifarious; 25.08.2011
comment
В последнее время был достигнут значительный прогресс в включении диапазонов в стандарт (N4128). См. github.com/ericniebler/range-v3 для предложения и эталонной реализации. - person Ela782; 21.02.2015
comment
@ Ela782: ... и все же кажется, что мы не увидим этого в C++ 17, верно? - person einpoklum; 07.05.2016
comment
@einpoklum: Нет, похоже, он точно не выживет. Однако, похоже, он на правильном пути. - person Ela782; 09.05.2016
comment
@Ela782 en.cppreference.com/w/cpp/experimental/ranges/ диапазон/диапазон - person Andreas detests censorship; 02.01.2019
comment
@Andreas Да, диапазоны некоторое время назад превратились в TS, но я не думаю, что когда-либо существовала эталонная реализация, которая попала в основные компиляторы в пространстве имен std::experimental::ranges. Я бы сказал, что range-v3 всегда была своего рода эталонной реализацией. Но теперь я считаю, что базовые вещи с диапазонами также недавно были проголосованы за C++20, так что мы действительно скоро получим их в std::! :-) - person Ela782; 03.01.2019
comment
это страница документации для предлагаемого представления::iota en.cppreference.com/w/ cpp/ranges/iota_view - person alwaysmpe; 20.06.2019

Насколько я знаю, в C++11 такого класса нет.

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

//your version
auto x = range<m,n>(); //m and n must be known at compile time

//my version
auto x = range(m,n);  //m and n may be known at runtime as well!

Вот код:

class range {
 public:
   class iterator {
      friend class range;
    public:
      long int operator *() const { return i_; }
      const iterator &operator ++() { ++i_; return *this; }
      iterator operator ++(int) { iterator copy(*this); ++i_; return copy; }

      bool operator ==(const iterator &other) const { return i_ == other.i_; }
      bool operator !=(const iterator &other) const { return i_ != other.i_; }

    protected:
      iterator(long int start) : i_ (start) { }

    private:
      unsigned long i_;
   };

   iterator begin() const { return begin_; }
   iterator end() const { return end_; }
   range(long int  begin, long int end) : begin_(begin), end_(end) {}
private:
   iterator begin_;
   iterator end_;
};

Тестовый код:

int main() {
      int m, n;
      std::istringstream in("10 20");
      if ( in >> m >> n ) //using in, because std::cin cannot be used at coliru.
      {
        if ( m > n ) std::swap(m,n); 
        for (auto i : range(m,n)) 
        {
             std::cout << i << " ";
        }
      }
      else 
        std::cout <<"invalid input";
}

Вывод:

10 11 12 13 14 15 16 17 18 19

демонстрация онлайн.

person Nawaz    schedule 25.08.2011
comment
Мне это нравится. Я думал о версии без шаблона. И я полагаю, что хороший компилятор хорошо бы оптимизировал его в случае, когда значения на самом деле являются постоянными. Я должен проверить это. - person Omnifarious; 25.08.2011
comment
@Nawaz: Я бы все равно создал шаблон для интегрального типа :) Я бы также предложил использовать псевдоним iterator для const_iterator, чтобы iterator производился от std::iterator, а range реализовывал cbegin и cend. Да, и... почему iterator::operator++ возвращает ссылку const? - person Matthieu M.; 25.08.2011
comment
+1, хорошее решение; однако вы также должны были сохранить версию цикла for OP только в своем тестовом коде. :) - person iammilind; 25.08.2011
comment
@Matthieu: В этом решении можно сделать много улучшений, помимо того, что вы предложили, оно также должно иметь параметр step (значение которого по умолчанию будет 1) и многие другие (с точки зрения дизайна). - person Nawaz; 25.08.2011
comment
@iammilind: я упомянул, что gcc-4.5.1 не реализовал цикл на основе диапазона for, поэтому я использовал обычный цикл for для целей ideone-demo. - person Nawaz; 25.08.2011
comment
Почему он повторяется от 0 до 9, если вы сказали ему повторять от 0 до 10? Или это должно быть startnumber, count (тогда имя переменных в конструкторе на самом деле не отражает его использование)? - person RedX; 25.08.2011
comment
@RedX: Это [m,n) . То есть он не включает верхний предел! - person Nawaz; 25.08.2011
comment
@RedX - это стандарт как в Python, так и в C++, когда диапазон обрабатывается как [begin, end). То есть не включать последнее значение, упомянутое в спецификации диапазона. - person Omnifarious; 25.08.2011
comment
@RedX: Dijkstra хорошо написал о том, почему маркировка диапазона лучше всего, как [begin, end). @OP: +1 за каламбур на циклах на основе диапазона, который не является каламбуром :-) - person Kerrek SB; 25.08.2011
comment
@Kerrek: Это потрясающая статья, которая на самом деле представляет собой очень сильный аргумент. Спасибо, что поделились. :-) - person Nawaz; 25.08.2011
comment
Этот ответ очень хорош, и мне нравится обсуждение комментариев под ним (особенно ссылка на статью Дейкстры). Но @Nicol Bolas лучше отвечает на мой прямой вопрос. - person Omnifarious; 25.08.2011
comment
@Omnifarious: мне больше нравится версия шаблона, потому что здесь компилятор не может оптимизировать ее даже для констант, если реализация находится в отдельной библиотеке. - person Diego Sevilla; 25.08.2011
comment
Преимущество версии без шаблона заключается в том, что длину ваших циклов не нужно знать во время компиляции. Конечно, вы можете сделать целочисленный тип шаблонным. - person CashCow; 17.12.2013
comment
@Nawaz: Не могли бы вы объяснить, почему вы возвращаете копию в iterator operator ++(int) и для чего это необходимо? - person weeska; 17.06.2015
comment
@weeska: Предполагается, что эта перегрузка реализует постфиксное приращение v++, которое должно возвращать значение до выполнения операции приращения. Я бы посоветовал вам изучить разницу между ++i и i++, где i объявлено как int. - person Nawaz; 17.06.2015
comment
@Nawaz: Спасибо - я не знал, что операторы приращения постов перегружаются таким образом. Нашел это, погуглив: cs.northwestern. образование/~riesbeck/programming/c++/ - person weeska; 17.06.2015

Я написал библиотеку под названием range точно для той же цели, за исключением того, что это диапазон времени выполнения, и идея в моем случае пришла из Python. Я рассматривал версию времени компиляции, но, по моему скромному мнению, нет никаких реальных преимуществ в получении версии времени компиляции. Вы можете найти библиотеку на Bitbucket, и она находится под лицензией Boost: Range. Это библиотека с одним заголовком, совместимая с С++ 03 и отлично работающая с циклами for на основе диапазона в С++ 11 :)

Возможности:

  • Настоящий контейнер с произвольным доступом со всеми наворотами!

  • Диапазоны можно сравнивать лексикографически.

  • Две функции exist(возвращает логическое значение) и find(возвращает итератор) для проверки существования числа.

  • Библиотека проходит модульное тестирование с использованием CATCH.

  • Примеры базового использования, работа со стандартными контейнерами, работа со стандартными алгоритмами и работа с диапазоном на основе циклов.

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

person AraK    schedule 28.08.2011
comment
В одноминутном вступлении говорится, что у меня нет доступа к Wiki. Вам нужно сделать свою вики общедоступной. - person Nicol Bolas; 29.08.2011
comment
@Nicol Bolas Мне очень жаль, теперь это общедоступно :) - person AraK; 29.08.2011
comment
Спасибо за это, это потрясающе. Я чувствую, что больше людей должны знать об этом. - person Rafael Kitover; 18.11.2019

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

#define RANGE(a, b) unsigned a=0; a<b; a++

Затем вы можете зациклить так:

for(RANGE(i, n)) {
    // code here
}

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

person user2664470    schedule 11.08.2014
comment
Обратите внимание, что for (RANGE(i, flag? n1: n2)) даст неожиданные результаты, потому что вы не выполнили одно из основных правил невредных макросов, заключающееся в заключении в скобки всех ваших параметров (включая, в данном случае, b). Ваш подход также не дает никакого преимущества в производительности по сравнению с подходом, основанным на объектах без макросов (например, ответ Наваза) . - person Quuxplusone; 05.09.2014
comment
Лично я бы сделал #define RANGE(t, v, s, e) (t v = s; v < e; ++v), чтобы его использовали как for RANGE(unsigned, i, 0, n) { .... - person Ethouris; 14.06.2021

Вот более простая форма, которая хорошо работает для меня. Есть ли риск в моем подходе?

r_iterator — это тип, который ведет себя, насколько это возможно, как long int. Поэтому многие операторы, такие как == и ++, просто переходят к long int. Я «раскрываю» базовый тип long int через преобразования operator long int и operator long int &.

#include <iostream>
using namespace std;

struct r_iterator {
        long int value;
        r_iterator(long int _v) : value(_v) {}
        operator long int () const { return value; }
        operator long int& ()      { return value; }
        long int operator* () const { return value; }
};
template <long int _begin, long int _end>
struct range {
        static r_iterator begin() {return _begin;}
        static r_iterator end  () {return _end;}
};
int main() {
        for(auto i: range<0,10>()) { cout << i << endl; }
        return 0;
}

(Редактировать: — мы можем сделать методы range статическими, а не константными.)

person Aaron McDaid    schedule 20.12.2011

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

#include <iostream>
#include <utility>
#include <stdexcept>

template<typename T, bool reverse = false> struct Range final {
    struct Iterator final{
        T value;
        Iterator(const T & v) : value(v) {}
        const Iterator & operator++() { reverse ? --value : ++value; return *this; }
        bool operator!=(const Iterator & o) { return o.value != value; }
        T operator*() const { return value; }
    };
    T begin_, end_;
    Range(const T & b, const T & e)  : begin_(b), end_(e) {
        if(b > e) throw std::out_of_range("begin > end");
    }

    Iterator begin() const { return reverse ? end_ -1 : begin_; }
    Iterator end() const { return reverse ? begin_ - 1: end_; }

    Range() = delete;
    Range(const Range &) = delete;
};

using UIntRange = Range<unsigned, false>;
using RUIntRange = Range<unsigned, true>;

Использование :

int main() {
    std::cout << "Reverse : ";
    for(auto i : RUIntRange(0, 10)) std::cout << i << ' ';
    std::cout << std::endl << "Normal : ";
    for(auto i : UIntRange(0u, 10u)) std::cout << i << ' ';
    std::cout << std::endl;
}
person OneOfOne    schedule 11.01.2014

ты пробовал использовать

template <class InputIterator, class Function>
   Function for_each (InputIterator first, InputIterator last, Function f);

Большую часть времени соответствует счету.

E.g.

template<class T> void printInt(T i) {cout<<i<<endl;}
void test()
{
 int arr[] = {1,5,7};
 vector v(arr,arr+3);

 for_each(v.begin(),v.end(),printInt);

}

Обратите внимание, что printInt можно заменить OFC на лямбду в C++0x. Также может быть еще одна небольшая вариация этого использования (строго для random_iterator)

 for_each(v.begin()+5,v.begin()+10,printInt);

Для Fwd только итератор

 for_each(advance(v.begin(),5),advance(v.begin(),10),printInt);
person Ajeet Ganga    schedule 25.08.2011
comment
Как бы вы это использовали? Я предполагаю, что вы использовали бы лямбду для функции, но я не уверен. - person Omnifarious; 25.08.2011
comment
Сказал бы вам, но вам придется принять ответ, если вы считаете, что это правильный способ его использования. :Р Шучу. Выложил пример уже. - person Ajeet Ganga; 25.08.2011
comment
Здесь вы можете использовать лямбду, поэтому auto range = myMultiMap.equal_range(key); for_each(range.first, range.second, [&]( decltype( *range.first ) const& item ) { // здесь идет код } ); - person CashCow; 23.06.2017

Вы можете легко сгенерировать возрастающую последовательность в C++11, используя std::iota():

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

template<typename T>
std::vector<T> range(T start, T end)
{
  std::vector<T> r(end+1-start, T(0));
  std::iota(r.begin(), r.end(), T(start));//increasing sequence
  return r;
}

int main(int argc, const char * argv[])
{
  for(auto i:range<int>(-3,5))
    std::cout<<i<<std::endl;

  return 0;
}
person blue scorpion    schedule 25.01.2016
comment
Класс range должен моделировать диапазон. Однако вы буквально строите его. Это пустая трата памяти и доступа к памяти. Решение сильно избыточно, потому что вектор не содержит реальной информации, кроме количества элементов и значения первого элемента (если он существует). - person not-a-user; 03.03.2017
comment
Да, это очень неэффективно. - person Omnifarious; 30.10.2017