Apakah ada kelas range dalam C ++ 11 untuk digunakan dengan range based for loops?

101

Saya mendapati diri saya menulis ini beberapa saat yang lalu:

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>();
}

Dan ini memungkinkan saya untuk menulis hal-hal seperti ini:

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

Sekarang, saya tahu apa yang saya tulis mungkin bukan kode terbaik. Dan mungkin ada cara untuk membuatnya lebih fleksibel dan berguna. Tapi bagi saya sepertinya sesuatu seperti ini seharusnya dijadikan bagian dari standar.

Jadi begitu? Apakah semacam pustaka baru ditambahkan untuk iterator melalui rentang bilangan bulat, atau mungkin rentang umum nilai skalar yang dihitung?

Beraneka ragam
sumber
17
+1. Saya ingin memiliki kelas seperti itu di utilitas saya. :-)
Nawaz
2
Ngomong-ngomong, apa gunanya menulis rangefungsi template? Itu tidak menambahkan apa pun ke penggunaan yang range_classdigunakan. Maksudku, range<0,10>()dan range_class<0,10>()terlihat persis sama!
Nawaz
2
@Nawaz: Ya, Anda benar. Saya memiliki beberapa penglihatan aneh bahwa saya dapat membuat fungsi menangani yang membedakan antara kasing dinamis dan statis, tetapi saya rasa itu tidak bisa dilakukan.
Omnifarious
2
@iammilind: Nawaz mengajukan pertanyaan yang sama 35 menit sebelumnya;)
Sebastian Mach
3
Untuk menjadi teliti, saya pikir implementasi ini memiliki bug, yaitu Anda tidak dapat menggunakannya untuk mengulangi seluruh rentang integer. Jika Anda memasukkan INT_MIN dan INT_MAX sebagai argumen template Anda, INT_MAX saat ditambahkan akan melimpah memberikan INT_MIN dan menyebabkan loop tak terbatas. "end" di STL seharusnya menjadi "satu melewati akhir" yang tidak bisa masuk ke dalam tipe integer itu sendiri, jadi saya tidak tahu bahwa ini sebenarnya dapat diimplementasikan secara efisien untuk tipe integer terluas pada platform tertentu. Untuk tipe integer yang lebih kecil Anda selalu dapat membuatnya menggunakan tipe yang lebih luas secara internal ...
Joseph Garvin

Jawaban:

59

Pustaka standar C ++ tidak memilikinya, tetapi Boost.Range memiliki boost :: count_range , yang tentunya memenuhi syarat. Anda juga bisa menggunakan boost :: irange , yang sedikit lebih fokus dalam cakupannya.

Pustaka rentang C ++ 20 akan memungkinkan Anda melakukan ini melalui view::iota(start, end).

Nicol Bolas
sumber
3
Ya, itulah sifat dari apa yang saya cari. Saya senang Boost melakukannya. Saya sedih panitia standar tidak memasukkannya untuk alasan apa pun. Ini akan menjadi pelengkap yang bagus untuk fitur range-base-for.
Omnifarious
Jawaban ini lebih baik menjawab pertanyaan langsung saya, maka saya akan memilihnya, meskipun jawaban Nawaz sangat bagus.
Omnifarious
6
Ada banyak kemajuan akhir-akhir ini untuk mendapatkan rentang ke standar (N4128). Lihat github.com/ericniebler/range-v3 untuk proposal dan implementasi referensi.
Ela782
1
@ Ela782: ... namun sepertinya kita tidak akan melihatnya di C ++ 17, bukan?
einpoklum
1
@Andreas Ya, range membuatnya menjadi TS beberapa waktu yang lalu, tapi saya rasa tidak ada / pernah ada implementasi referensi yang membuatnya menjadi kompiler utama di bawah std::experimental::rangesnamespace. range-v3selalu semacam implementasi referensi yang akan saya katakan. Tapi sekarang saya percaya hal-hal dasar juga baru-baru ini telah dipilih ke dalam C ++ 20, jadi kami akan std::segera mendapatkannya! :-)
Ela782
47

Sejauh yang saya tahu, tidak ada kelas seperti itu di C ++ 11.

Bagaimanapun, saya mencoba meningkatkan penerapan Anda. Saya membuatnya non-template , karena saya tidak melihat ada keuntungan dalam membuatnya template . Sebaliknya, ini memiliki satu kelemahan utama: bahwa Anda tidak dapat membuat rentang pada waktu proses, karena Anda perlu mengetahui argumen template pada waktu kompilasi itu sendiri.

//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!

Ini kodenya:

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

Kode tes:

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

Keluaran:

10 11 12 13 14 15 16 17 18 19

Demo onine .

Nawaz
sumber
3
Saya suka itu. Saya memikirkan tentang versi non-template. Dan saya kira kompilator yang baik akan mengoptimalkannya dengan baik dalam kasus ketika nilai sebenarnya konstan. Saya harus mengujinya.
Omnifarious
10
@Nawaz: Aku masih template itu, pada jenis terpisahkan :) Saya juga akan mengusulkan untuk alias iteratoruntuk const_iterator, memiliki iteratorberasal dari std::iteratordan memiliki rangemenerapkan cbegindan cend. Oh dan ... mengapa iterator::operator++mengembalikan referensi const ?
Matthieu M.
6
@RedX: Dijkstra memiliki tulisan yang bagus tentang mengapa pelabelan rentang adalah yang terbaik [begin, end). @OP: +1 untuk permainan kata-kata pada putaran berbasis jangkauan yang bukan permainan kata-kata :-)
Kerrek SB
2
Keuntungan dari versi non-template adalah panjang loop Anda tidak perlu diketahui pada waktu kompilasi. Anda dapat membuat template tipe integer, tentu saja.
CashCow
2
@weeska: Kelebihan beban itu seharusnya mengimplementasikan kenaikan postfix v++yang seharusnya mengembalikan nilai sebelum operasi kenaikan terjadi. Saya menyarankan Anda untuk menjelajahi perbedaan antara ++idan di i++mana idinyatakan int.
Nawaz
13

