web-dev-qa-db-fra.com

Existe-t-il un équivalent de la boucle Python "for ... else" en C++?

Python a une instruction for intéressante qui vous permet de spécifier une clause else.

Dans une construction comme celle-ci:

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

la clause else est exécutée après la for, mais uniquement si la for se termine normalement (et non par break).

Je me suis demandé s'il y avait un équivalent en C++? Puis-je utiliser for ... else?

57
Delgan

Un moyen plus simple d'exprimer votre logique réelle est d'utiliser std::none_of :

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

Si la proposition de plage pour C++ 17 est acceptée, nous espérons que cela simplifiera les choses:

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

Si cela ne vous dérange pas d'utiliser goto, vous pouvez également le faire de la manière suivante. Celui-ci enregistre les chèques extra if et la déclaration de variable de portée supérieure.

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

m_label:
...
20
ifyalciner

Oui, vous pouvez obtenir le même effet en: 

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

Voici mon implémentation brute en C++:

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

Vous pouvez utiliser une fonction lambda pour cela:

[&](){
  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();
}();

Cela devrait se comporter exactement comme "for..else" de Python et présenter certains avantages par rapport aux autres solutions:

  • C'est un véritable substitut pour "for..else": la section "for" peut avoir des effets secondaires (contrairement à none_of, dont le prédicat ne doit pas modifier son argument), et il a accès à la portée externe.
  • C'est plus lisible que de définir une macro spéciale.
  • Il ne nécessite aucune variable de drapeau spéciale.

Mais ... j'utiliserais la variable drapeau maladroite, moi-même.

10
Noah Black

Je ne suis pas au courant d'une manière élégante d'accomplir cela en C/C++ (n'impliquant pas une variable de drapeau). Les autres options suggérées sont beaucoup plus horribles que cela ...

Pour répondre à @Kerrek SB à propos d’usages réels, j’en ai trouvé quelques-uns dans mon code (extraits simplifiés).

Exemple 1: recherche/échec typique

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

Exemple 2: nombre de tentatives limité

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

Quelque chose comme:

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

devrait faire l'affaire, et il évite le break non structuré. 

3
James Kanze

Ce n'est pas seulement possible en C++, c'est aussi possible en C. Je vais m'en tenir au C++ pour rendre le code compréhensible:

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

Je doute que je laisse cela à travers une révision du code, mais cela fonctionne et est efficace. À mon avis, cela est également plus clair que certaines des autres suggestions.

2
Richard Urwin

Ce langage n'existe pas en C++, mais grâce à la "magie" du préprocesseur, vous pouvez en créer un vous-même. Par exemple, quelque chose comme ceci (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;
}

Cela devrait générer x = 1 else

Si vous remplacez if (*x == 2) { par if (*x == 3) {, le résultat devrait être x = 1 x = 2.

Si vous n'aimez pas le fait qu'une variable soit ajoutée à la portée actuelle, vous pouvez la modifier légèrement:

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

alors utiliser serait:

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

Ce n'est pas parfait, bien sûr, mais si vous l'utilisez avec précaution, vous économiserez un peu de dactylographie et, s'il est beaucoup utilisé, il fera partie du "vocabulaire" du projet.

1
srdjan.veljkovic

Réponse directe: non, vous ne pouvez probablement pas, ou au mieux, il est basé sur un compilateur. MAIS voici un morceau de macro qui fonctionne!

Quelques notes:

Je programme habituellement avec Qt, donc je suis habitué à avoir une boucle foreach et je n’ai jamais à traiter directement avec des itérateurs. 

J'ai testé cela avec le compilateur de Qt (v 5.4.2) mais cela devrait fonctionner. Ceci est grossier pour plusieurs raisons, mais fait généralement ce que vous voudriez. Je ne tolère pas le codage comme celui-ci, mais il n'y a aucune raison que cela ne fonctionne pas tant que vous respectez la syntaxe.

#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

Je suis venu ici parce que j'avais la même question, mais en C La meilleure chose que je sois sorti est

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

Explication: le (notTerminated = false) est une affectation qui renvoie toujours la valeur false, elle n’affectera jamais la condition et sera évaluée si la condition est vraie.

0
user1537765

Il n’ya probablement pas de solution unique qui convient le mieux à tous les problèmes. Dans mon cas, une variable indicateur et une boucle for basée sur une plage avec un spécificateur auto fonctionnaient mieux. Voici un équivalent du code en question:

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

C'est moins typé que en utilisant des itérateurs . En particulier, si vous utilisez la boucle for pour initialiser une variable, vous pouvez l’utiliser à la place du drapeau booléen.

Grâce à auto, il vaut mieux que std::none_of , si vous souhaitez intégrer la condition plutôt que d'appeler bar() (et si vous n'utilisez pas C++ 14).

J'ai eu une situation où les deux conditions se sont produites, le code ressemblait à ceci:

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
  }
}

Pas besoin d'itérateurs ici, car v indique si break s'est produit.

Utiliser std::none_of nécessiterait de spécifier explicitement le type d'éléments support_edges[x] dans les arguments d'une expression lambda.

0
arekolek

Vous pouvez utiliser for-else presque comme en Python en définissant deux macros:

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

Maintenant, vous mettez les for et les else dans la macro FORWITHELSE séparés par une virgule et utilisez BREAK au lieu de break Voici un exemple:

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

Vous devez vous rappeler de deux choses: mettre une virgule avant la else et utiliser BREAK au lieu de break.

0
Donald Duck