ThePhD / future_cxx

Work done today for the glory of tomorrow - or, C and C++ systems programming papers.
https://thephd.dev/portfolio/standard
48 stars 8 forks source link

pXXXX - Labeled loops for C++26 #10

Closed ThePhD closed 5 years ago

ThePhD commented 5 years ago

Ideal syntax:

while (condition) loop-label-name: { ... }
do { ... } while (condition) loop-label-name:;
for (init; condition; incr) loop-label-name: { ... }
for (type-seq var : expr ) loop-label-name: { ... }

Goal: to enable better control of moving in and out of nested loops, without having to create catch-all lambdas which scale poorly for templated code. It also is easier to implement for constexpr in almost all current constexpr engines. The usage would be like this:

for (int i = 0; i < 4; ++i)  outer_loop: {
     for (int j = 0; j < 4; ++j) inner_loop: {
          if (i > 2 && check_func(i, j)) {
               break outer_loop;
     }
}
ThePhD commented 5 years ago

This one will also require implementation before it gets anywhere, since it seems EWG is mostly unconvinced. (The latest papers from Alan Talbot were about "loop exit blocks", which does the same thing but doesn't allow you to jump multiple scopes.)

ThePhD commented 5 years ago

I was personally informed that EWG hates most of the alternatives. Presenting this paper at this time is a bad idea. Shelf it, maybe still for C++23, but for later after implementation experience.

Morwenn commented 5 years ago

To be honest that's the kind of job for which I would still use goto, if only it was available in constexpr functions :'(

strega-nil commented 5 years ago

It'd likely be useful to point out Rust and Go's solutions to the problem,

fn main() {
  'a: loop {
    'b: loop {
      break 'a;
    }
  }
}
func main() {
  a: for {
    b: for {
      break a;
    }
  }
}

(Go is as I remember it, not guaranteed correct)

ThePhD commented 5 years ago

@Morwenn That's part of the motivation: goto is hard to implement in constexpr processors because they currently do AST execution and arbitrarily jumping in those is a nightmare. Most implementers say its doable but not worth the effort.

Labeled loops, on the other hand, I have confirmed with at least 2 different frontend implementers are trivially-doable in constexpr. Plus, most gotos I have seen -- aside from C code -- has been for jumping out of multiple loops.

@ubsan We wanted to borrow Swift's syntax here for C++, ending up with a grammar like:

while (condition) label-name { ... }

do { ... } while (condition) label-name;

for (init; condition; incr) label-name { ... }

for (type-seq var : expr ) label-name { ... }

with break label-name; jumping out to that level, or continue label-name; jumping to the end of that loop and continuing its next iteration.

Morwenn commented 5 years ago

I was about to give you a bit of motivation but I realized that the algorithms from <algorithm> in libc++ that use goto aren't apparently trivially convertible to algorithms using labeled loops (those algorithms being std::stable_partition and std::nth_element). And it's unfortunate that those algorithms use goto because they will be required to be constexpr in C++20.

On the other hand, there is a pull request proposing to replace a break_outer variable by a simple use of goto in gfx/cpp-TimSort which is a popular implementation of TimSort in C++. I guess that it can be used as a real-world example. Another C++ implementation of TimSort [uses goto too] but unfortunately it can't be replaced by labeled loops in that case.

I found myself wanting to use goto once in an algorithm, and once again it doesn't fall into the category of labeled loops.

Ironically enough, most uses of goto I've found aren't to break out of inner loops ^^'

strega-nil commented 5 years ago

@ThePhD Swift's syntax is nasty af tho; not being able to see the labels at the start of the loop is really unfortunate.

ThePhD commented 5 years ago

I mean, we can move the labels to the start of the loop? I've got no real problems with that.

Morwenn commented 5 years ago

You could most likely reuse a goto kind of label in front of a loop, which has a few advantages:

outer: while (...) {
    inner: while (...) {
        if (condition) {
            break outer;
        }
    }
}

The only drawback I see would be people complaining that it acts as goto label with label: declared after the loop, but it doesn't feel like a huge drawback.

EDIT: I just discovered that this syntax matches the one in Java, so it wouldn't be surprising for people who have already been exposed to labeled loops in Java.

ThePhD commented 5 years ago

Has no paper number now because unsubmitted papers from the old system went straight to hell.

ThePhD commented 5 years ago

This paper is a lost cause.

Maybe in 10 years.

ThePhD commented 5 years ago

From another thread:

FWIW, I was tempted to write the paper you wanted to write. Discussion amongst a few members of the Committee made it painfully clear that the paper would go Nowhere, even with motivating examples, because 7+ proposals in the before-times already came around and effectively soured the taste of such in everyone's mouth on top of the already violent disposition against additional flow control statements.

You're better off just using "goto" for now, however heinous many may consider it. Maybe wait until C++32, when the ideas died down for a while and we have more complex iteration techniques after building up large amounts of ranges algorithms that might require more complex algorithm internals where breaking out of 2+ loops or similar would be justified.

Also note that constexpr goto is possible but deemed "hella effort", so the proposal hasn't gone anywhere for that just yet.

Morwenn commented 5 years ago

Bring it back in 15 years x)

Light7734 commented 1 year ago

Unpopular opinion: This makes it easier for non-advanced developers to write horrible, barley-readable code, and may encourage them to make longer and more complex functions instead of refactoring and extracting those loops into separate functions

...
for (int i = 0; i < 4; ++i)
    if(self_documenting_function_name(i))
        break;
...

auto self_documenting_function_name(int const num) -> bool
{
    // some explanations since you've dug this deep!
    for (int i = 0; num > 2 && i < 4 ; ++i)
        if(check_func(num, i))
            return true;

    return false;
}