C ++ 11 ters aralık tabanlı for döngüsü


321

Aralık tabanlı for döngüsü ile tersine bir konteyner üzerinde yineleyebilmem için yineleyicilerin yönünü tersine çevirecek bir konteyner adaptörü var mı?

Açık yineleyiciler ile bunu dönüştürür:

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

bunun içine:

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

Bunu dönüştürmek istiyorum:

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

buna:

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

Böyle bir şey var mı yoksa kendim yazmak zorunda mıyım?


17
Ters konteyner adaptörü, kulağa ilginç geliyor, ama sanırım bunu kendiniz yazmanız gerekecek. Standart komite acele edip açık yineleyiciler yerine aralık tabanlı algoritmaları uyarlarsa bu sorunla karşılaşmazdık.
deft_code

4
@deft_code: "yerine?" Neden yineleyici tabanlı algoritmalardan kurtulmak istersiniz? Onlar çok daha iyi ve daha az dan iterate yok durumlar için ayrıntılı konum beginiçin end, veya akış Yineleyiciler ve benzeri başa çıkmak için. Aralık algoritmaları harika olurdu, ancak yineleyici algoritmaları üzerinde gerçekten sadece sözdizimsel şeker (tembel değerlendirme olasılığı hariç).
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; };Geliştirilebilir ( constsürüm ekleme , vb.) ama işe yarıyor: vector<int> v {1, 2, 3}; reverse_adapter<decltype(v)> ra; for (auto& i : ra) cout << i;baskılar321
Seth Carnegie

10
@SethCarnegie: Ve güzel bir fonksiyonel form eklemek için: template<typename T> reverse_adapter<T> reverse_adapt_container(T &c) {return reverse_adapter<T>(c);}O zaman sadece for(auto &i: reverse_adapt_container(v)) cout << i;yinelemek için kullanabilirsiniz .
Nicol Bolas

2
@CR: Ben sanmıyorum gerektiğini bu düzen önemi var döngüler için özlü bir sözdizimi olarak kullanılamaz hale getirmek çünkü, demek. IMO, kısalık, anlamsal anlamınızdan daha önemli / yararlıdır, ancak özlüğe değer vermezseniz, stil rehberiniz, istediğiniz her türlü sonucu verebilir. Bu parallel_for, bir şekilde standarda dahil edilmiş olsaydı, daha güçlü bir "hangi düzeni umursamıyorum" koşuluyla bunun için ne olurdu. Tabii ki aralık tabanlı bir sözdizimsel şeker de olabilir :-)
Steve Jessop

Yanıtlar:


230

Aslında Boost böyle adaptörü var: 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

Aslında, C ++ 14'te çok az kod satırı ile yapılabilir.

Bu, Paul'ün çözümüne çok benzer bir fikirdir. C ++ 11 eksik olan şeyler nedeniyle, bu çözüm biraz gereksiz şişirilmiş (artı std kokuları tanımlamak). C ++ 14 sayesinde çok daha okunabilir hale getirebiliriz.

Temel gözlem, aralık tabanlı döngüler için, aralığın yineleyicilerine güvenerek begin()ve end()bunları elde etmek için çalıştığıdır. ADL sayesinde , kişinin özel begin()ve end()std :: ad alanında tanımlanması bile gerekmez .

İşte çok basit bir örnek çözüm:

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

Bu bir cazibe gibi çalışır, örneğin:

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

beklendiği gibi yazdırır

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

NOT std::rbegin() , std::rend()ve std::make_reverse_iterator()henüz GCC-4.9'da uygulanmamıştır. Bu örnekleri standarda göre yazıyorum, ancak kararlı g ++ 'da derlemiyorlardı. Bununla birlikte, bu üç işlev için geçici saplamalar eklemek çok kolaydır. İşte örnek bir uygulama, kesinlikle tamamlanmadı, ancak çoğu durumda yeterince iyi çalışıyor:

