Es ist großartig, C ++ 11 bereichsbasiert für Schleifen etwas nützlicher zu machen.

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 über operator==
  • @stefan: Um ehrlich zu sein, dass ' dumm ist. Es ist so, als würde ich sagen, dass ich ' 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).

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.