Есть ли класс диапазона в С ++ 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.