Có lớp phạm vi trong C ++ 11 để sử dụng với vòng lặp dựa trên phạm vi không?


101

Tôi thấy mình đã viết điều này chỉ một chút trước đây:

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

Và điều này cho phép tôi viết những thứ như thế này:

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

Bây giờ, tôi biết những gì tôi đã viết có lẽ không phải là mã tốt nhất. Và có thể có một cách để làm cho nó linh hoạt và hữu ích hơn. Nhưng đối với tôi, có vẻ như những thứ như thế này nên được tạo thành một phần của tiêu chuẩn.

Vậy là nó? Một số loại thư viện mới đã được thêm vào cho các trình vòng lặp trên một phạm vi số nguyên hoặc có thể là một phạm vi chung của các giá trị vô hướng được tính toán?


17
+1. Tôi muốn có những lớp học như vậy trong các tiện ích của tôi. :-)
Nawaz

2
Nhân tiện, điểm của rangechức năng viết mẫu là gì? Nó không thêm bất cứ điều gì vào việc sử dụng trong đó range_classđược sử dụng. Ý tôi là, range<0,10>()range_class<0,10>()trông giống hệt nhau!
Nawaz

2
@Nawaz: Vâng, bạn nói đúng. Tôi đã có một số tầm nhìn kỳ lạ rằng tôi có thể làm cho hàm xử lý sự khác biệt giữa trường hợp động và trường hợp tĩnh, nhưng tôi không nghĩ có thể thực hiện được.
Omnifarious

2
@iammilind: Nawaz hỏi những câu hỏi tương tự 35 phút trước bạn;)
Sebastian Mach

3
Nói một cách tổng thể, tôi nghĩ rằng việc triển khai này có một lỗi, đó là bạn không thể sử dụng nó để lặp lại trên toàn bộ phạm vi số nguyên. Nếu bạn cắm INT_MIN và INT_MAX làm đối số mẫu của mình, INT_MAX khi tăng lên sẽ cung cấp INT_MIN và gây ra các vòng lặp vô hạn. "end" trong STL được cho là "one over the end" không thể nằm gọn bên trong chính kiểu số nguyên, vì vậy tôi không biết rằng điều này thực sự có thể được triển khai hiệu quả cho kiểu số nguyên rộng nhất trên một nền tảng nhất định. Đối với các loại nguyên nhỏ bạn luôn có thể làm cho nó sử dụng một loại rộng hơn trong nội bộ ...
Joseph Garvin

Câu trả lời:


59

Thư viện tiêu chuẩn C ++ không có, nhưng Boost.Range có boost :: count_range , chắc chắn đủ điều kiện. Bạn cũng có thể sử dụng boost :: irange , tập trung hơn một chút về phạm vi.

Thư viện phạm vi của C ++ 20 sẽ cho phép bạn thực hiện điều này thông qua view::iota(start, end).


3
Vâng, đó chắc chắn là bản chất của những gì tôi đang tìm kiếm. Tôi rất vui vì Boost đã làm được điều đó. Tôi buồn là ủy ban tiêu chuẩn đã không đưa nó vào vì bất cứ lý do gì. Nó sẽ là một bổ sung tuyệt vời cho tính năng range-base-for.
Omnifarious

Câu trả lời này trả lời tốt hơn câu hỏi trực tiếp của tôi, và vì vậy tôi sẽ chọn nó, mặc dù câu trả lời của Nawaz rất hay.
Omnifarious

6
Gần đây, đã có nhiều tiến bộ để đưa phạm vi vào tiêu chuẩn (N4128). Xem github.com/ericniebler/range-v3 để biết đề xuất và triển khai tham chiếu.
Ela782

1
@ Ela782: ... và có vẻ như chúng ta sẽ không thấy nó trong C ++ 17, phải không?
einpoklum

