Wie kann ich mit C ++ "for" -Schleifen mit einer "if" -Bedingung vermeiden?


111

Bei fast dem gesamten Code, den ich schreibe, habe ich häufig Probleme mit der Reduzierung von Sätzen in Sammlungen, die letztendlich zu naiven "Wenn" -Bedingungen führen. Hier ist ein einfaches Beispiel:

for(int i=0; i<myCollection.size(); i++)
{
     if (myCollection[i] == SOMETHING)
     {
           DoStuff();
     }
}

Mit funktionalen Sprachen kann ich das Problem lösen, indem ich die Sammlung (einfach) auf eine andere Sammlung reduziere und dann alle Operationen an meinem reduzierten Satz ausführe. Im Pseudocode:

newCollection <- myCollection where <x=true
map DoStuff newCollection

Und in anderen C-Varianten wie C # könnte ich mit einer where-Klausel wie reduzieren

foreach (var x in myCollection.Where(c=> c == SOMETHING)) 
{
   DoStuff();
}

Oder besser (zumindest für meine Augen)

myCollection.Where(c=>c == Something).ToList().ForEach(d=> DoStuff(d));

Zugegeben, ich mache viel Paradigmenmischung und subjektiven / meinungsbasierten Stil, aber ich kann nicht anders, als das Gefühl zu haben, dass mir etwas wirklich Grundlegendes fehlt, das es mir ermöglichen könnte, diese bevorzugte Technik mit C ++ zu verwenden. Könnte mich jemand aufklären?


7
Aus der C ++ Standardbibliotheksfunktionalität können Sie es versuchen std::copy_if, aber die Auswahl ist nicht faul
Milleniumbug

14
Sie könnten an range-v3 interessiert sein . Es sollte auch als TS zu C ++ kommen und hoffentlich in einer zukünftigen Version standardisiert werden.
NathanOliver

12
Ich muss darauf hinweisen, dass das ifInnere , das forSie erwähnen, nicht nur den anderen Beispielen funktional ziemlich äquivalent ist, sondern in vielen Fällen wahrscheinlich auch schneller wäre. Auch für jemanden, der behauptet, funktionalen Stil zu mögen, scheint das, was Sie fördern, gegen das geliebte Konzept der Reinheit der funktionalen Programmierung zu DoStuffverstoßen, da es eindeutig Nebenwirkungen hat.
Pharap

60
Ich habe nie wirklich verstanden, warum Leute, die die gesamte Logik in einer einzigen Zeile kombinieren, sie irgendwie besser oder lesbarer aussehen lassen. Ihr C ++ - Snippet ganz oben ist für mich bei weitem das am besten lesbare aller Ihrer Möglichkeiten. Und da sich die Effizienz nicht ändert, kann ich nicht verstehen, warum Sie das lieber nicht schreiben möchten, es sei denn, Sie werden nach der Anzahl der von Ihnen gelöschten Codezeilen bezahlt.
Cody Gray

10
@CodyGray Einverstanden: Es ist nur syntaktischer Zucker. Und der Fragentitel ist irreführend, weil er sehr unterschiedlich ist, indem er verzweigt und unter Abstraktion versteckt wird.
Edmz

Antworten:


99

IMHO ist es einfacher und lesbarer, eine for-Schleife mit einem if darin zu verwenden. Wenn dies jedoch für Sie ärgerlich ist, können Sie for_each_ifFolgendes verwenden:

template<typename Iter, typename Pred, typename Op> 
void for_each_if(Iter first, Iter last, Pred p, Op op) {
  while(first != last) {
    if (p(*first)) op(*first);
    ++first;
  }
}

Anwendungsfall:

std::vector<int> v {10, 2, 10, 3};
for_each_if(v.begin(), v.end(), [](int i){ return i > 5; }, [](int &i){ ++i; });

Live-Demo


