C ++ 11 reverse-based for-loop สำหรับช่วง


321

มีอะแดปเตอร์คอนเทนเนอร์ที่จะเปลี่ยนทิศทางของตัววนซ้ำเพื่อให้ฉันสามารถวนซ้ำในคอนเทนเนอร์โดยย้อนกลับด้วย for-loop for range

ด้วยตัววนซ้ำอย่างชัดเจนฉันจะแปลงสิ่งนี้:

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

เป็นนี้

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

ฉันต้องการแปลงสิ่งนี้:

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

สำหรับสิ่งนี้:

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

มีอะไรแบบนี้หรือฉันต้องเขียนเองเหรอ?


17
ตัวแปลงคอนเทนเนอร์แบบย้อนกลับฟังดูน่าสนใจ แต่ฉันคิดว่าคุณต้องเขียนด้วยตัวเอง เราจะไม่มีปัญหานี้หากคณะกรรมการมาตรฐานจะรีบเร่งและปรับเปลี่ยนอัลกอริธึมตามช่วงแทนการวนซ้ำอย่างชัดเจน
deft_code

4
@deft_code: "แทนที่จะเป็น?" ทำไมคุณต้องการกำจัดอัลกอริธึมที่ใช้ตัววนซ้ำ? พวกเขากำลังดีขึ้นและน้อยลงอย่างมากสำหรับกรณีที่คุณไม่ทำซ้ำจากbeginไปendหรือเพื่อจัดการกับสตรีมตัววนซ้ำและสิ่งที่ชอบ อัลกอริธึมแบบพิสัยจะยอดเยี่ยม แต่จริงๆแล้วมันเป็นแค่น้ำตาลซินแทคติก (ยกเว้นความเป็นไปได้ของการประเมินที่ขี้เกียจ) เหนืออัลกอริทึมตัววนซ้ำ
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; };สามารถปรับปรุงได้ (เพิ่มconstรุ่น ฯลฯ ) แต่ใช้งานได้: vector<int> v {1, 2, 3}; reverse_adapter<decltype(v)> ra; for (auto& i : ra) cout << i;พิมพ์321
Seth Carnegie

10
@SethCarnegie: และเพื่อเพิ่มรูปแบบการทำงานที่ดี: template<typename T> reverse_adapter<T> reverse_adapt_container(T &c) {return reverse_adapter<T>(c);}ดังนั้นคุณสามารถใช้for(auto &i: reverse_adapt_container(v)) cout << i;เพื่อย้ำ
Nicol Bolas

2
@CR: ฉันไม่คิดว่ามันควรจะหมายความว่าเพราะมันจะทำให้มันใช้งานไม่ได้เป็นไวยากรณ์ที่กระชับสำหรับลูปที่คำสั่งซื้อมีความสำคัญ IMO ความรัดกุมสำคัญกว่า / มีประโยชน์มากกว่าความหมายเชิงความหมายของคุณ แต่ถ้าคุณไม่เห็นคุณค่าของความรัดกุมของคู่มือสไตล์ของคุณสามารถให้สิ่งที่คุณต้องการ นั่นเป็นสิ่งที่parallel_forจะเป็นไปได้ด้วยเงื่อนไขที่แข็งแกร่งยิ่งขึ้น "ฉันไม่สนใจสิ่งที่จะเป็นระเบียบ" หากมันถูกรวมเข้ากับมาตรฐานในบางรูปแบบ แน่นอนว่ามันอาจมีน้ำตาลซิน
Steve Jessop

คำตอบ:


230

ที่จริง Boost 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

ที่จริงแล้วใน C ++ 14 สามารถทำได้ด้วยโค้ดไม่กี่บรรทัด

นี่เป็นแนวคิดที่คล้ายกันมากกับโซลูชันของ @ Paul เนื่องจากสิ่งต่าง ๆ ที่ขาดหายไปจาก C ++ 11 วิธีการแก้ปัญหาดังกล่าวจึงมีป่องเล็กน้อยโดยไม่จำเป็น (รวมถึงการกำหนดด้วยกลิ่นมาตรฐาน) ขอบคุณ C ++ 14 ที่ทำให้เราอ่านได้มากขึ้น

การสังเกตที่สำคัญคือการทำงานแบบช่วงลูปโดยอาศัยการพึ่งพาbegin()และend()เพื่อให้ได้ตัววนซ้ำของช่วง ขอบคุณADLเราไม่จำเป็นต้องกำหนดค่ากำหนดเองbegin()และend()ใน std :: namespace

นี่คือวิธีแก้ปัญหาตัวอย่างที่ง่ายมาก:

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

มันใช้งานได้อย่างมีเสน่ห์เช่น:

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

พิมพ์ตามที่คาดไว้

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

หมายเหตุ std::rbegin() , std::rend()และstd::make_reverse_iterator()ยังไม่ได้ใช้งานใน GCC-4.9 ฉันเขียนตัวอย่างเหล่านี้ตามมาตรฐาน แต่จะไม่คอมไพล์ใน g ++ ที่เสถียร อย่างไรก็ตามการเพิ่มสตับชั่วคราวสำหรับทั้งสามฟังก์ชั่นนั้นง่ายมาก นี่คือตัวอย่างการใช้งานแน่นอนไม่สมบูรณ์แต่ทำงานได้ดีพอสำหรับกรณีส่วนใหญ่:

// --------------------------------------------------
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
มีโค้ดไม่กี่บรรทัด? ยกโทษให้ฉัน แต่นั่นก็คือเกินสิบ :-)
จอนนี่

