Is there an equivalent to the “for … else” Python loop in C++?

为君一笑 提交于 2019-12-03 04:02:25

问题


Python has an interesting for statement which lets you specify an else clause.

In a construct like this one:

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

the else clause is executed after the for, but only if the for terminates normally (not by a break).

I wondered if there was an equivalent in C++? Can I use for ... else?


回答1:


A simpler way to express your actual logic is with std::none_of:

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

If the range proposal for C++17 gets accepted, hopefully this will simplify to:

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



回答2:


If doesn't mind using goto also can be done in following way. This one saves from extra if check and higher scope variable declaration.

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

m_label:
...



回答3:


Yes you can achieve the same effect by:

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



回答4:


This is my rough implementation in C++:

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



回答5:


You could use a lambda function for this:

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

This should behave exactly like Python's "for..else", and it has some advantages over the other solutions:

  • It's a true drop-in replacement for "for..else": the "for" section can have side effects (unlike none_of, whose predicate must not modify its argument), and it has access to the outer scope.
  • It's more readable than defining a special macro.
  • It doesn't require any special flag variables.

But... I'd use the clunky flag variable, myself.




回答6:


I am not aware of an elegant way to accomplish this in C/C++ (not involving a flag variable). The suggested other options are much more horrible than that...

To answer @Kerrek SB about real life usages, I found a few in my code (simplified snippets)

Example 1: typical find/fail

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

Example 2: limited number of attempts

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



回答7:


Something like:

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

should do the trick, and it avoids the unstructured break.




回答8:


It's not only possible in C++, it's possible in C. I'll stick with C++ to make the code comprehensible though:

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

I doubt I'd let that through a code review, but it works and it's efficient. To my mind it's also clearer than some of the other suggestions.




回答9:


There is no such language construct in C++, but, thanks to the "magic" of the preprocessor, you can make one for yourself. For example, something like this (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;
}

This should output x = 1 else.

If you change if (*x == 2) { to if (*x == 3) {, the output should be x = 1 x = 2.

If you don't like the fact that a variable is added in the current scope, you can change it slightly:

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

then use would be:

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

It's not perfect, of course, but, if used with care, will save you some amount of typing and, if used a lot, would become a part of the project's "vocabulary".




回答10:


There probably isn't a single solution that fits best all problems. In my case a flag variable and a range-based for loop with an auto specifier worked best. Here's an equivalent of the code in question:

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

It is less typing than using iterators. Especially, if you use the for loop to initialize a variable, you may use that instead of the boolean flag.

Thanks to auto typing it is better than std::none_of, if you want to inline the condition rather than call bar() (and if you are not using C++14).

I had a situation where both conditions occurred, the code looked something like this:

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

No need for iterators here, because v indicates whether break occurred.

Using std::none_of would require specifying the type of support_edges[x] elements explicitly in arguments of a lambda expression.




回答11:


Direct answer: no, you probably can't, or it is compiler-based, at best. BUT here's a hack of a macro that kind of works!

A few notes:

I usually program with Qt, so I'm used to having a foreach loop, and never have to deal with iterators directly.

I tested this with Qt's compiler (v 5.4.2) but it should work. This is gross for several reasons, but generally does what you'd want. I don't condone coding like this, but there's no reason it shouldn't work as long as you're careful with the syntax.

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



回答12:


You can use for-else almost like in Python by defining two macros:

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

Now you put the for and the else inside the FORWITHELSE macro separated by a comma and use BREAK instead of break. Here is an example:

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

There are two things you need to remember: to put a comma before the else and to use BREAK instead of break.




回答13:


I came here because I had the same question, in C though. The best thing I came out with is

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

Explanation: the (notTerminated = false) is an assignment that will always return the false value, it will never affect the condition and will be evaluated iif the condition if true.




回答14:


I would accomplish this with a simple helper variable:

#include <stdio.h>
#include <stdbool.h>

int main()

{
    bool b;
    printf("Numbers which are multiples of 7:\n");

    for (int i=8; b=(i<12); i++)
    {
        if (i%7==0)
        {
            printf("%d", i);
            break;
        }
    }
    if (!b)
    {
        printf("no numbers found\n");
    }
    return 0;
}

This way, you need to implement the condition (in the above examplei<12) only at one place.



来源:https://stackoverflow.com/questions/24693694/is-there-an-equivalent-to-the-for-else-python-loop-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!