10
Das ist außergewöhnlich klug. Ich werde auch zustimmen, dass es nicht einfach ist und ich werde wahrscheinlich nur if-Bedingungen beim Programmieren von C ++ verwenden, die von anderen verwendet werden. Aber genau das brauche ich für meinen persönlichen Gebrauch! :)
Darkenor

14
@Default Das Übergeben von Iteratorpaaren anstelle von Containern ist sowohl flexibler als auch idiomatischer in C ++.
Mark B

8
@Slava, im Allgemeinen reduzieren Bereiche die Anzahl der Algorithmen nicht. Zum Beispiel brauchen Sie noch find_ifund findob sie auf Bereichen oder Iteratorpaaren funktionieren. (Es gibt einige Ausnahmen wie for_eachund for_each_n). Die Art und Weise neue algos für jeden Nieser Schreiben zu vermeiden , ist verschiedene Operationen mit der bestehenden algos zu verwenden, zB statt for_each_ifeinbetten der Bedingung in den vergangen aufrufbar for_each, zBfor_each(first, last, [&](auto& x) { if (cond(x)) f(x); });
Jonathan Wakely

9
Ich muss dem ersten Satz zustimmen: Die Standard-For-If-Lösung ist viel besser lesbar und einfacher zu bearbeiten. Ich denke, die Lambda-Syntax und die Verwendung einer Vorlage, die an einer anderen Stelle definiert wurde, um nur eine einfache Schleife zu handhaben, würden andere Entwickler irritieren oder möglicherweise verwirren. Sie opfern Lokalität und Leistung für ... was? Etwas in einer Zeile schreiben können?
user1354557

45
Husten @Darkenor, generell " außergewöhnlich clevere" Programmierung ist zu vermeiden, weil es den Mist von allen anderen nervt, auch von Ihrem zukünftigen Selbst.
Ryan

48

Boost bietet Bereiche, für die w / range-based verwendet werden kann. Bereiche haben den Vorteil , dass sie nicht kopieren , die zugrunde liegende Datenstruktur, sie nur bieten eine ‚Aussicht‘ (das heißt, begin(), end()für den Bereich und operator++(), operator==()für den Iterator). Dies könnte von Ihrem Interesse sein: http://www.boost.org/libs/range/doc/html/range/reference/adaptors/reference/filtered.html

#include <boost/range/adaptor/filtered.hpp>
#include <iostream>
#include <vector>

struct is_even
{
    bool operator()( int x ) const { return x % 2 == 0; }
};

int main(int argc, const char* argv[])
{
    using namespace boost::adaptors;

    std::vector<int> myCollection{1,2,3,4,5,6,7,8,9};

    for( int i: myCollection | filtered( is_even() ) )
    {
        std::cout << i;
    }
}

1
Darf ich vorschlagen, stattdessen das OPs-Beispiel zu verwenden, dh is_even=> condition, input=> myCollectionusw.
Standard

Dies ist eine ziemlich gute Antwort und definitiv das, was ich tun möchte. Ich werde das Akzeptieren zurückhalten, es sei denn, jemand kann eine standardkonforme Methode finden, die eine verzögerte / verzögerte Ausführung verwendet. Upvoted.
Darkenor

5
@Darkenor: Wenn Boost ein Problem für Sie ist (zum Beispiel ist es Ihnen aufgrund von Unternehmensrichtlinien und Manager-Weisheit verboten, es zu verwenden), kann ich eine vereinfachte Definition filtered()für Sie finden - das heißt, es ist besser, es zu verwenden eine unterstützte Bibliothek als irgendein Ad-hoc-Code.
Lorro

Ich stimme dir vollkommen zu. Ich habe es akzeptiert, weil der standardkonforme Weg zuerst kam, weil die Frage auf C ++ selbst ausgerichtet war, nicht auf die Boost-Bibliothek. Aber das ist wirklich hervorragend. Auch - ja, ich habe leider an vielen
Orten gearbeitet

@ LeeClagett :? .
Lorro

44