Saya menulis perpustakaan yang disebut rangeuntuk tujuan yang persis sama kecuali itu adalah rentang waktu berjalan, dan ide dalam kasus saya berasal dari Python. Saya menganggap versi waktu kompilasi, tetapi menurut pendapat saya yang sederhana, tidak ada keuntungan nyata untuk mendapatkan versi waktu kompilasi. Anda dapat menemukan pustaka tersebut di bitbucket, dan pustaka itu berada di bawah Lisensi Boost: Range . Ini adalah pustaka satu tajuk, kompatibel dengan C ++ 03 dan berfungsi seperti pesona dengan loop berbasis rentang di C ++ 11 :)

Fitur :

  • Wadah akses acak sejati dengan semua lonceng dan peluit!

  • Rentang dapat dibandingkan secara leksikografis.

  • Dua fungsi exist(mengembalikan bool), dan find(mengembalikan iterator) untuk memeriksa keberadaan angka.

  • Perpustakaan diuji unit menggunakan CATCH .

  • Contoh penggunaan dasar, bekerja dengan kontainer standar, bekerja dengan algoritme standar, dan bekerja dengan loop berbasis rentang.

Berikut adalah pengantar satu menit . Akhirnya, saya menyambut baik saran tentang perpustakaan kecil ini.

AraK
sumber
Pendahuluan satu menit mengatakan bahwa saya tidak memiliki akses ke Wiki. Anda perlu menjadikan wiki Anda publik.
Nicol Bolas
@Nicol Bolas Maaf, sekarang sudah terbuka :)
AraK
Terima kasih untuk ini, luar biasa. Saya merasa lebih banyak orang harus mengetahuinya.
Rafael Kitover
5

Saya menemukan bahwa boost::irangeitu jauh lebih lambat daripada loop integer kanonik. Jadi saya menetapkan solusi yang jauh lebih sederhana berikut menggunakan makro preprocessor:

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

Kemudian Anda dapat melakukan loop seperti ini:

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

Kisaran ini secara otomatis dimulai dari nol. Ini dapat dengan mudah diperpanjang untuk memulai dari nomor tertentu.

pengguna2664470
sumber
7
Perhatikan bahwa for (RANGE(i, flag? n1: n2))akan memberikan hasil yang mengejutkan, karena Anda gagal mengikuti salah satu Aturan Dasar Makro Non-Jahat, yaitu mengurung semua parameter Anda (termasuk, dalam hal ini, b). Pendekatan Anda juga tidak memberikan manfaat kinerja apa pun dibandingkan non-makro, pendekatan berbasis "objek jangkauan" (mis . Jawaban Nawaz ).
Quuxplusone
2

Ini adalah formulir sederhana yang bekerja dengan baik untuk saya. Apakah ada risiko dalam pendekatan saya?

r_iteratoradalah tipe yang berperilaku, sebanyak mungkin, seperti a long int. Oleh karena itu banyak operator seperti ==dan ++, cukup melewati ke long int. Saya 'mengekspos' int panjang yang mendasari melalui operator long intdan operator long int &konversi.

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

( Sunting: - kita dapat membuat metode rangestatis daripada const.)

Aaron McDaid
sumber
1

Ini mungkin sedikit terlambat tetapi saya baru saja melihat pertanyaan ini dan saya telah menggunakan kelas ini untuk sementara waktu sekarang:

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

Penggunaan:

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;
}
OneOfOne
sumber
0

sudahkah kamu mencoba menggunakan

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

Sebagian besar waktu sesuai dengan tagihan.

Misalnya

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);

}

Perhatikan bahwa printInt dapat OFC diganti dengan lambda di C ++ 0x. Juga satu variasi kecil dari penggunaan ini (hanya untuk random_iterator)

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

Hanya untuk iterator Fwd

 for_each(advance(v.begin(),5),advance(v.begin(),10),printInt);
Ajeet Ganga
sumber
Bagaimana Anda akan menggunakan ini? Saya menduga Anda akan menggunakan lambda untuk fungsinya, tapi saya tidak yakin.
Omnifarious
1
Akan memberi tahu Anda, tetapi Anda akan menerima jawabannya jika menurut Anda itu cara yang tepat untuk menggunakannya. : P Bercanda. Contoh sudah diposting.
Ajeet Ganga
Anda dapat menggunakan lambda di sini jadi auto range = myMultiMap.equal_range (key); for_each (range.first, range.second, [&] (dectype (* range.first) const & item) {// kode ditempatkan di sini});
CashCow
-3

Anda dapat dengan mudah membuat urutan yang meningkat di C ++ 11 menggunakan 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;
}
kalajengking biru
sumber
3
The rangekelas akan memodelkan jangkauan. Bagaimanapun Anda sedang membangunnya. Itu adalah pemborosan memori dan akses memori. Solusinya sangat redundan, karena vektor tidak menyimpan informasi nyata kecuali jumlah elemen dan nilai elemen pertama (jika ada).
bukan-pengguna
Ya, ini sangat tidak efisien.
Omnifarious