C ++ 11 membalikkan range-based untuk-loop

321

Apakah ada adaptor wadah yang akan membalikkan arah iterator sehingga saya dapat beralih di atas wadah secara terbalik dengan range-for-loop berbasis?

Dengan iterator eksplisit saya akan mengonversi ini:

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

dalam hal ini:

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

Saya ingin mengonversi ini:

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

untuk ini:

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

Apakah ada hal seperti itu atau saya harus menulisnya sendiri?

Alex B
sumber
17
Adaptor kontainer terbalik, kedengarannya menarik, tetapi saya pikir Anda harus menulisnya sendiri. Kami tidak akan memiliki masalah ini jika komite Standar akan bergegas dan mengadaptasi berbagai algoritma berdasarkan bukan iterator eksplisit.
deft_code
4
@deft_code: "bukan?" Mengapa Anda ingin menyingkirkan algoritma berbasis iterator? Mereka jauh lebih baik dan kurang bertele-tele untuk kasus-kasus di mana Anda tidak beralih dari beginke end, atau untuk berurusan dengan iterator aliran dan sejenisnya. Algoritme rentang akan menjadi besar, tetapi mereka benar-benar hanya gula sintaksis (kecuali untuk kemungkinan evaluasi malas) atas algoritma iterator.
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; };Dapat ditingkatkan (menambahkan constversi, dll) tetapi berfungsi: vector<int> v {1, 2, 3}; reverse_adapter<decltype(v)> ra; for (auto& i : ra) cout << i;mencetak321
Seth Carnegie
10
@SethCarnegie: Dan untuk menambahkan bentuk fungsional yang bagus: template<typename T> reverse_adapter<T> reverse_adapt_container(T &c) {return reverse_adapter<T>(c);}Jadi Anda bisa menggunakannya for(auto &i: reverse_adapt_container(v)) cout << i;untuk beralih.
Nicol Bolas
2
@CR: Saya tidak berpikir itu harus berarti itu, karena itu akan membuatnya tidak tersedia sebagai sintaksis singkat untuk loop di mana urutan itu penting. IMO keringkasan lebih penting / berguna daripada makna semantik Anda, tetapi jika Anda tidak menghargai keringkasan dari panduan gaya Anda dapat memberikan implikasi apa pun yang Anda inginkan. Itulah yang parallel_forakan terjadi, dengan kondisi "Saya tidak peduli urutan apa" yang lebih kuat, jika dimasukkan ke dalam standar dalam beberapa bentuk. Tentu saja bisa memiliki gula sintaksis berbasis rentang juga :-)
Steve Jessop

Jawaban:

230

Sebenarnya Meningkatkan memang memiliki adaptor seperti: 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';
}
kennytm
sumber
90

Sebenarnya, dalam C ++ 14 dapat dilakukan dengan beberapa baris kode.

Ini adalah ide yang sangat mirip dengan solusi @ Paul. Karena hal-hal yang hilang dari C ++ 11, solusi itu agak tidak perlu membengkak (ditambah mendefinisikan bau std). Berkat C ++ 14 kita bisa membuatnya lebih mudah dibaca.

Pengamatan utama adalah bahwa for-loop berbasis rentang bekerja dengan mengandalkan begin()dan end()untuk memperoleh iterator kisaran. Berkat ADL , seseorang bahkan tidak perlu mendefinisikan kebiasaan mereka begin()dan end()di std :: namespace.

Berikut ini adalah solusi sampel yang sangat sederhana:

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

Ini berfungsi seperti mantra, misalnya:

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

mencetak seperti yang diharapkan

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

CATATAN std::rbegin() ,, std::rend()dan std::make_reverse_iterator()belum diimplementasikan dalam GCC-4.9. Saya menulis contoh-contoh ini sesuai dengan standar, tetapi mereka tidak dapat dikompilasi di g stabil ++. Meskipun demikian, menambahkan bertopik sementara untuk ketiga fungsi ini sangat mudah. Berikut ini adalah contoh implementasi, jelas tidak lengkap tetapi berfungsi cukup baik untuk sebagian besar kasus:

// --------------------------------------------------
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());
}
Prikso NAI
sumber
35
Beberapa baris kode? Maafkan saya, tapi itu lebih dari sepuluh :-)
Jonny
4
Sebenarnya, ini 5-13, tergantung pada bagaimana Anda menghitung baris:) Work-arounds seharusnya tidak ada di sana, karena mereka adalah bagian dari perpustakaan. Terima kasih telah mengingatkan saya, btw, jawaban ini perlu diperbarui untuk versi kompiler terbaru, di mana semua baris tambahan tidak diperlukan sama sekali.
Prikso NAI
2
Saya pikir Anda lupa implementasi forward<T>Anda reverse.
Snake
3
Hm, jika Anda menempatkan ini di header, Anda berada using namespace stddi header, yang bukan ide yang baik. Atau apakah saya melewatkan sesuatu?
Estan
3
Sebenarnya, Anda tidak boleh menulis "menggunakan <anything>;" pada ruang lingkup file di header. Anda bisa meningkatkan yang di atas dengan memindahkan deklarasi menggunakan ke lingkup fungsi untuk begin () dan end ().
Chris Hartman
23

Ini harus bekerja di C ++ 11 tanpa dorongan:

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))
{
    ...
}
Paul Fultz II
sumber
58
IIRC menambahkan sesuatu ke namespace std adalah undangan untuk kegagalan epik.
BCS
35
Saya tidak yakin tentang arti normatif dari "kegagalan epik", tetapi kelebihan fungsi di stdnamespace memiliki perilaku yang tidak ditentukan per 17.6.4.2.1.
Casey
9
Itu dalam C ++ 14 rupanya , dengan nama ini.
HostileFork bilang jangan percaya SE
6
@MuhammadAnnaqeeb Bagian yang tidak menguntungkan adalah melakukan hal itu persis bertabrakan. Anda tidak dapat mengkompilasi dengan kedua definisi tersebut. Ditambah kompiler tidak diharuskan memiliki definisi tidak hadir di bawah C ++ 11 dan hanya muncul di bawah C ++ 14 (spec tidak mengatakan apa-apa tentang apa yang tidak ada di std :: namespace, hanya apa yang ada). Jadi ini akan menjadi kegagalan kompilasi yang sangat mungkin di bawah kompiler C ++ 11 yang memenuhi standar ... jauh lebih mungkin daripada jika itu adalah beberapa nama acak yang tidak ada dalam C ++ 14! Dan seperti yang ditunjukkan, itu adalah "perilaku tidak terdefinisi" ... jadi gagal mengkompilasi bukan yang terburuk yang mungkin dilakukan.
HostileFork mengatakan jangan percaya
2
@HostileFork Tidak ada nama tabrakan, make_reverse_iteratortidak ada dalam stdnamespace, sehingga tidak akan berbenturan dengan versi C ++ 14 itu.
Paul Fultz II
11

Apakah ini Bekerja untukmu:

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

misalnya:

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;
}
Khan Lau
sumber
1
dapatkah Anda menjelaskan lebih detail jawaban Anda?
Mostafiz
ini adalah loop range-base terbalik C ++ 11 tamplate kelas
Khan Lau
4

Jika Anda dapat menggunakan rentang v3 , Anda dapat menggunakan adaptor rentang terbalik ranges::view::reverseyang memungkinkan Anda melihat wadah secara terbalik.

Contoh kerja minimal:

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

Lihat DEMO 1 .

Catatan: Sesuai Eric Niebler , fitur ini akan tersedia dalam C ++ 20 . Ini bisa digunakan dengan <experimental/ranges/range>tajuk. Maka forpernyataannya akan terlihat seperti ini:

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

Lihat DEMO 2

PW
sumber
Pembaruan: Ruang ranges::viewnama telah diubah namanya menjadi ranges::views. Jadi, gunakan ranges::views::reverse.
nac001
2

Jika tidak menggunakan C ++ 14, maka saya menemukan solusi paling sederhana di bawah ini.

#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 .
Ini tidak berfungsi untuk wadah / tipe data (seperti array), yang tidak memiliki begin/rbegin, end/rendfungsi.

iammilind
sumber
0

Anda cukup menggunakan BOOST_REVERSE_FOREACHyang diulang mundur. Misalnya kodenya

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

menghasilkan output berikut:

4

3

2

1

0
Catriel
sumber