Anstatt wie bei der akzeptierten Antwort einen neuen Algorithmus zu erstellen, können Sie einen vorhandenen Algorithmus mit einer Funktion verwenden, die die Bedingung anwendet:

std::for_each(first, last, [](auto&& x){ if (cond(x)) { ... } });

Oder wenn Sie wirklich einen neuen Algorithmus wollen, verwenden Sie ihn dort zumindest wieder, for_eachanstatt die Iterationslogik zu duplizieren:

template<typename Iter, typename Pred, typename Op> 
  void
  for_each_if(Iter first, Iter last, Pred p, Op op) {
    std::for_each(first, last, [&](auto& x) { if (p(x)) op(x); });
  }

Viel besser und klarer für die Verwendung der Standardbibliothek.
anonym

4
Weil std::for-each(first, last, [&](auto& x) {if (p(x)) op(x); });ist total einfacher als for (Iter x = first; x != last; x++) if (p(x)) op(x);}?
user253751

2
Die Wiederverwendung der Standardbibliothek durch @immibis bietet weitere Vorteile, z. B. die Überprüfung der Iteratorgültigkeit oder (in C ++ 17) die einfachere Parallelisierung, indem einfach ein weiteres Argument hinzugefügt wird: std::for_each(std::execution::par, first, last, ...);Wie einfach ist es, diese Dinge einer handschriftlichen Schleife hinzuzufügen?
Jonathan Wakely

1
#pragma omp parallel für
Mark K Cowan

2
@mark Entschuldigung, eine zufällige Eigenart Ihres Quellcodes oder Ihrer Build-Kette hat dazu geführt, dass diese ärgerlich fragile parallele, nicht standardmäßige Compiler-Erweiterung ohne Diagnose keine Leistungssteigerung erzeugt.
Yakk - Adam Nevraumont

21

Die Idee zu vermeiden

for(...)
    if(...)

Konstrukte als Antimuster sind zu breit.

Es ist völlig in Ordnung, mehrere Elemente, die einem bestimmten Ausdruck entsprechen, innerhalb einer Schleife zu verarbeiten, und der Code kann nicht viel klarer werden. Wenn die Verarbeitung zu groß wird, um auf den Bildschirm zu passen, ist dies ein guter Grund, eine Unterroutine zu verwenden, aber die Bedingung wird am besten innerhalb der Schleife platziert, d. H.

for(...)
    if(...)
        do_process(...);

ist weitaus vorzuziehen

for(...)
    maybe_process(...);

Es wird zu einem Antimuster, wenn nur ein Element übereinstimmt, da es dann klarer wäre, zuerst nach dem Element zu suchen und die Verarbeitung außerhalb der Schleife durchzuführen.

for(int i = 0; i < size; ++i)
    if(i == 5)

ist ein extremes und offensichtliches Beispiel dafür. Subtiler und damit häufiger ist ein Fabrikmuster wie

for(creator &c : creators)
    if(c.name == requested_name)
    {
        unique_ptr<object> obj = c.create_object();
        obj.owner = this;
        return std::move(obj);
    }

Dies ist schwer zu lesen, da es nicht offensichtlich ist, dass der Body-Code nur einmal ausgeführt wird. In diesem Fall ist es besser, die Suche zu trennen:

creator &lookup(string const &requested_name)
{
    for(creator &c : creators)
        if(c.name == requested_name)
            return c;
}

creator &c = lookup(requested_name);
unique_ptr obj = c.create_object();

Es gibt immer noch ein ifinnerhalb von a for, aber aus dem Kontext wird klar, was es tut, es besteht keine Notwendigkeit, diesen Code zu ändern, es sei denn, die Suche ändert sich (z. B. zu a map), und es ist sofort klar, dass create_object()nur einmal aufgerufen wird, weil es so ist nicht in einer Schleife.