1
@Andreas Có, các phạm vi đã biến nó thành TS một thời gian trước, nhưng tôi không nghĩ rằng có / đã từng có một triển khai tham chiếu nào đưa nó vào các trình biên dịch chính trong std::experimental::rangeskhông gian tên. range-v3luôn luôn là một loại triển khai tham chiếu mà tôi muốn nói. Nhưng bây giờ tôi tin rằng những thứ trong phạm vi cơ bản gần đây cũng đã được bình chọn vào C ++ 20, vì vậy chúng tôi thực sự sẽ std::sớm nhận được nó ! :-)
Ela782

47

Theo như tôi biết, không có lớp nào như vậy trong C ++ 11.

Dù sao, tôi đã cố gắng cải thiện việc triển khai của bạn. Tôi đã làm cho nó không phải là khuôn mẫu , vì tôi không thấy bất kỳ lợi thế nào trong việc làm nó thành khuôn mẫu . Ngược lại, nó có một nhược điểm lớn: bạn không thể tạo phạm vi trong thời gian chạy, vì bạn cần biết các đối số mẫu tại chính thời gian biên dịch.

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

Đây là mã:

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

Mã kiểm tra:

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

Đầu ra:

10 11 12 13 14 15 16 17 18 19

Bản demo của Onine .


3
Tôi thích nó. Tôi đã nghĩ về một phiên bản không theo mẫu. Và tôi cho rằng một trình biên dịch tốt sẽ tối ưu hóa nó tốt trong trường hợp các giá trị thực sự không đổi. Tôi sẽ phải kiểm tra điều đó.
Omnifarious

10
@Nawaz: Tôi vẫn tạo mẫu cho nó, trên loại tích phân :) Tôi cũng muốn đề xuất bí danh iteratorcho const_iterator, có iteratornguồn gốc từ std::iteratorvà có rangetriển khai cbegincend. Ồ và ... tại sao iterator::operator++lại trả về một tham chiếu const ?
Matthieu M.

6
@RedX: Dijkstra có một bài viết tốt về lý do tại sao ghi nhãn phạm vi là tốt nhất [begin, end). @OP: +1 cho chơi chữ trên vòng phạm vi dựa trên đó không phải là một sự chơi chữ :-)
Kerrek SB

2
Ưu điểm của phiên bản không phải mẫu là độ dài của các vòng lặp của bạn không cần biết tại thời điểm biên dịch. Tất nhiên, bạn có thể tạo kiểu số nguyên.
CashCow 17/1213

2
@weeska: Quá tải đó được cho là thực hiện tăng số hậu v++tố được cho là trả về giá trị trước khi hoạt động tăng dần diễn ra. Tôi khuyên bạn nên khám phá sự khác biệt giữa ++ii++nơi iđược khai báo int.
Nawaz

13

Tôi đã viết một thư viện được gọi rangecho cùng một mục đích ngoại trừ nó là phạm vi thời gian chạy và ý tưởng trong trường hợp của tôi đến từ Python. Tôi đã xem xét một phiên bản thời gian biên dịch, nhưng theo ý kiến ​​khiêm tốn của tôi, không có lợi thế thực sự để đạt được phiên bản thời gian biên dịch. Bạn có thể tìm thấy thư viện trên bitbucket và nó nằm trong Giấy phép Tăng cường: Phạm vi . Nó là một thư viện một tiêu đề, tương thích với C ++ 03 và hoạt động giống như sự quyến rũ với các vòng lặp for dựa trên phạm vi trong C ++ 11 :)

Các tính năng :

  • Một thùng chứa truy cập ngẫu nhiên thực sự với tất cả các chuông và còi!

  • Các phạm vi có thể được so sánh theo từ điển.

  • Hai hàm exist(trả về bool) và find(trả về trình lặp) để kiểm tra sự tồn tại của một số.

  • Thư viện được kiểm tra đơn vị bằng CATCH .

  • Ví dụ về cách sử dụng cơ bản, làm việc với các vùng chứa tiêu chuẩn, làm việc với các thuật toán tiêu chuẩn và làm việc với các vòng lặp dựa trên phạm vi.

Đây là phần giới thiệu dài một phút . Cuối cùng, tôi hoan nghênh mọi gợi ý về thư viện nhỏ bé này.


