2011-12-17 43 views
225

Có bộ điều hợp vùng chứa nào có thể đảo ngược hướng của các trình vòng lặp để tôi có thể lặp qua một vùng chứa ngược lại với vòng lặp dựa trên phạm vi không?C++ 11 đảo ngược dựa trên phạm vi cho vòng lặp

Với lặp rõ ràng tôi sẽ chuyển đổi 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 này:

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

này:

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

Có điều gì đó hay tôi phải tự viết nó?

+11

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

+0

@ Tôi biết tôi có thể viết nó, nhưng đó không phải là vấn đề. Nếu tôi viết nó, nó sẽ trở thành một trong những chức năng hữu ích mà không thuộc bất kỳ nơi nào đặc biệt (tm), vì vậy bạn kết thúc việc viết mã của bạn với phần đầu tiện ích đã biết và trộn hệ thống xây dựng của bạn để chia sẻ nó qua các dự án . Bởi lý do này, chúng ta vẫn nên sử dụng BOOST_FOREACH thay vì phạm vi cho. Và vâng, tôi lười biếng. –

+4

@deft_code: "thay vì?" Tại sao bạn muốn loại bỏ các thuật toán dựa trên iterator? Chúng tốt hơn và ít chi tiết hơn đối với các trường hợp bạn không lặp lại từ 'begin' to' end', hoặc để xử lý các trình lặp dòng và tương tự. Phạm vi thuật toán sẽ là tuyệt vời, nhưng họ đang thực sự chỉ cú pháp đường (ngoại trừ khả năng đánh giá lười biếng) trên các thuật toán lặp. –

Trả lời

171

Trên thực tế Tăng không có bộ chuyển đổi như: 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'; 
} 
11

Liệu tác phẩm này với 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; 
} 
21

này nên làm việc trong C++ 11 mà không 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)) 
{ 
    ... 
} 
+47

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

+24

Tôi không chắc chắn về ý nghĩa quy tắc của "thất bại hoành tráng", nhưng quá tải một hàm trong không gian tên 'std' có hành vi không xác định trên mỗi 17.6.4.2.1. – Casey

+8

Có trong [C++ 14] (http://en.cppreference.com/w/cpp/iterator/make_reverse_iterator), dưới tên này. – HostileFork

53

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

Đây là một ý tưởng rất giống với giải pháp @ Paul. Do những điều thiếu trong C++ 11, giải pháp đó là một chút không cần thiết cồng kềnh (cộng với việc xác định mùi hôi thối). Nhờ C++ 14 chúng tôi có thể làm cho nó dễ đọc hơn 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 trên begin()end() để có được trình vòng lặp của phạm vi. Nhờ có ADL, bạn thậm chí không cần phải xác định begin()end() tùy chỉnh trong không gian tên std :: của mình.

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

// ------------------------------------------------------------------- 
// --- Reversed iterable 
using std::rbegin, 
     std::rend; 

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

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

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

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

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

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

int main (int, char**) 
{ 
    // 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) << '\n'; 
    print_iterable(cout, reverse(ints_vec)) << '\n'; 


    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 những 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 các nhánh tạm thời cho ba hàm này là rất dễ dàng.Đây là một thực hiện mẫu, chắc chắn không phải hoàn 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()); 
} 

CẬP NHẬT 22 Oct 2017

Nhờ estan cho trỏ này ra.

Thực hiện mẫu câu trả lời ban đầu sử dụng using namespace std;, điều này sẽ gây ra bất kỳ tệp nào bao gồm việc triển khai này (phải nằm trong tệp tiêu đề), cũng nhập toàn bộ không gian tên std.

Sửa đổi triển khai mẫu để đề xuất using std::rbegin, std::rend thay thế.

+0

Chỉ cần một báo cáo rằng điều này hoạt động hoàn hảo trên clang 3.7.0 với -std = C++ 14 – friedmud

+18

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

+1

Thực ra, đó là 5-13, tùy thuộc vào cách bạn đếm các dòng:) Các work-arounds không nên ở đó, vì chúng là một phần của thư viện. Cảm ơn bạn đã nhắc 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 mà tất cả các dòng phụ không cần thiết. –

2

Nếu không sử dụng C++ 14, thì tôi tìm thấy 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}; } 

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

4
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; 
    } 
+0

bạn có thể giải thích chi tiết hơn về câu trả lời của bạn không? – Mostafiz

+0

đây là một vòng lặp ngược cơ sở dãy C++ 11 tamplate –

Các vấn đề liên quan