C ++ 11. Wahrscheinlich eines der schönsten Features (meiner Meinung nach) ist das sogenannte Range-Based-for-Loop. Anstelle von
for ( std::size_t i(0); i < range.size(); ++i ) { // do something to range[i]; }
oder
for ( Range::iterator it(range.begin()); it != range.end(); ++it ) { // do something to *it; }
oder vereinfacht mit C ++ 11 auto:
for ( auto it(range.begin()); it != range.end(); ++it ) { // do something to *it; }
Wir können Folgendes sagen:
for ( auto& entry : range ) { // do something with entry. }
Das ist sehr ausdrucksstark: Wir reden über den Eintrag und nicht über die i-te Position im Bereich oder den Iterator, der auf einen Eintrag zeigt. Dieser Syntax fehlt jedoch die Fähigkeit, Unterbereiche zu haben (z. B. Ignorieren des letzten Eintrags). In einer Reihe kleiner Hilfsstrukturen / -methoden möchte ich diese Funktionalität auf saubere Weise hinzufügen.
Soviel zu meiner Motivation 😉 Nun zum eigentlichen Geschäft.
In diesem Beitrag gehe ich auf Bedingungen für die Einträge von range
ein. Grundsätzlich sollte der Hilfecode das Äquivalent von
for ( auto& entry : range ) { if ( condition(entry) ) { // do something to entry. } }
ausführen, jedoch ohne diese Einrückungsstufe.
Hier ist der Code für ConditionalRange:
template <typename Range, typename Runnable> ConditionalRange<Range, Runnable> makeConditionalRange(Range& range, Runnable&& condition) { static_assert(std::is_same<decltype(condition(*std::declval<Range>().begin())), bool>::value, "Condition must return a boolean value."); return ConditionalRange<Range, Runnable>(range, std::forward<Runnable>(condition)); } template <typename Range, typename Runnable> struct ConditionalRange { public: friend ConditionalRange makeConditionalRange<>(Range&, Runnable&&); public: using iterator_type = ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>; iterator_type begin() const { auto b = range.begin(); while ( b != range.end() && !condition(*b) ) { ++b; } return ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>(b, range.end(), condition); } iterator_type end() const { return ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>(range.end(), range.end(), condition); } private: ConditionalRange(Range& range_, Runnable&& condition_) : range(range_), condition(std::forward<Runnable>(condition_)) { } private: Range& range; Runnable condition; };
Dies ist die Hilfsstruktur für den Iterator:
template <typename Iterator, typename Runnable> struct ConditionalIterator { private: Iterator iterator; Iterator end; const Runnable& condition; public: ConditionalIterator(Iterator b, Iterator e, const Runnable& r) : iterator(b), end(e), condition(r) { } auto operator*() -> decltype(*iterator) { return *iterator; } ConditionalIterator& operator++() { do { ++iterator; } while ( iterator != end && !condition(*iterator) ); return *this; } bool operator==(const ConditionalIterator& second) { return iterator == second.iterator; } bool operator!=(const ConditionalIterator& second) { return !(*this == second); } };
Die beabsichtigte Verwendung ist:
std::vector<int> ns{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; for ( const auto& n : makeConditionalRange(ns, [](int a) { return a % 2 == 0; }) ) { std::cout << n << " "; } std::cout << "\n";
Demo: hier klicken!
Was sind Schwächen meines Ansatzes? Mit welchen Argumenten scheitert es, obwohl es nicht in der perfekten Welt sein sollte?
Kommentare
- Ich kann keine Schwächen sehen, aber ich Denken Sie, Sie sollten in Betracht ziehen, makeConditionalRange mit etwas umzubenennen, das sich auf das Erscheinungsbild des Clientcodes konzentriert, und nicht darauf, " einen bedingten Bereich zu erstellen " bedeutet, dass der Client-Code stattdessen so aussehen sollte:
for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
- Die Absicht hier unterscheidet sich also erheblich von einem Boost
filter_iterator
? - @JerryCoffin Nein, ' t. Ich habe einfach ' hat Boost nicht untersucht 😉
- Der häufigste Test für Iteratoren
operator!=
optimiert dies überoperator==
- @stefan: Um ehrlich zu sein, dass ' dumm ist. Es ist so, als würde ich sagen, dass ich ' Ich werde kein Auto von den Standardherstellern kaufen weil ich eine bauen möchte, die selbst von Seetang abläuft, um sicherzustellen, dass sie keine Umweltverschmutzung verursacht. Das Problem ist, dass Ihre Bibliotheken unendlich fehlerhafter sind als Boost, da Sie nur eine Gruppe von Augen haben, die sie betrachten. Bei Boost überprüfen und validieren Tausende von Personen die Code-Korrektur und geben Feedback, um sicherzustellen, dass die richtigen Redewendungen korrekt verwendet werden. Lesen Sie: stackoverflow.com/a/149305/14065
Antwort
Sie können geschweifte Klammern verwenden, um von Ihren Funktionen zurückzukehren. Auf diese Weise müssen Sie keine langen und umständlichen Typen wiederholen (DRY, so oft wie möglich). Beispiel: ConditionalRange::end
:
iterator_type end() const { return { range.end(), range.end(), condition }; }
Außerdem möchten Sie wahrscheinlich, dass Ihre Funktion auch Arrays im C-Stil akzeptiert. Daher sollten Sie std::begin
und std::end
anstatt die Mitgliedsfunktionen aufzurufen:
iterator_type end() const { return { std::end(range), std::end(range), condition }; }
Damit (überall in Ihrem Code) und einigen geringfügigen Änderungen funktioniert Ihr Code auch für C- Style-Arrays (getestet hier und es funktioniert einwandfrei).
Benennen Ihrer Funktion
Der Name makeConditionalRange
ist ziemlich lang. Die von Ihnen erstellte Funktion ist bereits in anderen Bibliotheken und Programmiersprachen (Python fällt mir ein) unter dem Namen filter
vorhanden. Sie sollten dies berücksichtigen Ändern des Namens, damit viele Benutzer den Namen auf den ersten Blick erkennen (außerdem wird er kürzer).