Phần giới thiệu dài một phút nói rằng tôi không có quyền truy cập vào Wiki. Bạn cần đặt wiki của mình ở chế độ công khai.
Nicol Bolas

@Nicol Bolas Tôi thực sự xin lỗi, bây giờ nó đã được công khai :)
AraK

Cảm ơn bạn vì điều này, thật tuyệt vời. Tôi cảm thấy như nhiều người nên biết về nó.
Rafael Kitover

5

Tôi thấy rằng boost::irangenó chậm hơn nhiều so với vòng lặp số nguyên chuẩn. Vì vậy, tôi đã giải quyết giải pháp đơn giản hơn nhiều sau đây bằng cách sử dụng macro bộ xử lý trước:

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

Sau đó, bạn có thể lặp lại như thế này:

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

Phạm vi này tự động bắt đầu từ 0. Nó có thể dễ dàng mở rộng để bắt đầu từ một số nhất định.


7
Lưu ý rằng điều đó for (RANGE(i, flag? n1: n2))sẽ mang lại kết quả đáng ngạc nhiên, bởi vì bạn đã không tuân theo một trong các Quy tắc Cơ bản của Macro Không Ác ma, đó là đặt dấu ngoặc đơn cho tất cả các tham số của bạn (bao gồm cả trong trường hợp này b). Cách tiếp cận của bạn cũng không cung cấp bất kỳ lợi ích hiệu suất nào so với cách tiếp cận dựa trên "đối tượng phạm vi", không phải macro (ví dụ: câu trả lời của Nawaz ).
Quuxplusone

2

Đây là một biểu mẫu đơn giản hơn đang hoạt động tốt đối với tôi. Có bất kỳ rủi ro nào trong cách tiếp cận của tôi không?

r_iteratorlà một loại hoạt động, càng nhiều càng tốt, giống như một long int. Do đó, nhiều toán tử như ==++, chỉ cần chuyển qua long int. Tôi 'phơi bày' int dài cơ bản thông qua operator long intoperator long int &chuyển đổi.

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

( Chỉnh sửa: - chúng ta có thể tạo các phương thức rangestatic thay vì const.)


1

Điều này có thể hơi muộn nhưng tôi vừa thấy câu hỏi này và tôi đã sử dụng lớp này một thời gian:

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

Sử dụng :

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

bạn đã thử sử dụng chưa

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

Hầu hết thời gian phù hợp với hóa đơn.

Ví dụ

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

}

Lưu ý rằng OFC có thể được thay thế printInt bằng lambda trong C ++ 0x. Ngoài ra, một biến thể nhỏ khác của cách sử dụng này có thể là (đối với random_iterator)

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

Đối với trình lặp chỉ Fwd

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

Bạn sẽ sử dụng cái này như thế nào? Tôi đoán bạn sẽ sử dụng lambda cho hàm, nhưng tôi không chắc.
Omnifarious

1
Sẽ nói với bạn, nhưng bạn sẽ chấp nhận câu trả lời nếu bạn nghĩ rằng nó là cách đúng đắn để sử dụng nó. : P Đùa. Đã đăng ví dụ rồi.
Ajeet Ganga

Bạn có thể sử dụng lambda ở đây để auto range = myMultiMap.equal_range (key); for_each (range.first, range.second, [&] (statementtype (* range.first) const & item) {// mã ở đây});
CashCow

-3

Bạn có thể dễ dàng tạo một chuỗi tăng dần trong C ++ 11 bằng cách sử dụng 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
Các rangelớp học sẽ mô hình phạm vi. Tuy nhiên bạn đang xây dựng nó theo đúng nghĩa đen. Đó là sự lãng phí bộ nhớ và truy cập bộ nhớ. Giải pháp là rất dư thừa, vì vectơ không chứa thông tin thực ngoại trừ số phần tử và giá trị của phần tử đầu tiên (nếu nó tồn tại).
not-a-user

Vâng, điều này là rất kém hiệu quả.
Omnifarious
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.