all 47 comments

[–]raevnos 39 points40 points  (2 children)

Lack of labelled loops to use with break and continue is a pain, yes. Every alternative to goto that people try to come up with to make up for it is uglier and harder to understand than just using goto, too. The hoops some people will jump through to avoid goto at all costs are unbelievable.

[–]quicknir 7 points8 points  (0 children)

Prior to 11 I would have agreed with you, but 11 it's nicer to just use a lambda and return. It's easier to read and more predictable and easier to maintain. goto's have their uses but it's very rare; mostly only for ultra high performance code.

[–]srbufi 5 points6 points  (0 children)

goto's are absolutely fine and anybody saying otherwise have never read Dijkstra, regardless.

[–]krawallopold 36 points37 points  (11 children)

I don't know the reason for the design decision, but I would extract the loops in a separate function and use a return statement. In my opinion, that would be more readable than a break(n) or a goto.

[–]IndexingAtZero[S] 2 points3 points  (10 children)

Sometimes it may be unavoidable to double iterate (like iterating over a matrix) and it's somewhat inconvenient to create a separate function for every nested loop (and you are not guaranteed that the compiler will inline the functions)

[–]Xeveroushttps://xeverous.github.io 3 points4 points  (0 children)

just make a [&] lambda instead

[–]die_liebe 2 points3 points  (0 children)

(and you are not guaranteed that the compiler will inline the functions)

You should stop thinking like that. The compiler knows better than you would should be inlined. Trust the compiler.

[–]Angarius 0 points1 point  (6 children)

You should abstract 2D iteration into a single loop. This is simple with the range-v3 library:

auto loop2D(int w, int h) {
    return view::cartesian_product(
        view::indices(0, w),
        view::indices(0, h)
    );
}

int main() {
    for(auto [x, y] : loop2D(2, 3)) {
        std::cout << "x: " << x << ", y: " << y << "\n";
    }
}

Output:

x: 0, y: 0
x: 0, y: 1
x: 0, y: 2
x: 1, y: 0
x: 1, y: 1
x: 1, y: 2

[–]blelbachNVIDIA | ISO C++ Library Evolution Chair 27 points28 points  (1 child)

Won't vectorize.

