it-swarm.com.de

Gibt es ein Äquivalent zur Python-Schleife "for ... else" in C++?

Python hat eine interessante for-Anweisung, mit der Sie eine else-Klausel angeben können.

In einem Konstrukt wie diesem:

for i in foo:
  if bar(i):
    break
else:
  baz()

die else-Klausel wird nach der for ausgeführt, jedoch nur, wenn die for normal endet (nicht durch eine break).

Ich fragte mich, ob es in C++ ein Äquivalent gab. Kann ich for ... else verwenden?

57
Delgan

Eine einfachere Möglichkeit, Ihre tatsächliche Logik auszudrücken, ist mit std::none_of :

if (std::none_of(std::begin(foo), std::end(foo), bar))
    baz();

Wenn der Bereichsvorschlag für C++ 17 akzeptiert wird, vereinfacht sich dies hoffentlich:

if (std::none_of(foo, bar)) baz();
32
Tony Delroy

Wenn es Ihnen nichts ausmacht, goto zu verwenden, können Sie dies auch auf folgende Weise tun. Dies erspart die zusätzliche if-Prüfung und die Deklaration von Variablendaten mit höherem Gültigkeitsbereich.

for(int i = 0; i < foo; i++)
     if(bar(i))
         goto m_label;
baz();

m_label:
...
20
ifyalciner

Ja, Sie können den gleichen Effekt erzielen durch: 

auto it = std::begin(foo);
for (; it != std::end(foo); ++it)
     if(bar(*it))
         break;
if(it == std::end(foo))
    baz();
13
haccks

Dies ist meine grobe Implementierung in C++:

bool other = true;
for (int i = 0; i > foo; i++) {
     if (bar[i] == 7) {
          other = false;
          break;
     }
} if(other)
     baz();
11
Easton

Sie können dazu eine Lambda-Funktion verwenden:

[&](){
  for (auto i : foo) {
    if (bar(i)) {
      // early return, to skip the "else:" section.
      return;
    }
  }
  // foo is exhausted, with no item satisfying bar(). i.e., "else:"
  baz();
}();

Dies sollte sich genau wie Pythons "for..else" verhalten und hat gegenüber den anderen Lösungen einige Vorteile:

  • Es ist ein echter Ersatz für "for..else": Der Abschnitt "for" kann Nebenwirkungen haben (im Gegensatz zu none_of, dessen Prädikat sein Argument nicht ändern darf) und hat Zugriff auf den äußeren Bereich.
  • Es ist lesbarer als das Definieren eines speziellen Makros.
  • Es sind keine speziellen Flagvariablen erforderlich.

Aber ... ich würde selbst die klobige Flagvariable verwenden.

10
Noah Black

Mir ist keine elegante Möglichkeit bekannt, dies in C/C++ zu erreichen (ohne Flag-Variable). Die vorgeschlagenen anderen Optionen sind viel schrecklicher als das ...

Um @Kerrek SB über die realen Verwendungen zu beantworten, habe ich einige in meinem Code gefunden (vereinfachte Auszüge)

Beispiel 1: typisches Suchen/Nichtbestehen

for item in elements:
    if condition(item):
        do_stuff(item)
        break
else: #for else
    raise Exception("No valid item in elements")

Beispiel 2: begrenzte Anzahl von Versuchen

for retrynum in range(max_retries):
    try:
        attempt_operation()
    except SomeException:
        continue
    else:
        break
else: #for else
    raise Exception("Operation failed {} times".format(max_retries))
4
agomcas

So etwas wie:

auto it = foo.begin(), end = foo.end();
while ( it != end && ! bar( *it ) ) {
    ++ it;
}
if ( it != foo.end() ) {
    baz();
}

sollte den Trick tun, und es vermeidet die unstrukturierte break

3
James Kanze

Das ist nicht nur in C++ möglich, sondern auch in C. Ich bleibe bei C++, um den Code verständlich zu machen:

for (i=foo.first(); i != NULL || (baz(),0); i = i.next())
{
    if bar(i):
        break;
}

Ich bezweifle, dass ich das durch eine Codeüberprüfung lasse, aber es funktioniert und ist effizient. Meiner Meinung nach ist es auch klarer als einige der anderen Vorschläge.

2
Richard Urwin

Es gibt kein derartiges Sprachkonstrukt in C++, aber dank der "Magie" des Präprozessors können Sie eines für sich selbst erstellen. Zum Beispiel so etwas (C++ 11):

#include <vector>
#include <iostream>
using namespace std;

#define FOR_EACH(e, c, b) auto e = c.begin(); for (; e != c.end(); ++e) {b} if (e == c.end()) {}

int main()
{
    vector<int> v;
    v.Push_back(1);
    v.Push_back(2);

    FOR_EACH(x, v, {
        if (*x == 2) {
            break;
        }        
        cout << "x = " << *x << " ";
    })
    else {
        cout << "else";
    }

    return 0;
}

Dies sollte x = 1 else ausgeben. 

