Чи існує клас діапазону в C ++ 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. Я хотів би, щоб такі заняття були у моїх комунальних службах. :-)
Наваз

2
До речі, в чому сенс написання rangeшаблону функції? Це не додає нічого до того, у якому range_classвін використовується. Я маю на увазі, range<0,10>()і range_class<0,10>()виглядайте точно так само!
Наваз

2
@Nawaz: Так, ти маєш рацію. У мене було якесь дивне бачення того, що я можу змусити функцію управління диференціювати між динамічним та статичним випадком, але я не думаю, що це можна зробити.
всезначний

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

3
Щоб бути педантичним, я думаю, що в цій реалізації є помилка, яка полягає в тому, що ви не можете використовувати її для ітерації у всьому цілому діапазоні. Якщо ви підключите INT_MIN та INT_MAX як аргументи шаблону, INT_MAX при збільшенні переповнюється, даючи INT_MIN і спричиняючи нескінченні петлі. "end" в STL повинен бути "один минулий кінець", який не може поміститися всередині самого цілого типу, тому я не знаю, що це може бути реально реалізовано ефективно для самого широкого цілого типу на даній платформі. Для менших цілочисельних типів ви завжди можете змусити їх використовувати ширший тип внутрішньо ...
Джозеф Гарвін

Відповіді:


59

Стандартна бібліотека C ++ не має такої, але Boost.Range має boost :: counting_range , що, безумовно, відповідає. Ви також можете використовувати boost :: irange , який є трохи більш орієнтованим за обсягом.

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


3
Так, це безумовно природа того, що я б шукав. Я радий, що Boost це зробив. Мені сумно, що стандартний комітет не включив його з будь-якої причини. Це було б чудовим доповненням до функції діапазону-база-для.
всезначний

Ця відповідь краще відповідає на моє пряме запитання, і тому я виберу її, навіть якщо відповідь Наваза дуже хороша.
всезначний

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

47

Наскільки мені відомо, такого класу в С ++ 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

Онема демонстрація .


3
Мені це подобається. Я подумав про нешаблонну версію. І я вважаю, що хороший компілятор оптимізував би це в тому випадку, коли значення насправді є постійними. Мені доведеться це перевірити.
всезначний

10
@Nawaz: Я все-таки шаблонував би його на цілісний тип :) Я також пропоную псевдонім iteratorдо const_iterator, матимемо iteratorпоходження від std::iteratorі матимемо rangeрэалізацію cbeginта cend. О та ... чому iterator::operator++повертає посилання на const ?
Матьє М.

6
@RedX: Dijkstra добре описав, чому маркування діапазону найкраще [begin, end). @OP: +1 для каламбура на циклі, що базуються на діапазоні, що не є каламбуром :-)
Kerrek SB

2
Перевага нешаблонної версії полягає в тому, що довжини циклів не потрібно знати під час компіляції. Ви, звичайно, можете зробити цілий тип шаблоном.
CashCow

2
@weeska: Це перевантаження повинно реалізувати приріст постфікса, v++який повинен повернути значення до того, як відбулася операція збільшення. Я б порадив вам вивчити різницю між ++iі i++де iоголошуються int.
Наваз

13

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

Особливості :

  • Справжній контейнер з випадковим доступом з усіма дзвіночками!

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

  • Дві функції exist(повертає bool) і find(повертає ітератор) для перевірки існування числа.

  • Бібліотека перевірена одиницею за допомогою CATCH .

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

Ось однохвилинне вступ . Нарешті, я вітаю будь-які пропозиції щодо цієї крихітної бібліотеки.


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

@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))це дасть дивовижні результати, оскільки ви не дотримувались одного з Основних правил не злого макроса, який полягає в дужці всіх ваших параметрів (у тому числі, у цьому випадку b). Ваш підхід також не дає ніякої користі від продуктивності порівняно з не-макросним підходом, що базується на "об'єкті діапазону" (наприклад , відповідь Наваза ).
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статичних замість const.)


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 можна 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);

Як би ти цим скористався? Я здогадуюсь, ви б використовували лямбда для функції, але я не впевнений.
всезнайко

1
Я б сказав вам, але ви отримаєте відповідь, якщо вважаєте, що це правильний спосіб її використання. : P жартую. Приклад уже розміщено.
Ajeet Ganga

Ви можете використовувати лямбда тут, щоб автоматично діапазон = myMultiMap.equal_range (ключ); 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Клас повинен моделювати діапазон. Однак ви буквально це будуєте. Це витрата пам’яті та доступу до пам’яті. Рішення є надлишковим, оскільки вектор не містить реальної інформації, крім кількості елементів та значення першого елемента (якщо він існує).
не-користувач

Так, це дуже неефективно.
всезначний
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.