C ++ 11 loop for baseado em intervalo reverso

321

Existe um adaptador de contêiner que inverta a direção dos iteradores para que eu possa iterar sobre um contêiner invertido com loop for baseado em intervalo?

Com iteradores explícitos, eu converteria isso:

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

nisso:

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

Eu quero converter isso:

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

para isso:

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

Existe uma coisa dessas ou eu mesmo tenho que escrever?

Alex B
fonte
17
Um adaptador de contêiner reverso, parece interessante, mas acho que você precisará escrevê-lo. Não teríamos esse problema se o comitê Standard agilizasse e adaptasse algoritmos baseados em intervalo em vez de iteradores explícitos.
Deft_code
4
@deft_code: "em vez de?" Por que você quer se livrar dos algoritmos baseados no iterador? Eles são muito melhores e menos detalhados para casos em que você não itera de beginpara endou para lidar com iteradores de fluxo e afins. Os algoritmos de alcance seriam ótimos, mas são realmente apenas açúcar sintático (exceto a possibilidade de avaliação lenta) sobre algoritmos de iterador.
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; };Ele pode ser melhorado (adicionando constversões, etc.) mas funciona: vector<int> v {1, 2, 3}; reverse_adapter<decltype(v)> ra; for (auto& i : ra) cout << i;impressões321
Seth Carnegie
10
@SethCarnegie: E para adicionar uma boa forma funcional: template<typename T> reverse_adapter<T> reverse_adapt_container(T &c) {return reverse_adapter<T>(c);}Então você pode usar apenas for(auto &i: reverse_adapt_container(v)) cout << i;para iterar.
Nicol Bolas
2
@ CR: Eu não acho que deveria significar isso, porque isso tornaria indisponível como uma sintaxe concisa para loops onde a ordem importa. Na OMI, a concisão é mais importante / útil do que o seu significado semântico, mas se você não valoriza a concisão de seu guia de estilo, pode dar a ele qualquer implicação que desejar. É o que parallel_forseria para isso, com uma condição ainda mais forte de "não ligo para que ordem", se fosse incorporada ao padrão de alguma forma. Claro que poderia ter um açúcar sintático baseado em gama também :-)
Steve Jessop

Respostas:

230

Na verdade Impulso tem como adaptador: 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
fonte
90

Na verdade, no C ++ 14, isso pode ser feito com muito poucas linhas de código.

Essa idéia é muito semelhante à solução do @ Paul. Devido a coisas que faltam no C ++ 11, essa solução é um pouco desnecessariamente inchada (além de definir os cheiros std). Graças ao C ++ 14, podemos torná-lo muito mais legível.

A principal observação é que os loops for-range baseados em intervalo funcionam confiando begin()e end()para adquirir os iteradores do intervalo. Graças à ADL , não é necessário definir seu costume begin()e end()no espaço de nome std ::.

Aqui está uma solução de amostra muito simples:

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

Isso funciona como um encanto, por exemplo:

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 como esperado

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

NOTA std::rbegin() , std::rend()e std::make_reverse_iterator()ainda não estão implementadas no GCC-4.9. Escrevo esses exemplos de acordo com o padrão, mas eles não seriam compilados no g ++ estável. No entanto, adicionar stubs temporários para essas três funções é muito fácil. Aqui está uma implementação de amostra, definitivamente não concluída, mas funciona bem o suficiente para a maioria dos casos:

// --------------------------------------------------
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
fonte
35
Poucas linhas de código? Perdoe-me, mas isso é mais de dez :-)
Jonny
4
Na verdade, são 5-13, dependendo de como você conta as linhas:) As soluções alternativas não devem estar lá, pois fazem parte da biblioteca. Obrigado por me lembrar, btw, esta resposta precisa ser atualizada para versões recentes do compilador, onde todas as linhas extras não são necessárias.
Prikso NAI
2
Eu acho que você esqueceu forward<T>na sua reverseimplementação.
Cobra
3
Hum, se você colocar isso em um cabeçalho, você está using namespace stdem um cabeçalho, o que não é uma boa ideia. Ou eu estou esquecendo de alguma coisa?
estan
3
Na verdade, você não deveria estar escrevendo "using <anything>;" no escopo do arquivo em um cabeçalho. Você pode melhorar o acima, movendo as declarações using para o escopo da função para begin () e end ().
22618 Chris Hartman
23

Isso deve funcionar no C ++ 11 sem aumento:

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
fonte
58
O IIRC adicionar algo ao namespace std é um convite à falha épica.
BCS
35
Não tenho certeza do significado normativo de "falha épica", mas a sobrecarga de uma função no stdespaço para nome tem um comportamento indefinido em 17.6.4.2.1.
Casey
9
Aparentemente, está em C ++ 14 , com esse nome.
HostileFork diz que não confia em SE
6
@MuhammadAnnaqeeb A parte lamentável é que isso acontece exatamente. Você não pode compilar com as duas definições. Além disso, o compilador não precisa que a definição não esteja presente no C ++ 11 e apareça apenas no C ++ 14 (a especificação não diz nada sobre o que não está no espaço de nome std ::, apenas o que está). Portanto, isso seria uma falha de compilação muito provável em um compilador C ++ 11 compatível com os padrões ... muito mais provável do que se fosse algum nome aleatório que não estivesse no C ++ 14! E, como apontado, é "comportamento indefinido" ... então, não compilar é o pior que pode fazer.
HostileFork diz que não confia em SE
2
@HostileFork Não há colisão de nomes, make_reverse_iteratornão está no stdespaço para nome, portanto não entrará em conflito com a versão C ++ 14.
Paul Fultz II
11

Isso funciona para você:

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

por exemplo:

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
fonte
1
você pode explicar mais detalhes da sua resposta?
Mostafiz 29/04
este é um circuito-base de gama de Tamplate C ++ 11 classe inversa
Khan Lau
4

Se você pode usar o intervalo v3 , pode usar o adaptador de intervalo reverso, ranges::view::reverseque permite visualizar o contêiner ao contrário.

Um exemplo de trabalho mínimo:

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

Veja DEMO 1 .

Nota: Conforme Eric Niebler , esse recurso estará disponível no C ++ 20 . Isso pode ser usado com o <experimental/ranges/range>cabeçalho. Em seguida, a fordeclaração será assim:

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

Veja DEMO 2

PW
fonte
Atualização: o ranges::viewespaço para nome foi renomeado para ranges::views. Então, use ranges::views::reverse.
Nac001 03/12/19
2

Se não estiver usando o C ++ 14, encontrarei abaixo a solução mais simples.

#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 .
Ele não funciona para os contêineres / tipos de dados (como matriz), que não tem begin/rbegin, end/rendfunções.

iammilind
fonte
0

Você pode simplesmente usar o BOOST_REVERSE_FOREACHque itera para trás. Por exemplo, o código

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

gera a seguinte saída:

4

3

2

1

0
Catriel
fonte