Döngüler için aralığa dayalı olarak kullanılmak üzere C ++ 11'de bir aralık sınıfı var mı?


101

Biraz önce kendimi bunu yazarken buldum:

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

Bu da şöyle şeyler yazmama izin veriyor:

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

Şimdi, yazdığımın belki de en iyi kod olmadığını biliyorum. Ve belki onu daha esnek ve kullanışlı hale getirmenin bir yolu vardır. Ama bana öyle geliyor ki, bunun gibi bir şey standardın bir parçası olmalıydı.

Öyle mi? Bir tam sayı aralığında yineleyiciler için bir tür yeni kitaplık eklendi mi, yoksa genel bir hesaplanmış skaler değerler aralığı mı?


17
+1. Yardımcı programlarımda bu tür dersler olmasını istiyorum. :-)
Nawaz

2
Bu arada, rangeşablon işlevi yazmanın amacı nedir? range_classKullanıldığı kullanıma hiçbir şey eklemez . Demek istediğim range<0,10>()ve range_class<0,10>()tamamen aynı görünüyorsun!
Nawaz

2
@Nawaz: Evet, haklısın. Fonksiyonun dinamik ve statik durum arasında ayrım yapmasını sağlayabileceğime dair garip bir vizyonum vardı, ancak yapılabileceğini sanmıyorum.
Omnifarious

2
@iammilind: Nawaz aynı soruyu sizden 35 dakika önce sordu;)
Sebastian Mach

3
Bilgiçlik taslamak gerekirse, bu uygulamanın bir hatası olduğunu düşünüyorum, bu da onu tüm tamsayı aralığında yinelemek için kullanamayacağınızdır. INT_MIN ve INT_MAX'ı şablon bağımsız değişkenleriniz olarak eklerseniz, artırıldığında INT_MAX taşma INT_MIN verir ve sonsuz döngülere neden olur. STL'deki "son" un, tamsayı türünün kendisine sığamayan "sonu bir geçmiş" olması gerekiyor, bu nedenle bunun belirli bir platformdaki en geniş tam sayı türü için gerçekten verimli bir şekilde uygulanabileceğini bilmiyorum. Daha küçük tam sayı türleri için her zaman dahili olarak daha geniş bir tür kullanmasını sağlayabilirsiniz ...
Joseph Garvin

Yanıtlar:


59

C ++ standart kitaplığında bir tane yoktur, ancak Boost.Range kesinlikle uygun olan boost :: counting_range'e sahiptir . Ayrıca , kapsamı biraz daha odaklı olan boost :: irange'ı da kullanabilirsiniz .

C ++ 20'nin aralık kitaplığı, bunu aracılığıyla yapmanıza izin verecektir view::iota(start, end).


3
Evet, kesinlikle aradığım şeyin doğası bu. Boost'un yaptığına sevindim. Standart komitenin bunu herhangi bir nedenle dahil etmediğine üzüldüm. Menzil bazlı özelliğin harika bir tamamlayıcısı olurdu.
Omnifarious

Bu cevap direkt sorumu daha iyi cevaplıyor ve bu yüzden Nawaz'ın cevabı çok iyi olsa da onu seçeceğim.
Omnifarious

6
Son zamanlarda, aralıkları standarda (N4128) sokmak için çok ilerleme kaydedildi. Bir teklif ve referans uygulaması için github.com/ericniebler/range-v3 adresine bakın .
Ela782

1
@ Ela782: ... ve yine de C ++ 17'de görmeyeceğiz, değil mi?
einpoklum

1
@Andreas Evet, aralıklar onu bir süre önce bir TS'ye dönüştürdü, ancak std::experimental::rangesad alanı altındaki büyük derleyicilerde onu yapan bir referans uygulaması olduğunu / olduğunu sanmıyorum . range-v3her zaman bir çeşit referans uygulamasıydı diyeceğim. Ama şimdi temel ürün yelpazesinin de yakın zamanda C ++ 20'de oylandığına inanıyorum, bu yüzden gerçekten std::yakında alacağız ! :-)
Ela782

47

Bildiğim kadarıyla C ++ 11'de böyle bir sınıf yok.

Her neyse, uygulamanızı iyileştirmeye çalıştım. Ben yapılan numune olmayan herhangi görmüyorum olarak, avantaj o yapımında şablona . Aksine, büyük bir dezavantajı vardır: derleme zamanında şablon argümanlarını bilmeniz gerektiğinden, çalışma zamanında aralığı oluşturamazsınız.

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

İşte kod:

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

Test kodu:

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

Çıktı:

10 11 12 13 14 15 16 17 18 19

Onine demosu .


3
Bunu sevdim. Şablon olmayan bir versiyon düşündüm. Ve sanırım iyi bir derleyici, değerlerin aslında sabit olduğu durumda onu iyi optimize eder. Bunu test etmem gerekecek.
Omnifarious

10
@Nawaz: İntegral türünde hala şablon yapardım :) Ayrıca takma ad iteratoryapmayı const_iterator, iteratortüretmeyi std::iteratorve rangeuygulama cbeginve cend. Oh ve ... neden iterator::operator++bir const referansı döndürür ?
Matthieu M.

6
@RedX: Dijkstra , aralık etiketlemesinin neden en iyisi olduğuna dair iyi bir yazıya sahip [begin, end). @OP: Bir kelime oyunu olmayan menzile dayalı döngülerde kelime oyunu için +1 :-)
Kerrek SB

