web-dev-qa-db-fra.com

C++ 11 basé sur la plage inverse pour la boucle

Existe-t-il un adaptateur de conteneur qui inverserait la direction des itérateurs afin que je puisse effectuer une itération inverse sur un conteneur en inversant la boucle for basée sur la plage?

Avec des itérateurs explicites, je convertirais ceci:

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

dans ceci:

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

Je veux convertir ceci:

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

pour ça:

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

Existe-t-il une telle chose ou dois-je l'écrire moi-même?

278
Alex B

En fait, Boost a un tel adaptateur: 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';
}
212
kennytm

En réalité, en C++ 14, cela peut être fait avec très peu de lignes de code.

Ceci est une idée très similaire à la solution de @ Paul. En raison d'éléments manquants dans C++ 11, cette solution est un peu inutilement gonflée (plus la définition d'odeurs std). Grâce à C++ 14, nous pouvons le rendre plus lisible.

L'observation clé est que les boucles for basées sur les plages fonctionnent en utilisant begin() et end() afin d'acquérir les itérateurs de la gamme. Grâce à ADL , vous n'avez même pas besoin de définir leurs fonctions begin() et end() personnalisées dans l'espace de noms std ::.

Voici une solution très simple:

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

Cela fonctionne comme un charme, par exemple:

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

imprime comme prévu

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

NOTEstd::rbegin(), std::rend() et std::make_reverse_iterator() ne sont pas encore implémentés dans GCC-4.9. J'écris ces exemples selon le standard, mais ils ne compileraient pas en version stable g ++. Néanmoins, l’ajout de stubs temporaires pour ces trois fonctions est très simple. Voici un exemple d'implémentation, certainement pas complet mais fonctionne assez bien dans la plupart des cas:

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

UPDATE 22 oct 2017 

Merci à estan pour le signaler.

L'exemple d'implémentation de réponse d'origine utilise using namespace std;, ce qui obligerait tout fichier incluant cette implémentation (qui doit figurer dans le fichier d'en-tête) à importer également tout l'espace de noms std.

Révision de l'exemple d'implémentation pour proposer using std::rbegin, std::rend à la place.

71
Prikso NAI

Cela devrait fonctionner en C++ 11 sans boost:

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))
{
    ...
}
22
Paul Fultz II

Est-ce que ça marche pour toi:

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

par exemple:

    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;
    }
6
Khan Lau

Si je n'utilise pas C++ 14, je trouve ci-dessous la solution la plus simple.

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

Démo .
Cela ne fonctionne pas pour les conteneurs/types de données (comme array), qui n’ont pas de fonctions begin/rbegin, end/rend.

2
iammilind

Si vous pouvez utiliser range v3 , vous pouvez utiliser l'adaptateur de plage inversée ranges::view::reverse qui vous permet d'afficher le conteneur en sens inverse.

Un exemple de travail 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;
}

Voir DEMO 1 .

Remarque: Selon Eric Niebler , cette fonctionnalité sera disponible dans C++ 20. Ceci peut être utilisé avec l'en-tête <experimental/ranges/range>. Ensuite, l'instruction for ressemblera à ceci:

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

Voir DEMO 2

0
P.W

Vous pouvez simplement utiliser BOOST_REVERSE_FOREACH qui itère en arrière. Par exemple, le code

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

génère la sortie suivante:

4

3

2

1

0
0
Catriel