มีคลาสเรนจ์ใน C ++ 11 สำหรับใช้กับ range ตามลูปหรือไม่?


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: Nawaz ถามคำถามเดียวกันก่อนหน้าคุณ 35 นาที;)
Sebastian Mach

3
เพื่อเป็นการอวดดีฉันคิดว่าการใช้งานนี้มีข้อบกพร่องซึ่งคุณไม่สามารถใช้เพื่อทำซ้ำในช่วงจำนวนเต็มทั้งหมดได้ หากคุณเสียบ INT_MIN และ INT_MAX เป็นอาร์กิวเมนต์เทมเพลตของคุณ INT_MAX เมื่อเพิ่มขึ้นจะมากเกินไปจะให้ INT_MIN และทำให้เกิดลูปที่ไม่มีที่สิ้นสุด "end" ใน STL ควรจะเป็น "one เลยจุดสิ้นสุด" ซึ่งไม่สามารถใส่ลงในประเภทจำนวนเต็มได้เองดังนั้นฉันจึงไม่รู้ว่าสิ่งนี้สามารถใช้งานได้อย่างมีประสิทธิภาพสำหรับประเภทจำนวนเต็มที่กว้างที่สุดบนแพลตฟอร์มที่กำหนด สำหรับประเภทจำนวนเต็มขนาดเล็กคุณสามารถทำให้มันใช้ประเภทที่กว้างขึ้นภายในได้เสมอ ...
Joseph Garvin

คำตอบ:


59

ไลบรารีมาตรฐาน C ++ ไม่มี แต่Boost.Range มี boost :: count_rangeซึ่งมีคุณสมบัติครบถ้วน คุณยังสามารถใช้boost :: irangeซึ่งเน้นในขอบเขตมากขึ้นเล็กน้อย

ไลบรารีช่วงของ C ++ 20 จะช่วยให้คุณทำสิ่งนี้ผ่านview::iota(start, end).


3
ใช่นั่นเป็นธรรมชาติของสิ่งที่ฉันกำลังมองหา ฉันดีใจที่ Boost ทำได้ ฉันเสียใจที่คณะกรรมการมาตรฐานไม่ได้รวมไว้ด้วยเหตุผลใดก็ตาม มันจะเป็นส่วนเสริมที่ดีสำหรับคุณลักษณะ range-base-for
Omnifarious

คำตอบนี้ตอบคำถามตรงของฉันได้ดีกว่าดังนั้นฉันจึงจะเลือกแม้ว่าคำตอบของ Nawaz จะดีมากก็ตาม
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

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++ส่งคืนการอ้างอิงconst
Matthieu M.

6
@RedX: Dijkstra[begin, end)มีดีเขียนขึ้นเกี่ยวกับเหตุผลที่ติดฉลากเป็นช่วงที่ดีที่สุดเป็น @OP: +1 สำหรับการเล่นในลูปตามระยะที่ไม่ใช่การเล่น :-)
Kerrek SB

2
ข้อดีของเวอร์ชันที่ไม่ใช่เทมเพลตคือไม่จำเป็นต้องทราบความยาวของลูปในขณะคอมไพล์ คุณสามารถทำให้ประเภทจำนวนเต็มเป็นเทมเพลตได้แน่นอน
CashCow

2
@weeska: การโอเวอร์โหลดนั้นควรจะใช้การเพิ่ม postfix v++ซึ่งควรจะคืนค่าก่อนที่การดำเนินการเพิ่มจะเกิดขึ้น ผมแนะนำให้คุณไปสำรวจความแตกต่างระหว่าง++iและi++ที่ถูกประกาศให้เป็นi int
Nawaz

13

ฉันเขียนไลบรารีที่เรียกrangeเพื่อวัตถุประสงค์เดียวกันทุกประการยกเว้นว่าเป็นช่วงเวลาทำงานและแนวคิดในกรณีของฉันมาจาก Python ฉันถือว่าเป็นเวอร์ชันคอมไพล์ไทม์ แต่ในความเห็นที่ต่ำต้อยของฉันไม่มีประโยชน์ที่แท้จริงที่จะได้รับเวอร์ชันเวลาคอมไพล์ คุณสามารถค้นหาห้องสมุด bitbucket และมันก็อยู่ภายใต้ใบอนุญาต Boost: ช่วง เป็นไลบรารีส่วนหัวเดียวเข้ากันได้กับ C ++ 03 และใช้งานได้อย่างมีเสน่ห์ด้วย range-based สำหรับลูปใน C ++ 11 :)

คุณสมบัติ :

  • ภาชนะสุ่มที่แท้จริงพร้อมระฆังและนกหวีด!

  • ช่วงสามารถเปรียบเทียบได้ตามศัพท์

  • สองฟังก์ชั่นexist(ส่งคืนบูล) และfind(ส่งคืนตัววนซ้ำ) เพื่อตรวจสอบการมีอยู่ของตัวเลข

  • ห้องสมุดเป็นหน่วยทดสอบโดยใช้ที่จับได้

  • ตัวอย่างการใช้งานพื้นฐานการทำงานกับคอนเทนเนอร์มาตรฐานการทำงานกับอัลกอริทึมมาตรฐานและการทำงานกับช่วงที่อิงกับลูป

นี่คือการแนะนำหนึ่งนาที สุดท้ายนี้ฉันยินดีรับข้อเสนอแนะเกี่ยวกับห้องสมุดเล็ก ๆ นี้


บทนำหนึ่งนาทีบอกว่าฉันไม่สามารถเข้าถึง Wiki ได้ คุณต้องทำให้วิกิของคุณเป็นสาธารณะ
Nicol Bolas

@Nicol Bolas ฉันขอโทษจริงๆตอนนี้เปิดเผยต่อสาธารณะแล้ว :)
AraK

ขอบคุณสำหรับสิ่งนี้มันยอดเยี่ยมมาก ฉันรู้สึกว่ามีคนรู้เรื่องนี้มากขึ้น
Rafael Kitover

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) นอกจากนี้แนวทางของคุณยังไม่ได้ให้ประโยชน์ด้านประสิทธิภาพใด ๆ เหนือแนวทางที่เป็น "วัตถุช่วง" ที่ไม่ใช่มาโคร (เช่นคำตอบของ 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 สามารถแทนที่ OFC ด้วยแลมด้าใน C ++ 0x นอกจากนี้ยังสามารถใช้รูปแบบอื่น ๆ อีกเล็กน้อย (สำหรับ random_iterator)

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

สำหรับ Fwd iterator เท่านั้น

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

คุณจะใช้สิ่งนี้อย่างไร? ฉันเดาว่าคุณจะใช้แลมด้าสำหรับฟังก์ชันนี้ แต่ฉันไม่แน่ใจ
Omnifarious

1
จะบอกคุณ แต่คุณจะยอมรับคำตอบถ้าคุณคิดว่ามันเป็นวิธีที่ถูกต้องที่จะใช้มัน : P ล้อเล่น. โพสต์ตัวอย่างเรียบร้อยแล้ว
Ajeet Ganga

คุณสามารถใช้แลมด้าได้ที่นี่เพื่อให้ช่วงอัตโนมัติ = myMultiMap.equal_range (คีย์); for_each (range.first, range.second, [&] (Decltype (* range.first) const & item) {// code ไปที่นี่});
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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.