2
Şablon olmayan sürümün avantajı, döngülerinizin uzunluklarının derleme sırasında bilinmesine gerek olmamasıdır. Elbette tamsayı türünü şablon haline getirebilirsiniz.
CashCow

2
@weeska: Bu aşırı yüklemenin , artırma işlemi gerçekleşmeden öncev++ değeri döndürmesi beklenen son ek artışını uygulaması gerekiyordu . Ben arasındaki farkı keşfetmek için öneriyorum ve nerede olduğu beyan edilir . ++ii++iint
Nawaz

13

rangeBir çalışma zamanı aralığı olması dışında tamamen aynı amaç için çağrılan bir kitaplık yazdım ve benim durumumdaki fikir Python'dan geldi. Derleme zamanı sürümünü düşünmüştüm, ancak mütevazi görüşüme göre derleme zamanı sürümünü elde etmenin gerçek bir avantajı yok. Kitaplığı bitbucket'ta bulabilirsiniz ve Yükseltme Lisansı: Aralık altındadır . C ++ 03 ile uyumlu, tek başlıklı bir kitaplıktır ve C ++ 11'deki aralık tabanlı döngülerle cazibe gibi çalışır :)

Özellikler :

  • Tüm zil ve ıslıklarla gerçek bir rastgele erişimli konteyner!

  • Aralıklar sözlükbilimsel olarak karşılaştırılabilir.

  • Bir sayının varlığını kontrol etmek için iki işlev exist(bool döndürür) ve find(yineleyici döndürür).

  • Kitaplık, CATCH kullanılarak birim testine tabi tutulmuştur .

  • Temel kullanım örnekleri, standart konteynerlerle çalışma, standart algoritmalarla çalışma ve döngüler için aralık tabanlı çalışma.

İşte bir dakikalık bir giriş . Son olarak, bu küçük kitaplık hakkında herhangi bir öneriye açığım.


Bir dakikalık giriş, Wiki'ye erişimim olmadığını söylüyor. Wiki'nizi herkese açık hale getirmelisiniz.
Nicol Bolas

@Nicol Bolas Gerçekten üzgünüm, şimdi halka açık :)
AraK

Bunun için teşekkürler, harika. Daha fazla insanın bilmesi gerektiğini düşünüyorum.
Rafael Kitover

5

Bunun boost::irangekanonik tamsayı döngüsünden çok daha yavaş olduğunu buldum . Bu yüzden, bir önişlemci makrosu kullanarak aşağıdaki çok daha basit çözüme karar verdim:

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

O zaman şu şekilde döngü yapabilirsiniz:

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

Bu aralık otomatik olarak sıfırdan başlar. Belirli bir sayıdan başlamak için kolayca genişletilebilir.


7
for (RANGE(i, flag? n1: n2))Bunun şaşırtıcı sonuçlar doğuracağına dikkat edin , çünkü Kötü Olmayan Makroların Temel Kurallarından birini, yani tüm parametrelerinizi parantez içinde tutmayı başaramadınız (bu durumda dahil b). Yaklaşımınız ayrıca makro olmayan, "aralık nesnesi" temelli yaklaşıma göre herhangi bir performans avantajı sağlamaz (örn. Nawaz'ın cevabı ).
Quuxplusone

2

İşte benim için güzel çalışan daha basit bir form. Yaklaşımımda herhangi bir risk var mı?

r_iteratormümkün olduğunca a gibi davranan bir türdür long int. Gibi nedenle birçok operatör ==ve ++basitçe için geçmesi long int. Temel uzun int'i operator long intve operator long int &dönüşümleri aracılığıyla 'açığa çıkarırım' .

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

( Düzenleme: - rangeconst yerine statik yöntemler yapabiliriz .)


1

Bu biraz geç olabilir ama bu soruyu yeni gördüm ve bir süredir bu dersi kullanıyorum:

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

Kullanım:

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

kullanmayı denedin mi

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

Çoğu zaman faturaya uyuyor.

Örneğin

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'in OFC'nin C ++ 0x'deki bir lambda ile değiştirilebileceğini unutmayın. Ayrıca bu kullanımın küçük bir varyasyonu daha olabilir (kesinlikle random_iterator için)

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

Yalnızca Fwd yineleyici için

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

Bunu nasıl kullanırsın? İşlev için bir lambda kullanacağınızı tahmin ediyorum, ancak emin değilim.
Omnifarious

1
Size söylerdim, ancak doğru şekilde kullanacağınızı düşünüyorsanız yanıtı kabul etmiş olacaksınız. : P Şaka. Örnek zaten yayınlandı.
Ajeet Ganga

Burada bir lambda kullanabilirsiniz, böylece auto range = myMultiMap.equal_range (key); for_each (aralık.first, aralık.saniye, [&] (decltype (* aralık.first) sabit & öğe) {// kod buraya gelir});
CashCow

-3

C ++ 11'de std :: iota () kullanarak kolayca artan bir dizi oluşturabilirsiniz:

#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
rangeSınıf aralığını modellemek olacaktır. Ancak tam anlamıyla inşa ediyorsunuz. Bu bir hafıza ve hafıza kaybıdır. Çözüm, fazlasıyla fazlalıktır, çünkü vektör, eleman sayısı ve ilk elemanın değeri (eğer varsa) dışında gerçek bilgi tutmaz.
kullanıcı değil

Evet, bu çok verimsiz.
Omnifarious
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.