399 Stimmen

C++11 Umgekehrte bereichsbasierte for-Schleife

Gibt es einen Container-Adapter, der die Richtung der Iteratoren umkehren würde, so dass ich über einen Container in umgekehrter Richtung mit Bereich-basierte for-Schleife iterieren kann?

Bei expliziten Iteratoren würde ich dies umsetzen:

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

in diese:

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

Ich möchte dies umwandeln:

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

dazu:

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

Gibt es so etwas, oder muss ich es selbst schreiben?

22 Stimmen

Ein Reverse-Container-Adapter klingt interessant, aber ich denke, Sie müssen ihn selbst schreiben. Wir hätten dieses Problem nicht, wenn sich das Standardkomitee beeilen würde und bereichsbasierte Algorithmen anstelle von expliziten Iteratoren anpassen würde.

0 Stimmen

@Seth Ich weiß, ich könnte es schreiben, aber das ist nicht der Punkt. Wenn ich es schreibe, wird es zu einer dieser Utility Functions That Don't Belong Anywhere In Particular(tm), so dass du am Ende deinen Code mit dem Include eines besagten Utility-Headers bestreust und dein Build-System umstellst, um es projektübergreifend zu nutzen. Mit dieser Argumentation sollten wir immer noch BOOST_FOREACH anstelle von range-for verwenden. Und ja, ich bin faul.

5 Stimmen

@deft_code: "anstelle von?" Warum sollten Sie Iterator-basierte Algorithmen abschaffen wollen? Sie sind viel besser und weniger langatmig für Fälle, in denen man keine Iteration von begin a end oder für den Umgang mit Stream-Iteratoren und Ähnlichem. Bereichsalgorithmen wären großartig, aber sie sind wirklich nur syntaktischer Zucker (abgesehen von der Möglichkeit der trägen Auswertung) für Iterator-Algorithmen.

280voto

kennytm Punkte 488916

Tatsächlich hat Boost einen solchen Adapter: 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';
}

13 Stimmen

Ich habe es einfach satt, immer wieder Antworten zu sehen, die auf die eine oder andere Weise einen Aufschwung bringen.

125voto

Prikso NAI Punkte 2464

In C++14 kann dies mit wenigen Zeilen Code erreicht werden.

Die Idee ist der von @Paul sehr ähnlich. Aufgrund von Dingen, die in C++11 fehlen, ist diese Lösung ein wenig unnötig aufgebläht (plus Definition in std smells). Dank C++14 können wir es viel lesbarer machen.

Die wichtigste Beobachtung ist, dass bereichsbasierte for-Schleifen funktionieren, indem sie sich auf begin() y end() um die Iteratoren des Bereichs zu erfassen. Dank ADL muss man noch nicht einmal seine eigene Norm definieren. begin() y end() im std:: Namespace.

Hier ist ein sehr einfaches Lösungsbeispiel:

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

Das funktioniert zum Beispiel sehr gut:

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

druckt wie erwartet

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

ANMERKUNG std::rbegin() , std::rend() y std::make_reverse_iterator() sind im GCC-4.9 noch nicht implementiert. Ich schreibe diese Beispiele nach dem Standard, aber sie würden nicht in stabilem G++ kompilieren. Dennoch ist es sehr einfach, temporäre Stubs für diese drei Funktionen hinzuzufügen. Hier ist eine Beispielimplementierung, definitiv nicht vollständig funktioniert aber in den meisten Fällen gut genug:

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

0 Stimmen

Nur ein Bericht, dass dies funktioniert perfekt auf Clang 3.7.0 mit -std=c++14

74 Stimmen

Wenige Zeilen Code? Verzeihen Sie mir, aber das sind mehr als zehn :-)

4 Stimmen

Eigentlich sind es 5-13, je nachdem, wie man die Zeilen zählt : ) Die Umgehungslösungen sollten dort nicht sein, da sie Teil der Bibliothek sind. Danke, dass Sie mich daran erinnern, btw, diese Antwort muss für aktuelle Compiler-Versionen aktualisiert werden, wo all die zusätzlichen Zeilen überhaupt nicht benötigt werden.

50voto

Sorush Punkte 2165

Dieses Beispiel stammt von cpp-Referenz . Es funktioniert mit:

GCC 10.1+ mit Flagge -std=c++20

#include <ranges>
#include <iostream>

int main()
{
    static constexpr auto il = {3, 1, 4, 1, 5, 9};

    std::ranges::reverse_view rv {il};
    for (int i : rv)
        std::cout << i << ' ';

    std::cout << '\n';

    for(int i : il | std::views::reverse)
        std::cout << i << ' ';
}

30voto

P.W Punkte 25719

Wenn Sie Folgendes verwenden können Bereich v3 können Sie den Adapter mit umgekehrter Reichweite verwenden ranges::view::reverse die es Ihnen ermöglicht, den Container in umgekehrter Reihenfolge zu betrachten.

Ein minimales Arbeitsbeispiel:

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

Siehe DEMO 1 .

Anmerkung: Gemäß Eric Niebler wird diese Funktion verfügbar sein in C++20 . Dies kann mit dem <experimental/ranges/range> Kopfzeile. Dann wird die for Anweisung wird folgendermaßen aussehen:

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

Siehe DEMO 2

5 Stimmen

Aktualisierung: Die ranges::view Namespace wurde umbenannt in ranges::views . Also, verwenden ranges::views::reverse .

27voto

Paul Fultz II Punkte 16880

Dies sollte in C++11 ohne Boost funktionieren:

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))
{
    ...
}

80 Stimmen

IIRC Hinzufügen von etwas zu Namespace std ist eine Einladung zu epic fail.

44 Stimmen

Ich bin mir über die normative Bedeutung von "epic fail" nicht sicher, aber das Überladen einer Funktion in der std Namespace hat ein undefiniertes Verhalten gemäß 17.6.4.2.1.

9 Stimmen

Es ist in C++14 offenbar unter diesem Namen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X