// --------------------------------------------------
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
Birkaç satır kod mu? Beni affet ama bu ondan fazla :-)
Jonny

4
Aslında, satırları nasıl saydığınıza bağlı olarak bu 5-13'tür:) Kitaplığın bir parçası olduğu için geçici çözümler orada olmamalıdır. Hatırlattığınız için teşekkürler, btw, bu cevabın, tüm ekstra satırlara ihtiyaç duyulmayan son derleyici sürümleri için güncellenmesi gerekiyor.
Prikso NAI

2
Ben unuttum düşünüyorum forward<T>sizin içinde reverseuygulanması.
Yılan

3
Hm, bunu bir başlığa koyarsanız using namespace std, bir başlıktasınız, bu iyi bir fikir değildir. Yoksa bir şey mi kaçırıyorum?
estan

3
Aslında, "herhangi bir şey kullanarak" yazmamalısınız; başlıktaki dosya kapsamında. Kullanarak bildirimleri begin () ve end () için işlev kapsamına taşıyarak yukarıdakileri iyileştirebilirsiniz.
Chris Hartman

23

Bu destek olmadan C ++ 11'de çalışmalıdır:

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 ad alanı std'ye bir şey eklemek epik başarısızlık için bir davettir.
BCS

35
"Epic fail" in normatif anlamından emin değilim, ancak stdisim alanındaki bir fonksiyonun aşırı yüklenmesi 17.6.4.2.1'de tanımlanmamış bir davranışa sahip.
Casey

9
Bu öyle görünüşe 14 C ++ bu isim altında.
HostileFork SE

6
@MuhammadAnnaqeeb Talihsiz şey, bunu yapmanın tam olarak çarpışmasıdır. Her iki tanımla da derleyemezsiniz. Artı derleyici tanım olması gerekmez değil 11 C ++ altında bulunması ve sadece C ++ 14 altında görünür (Spec ne hakkında hiçbir şey söylemez değil sadece ne, std :: ad). Yani bu, standartlara uygun bir C ++ 11 derleyicisi altında çok olası bir derleme hatası olurdu ... C ++ 14'te olmayan bazı rastgele adlardan çok daha olasıdır ! Ve belirtildiği gibi, bu "tanımsız davranış" ... bu yüzden derleyememek, yapabileceği en kötü şey değil.
HostileFork SE

2
@HostileFork Ad çakışması yok make_reverse_iterator, stdad alanında değil , bu yüzden C ++ 14 sürümü ile çakışmayacak .
Paul Fultz II

11

Bu senin için uygun mu:

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

Örneğin:

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
Lütfen cevabınızın daha fazla detayını açıklayabilir misiniz?
Mostafiz

Bu bir ters aralık temel döngü C ++ 11 sınıfı tamplate
Khan Lau

4

Kullanmak Eğer aralık v3 , ters aralığı adaptörü kullanarak ranges::view::reverseters kabı görüntülemek için izin verir.

Minimal bir çalışma örneği:

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

Bkz. DEMO 1 .

Not: As per Eric Niebler , bu özellik mevcut olacak 20 C ++ . Bu <experimental/ranges/range>başlık ile kullanılabilir . Sonra forifade şöyle görünecektir:

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

Bkz. DEMO 2


Güncelleme: ranges::viewAd alanı olarak yeniden adlandırıldı ranges::views. Yani, kullanın ranges::views::reverse.
nac001

2

C ++ 14 kullanmıyorsanız, en basit çözümü aşağıda bulabilirsiniz.

#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 . İşlevleri
olmayan kapsayıcılar / veri türleri (dizi gibi) için begin/rbegin, end/rendçalışmaz.


0

Sadece BOOST_REVERSE_FOREACHgeriye doğru yineleyerek kullanabilirsiniz . Örneğin, kod

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

aşağıdaki çıktıyı üretir:

4

3

2

1

0
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.