Ich mag dies als nachdenklichen und ausgewogenen Überblick, auch wenn es sich gewissermaßen weigert, die gestellte Frage zu beantworten. Ich finde, dass der for( range ){ if( condition ){ action } }Stil es einfach macht, Dinge einzeln zu lesen, und nur Kenntnisse über die grundlegenden Sprachkonstrukte verwendet.
PJTraill

@PJTraill, die Art und Weise, wie die Frage formuliert wurde, erinnerte mich an Raymond Chens Schimpfen gegen das For-If -Antimuster, das mit Fracht kultiviert wurde und irgendwie zu einem absoluten wurde. Ich stimme vollkommen zu, dass dies for(...) if(...) { ... }oft die beste Wahl ist (deshalb habe ich die Empfehlung qualifiziert, die Aktion in eine Unterroutine aufzuteilen).
Simon Richter

1
Vielen Dank für den Link, der die Dinge für mich klargestellt hat: Der Name " for-if " ist irreführend und sollte so etwas wie " for-all-if-one " oder " Lookup-Vermeidung " sein. Es erinnert mich an die Art und Weise, wie die Abstraktionsinversion von Wikipedia im Jahr 2005 beschrieben wurde, als man „ einfache Konstrukte auf komplexen (Einsen) erstellt“ - bis ich sie umschrieb! Eigentlich würde ich mich nicht einmal beeilen, das Lookup-Process-Exit-Formular zu for(…)if(…)…korrigieren, wenn es der einzige Ort wäre, an dem Lookup stattgefunden hat.
PJTraill

17

Hier ist eine schnelle, relativ minimale filterFunktion.

Es braucht ein Prädikat. Es gibt ein Funktionsobjekt zurück, das eine Iterierbarkeit annimmt.

Es gibt eine Iterable zurück, die in einer for(:)Schleife verwendet werden kann.

template<class It>
struct range_t {
  It b, e;
  It begin() const { return b; }
  It end() const { return e; }
  bool empty() const { return begin()==end(); }
};
template<class It>
range_t<It> range( It b, It e ) { return {std::move(b), std::move(e)}; }

template<class It, class F>
struct filter_helper:range_t<It> {
  F f;
  void advance() {
    while(true) {
      (range_t<It>&)*this = range( std::next(this->begin()), this->end() );
      if (this->empty())
        return;
      if (f(*this->begin()))
        return;
    }
  }
  filter_helper(range_t<It> r, F fin):
    range_t<It>(r), f(std::move(fin))
  {
      while(true)
      {
          if (this->empty()) return;
          if (f(*this->begin())) return;
          (range_t<It>&)*this = range( std::next(this->begin()), this->end() );
      }
  }
};

template<class It, class F>
struct filter_psuedo_iterator {
  using iterator_category=std::input_iterator_tag;
  filter_helper<It, F>* helper = nullptr;
  bool m_is_end = true;
  bool is_end() const {
    return m_is_end || !helper || helper->empty();
  }

  void operator++() {
    helper->advance();
  }
  typename std::iterator_traits<It>::reference
  operator*() const {
    return *(helper->begin());
  }
  It base() const {
      if (!helper) return {};
      if (is_end()) return helper->end();
      return helper->begin();
  }
  friend bool operator==(filter_psuedo_iterator const& lhs, filter_psuedo_iterator const& rhs) {
    if (lhs.is_end() && rhs.is_end()) return true;
    if (lhs.is_end() || rhs.is_end()) return false;
    return lhs.helper->begin() == rhs.helper->begin();
  }
  friend bool operator!=(filter_psuedo_iterator const& lhs, filter_psuedo_iterator const& rhs) {
    return !(lhs==rhs);
  }
};
template<class It, class F>
struct filter_range:
  private filter_helper<It, F>,
  range_t<filter_psuedo_iterator<It, F>>
{
  using helper=filter_helper<It, F>;
  using range=range_t<filter_psuedo_iterator<It, F>>;

  using range::begin; using range::end; using range::empty;

  filter_range( range_t<It> r, F f ):
    helper{{r}, std::forward<F>(f)},
    range{ {this, false}, {this, true} }
  {}
};