4
ที่จริงแล้วมัน 5-13 ขึ้นอยู่กับวิธีที่คุณนับบรรทัด:) ไม่ควรมีการแก้ไขปัญหาเนื่องจากมันเป็นส่วนหนึ่งของห้องสมุด ขอขอบคุณที่เตือนฉันด้วย btw คำตอบนี้ต้องได้รับการอัปเดตสำหรับคอมไพเลอร์รุ่นล่าสุดโดยที่ไม่จำเป็นต้องเพิ่มบรรทัดทั้งหมดเลย
Prikso NAI

2
ฉันคิดว่าคุณลืมforward<T>ในreverseการใช้งานของคุณ
SnakE

3
หืมถ้าคุณใส่สิ่งนี้ไว้ในส่วนหัวคุณจะอยู่using namespace stdในส่วนหัวซึ่งไม่ใช่ความคิดที่ดี หรือฉันกำลังพลาดอะไรอยู่?
estan

3
จริงๆแล้วคุณไม่ควรเขียนว่า "using <anything>;" ที่ขอบเขตไฟล์ในส่วนหัว คุณสามารถปรับปรุงข้างต้นได้โดยการย้ายการประกาศที่ใช้ลงในขอบเขตฟังก์ชั่นสำหรับ start () และ end ()
Chris Hartman

23

สิ่งนี้จะทำงานใน C ++ 11 โดยไม่ต้องเพิ่ม:

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 การเพิ่มสิ่งใดลงในเนมสเปซ std คือคำเชิญไปยังมหากาพย์ความล้มเหลว
BCS

35
ฉันไม่แน่ใจเกี่ยวกับความหมายเชิงบรรทัดฐานของ "มหากาพย์ความล้มเหลว" แต่การใช้งานมากเกินไปในstdเนมสเปซมีพฤติกรรมที่ไม่ได้กำหนดต่อ 17.6.4.2.1
Casey


6
@MuhammadAnnaqeeb บิตที่โชคร้ายคือการทำเช่นนั้นชนกันอย่างแน่นอน คุณไม่สามารถคอมไพล์ด้วยคำจำกัดความทั้งสอง ยิ่งไปกว่านั้นคอมไพเลอร์ไม่จำเป็นต้องมีคำจำกัดความที่ไม่อยู่ภายใต้ C ++ 11 และปรากฏเฉพาะภายใต้ C ++ 14 (ข้อมูลจำเพาะไม่ได้พูดอะไรเกี่ยวกับสิ่งที่ไม่ได้อยู่ใน std :: namespace, อะไรคือ) ดังนั้นนี่จะเป็นความล้มเหลวในการรวบรวมที่น่าจะเป็นไปได้มากภายใต้คอมไพเลอร์ C ++ 11 ที่เป็นไปตามมาตรฐาน ... มีโอกาสมากกว่าที่จะเป็นชื่อสุ่มที่ไม่ได้อยู่ใน C ++ 14! และตามที่ระบุไว้มันเป็น "พฤติกรรมที่ไม่ได้กำหนด" ... ดังนั้นการไม่สามารถรวบรวมได้จึงไม่ใช่วิธีที่แย่ที่สุดที่จะทำได้
HostileFork บอกว่าอย่าเชื่อถือ SE

2
@HostileFork ไม่มีการชนกันของชื่อmake_reverse_iteratorไม่ได้อยู่ในstdเนมสเปซดังนั้นจึงไม่ขัดแย้งกับเวอร์ชัน C ++ 14
Paul Fultz II

11

สิ่งนี้ใช้ได้ผลสำหรับคุณ:

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

เช่น:

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
คุณช่วยอธิบายรายละเอียดเพิ่มเติมของคำตอบได้ไหม?
Mostafiz

นี่คือแทมเพลทระดับฐานย้อนกลับ C ++ 11
Khan Lau

4

หากคุณสามารถใช้ช่วง v3คุณสามารถใช้อะแดปเตอร์ช่วงย้อนกลับranges::view::reverseซึ่งช่วยให้คุณดูภาชนะบรรจุในสิ่งที่ตรงกันข้าม

ตัวอย่างการทำงานที่น้อยที่สุด:

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

ดูDEMO 1

หมายเหตุ:เป็นต่อเอริค Nieblerคุณลักษณะนี้จะสามารถใช้ได้ในภาษา C ++ 20 สามารถใช้กับ<experimental/ranges/range>ส่วนหัว จากนั้นforคำสั่งจะมีลักษณะดังนี้:

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

ดูตัวอย่าง2


อัปเดต: ผู้ranges::viewnamespace ranges::viewsได้รับการเปลี่ยนชื่อเป็น ranges::views::reverseดังนั้นการใช้งาน
nac001

2

หากไม่ได้ใช้ C ++ 14 ฉันจะพบวิธีแก้ปัญหาที่ง่ายที่สุดด้านล่าง

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

การสาธิต
มันไม่ทำงานสำหรับคอนเทนเนอร์ / ประเภทข้อมูล (เช่นอาร์เรย์) ซึ่งไม่มีbegin/rbegin, end/rendฟังก์ชั่น


0

คุณสามารถใช้BOOST_REVERSE_FOREACHมันวนซ้ำย้อนหลังได้ ตัวอย่างเช่นรหัส

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

สร้างผลลัพธ์ต่อไปนี้:

4

3

2

1

0
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.