C ++ 11 रिवर्स रेंज-आधारित लूप के लिए


321

क्या कोई कंटेनर एडेप्टर है जो पुनरावृत्तियों की दिशा को उलट देगा, इसलिए मैं रेंज-आधारित फॉर-लूप के साथ एक कंटेनर पर उल्टा कर सकता हूं?

स्पष्ट पुनरावृत्तियों के साथ मैं इसे परिवर्तित करूंगा:

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, या स्ट्रीम iterators और तरह से निपटने के लिए। रेंज एल्गोरिदम बहुत अच्छा होगा, लेकिन वे वास्तव में सिंटैक्टिक शुगर हैं (इटेरियम एल्गोरिदम पर अधिक आलसी मूल्यांकन की संभावना को छोड़कर)।
निकोल बोलस

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
सेठ कार्नेगी

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;करने के लिए उपयोग कर सकते हैं ।
निकोल बोल्स

2
@CR: मुझे नहीं लगता कि इसका मतलब यह होना चाहिए , क्योंकि यह लूप्स के लिए एक संक्षिप्त सिंटैक्स के रूप में अनुपलब्ध होगा जहां ऑर्डर मायने रखता है। IMO की संक्षिप्तता आपके अर्थ अर्थ की तुलना में अधिक महत्वपूर्ण / उपयोगी है, लेकिन यदि आप toc की मूल्य को महत्व नहीं देते हैं, तो आपका स्टाइल गाइड आपको जो भी निहितार्थ देना चाहता है, दे सकता है। यह उसी तरह का parallel_forहोगा जो इससे भी अधिक मजबूत होगा "मुझे परवाह नहीं है कि क्या आदेश" स्थिति है, अगर इसे किसी रूप में मानक में शामिल किया गया है। बेशक यह एक रेंज-आधारित सिंटैक्टिक शुगर भी हो सकता है :-)
स्टीव जेसप

जवाबों:


230

दरअसल बूस्ट में ऐसे एडॉप्टर होते हैं 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 में यह कोड की बहुत कम लाइनों के साथ किया जा सकता है।

यह @ पॉल के समाधान के विचार में बहुत समान है। C ++ 11 से गायब होने वाली चीजों के कारण, वह समाधान थोड़ा अनावश्यक रूप से फूला हुआ है (साथ ही साथ std की बदबू आ रही है)। 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 में लागू नहीं हैं। मैं इन उदाहरणों को मानक के अनुसार लिखता हूं, लेकिन वे स्थिर जी ++ में संकलित नहीं करेंगे। फिर भी, इन तीन कार्यों के लिए अस्थायी स्टब्स जोड़ना बहुत आसान है। यहाँ एक नमूना कार्यान्वयन है, निश्चित रूप से पूरा नहीं हुआ है लेकिन अधिकांश मामलों के लिए पर्याप्त रूप से काम करता है:

// --------------------------------------------------
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, इस उत्तर को हाल ही में संकलक संस्करणों के लिए अद्यतन करने की आवश्यकता है, जहां सभी अतिरिक्त लाइनों की बिल्कुल भी आवश्यकता नहीं है।
प्रिसो एनएआई

2
मुझे लगता है कि आप forward<T>अपने reverseकार्यान्वयन में भूल गए ।
स्नेक

3
हम्म, यदि आप इसे हेडर में रखते हैं, तो आप using namespace stdहेडर में हैं, जो कि एक अच्छा विचार नहीं है। या क्या मैं कुछ न कुछ भूल रहा हूं?
एस्टन

3
वास्तव में, आपको "कुछ भी> का उपयोग करके" नहीं लिखा जाना चाहिए; एक हैडर में फाइल स्कोप। आप शुरुआत () और अंत () के लिए फ़ंक्शन गुंजाइश में उपयोग घोषणाओं को आगे बढ़ाकर ऊपर सुधार कर सकते हैं।
क्रिस हार्टमैन

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 में कुछ भी जोड़ने से महाकाव्य विफल होने का निमंत्रण है।
बीसीएस

35
मैं "महाकाव्य असफल" के प्रामाणिक अर्थ के बारे में निश्चित नहीं हूं, लेकिन stdनाम स्थान में एक फ़ंक्शन को ओवरलोड करने से प्रति 17.6.4.2.1 अपरिभाषित व्यवहार होता है।
केसी

9

6
@MuhammadAnnaqeeb दुर्भाग्यपूर्ण बिट यह है कि ऐसा करना बिल्कुल टकराता है। आप दोनों परिभाषाओं का संकलन नहीं कर सकते। साथ ही कंपाइलर को C ++ 11 के तहत परिभाषा मौजूद नहीं होने की आवश्यकता नहीं है और केवल C ++ 14 के तहत दिखाई देते हैं (कल्पना इस बारे में कुछ नहीं कहती है कि std में नहीं है :: नाम स्थान, बस क्या है)। तो यह एक मानक-अनुपालन C ++ 11 संकलक के तहत एक बहुत ही संभावित संकलन विफलता होगी ... अगर यह कुछ यादृच्छिक नाम C ++ 14 में नहीं था , तो इससे कहीं अधिक संभावना है ! और जैसा कि बताया गया है, यह "अपरिभाषित व्यवहार" है ... इसलिए संकलन करने में असफल होना सबसे बुरा नहीं है।
HostileFork का कहना है कि

2
@HostileFork का कोई नाम टक्कर make_reverse_iteratorनहीं है, stdनामस्थान में नहीं है , इसलिए यह C ++ 14 संस्करण के साथ नहीं होगा।
पॉल फुल्ट्ज़ 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
क्या आप अपने उत्तर के बारे में विस्तार से बता सकते हैं?
मोस्टाफिज

यह एक रिवर्स रेंज-बेस लूप C ++ 11 क्लास टैम्पलेट है
खान लाउ

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

नोट: एरिक निबलर के अनुसार , यह सुविधा C ++ 20 में उपलब्ध होगी । इसका उपयोग <experimental/ranges/range>हेडर के साथ किया जा सकता है । फिर forबयान इस तरह दिखेगा:

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

DEMO 2 देखें


अद्यतन: ranges::viewनाम स्थान का नाम बदल दिया गया है ranges::views। तो, उपयोग करें ranges::views::reverse
nac001 20

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जो पीछे की ओर iterates का उपयोग कर सकते हैं। उदाहरण के लिए, कोड

#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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.