template<class F>
auto filter( F&& f ) {
    return [f=std::forward<F>(f)](auto&& r)
    {
        using std::begin; using std::end;
        using iterator = decltype(begin(r));
        return filter_range<iterator, std::decay_t<decltype(f)>>{
            range(begin(r), end(r)), f
        };
    };
};

Ich habe Abkürzungen genommen. Eine echte Bibliothek sollte echte Iteratoren sein, nicht die for(:)qualifizierenden Pseudofasken, die ich gemacht habe.

Am Verwendungsort sieht es so aus:

int main()
{
  std::vector<int> test = {1,2,3,4,5};
  for( auto i: filter([](auto x){return x%2;})( test ) )
    std::cout << i << '\n';
}

Das ist ziemlich schön und druckt

1
3
5

Live-Beispiel .

Es gibt eine vorgeschlagene Ergänzung zu C ++ namens Rangesv3, die so etwas und mehr macht. boosthat auch Filterbereiche / Iteratoren zur Verfügung. Boost hat auch Helfer, die das Schreiben der oben genannten viel kürzer machen.


15

Ein Stil, der genug verwendet wird, um zu erwähnen, aber noch nicht erwähnt wurde, ist:

for(int i=0; i<myCollection.size(); i++) {
  if (myCollection[i] != SOMETHING)
    continue;

  DoStuff();
}

Vorteile:

  • Ändert nicht die Einrückungsstufe, DoStuff();wenn die Komplexität der Bedingungen zunimmt. Logischerweise DoStuff();sollte es sich auf der obersten Ebene der forSchleife befinden, und das ist es auch.
  • Es wird sofort klargestellt, dass die Schleife über das SOMETHINGs der Sammlung iteriert , ohne dass der Leser überprüfen muss, ob nach dem Schließen }des ifBlocks nichts mehr vorhanden ist .
  • Benötigt keine Bibliotheken oder Hilfsmakros oder -funktionen.

Nachteile:

  • continueWie andere Anweisungen zur Flusskontrolle wird sie auf eine Weise missbraucht, die zu schwer zu befolgendem Code führt, so dass einige Leute gegen jede Verwendung von Code sind : Es gibt einen gültigen Codierungsstil, den einige befolgen, der vermeidet continue, der vermeidetbreak andere als andere in a switchvermeidet das returnandere als am Ende einer Funktion.

3
Ich würde argumentieren, dass in einer forSchleife, die zu vielen Zeilen führt, eine zweizeilige Zeile "Wenn nicht, weiter" viel klarer, logischer und lesbarer ist. Sagen Sie sofort "Überspringen Sie dies, wenn", nachdem die forAnweisung gut gelesen wurde, und rücken Sie, wie Sie sagten, die verbleibenden funktionalen Aspekte der Schleife nicht ein. Wenn das continueweiter unten ist, wird jedoch eine gewisse Klarheit geopfert (dh wenn eine Operation immer vor der ifAnweisung ausgeführt wird).
anonym

11
for(auto const &x: myCollection) if(x == something) doStuff();

Sieht ziemlich nach C ++ aus for für mich Verständnis aus. Für dich?


Ich glaube nicht, dass das Auto-Schlüsselwort vor C ++ 11 vorhanden war, daher würde ich nicht sagen, dass es sehr klassisches C ++ ist. Wenn ich hier im Kommentar eine Frage stellen darf, würde "auto const" dem Compiler mitteilen, dass er alle Elemente nach Belieben neu anordnen kann? Vielleicht ist es für den Compiler einfacher zu planen, Verzweigungen zu vermeiden, wenn dies der Fall ist.
Mathreadler