Wenn Sie if (*x == 2) { in if (*x == 3) { ändern, sollte die Ausgabe x = 1 x = 2 sein.

Wenn Ihnen die Tatsache, dass eine Variable im aktuellen Bereich hinzugefügt wird, nicht gefällt, können Sie sie leicht ändern:

#define FOR_EACH(e, c, b, otherwise) {auto e = c.begin(); for (; e != c.end(); ++e) {b} if (e == c.end()) {} otherwise }

dann wäre verwenden:

FOR_EACH(x, v, {
    if (*x == 2) {
        break;
    }        
    cout << "x = " << *x << " ";
},
else {
    cout << "else";
})

Es ist natürlich nicht perfekt, aber wenn es mit Sorgfalt eingesetzt wird, erspart es Ihnen etwas Schreibarbeit und, wenn es häufig verwendet wird, würde es Teil des "Vokabulars" des Projekts werden.

1

Direkte Antwort: Nein, Sie können es wahrscheinlich nicht, oder es ist bestenfalls Compiler-basiert. ABER hier ist ein Hack eines Makros, das funktioniert!

Ein paar Anmerkungen:

Normalerweise programmiere ich mit Qt, also bin ich daran gewöhnt, eine foreach-Schleife zu haben, und muss mich nie direkt mit Iteratoren beschäftigen. 

Ich habe dies mit Qt's Compiler (v 5.4.2) getestet, aber es sollte funktionieren. Dies ist aus mehreren Gründen eklig, tut aber im Allgemeinen, was Sie möchten. Ich akzeptiere Codierung nicht so, aber es gibt keinen Grund, warum es nicht funktionieren sollte, solange Sie mit der Syntax vorsichtig sind.

#include <iostream>
#include <vector>

#define for_else(x, y) __broke__ = false; for(x){y} if (__broke__) {}
#define __break__ __broke__ = true; break

bool __broke__;  // A global... wah wah.

class Bacon {
  public:
    Bacon(bool eggs);

    inline bool Eggs() {return eggs_;}

  private:
    bool eggs_;
};

Bacon::Bacon(bool eggs) {
  eggs_ = eggs;
}

bool bar(Bacon *bacon) {
  return bacon->Eggs();
}

void baz() {
  std::cout << "called baz\n";
}

int main()
{
  std::vector<Bacon *>bacons;

  bacons.Push_back(new Bacon(false));
  bacons.Push_back(new Bacon(false));
  bacons.Push_back(new Bacon(false));

  for_else (uint i = 0; i < bacons.size(); i++,
      std::cout << bacons.at(i)->Eggs();
      if (bar(bacons.at(i))) {
        __break__;
      }
  ) else {
    baz();
  }

  bacons.Push_back(new Bacon(true));
  bacons.Push_back(new Bacon(false));

  for_else (uint i = 0; i < bacons.size(); i++,
      std::cout << bacons.at(i)->Eggs();
      if (bar(bacons.at(i))) {
        __break__;
      }
  ) else {
    baz();
  }

  return EXIT_SUCCESS;
}
0
Charlie

Ich bin hergekommen, weil ich in C die gleiche Frage hatte. Das Beste, was ich herausgekommen bin, ist

bool notTerminated = true;
for (int i = 0; i < 50 || (notTerminated = false); i++)
    if (bar(i))
        break;
if (! notTerminated)
    baz();

Erläuterung: Der (notTerminated = false) ist eine Zuweisung, die immer den falschen Wert zurückgibt. Er hat keine Auswirkungen auf die Bedingung und wird ausgewertet, wenn die Bedingung wahr ist.

0
user1537765

Es gibt wahrscheinlich keine einzige Lösung, die am besten zu allen Problemen passt. In meinem Fall haben eine Flagvariable und eine bereichsbasierte for-Schleife mit einem auto-Bezeichner am besten funktioniert. Hier ist ein Äquivalent des fraglichen Codes:

bool none = true;
for (auto i : foo) {
  if (bar(i)) {
    none = false;
    break;
  }
}
if (none) baz();

Es ist weniger Tipparbeit als mit Iteratoren . Insbesondere wenn Sie die Variable for zum Initialisieren einer Variablen verwenden, können Sie dies anstelle des booleschen Flags verwenden.

Dank auto-Eingabe ist es besser als std::none_of , wenn Sie die Bedingung einschließen möchten, anstatt bar() aufzurufen (und wenn Sie nicht C++ 14 verwenden).

Ich hatte eine Situation, in der beide Bedingungen vorkamen, der Code sah ungefähr so ​​aus:

for (auto l1 : leaves) {
  for (auto x : vertices) {
    int l2 = -1, y;
    for (auto e : support_edges[x]) {
      if (e.first != l1 && e.second != l1 && e.second != x) {
        std::tie(l2, y) = e;
        break;
      }
    }
    if (l2 == -1) continue;

    // Do stuff using vertices l1, l2, x and y
  }
}

Hier sind keine Iteratoren erforderlich, da v angibt, ob break aufgetreten ist.

Die Verwendung von std::none_of erfordert die explizite Angabe des Typs von support_edges[x]-Elementen in Argumenten eines Lambda-Ausdrucks.

0
arekolek

Sie können for-else fast wie in Python verwenden, indem Sie zwei Makros definieren:

#define BREAK {CONTINUETOELSE = false; break;}
#define FORWITHELSE(x, y) {bool CONTINUETOELSE = true; x if(!CONTINUETOELSE){} y}

Jetzt fügen Sie die for und die else in das Makro FORWITHELSE, die durch ein Komma getrennt sind, und verwenden BREAK anstelle von break. Hier ist ein Beispiel:

FORWITHELSE(
    for(int i = 0; i < foo; i++){
        if(bar(i)){
            BREAK;
        }
    },
    else{
        baz();
    }
)

Es gibt zwei Dinge, die Sie beachten sollten: Ein Komma vor else setzen und BREAK anstelle von break verwenden.

0
Donald Duck