Vòng lặp dựa trên phạm vi đảo ngược C ++ 11


321

Có một bộ chuyển đổi container nào sẽ đảo ngược hướng của các trình vòng lặp để tôi có thể lặp qua một container ngược lại với vòng lặp for-loop dựa trên phạm vi không?

Với các trình vòng lặp rõ ràng tôi sẽ chuyển đổi điều này:

for (auto i = c.begin(); i != c.end(); ++i) { ...

vào đây:

for (auto i = c.rbegin(); i != c.rend(); ++i) { ...

Tôi muốn chuyển đổi cái này:

for (auto& i: c) { ...

đến đây:

for (auto& i: std::magic_reverse_adapter(c)) { ...

Có một điều như vậy hoặc tôi phải tự viết nó?


17
Một bộ chuyển đổi container ngược, nghe có vẻ thú vị, nhưng tôi nghĩ bạn sẽ phải tự viết nó. Chúng tôi sẽ không gặp vấn đề này nếu ủy ban Tiêu chuẩn sẽ nhanh chóng điều chỉnh các thuật toán dựa trên phạm vi thay vì các trình lặp rõ ràng.
deft_code

4
@deft_code: "thay vì?" Tại sao bạn muốn thoát khỏi các thuật toán dựa trên iterator? Họ là tốt hơn và ít tiết đối với trường hợp bạn không lặp từ nhiều beginđến end, hoặc để đối phó với biến lặp suối và những thứ tương tự. Các thuật toán phạm vi sẽ là tuyệt vời, nhưng chúng thực sự chỉ là cú pháp cú pháp (ngoại trừ khả năng đánh giá lười biếng) so với các thuật toán lặp.
Nicol Bolas

17
@deft_code template<typename T> class reverse_adapter { public: reverse_adapter(T& c) : c(c) { } typename T::reverse_iterator begin() { return c.rbegin(); } typename T::reverse_iterator end() { return c.rend(); } private: T& c; };Nó có thể được cải thiện (thêm constphiên bản, v.v.) nhưng nó hoạt động: vector<int> v {1, 2, 3}; reverse_adapter<decltype(v)> ra; for (auto& i : ra) cout << i;bản in321
Seth Carnegie

10
@SethCarnegie: Và để thêm một hình thức chức năng tốt đẹp: template<typename T> reverse_adapter<T> reverse_adapt_container(T &c) {return reverse_adapter<T>(c);}Vì vậy, sau đó bạn chỉ có thể sử dụng for(auto &i: reverse_adapt_container(v)) cout << i;để lặp lại.
Nicol Bolas

2
@CR: Tôi không nghĩ rằng nó nên có nghĩa là, bởi vì đó sẽ làm cho nó không có sẵn như một cú pháp ngắn gọn cho vòng nơi trật tự không thành vấn đề. IMO sự đồng nhất là quan trọng / hữu ích hơn ý nghĩa ngữ nghĩa của bạn, nhưng nếu bạn không coi trọng sự cụ thể của hướng dẫn phong cách của bạn có thể cung cấp cho nó bất cứ hàm ý nào bạn muốn. Đó là loại những gì parallel_forsẽ dành cho, với điều kiện "Tôi không quan tâm thứ tự" nào mạnh mẽ hơn nữa, nếu nó được đưa vào tiêu chuẩn dưới một hình thức nào đó. Tất nhiên, nó cũng có thể có đường cú pháp dựa trên phạm vi :-)
Steve Jessop

Câu trả lời:


230

Trên thực tế Boost có bộ chuyển đổi như vậy : boost::adaptors::reverse.

#include <list>
#include <iostream>
#include <boost/range/adaptor/reversed.hpp>

int main()
{
    std::list<int> x { 2, 3, 5, 7, 11, 13, 17, 19 };
    for (auto i : boost::adaptors::reverse(x))
        std::cout << i << '\n';
    for (auto i : x)
        std::cout << i << '\n';
}

90

Trên thực tế, trong C ++ 14 nó có thể được thực hiện với rất ít dòng mã.

Đây là một ý tưởng rất giống với giải pháp của @ Paul. Do những thứ còn thiếu từ C ++ 11, giải pháp đó hơi phồng lên một cách không cần thiết (cộng với việc xác định trong mùi std). Nhờ C ++ 14, chúng tôi có thể làm cho nó dễ đọc hơn rất nhiều.

Quan sát chính là các vòng lặp dựa trên phạm vi hoạt động bằng cách dựa vào begin()end()để có được các vòng lặp của phạm vi. Nhờ có ADL , người ta thậm chí không cần xác định tùy chỉnh của họ begin()end()trong không gian tên std ::.

Đây là một giải pháp mẫu rất đơn giản:

// -------------------------------------------------------------------
// --- Reversed iterable

template <typename T>
struct reversion_wrapper { T& iterable; };

template <typename T>
auto begin (reversion_wrapper<T> w) { return std::rbegin(w.iterable); }

template <typename T>
auto end (reversion_wrapper<T> w) { return std::rend(w.iterable); }

template <typename T>
reversion_wrapper<T> reverse (T&& iterable) { return { iterable }; }

Điều này hoạt động như một nét duyên dáng, ví dụ:

template <typename T>
void print_iterable (std::ostream& out, const T& iterable)
{
    for (auto&& element: iterable)
        out << element << ',';
    out << '\n';
}

int main (int, char**)
{
    using namespace std;

    // on prvalues
    print_iterable(cout, reverse(initializer_list<int> { 1, 2, 3, 4, }));

    // on const lvalue references
    const list<int> ints_list { 1, 2, 3, 4, };
    for (auto&& el: reverse(ints_list))
        cout << el << ',';
    cout << '\n';

    // on mutable lvalue references
    vector<int> ints_vec { 0, 0, 0, 0, };
    size_t i = 0;
    for (int& el: reverse(ints_vec))
        el += i++;
    print_iterable(cout, ints_vec);
    print_iterable(cout, reverse(ints_vec));

    return 0;
}

in như mong đợi

4,3,2,1,
4,3,2,1,
3,2,1,0,
0,1,2,3,

LƯU Ý std::rbegin() , std::rend()std::make_reverse_iterator()chưa được triển khai trong GCC-4.9. Tôi viết các ví dụ này theo tiêu chuẩn, nhưng chúng sẽ không biên dịch trong g ++ ổn định. Tuy nhiên, việc thêm sơ khai tạm thời cho ba chức năng này là rất dễ dàng. Đây là một triển khai mẫu, chắc chắn không hoàn thành nhưng hoạt động đủ tốt cho hầu hết các trường hợp:

// --------------------------------------------------
template <typename I>
reverse_iterator<I> make_reverse_iterator (I i)
{
    return std::reverse_iterator<I> { i };
}

// --------------------------------------------------
template <typename T>
auto rbegin (T& iterable)
{
    return make_reverse_iterator(iterable.end());
}

template <typename T>
auto rend (T& iterable)
{
    return make_reverse_iterator(iterable.begin());
}

// const container variants

template <typename T>
auto rbegin (const T& iterable)
{
    return make_reverse_iterator(iterable.end());
}

template <typename T>
auto rend (const T& iterable)
{
    return make_reverse_iterator(iterable.begin());
}

35
Vài dòng mã? Hãy tha thứ cho tôi nhưng đó là hơn mười :-)
Jonny

4
Trên thực tế, đó là 5-13, tùy thuộc vào cách bạn đếm dòng :) Các công việc xung quanh không nên ở đó, vì chúng là một phần của thư viện. Cảm ơn đã nhắc nhở tôi, btw, câu trả lời này cần được cập nhật cho các phiên bản trình biên dịch gần đây, nơi tất cả các dòng bổ sung không cần thiết.
Prikso NAI

2
Tôi nghĩ rằng bạn đã quên forward<T>trong reverseviệc thực hiện của bạn .
SnakE

3
Hừm, nếu bạn đặt cái này trong một tiêu đề, bạn đang using namespace stdở trong một tiêu đề, đó không phải là một ý tưởng tốt. Hay tôi đang thiếu một cái gì đó?
estan

3
Trên thực tế, bạn không nên viết "bằng cách sử dụng <anything>;" tại phạm vi tập tin trong một tiêu đề. Bạn có thể cải thiện những điều trên bằng cách di chuyển các khai báo sử dụng vào phạm vi hàm cho start () và end ().
Chris Hartman

23

Điều này sẽ hoạt động trong C ++ 11 mà không cần tăng:

namespace std {
template<class T>
T begin(std::pair<T, T> p)
{
    return p.first;
}
template<class T>
T end(std::pair<T, T> p)
{
    return p.second;
}
}

template<class Iterator>
std::reverse_iterator<Iterator> make_reverse_iterator(Iterator it)
{
    return std::reverse_iterator<Iterator>(it);
}

template<class Range>
std::pair<std::reverse_iterator<decltype(begin(std::declval<Range>()))>, std::reverse_iterator<decltype(begin(std::declval<Range>()))>> make_reverse_range(Range&& r)
{
    return std::make_pair(make_reverse_iterator(begin(r)), make_reverse_iterator(end(r)));
}

for(auto x: make_reverse_range(r))
{
    ...
}

58
IIRC thêm bất cứ điều gì vào không gian tên std là một lời mời đến thất bại sử thi.
BCS

35
Tôi không chắc về ý nghĩa quy phạm của "thất bại sử thi", nhưng quá tải một chức năng trong stdkhông gian tên có hành vi không xác định theo 17.6.4.2.1.
Casey

9
Rõ ràng là trong C ++ 14 , dưới cái tên này.
HostileFork nói không tin tưởng vào

6
@MuhammadAnnaqeeb Điều không may là làm như vậy va chạm chính xác. Bạn không thể biên dịch với cả hai định nghĩa. Thêm vào đó, trình biên dịch không bắt buộc phải có định nghĩa không có trong C ++ 11 và chỉ xuất hiện trong C ++ 14 (thông số kỹ thuật không nói gì về những gì không có trong không gian tên std ::, chỉ là những gì). Vì vậy, đây sẽ là một lỗi biên dịch rất có khả năng trong trình biên dịch C ++ 11 tuân thủ tiêu chuẩn ... nhiều khả năng hơn là nếu đó là một tên ngẫu nhiên không có trong C ++ 14! Và như đã chỉ ra, đó là "hành vi không xác định" ... vì vậy việc không biên dịch không phải là điều tồi tệ nhất có thể xảy ra.
HostileFork nói không tin tưởng SE

2
@HostileFork Không có xung đột tên, make_reverse_iteratorkhông có trong stdkhông gian tên, vì vậy nó sẽ không xung đột với phiên bản C ++ 14 của nó.
Paul Fultz II

11

Công việc này là dành cho bạn:

#include <iostream>
#include <list>
#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>
#include <boost/range/iterator_range.hpp>

int main(int argc, char* argv[]){

  typedef std::list<int> Nums;
  typedef Nums::iterator NumIt;
  typedef boost::range_reverse_iterator<Nums>::type RevNumIt;
  typedef boost::iterator_range<NumIt> irange_1;
  typedef boost::iterator_range<RevNumIt> irange_2;

  Nums n = {1, 2, 3, 4, 5, 6, 7, 8};
  irange_1 r1 = boost::make_iterator_range( boost::begin(n), boost::end(n) );
  irange_2 r2 = boost::make_iterator_range( boost::end(n), boost::begin(n) );


  // prints: 1 2 3 4 5 6 7 8 
  for(auto e : r1)
    std::cout << e << ' ';

  std::cout << std::endl;

  // prints: 8 7 6 5 4 3 2 1
  for(auto e : r2)
    std::cout << e << ' ';

  std::cout << std::endl;

  return 0;
}

7
template <typename C>
struct reverse_wrapper {

    C & c_;
    reverse_wrapper(C & c) :  c_(c) {}

    typename C::reverse_iterator begin() {return c_.rbegin();}
    typename C::reverse_iterator end() {return c_.rend(); }
};

template <typename C, size_t N>
struct reverse_wrapper< C[N] >{

    C (&c_)[N];
    reverse_wrapper( C(&c)[N] ) : c_(c) {}

    typename std::reverse_iterator<const C *> begin() { return std::rbegin(c_); }
    typename std::reverse_iterator<const C *> end() { return std::rend(c_); }
};


template <typename C>
reverse_wrapper<C> r_wrap(C & c) {
    return reverse_wrapper<C>(c);
}

ví dụ:

int main(int argc, const char * argv[]) {
    std::vector<int> arr{1, 2, 3, 4, 5};
    int arr1[] = {1, 2, 3, 4, 5};

    for (auto i : r_wrap(arr)) {
        printf("%d ", i);
    }
    printf("\n");

    for (auto i : r_wrap(arr1)) {
        printf("%d ", i);
    }
    printf("\n");
    return 0;
}

1
bạn có thể vui lòng giải thích chi tiết hơn câu trả lời của bạn?
Mostafiz

đây là một vòng lặp cơ sở phạm vi đảo ngược lớp C ++ 11
Khan Lau

4

Nếu bạn có thể sử dụng phạm vi v3 , bạn có thể sử dụng bộ điều hợp phạm vi đảo ngược ranges::view::reversecho phép bạn xem container ngược lại.

Một ví dụ làm việc tối thiểu:

#include <iostream>
#include <vector>
#include <range/v3/view.hpp>

int main()
{
    std::vector<int> intVec = {1, 2, 3, 4, 5, 6, 7, 8, 9};

    for (auto const& e : ranges::view::reverse(intVec)) {
        std::cout << e << " ";   
    }
    std::cout << std::endl;

    for (auto const& e : intVec) {
        std::cout << e << " ";   
    }
    std::cout << std::endl;
}

Xem DEMO 1 .

Lưu ý: Theo Eric Niebler , tính năng này sẽ có trong C ++ 20 . Điều này có thể được sử dụng với <experimental/ranges/range>tiêu đề. Sau đó, fortuyên bố sẽ như thế này:

for (auto const& e : view::reverse(intVec)) {
       std::cout << e << " ";   
}

Xem DEMO 2


Cập nhật: Không ranges::viewgian tên đã được đổi tên thành ranges::views. Vì vậy, sử dụng ranges::views::reverse.
nac001

2

Nếu không sử dụng C ++ 14, thì tôi tìm bên dưới giải pháp đơn giản nhất.

#define METHOD(NAME, ...) auto NAME __VA_ARGS__ -> decltype(m_T.r##NAME) { return m_T.r##NAME; }
template<typename T>
struct Reverse
{
  T& m_T;

  METHOD(begin());
  METHOD(end());
  METHOD(begin(), const);
  METHOD(end(), const);
};
#undef METHOD

template<typename T>
Reverse<T> MakeReverse (T& t) { return Reverse<T>{t}; }

Bản demo .
Nó không hoạt động đối với các container / kiểu dữ liệu (như mảng), không có begin/rbegin, end/rendchức năng.


0

Bạn chỉ có thể sử dụng BOOST_REVERSE_FOREACHmà lặp đi lặp lại. Ví dụ: mã

#include <iostream>
#include <boost\foreach.hpp>

int main()
{
    int integers[] = { 0, 1, 2, 3, 4 };
    BOOST_REVERSE_FOREACH(auto i, integers)
    {
        std::cout << i << std::endl;
    }
    return 0;
}

tạo đầu ra sau:

4

3

2

1

0
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.