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


101

Я обнаружил, что пишу это совсем недавно:

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
}

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

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


17
+1. Хотелось бы, чтобы такие классы были в моих утилитах. :-)
Nawaz

2
Кстати, а какой смысл писать rangeшаблонную функцию? Это не добавляет ничего к использованию, в котором range_classиспользуется. Я имею в виду, range<0,10>()и range_class<0,10>()выглядишь точно так же!
Nawaz

2
@Nawaz: Да, ты прав. У меня было странное видение, что я могу сделать дескриптор функции, дифференцируя динамический и статический случай, но я не думаю, что это возможно.
Omnifarious

2
@iammilind: Наваз задал тот же вопрос на 35 минут раньше вас;)
Себастьян Мах

3
Чтобы быть педантичным, я думаю, что в этой реализации есть ошибка, заключающаяся в том, что вы не можете использовать ее для итерации по всему целочисленному диапазону. Если вы подключаете INT_MIN и INT_MAX в качестве аргументов шаблона, INT_MAX при увеличении переполнения даст INT_MIN и вызовет бесконечные циклы. «конец» в STL должен быть «один за концом», что не может уместиться внутри самого целочисленного типа, поэтому я не знаю, действительно ли это может быть эффективно реализовано для самого широкого целочисленного типа на данной платформе. Для меньших целочисленных типов вы всегда можете заставить их использовать более широкий тип внутри ...
Джозеф Гарвин

Ответы:


59

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

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


3
Да, это определенно характер того, что я ищу. Я рад, что Boost сделал это. Мне грустно, что комитет по стандартам не включил его по какой-либо причине. Это было бы отличным дополнением к функции базового диапазона.
Omnifarious

Этот ответ лучше отвечает на мой прямой вопрос, поэтому я выберу его, хотя ответ Наваза очень хорош.
Omnifarious

6
В последнее время был достигнут значительный прогресс, чтобы включить диапазоны в стандарт (N4128). См. Github.com/ericniebler/range-v3 для предложения и справочной реализации.
Ela782

1
@ Ela782: ... и все же кажется, что мы не увидим этого в C ++ 17, верно?
einpoklum

1
@Andreas Да, некоторое время назад диапазоны были включены в TS, но я не думаю, что существует / была когда-либо эталонная реализация, которая делала бы ее в основных компиляторах в std::experimental::rangesпространстве имен. range-v3Я бы сказал, всегда была эталонной реализацией. Но теперь я считаю, что базовый диапазон диапазонов также недавно был включен в C ++ 20, так что мы действительно std::скоро его добавим! :-)
Ela782 03

47

Насколько мне известно, в 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

Демо Onine .


3
Мне это нравится. Я подумал о версии без шаблона. И я полагаю, что хороший компилятор хорошо бы его оптимизировал в случае, когда значения действительно постоянны. Я должен это проверить.
Omnifarious

10
@Nawaz: Я бы по-прежнему использовал шаблон для интегрального типа :) Я бы также предложил использовать псевдоним iteratorдля const_iterator, iteratorунаследовать от std::iteratorи rangeреализовать cbeginи cend. Да и ... почему iterator::operator++возвращает константную ссылку?
Matthieu M.

6
@RedX: У Дейкстры есть хорошая статья о том, почему маркировка диапазона лучше всего [begin, end). @OP: +1 за каламбур на циклах на основе диапазона, что не является каламбуром :-)
Керрек С.Б.

2
Преимущество версии без шаблона заключается в том, что длину ваших циклов не нужно знать во время компиляции. Конечно, вы можете сделать целочисленный тип шаблонным.
CashCow

2
@weeska: Предполагается, что эта перегрузка реализует постфиксное приращение, v++которое должно возвращать значение до того, как произошла операция приращения. Я бы посоветовал изучить разницу между заявленным ++iи i++где он iнаходится int.
Nawaz

13

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

Особенности :

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

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

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

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

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

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


В одноминутном введении говорится, что у меня нет доступа к Wiki. Вам необходимо сделать свою вики общедоступной.
Никол Болас

@Nicol Bolas, мне очень жаль, теперь это публично :)
AraK

Спасибо за это, это потрясающе. Я чувствую, что об этом должно знать больше людей.
Рафаэль Китовер

5

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

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

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

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

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


7
Обратите внимание, это for (RANGE(i, flag? n1: n2))приведет к удивительным результатам, поскольку вы не выполнили одно из основных правил макросов Non-Evil, заключающееся в заключении в скобки всех ваших параметров (включая, в данном случае, b). Ваш подход также не дает никакого преимущества в производительности по сравнению с немакросъемочным подходом, основанным на "объектах диапазона" (например , ответ Nawaz ).
Quuxplusone

2

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

r_iterator- это тип, который ведет себя, насколько это возможно, как long int. Поэтому многие операторы, такие как ==и ++, просто передаются в long int. Я «разоблачить» лежащий в основе длинного 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статическими вместо константы.)


1

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

#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;
}

0

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

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

В большинстве случаев отвечает всем требованиям.

Например

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 может быть заменен на лямбда в 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);

Как бы вы это использовали? Я предполагаю, что вы бы использовали лямбда для функции, но я не уверен.
Omnifarious

1
Сказал бы вам, но вам придется принять ответ, если вы считаете, что это правильный способ его использования. : P Шучу. Пример выложил уже.
Ajeet Ganga

Здесь вы можете использовать лямбду, поэтому auto range = myMultiMap.equal_range (key); for_each (range.first, range.second, [&] (decltype (* range.first) const & item) {// здесь идет код});
CashCow

-3

Вы можете легко сгенерировать возрастающую последовательность в 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;
}

3
rangeКласс должен моделировать диапазон. Однако вы его буквально строите. Это пустая трата памяти и обращений к ней. Решение очень избыточно, потому что вектор не содержит реальной информации, кроме количества элементов и значения первого элемента (если он существует).
not-a-user

Да, это очень неэффективно.
Omnifarious
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.