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());
}
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
aend
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.20 Stimmen
@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; };
Sie kann verbessert werden (Hinzufügen vonconst
Versionen, usw.), aber es funktioniert:vector<int> v {1, 2, 3}; reverse_adapter<decltype(v)> ra; for (auto& i : ra) cout << i;
druckt321
11 Stimmen
@SethCarnegie: Und um eine schöne funktionale Form hinzuzufügen:
template<typename T> reverse_adapter<T> reverse_adapt_container(T &c) {return reverse_adapter<T>(c);}
Sie können also einfach Folgendes verwendenfor(auto &i: reverse_adapt_container(v)) cout << i;
zu iterieren.1 Stimmen
Auch wenn die bereichsbasierte for-Schleife so definiert ist, dass sie fortlaufend von
begin
aend
Ich denke, semantisch gesehen bedeutet es, dass die Reihenfolge der Operation nicht wichtig ist.2 Stimmen
@C.R: Ich glaube nicht, dass es なければならない meinen, denn dann wäre sie als prägnante Syntax für Schleifen, bei denen die Reihenfolge eine Rolle spielt, nicht mehr verfügbar. IMO ist die Prägnanz wichtiger/nützlicher als die semantische Bedeutung, aber wenn Sie keinen Wert auf die Prägnanz legen, kann Ihr Styleguide ihr jede beliebige Bedeutung geben. Das ist in etwa das, was
parallel_for
wäre, mit einer noch stärkeren "Es ist mir egal, in welcher Reihenfolge"-Bedingung, wenn sie in irgendeiner Form in die Norm aufgenommen würde. Natürlich könnte es auch einen bereichsbezogenen syntaktischen Zucker geben :-)