1
@mathreadler Je früher die Leute aufhören, sich um "klassisches C ++" zu sorgen, desto besser. C ++ 11 war ein makroevolutionäres Ereignis für die Sprache und ist 5 Jahre alt: Es sollte das Minimum sein, das wir anstreben. Wie auch immer, das OP hat das und C ++ 14 markiert (noch besser!). Nein, auto consthat keinerlei Einfluss auf die Iterationsreihenfolge. Wenn Sie nach Fernkampf suchen for, werden Sie feststellen, dass im Grunde eine Standardschleife von begin()bis end()mit impliziter Dereferenzierung ausgeführt wird. Es gibt keine Möglichkeit, die Bestellgarantien (falls vorhanden) des zu iterierenden Containers zu brechen. es wäre vom Erdboden gelacht worden
underscore_d

1
@mathreadler, eigentlich war es, es hatte nur eine ganz andere Bedeutung. Was nicht vorhanden war, ist Range-for ... und jede andere eindeutige C ++ 11-Funktion. Was ich hier meinte war, dass Range-fors, std::futures, std::functions, selbst diese anonymen Verschlüsse sehr gut C ++ in der Syntax sind; Jede Sprache hat ihre eigene Sprache, und wenn neue Funktionen integriert werden, versucht sie, die alte bekannte Syntax nachzuahmen.
Bipll

@underscore_d, ein Compiler darf alle Transformationen durchführen, vorausgesetzt, die Als-ob-Regel wird eingehalten, nicht wahr?
Bipll

1
Hmmm, und was kann damit gemeint sein?
Bipll

7

Wenn DoStuff () in Zukunft irgendwie von i abhängig wäre, würde ich diese garantierte verzweigungsfreie Bitmaskierungsvariante vorschlagen.

unsigned int times = 0;
const int kSize = sizeof(unsigned int)*8;
for(int i = 0; i < myCollection.size()/kSize; i++){
  unsigned int mask = 0;
  for (int j = 0; j<kSize; j++){
    mask |= (myCollection[i*kSize+j]==SOMETHING) << j;
  }
  times+=popcount(mask);
}

for(int i=0;i<times;i++)
   DoStuff();

Wobei popcount eine Funktion ist, die eine Populationszählung durchführt (Anzahl der Bits = 1). Es wird einige Freiheiten geben, um i und ihren Nachbarn fortgeschrittenere Einschränkungen aufzuerlegen. Wenn dies nicht benötigt wird, können wir die innere Schleife entfernen und die äußere Schleife neu erstellen

for(int i = 0; i < myCollection.size(); i++)
  times += (myCollection[i]==SOMETHING);

gefolgt von einem

for(int i=0;i<times;i++)
   DoStuff();

6

Wenn Sie die Sammlung nicht neu anordnen möchten, ist std :: partition auch günstig.

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

void DoStuff(int i)
{
    std::cout << i << '\n';
}

int main()
{
    using namespace std::placeholders;

    std::vector<int> v {1, 2, 5, 0, 9, 5, 5};
    const int SOMETHING = 5;

    std::for_each(v.begin(),
                  std::partition(v.begin(), v.end(),
                                 std::bind(std::equal_to<int> {}, _1, SOMETHING)), // some condition
                  DoStuff); // action
}

Aber std::partition ordnet den Container neu.
Celtschk

5

Ich bin beeindruckt von der Komplexität der oben genannten Lösungen. Ich wollte ein einfaches vorschlagen, #define foreach(a,b,c,d) for(a; b; c)if(d)aber es weist einige offensichtliche Defizite auf. Sie müssen beispielsweise daran denken, in Ihrer Schleife Kommas anstelle von Semikolons zu verwenden, und Sie können den Kommaoperator in aoder nicht verwenden c.

#include <list>
#include <iostream>

using namespace std; 

#define foreach(a,b,c,d) for(a; b; c)if(d)

int main(){
  list<int> a;

  for(int i=0; i<10; i++)
    a.push_back(i);

  for(auto i=a.begin(); i!=a.end(); i++)
    if((*i)&1)
      cout << *i << ' ';
  cout << endl;

  foreach(auto i=a.begin(), i!=a.end(), i++, (*i)&1)
    cout << *i << ' ';
  cout << endl;

  return 0;
}