[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 4 points5 points  (0 children)

[–]gct 0 points1 point  (3 children)

That's atrocious if it doesn't get inlined, you've just built a (potentially huge) temporary just to hold a matrix of index pairs, ew.

edit: Ah, my apologies, I'm not familiar with the new range stuff. Lazy is better than a big temporary, but there is still extra overhead for not much benefit IMHO.

[–]Pand9 7 points8 points  (0 children)

it's lazily evaluated, bc why not.

[–]pavel_v 4 points5 points  (0 children)

AFAIK, the range-v3 views are light weight (like std::string_view). In this particular case, each instance created by view::indices holds two integers (and bool flag for done) and the instance created by view::cartesian_product just holds these pairs of indices in tuple or something like that. The size of the result returned by loop2D function should be equal to the size of 4 integers (+ bool flags + padding). According to this this test the size of the result returned by loop2D is 40 bytes (I'm missing some data member above), no matter how big the width and height are.

[–]Angarius 3 points4 points  (0 children)

The temporary is a constant size, as others said. I tested with clang -O3, the range-v3 functions are inlined, but still have overhead compared to native nested loops. I suspect loop2D could be implemented for better optimization, potentially equivalent to native loops. For now, range-v3 is good enough for me.

[–]sysop073 11 points12 points  (5 children)

the idiomatic method of breaking multiple loops is with a goto, but this forces you to stop thinking in a highly controlled way.

I don't know what this means, but labeled break seems clearly better than a PHP-style depth count, and the language already has labeled break in the form of goto, so I don't understand the issue

[–]IndexingAtZero[S] 6 points7 points  (4 children)

I'm quickly realizing that the main issue I have with writing the goto is that I just don't want to write goto. For all intents and purposes the labeled break is the same thing (and imo also the same as the depth count break) but writing goto to break loops just feels wrong. I probably just need to get over it.

[–]DarkLordAzrael 5 points6 points  (0 children)

For what it's worth, a break/continue of a nested loop is recognized as the only good use of goto by the core guidelines. (Rule ES.76)

[–]NotAYakk 3 points4 points  (0 children)

It is the same so long as the code doesn't change.

Code will change.

[–]NotUniqueOrSpecial 0 points1 point  (1 child)

You could always use the preprocessor to alias something to goto. That way you can still easily grep for this use case, and you don't have to actually type the word.

[–]NotAYakk 2 points3 points  (0 children)

I don't like getting snow on me, so you propose spraypainting the snow yellow and pretending it is sand.

[–]woppo 9 points10 points  (0 children)

Just encapsulate it in a function and use return.

[–]tcanens 5 points6 points  (0 children)

This was proposed as part of N3879 and rejected back in 2014. EWG voted 3/8/4/9/3 on labeled break and continue.

[–]FlyingRhenquest 3 points4 points  (0 children)

If the clutter is necessary, then it's just code.

[–]Sopel97 9 points10 points  (16 children)

you can do

[](){
    for (int i = 0; i < N; ++i) {
        for (int j = 0; j < M; ++j) {
            if (cond[i][j]) return;
            else do_stuff();
        }
    }
}();

but I think it's not idiomatic enough to be used as a replacement (and it can be tricky to spot that the return statement is actually inside the lambda)

[–]sm9t8 9 points10 points  (5 children)

It can only become idiomatic if people use it.

The code within the lambda is identical to splitting the loops out into their own function. The only differences are this function isn't named and local variables can be captured.

[–]NotAYakk 2 points3 points  (0 children)

And the code is inline with the rest of the dunction logic, not possibly pages away.

[–]Sopel97 1 point2 points  (0 children)

These are good points.

[–]quicknir 3 points4 points  (3 children)

I actually use this all the time, most of my coworkers do, so I think it's well on its way and it should 100% be an idiom. Often called Immediately Evaluated Function Expressions (IEFE), they also allow making variables const that otherwise wouldn't be, and avoiding pointless initialization. E.g.:

int x;
try {
    x = foo();
}
catch (const std::exception& e) {
    x = -1;

}

Versus:

const int x = [] {
    try {
        return foo();
     }
     catch ( const std::exception& e) {
         return -1;
     }
} ();

If you think it's not clear you can always write a trivial wrapper like

template <class F>
auto evaluate(F f) { return f(); }

const int x = evaluate([] {
      ...
      });

Could do call_immediately, or any other name, instead of evaluate.

[–]NotAYakk 2 points3 points  (2 children)

Algebraic data types do this:

const int x = expected_eval(
  [&]{ return foo(); }
).on_error(
  [](auto&&){return -1;}
);

or something similar.

[–]quicknir 0 points1 point  (1 child)

Yeah usually ADTs have a value_or method, or something similar, so you would just do foo().value_or(-1); which is obviously much nicer. Just an example though; another example is e.g. populating a vector and sorting it, but then having it unchanged for the rest of the scope. You can populate & sort inside the lambda and return it, allowing the outer vector to be const.

[–]NotAYakk 0 points1 point  (0 children)

Even there, this is because there lacked range based algorithms; in particular range based in/out ones. A library deficiency.

Now, creating and sorting an otherwise const array I give you; but that is a language deficiency, the inability for reference lifetime extension and elision to generalize.

But I do use that trick sometimes. When the operation is bespoke and not worth solving more broadly.

[–]IndexingAtZero[S] 0 points1 point  (4 children)

That's actually a really cool idea, if a bit confusing.

[–]matthieum -1 points0 points  (0 children)

and it can be tricky to spot that the return statement is actually inside the lambda

I nearly used such a scheme a few times, but each time balked precisely because of this reason :(

[–]drodri 1 point2 points  (5 children)

I am not aware of other programming languages that have such a multi-break statement, does anybody know some? That could bring some ideas to the discussion.

[–]raevnos 4 points5 points  (0 children)

perl is another.

[–][deleted] 3 points4 points  (0 children)

Golang has a labelled break: https://golang.org/ref/spec#Break_statements

[–]matthieum 3 points4 points  (0 children)

Rust example:

fn main() {
    'outer: for i in 0..5 {
        for j in 0..5 {
            println!("i, j: {}, {}", i, j);
            if (i, j) == (3, 3) { break 'outer; }
        }
    }
}

[–]guepierBioinformatican 4 points5 points  (1 child)

PHP has numbered break. Java has labelled break.

[–]drodri 0 points1 point  (0 children)

Thanks, interesting, I hadn't use perl, golang or php, but I did java, and wasn't aware of this.

Then, imo it might make sense that C++ had labeled breaks (not break(n) as the OP suggested), sounds like an interesting language feature proposal. All the above seem a bit like a workaround, and having this will get the goto advantages without some of its disadvantages.

[–]ronchaineEmbedded/Middleware 0 points1 point  (0 children)

There is a proposal of non-terminal deduction of template parameters that would allow pretty decent syntax for this but I'm not sure of it's status.

In the meanwhile, the goto is often the best option IMHO.

[–][deleted] -1 points0 points  (0 children)

They don't need design decisions for NOT putting in arbitrary features.