3
Die Komplexität einiger Antworten ist nur deshalb hoch, weil sie zuerst eine wiederverwendbare generische Methode anzeigen (die Sie nur einmal ausführen würden) und diese dann verwenden. Nicht effektiv, wenn Sie eine Schleife mit einer if-Bedingung in Ihrer gesamten Anwendung haben, aber sehr effektiv, wenn sie tausendmal auftritt.
Gnasher729

1
Wie die meisten Vorschläge macht es dies schwieriger und nicht einfacher, den Bereich und die Auswahlbedingung zu identifizieren. Und die Verwendung eines Makros erhöht die Unsicherheit darüber, wann (und wie oft) Ausdrücke ausgewertet werden, auch wenn es hier keine Überraschungen gibt.
PJTraill

2

Eine andere Lösung für den Fall, dass die i: s wichtig sind. Dieser erstellt eine Liste, die die Indizes ausfüllt, für die doStuff () aufgerufen werden soll. Auch hier geht es vor allem darum, die Verzweigung zu vermeiden und gegen pipelinierbare Rechenkosten einzutauschen.

int buffer[someSafeSize];
int cnt = 0; // counter to keep track where we are in list.
for( int i = 0; i < container.size(); i++ ){
   int lDecision = (container[i] == SOMETHING);
   buffer[cnt] = lDecision*i + (1-lDecision)*buffer[cnt];
   cnt += lDecision;
}

for( int i=0; i<cnt; i++ )
   doStuff(buffer[i]); // now we could pass the index or a pointer as an argument.

Die "magische" Linie ist die Pufferladelinie, die arithmetisch berechnet, ob der Wert beibehalten und in Position bleiben oder die Position hochgezählt und der Wert erhöht werden soll. Also tauschen wir einen potenziellen Zweig gegen Logik und Arithmetik und vielleicht gegen Cache-Treffer aus. Ein typisches Szenario, in dem dies nützlich wäre, ist, wenn doStuff () eine kleine Anzahl von Pipeline-Berechnungen durchführt und jede Verzweigung zwischen Aufrufen diese Pipelines unterbrechen könnte.

Dann durchlaufen Sie einfach den Puffer und führen doStuff () aus, bis wir cnt erreichen. Dieses Mal wird der aktuelle i im Puffer gespeichert, damit wir ihn bei Bedarf im Aufruf von doStuff () verwenden können.


1

Man kann Ihr Codemuster als Anwenden einer Funktion auf eine Teilmenge eines Bereichs beschreiben, oder mit anderen Worten: Anwenden auf das Ergebnis der Anwendung eines Filters auf den gesamten Bereich.

Dies ist auf einfachste Weise mit der Range-v3-Bibliothek von Eric Neibler möglich . obwohl es ein bisschen nervig ist, weil Sie mit Indizes arbeiten möchten:

using namespace ranges;
auto mycollection_has_something = 
    [&](std::size_t i) { return myCollection[i] == SOMETHING };
auto filtered_view = 
    views::iota(std::size_t{0}, myCollection.size()) | 
    views::filter(mycollection_has_something);
for (auto i : filtered_view) { DoStuff(); }

Aber wenn Sie bereit sind, auf Indizes zu verzichten, erhalten Sie:

auto is_something = [&SOMETHING](const decltype(SOMETHING)& x) { return x == SOMETHING };
auto filtered_collection = myCollection | views::filter(is_something);
for (const auto& x : filtered_collection) { DoStuff(); }

Das ist meiner Meinung nach schöner.

PS - Die Bereichsbibliothek wird hauptsächlich in den C ++ - Standard in C ++ 20 übernommen.


0

Ich werde nur Mike Acton erwähnen, er würde definitiv sagen:

Wenn Sie das tun müssen, haben Sie ein Problem mit Ihren Daten. Sortieren Sie Ihre Daten!

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.