P3628R0
break label; and continue label;, tl; dr

Published Proposal,

This version:
https://eisenwave.github.io/cpp-proposals/break-continue-label-tldr.html
Author:
Audience:
SG22, SG17
Project:
ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
Source:
eisenwave/cpp-proposals

Abstract

Abridged version of [P3568R0].

1. Introduction

Proposed functionality:

outer: for (auto x : xs) {
    for (auto y : ys) {
        if (/* ... */) {
            continue outer; // OK, continue applies to outer for loop
            break outer;    // OK, break applies to outer for loop  
        }
    }
}

switch_label: switch (/* ... */) {
    default: while (true) {
        if (/* ...*/) {
            break switch_label; // OK, break applies to switch, not to while loop
        }
    }
}

break outer;       // error: cannot break loop from the outside
goto outer;        // OK, used to be OK, and is unaffected by this proposal

switch_label:;     // OK, labels can be reused
goto switch_label; // error: jump target is ambiguous

Decisions required:

  1. Do we want break label at all?

  2. Do we want this syntax?

Functionality was proposed before in [N3879] and rejected at Rapperswil 2014 ([N4327]):

Straw poll, proposal as a whole:

SF F N A SA
1 1 1 13 10

"break label;" + "continue label;"

SF F N A SA
3 8 4 9 3

However, things have changed since 2014:

2. Motivation

2.1. No good alternative

process_files: for (const File& text_file : files) {
    for (std::string_view line : text_file.lines()) {
        if (makes_me_angry(line)) {
            continue process_files;
        }
        consume(line);
    }
    std::println("Processed {}", text_file.path());
}
std::println("Processed all files");

2.1.1. goto

for (const File& text_file : files) {
    for (std::string_view line : text_file.lines()) {
        if (makes_me_angry(line)) {
            goto done_with_file;
        }
        consume(line);
    }
    std::println("Processed {}", text_file.path());
    done_with_file:
}
std::println("Processed all files");

Works, but:

2.1.2. Immediately invoked lambda expression (IILE)

for (const File& text_file : files) {
    [&] {
        for (std::string_view line : text_file.lines()) {
            if (makes_me_angry(line)) {
                return;
            }
            consume(line);
        }
        std::println("Processed {}", text_file.path());
    }();
}
std::println("Processed all files");

Works, but:

2.1.3. Mutable bool state

for (const File& text_file : files) {
    bool success = true;
    for (std::string_view line : text_file.lines()) {
        if (makes_me_angry(line)) {
            success = false;
            break;
        }
        consume(line);
    }
    if (success) {
        std::println("Processed {}", text_file.path());
    }
}
std::println("Processed all files");

Works, but:

2.2. Argumentum ad populum

Should C++ have "break label" and "continue label" statements to apply break/continue to nested loops or switches?
SF F N A SA
21 21 12 6 4

Source: Together C & C++ (Discord server).

Language Syntax Labeled
breaks
Labeled
continues
Σ break continue gotos
Java label: for (...)
break label;
continue label;
424K files 152K files 576K files N/A
JavaScript label: for (...)
break label;
continue label;
53.8K files 68.7K files 122.5K files N/A
Perl label: for (...)
last label;
next label;
34.9K files 31.7K files 66.6K files 16.9K files
Rust label: for (...)
break 'label;
continue 'label;
30.6K files 29.1K files 59.7K files N/A
TypeScript label: for (...)
break label;
continue label;
11.6K files 9K files 20.6K files N/A
Swift label: for ...
break label
continue label
12.6K files 5.6K files 18.2K files N/A
Kotlin label@ for (...)
break@label
continue@label
8.7K files 7.6K files 16.3K files N/A
D label: for (...)
break label;
continue label;
3.5K files 2.6K files 6.1K files 12.3K files
Go label: for ...
break label;
continue label;
270 files 252 files 522 1.5K files
Cpp2 (cppfront) label: for ...
break label;
continue label;
N/A N/A N/A N/A
C label: for (...)
break label;
continue label;
N/A N/A N/A 7.8M files

3. Design Considerations

3.1. Alternative break and continue forms

No, thanks! Received poorly, too exotic. WG14 wants break label;.

3.2. constexpr break label; and continue label;

Yes, absolutely! See motivation; easier to implement than constexpr goto because almost exists already:

while (/* ... */) {
    if (/* ... */) {
        { { { { { break; } } } } }
    }
}

3.3. Syntax warzone

[N3377] syntax:

for outer (/* ...*/) {
    while (/* ... */) break outer;
    while outer (/* ... */) {
        // OK, applies to the enclosing while loop
    }
}

3.3.1. N3377 benefits

Note: There are some other minor arguments for [N3377], but we don’t have the time.

3.3.2. N3377 problems

3.3.2.1. Breaking precedent of most prior art

Breaks most prior art; see language stats above and examples below:

Perl:

goto LINE;
LINE: while (true) {
    last LINE;  # like our proposed break LINE
}

Go:

goto OuterLoop
OuterLoop: for {
    break OuterLoop
}

D:

goto outer;
outer: while (true) {
    break outer;
}

However, Ada:

goto Target;
<<Target>>
Outer: loop
    exit Outer; -- like our proposed break Outer
end loop Outer;
3.3.2.2. Repetition

[N3377] is bad when loop is targeted by break and goto:

goto outer;
// ...
outer: while outer(true) {
    while(true) {
        break outer;
    }
}
3.3.2.3. Extendability
label: {
    // OK in Java, JS, TS
    break label;
}
3.3.2.4. Blocking contextual keywords
while parallel(/* ... */)
3.3.2.5. Labeling loops expanded from macros

Example from uthash:

#define HASH_ITER(hh,head,el,tmp) for (/* ... */)

With [N3355]:

struct my_struct *current_user, *tmp;

outer: HASH_ITER(hh, users, current_user, tmp) {
    for (/* ... */) {
        if (/* ... */) break outer;
    }
}

With [N3377]: ask library author to change macro or make your own.

3.4. Changes to labels

As stated before, we relax labels as follows:

outer: while (true) {
    inner: while (true) {
        break outer; // breaks enclosing outer while loop
    }
}

outer: while (true) { // OK, reusing label is permitted
    inner: while (true) {
        break outer; // breaks enclosing outer while loop
    }
}

goto outer; // error: ambiguous jump target

3.4.1. New labels - goto

x: f(); // OK
x: g(); // OK
goto x; // error: jump is ambiguous

No existing code broken.

3.4.2. New labels - nesting?

Another case to consider is the following:

l: while (true) {
    l: for (true) { // OK
        break l; // breaks for loop
    }
}

3.4.3. New labels - direct duplicates?

l: l: l: l: for(); // OK

3.4.4. New labels - what about break label for loops with more than one label?

x: y: while (true) {
    break x; // OK
}

4. Impact on existing code

None.

5. Implementation experience

6. Proposed wording

The wording is relative to [N5001].

Update [stmt.label] paragraph 1 as follows:

A label can be added to a statement or used anywhere in a compound-statement.
label:
attribute-specifier-seqopt identifier :
attribute-specifier-seqopt case constant-expression :
attribute-specifier-seqopt default :
labeled-statement:
label statement
The optional attribute-specifier-seq appertains to the label. The only use of a label with an identifier is as the target of a goto. No two labels in a function shall have the same identifier. A label can be used in a goto statement ([stmt.goto]) before its introduction.

[ Note: Multiple identical labels within the same function are permitted, but such duplicate labels cannot be used in a goto statement. — end note ]

In [stmt.label] insert a new paragraph after paragraph 1:

A label L of the form attribute-specifier-seqopt identifier : labels a statement S if
  • L is the label and S is the statement of a labeled-statement X, or
  • L labels X (recursively).
[ Example:
a: b: while (0) { }            // both a: and b: label the loop
c: { d: switch (0) {           // unlike c:, d: labels the switch statement
    default: while (0) { }     // default: labels nothing
} }
end example ]

Note: This defines the term (to) label, which is used extensively below. We also don’t want case or default labels to label statements, since this would inadvertently permit break i given case i:, considering how we word [stmt.break].

Update [stmt.label] paragraph 3 as follows:

A control-flow-limited statement is a statement S for which:

Note: While the restriction still primarily applies to goto (preventing the user from e.g. jumping into an if constexpr statement), if other statements can also refer to labels, it is misleading to say "statement ([stmt.goto])" as if goto was the only relevant statement.


Update [stmt.jump.general] paragraph 1 as follows:

Jump statements unconditionally transfer control.
jump-statement:
goto identifier ;
break identifieropt ;
continue identifieropt ;
return expr-or-braced-init-listopt ;
goto identifier ;

Note: goto is being relocated to the top so that all the jump statements with an identifier are grouped together. Of these three, goto is being listed first because it models the concept of "jumping somewhere" most literally; every following statement is more sophisticated or even defined as equivalent to goto (in the case of continue).


Update [stmt.break] paragraph 1 as follows:

A breakable statement is an iteration-statement ([stmt.iter]) or a switch statement ([stmt.switch]). A break statement shall be enclosed by ([stmt.pre]) a breakable statement an iteration-statement ([stmt.iter]) or a switch statement ([stmt.switch]) . If present, the identifier shall be part of a label L which labels ([stmt.label]) an enclosing breakable statement. The break statement causes termination of : the smallest such enclosing statement; control Control passes to the statement following the terminated statement, if any.

[ Example:
a: b: while (/* ... */) {
    a: a: c: for (/* ... */) {
        break;              // OK, terminates enclosing for loop
        break a;            // OK, same
        break b;            // OK, terminates enclosing while loop
        y: { break y; }     // error: break does not refer to a breakable statement
    }
    break c;                // error: break does not refer to an enclosing statement
}
break;                      // error: break must be enclosed by a breakable statement
end example ]

Update [stmt.cont] paragraph 1 as follows:

A continue statement shall be enclosed by ([stmt.pre]) an iteration-statement ([stmt.iter]). If present, the identifier shall be part of a label L which labels ([stmt.label]) an enclosing iteration-statement. The continue statement causes control to pass to the loop-continuation portion of : the smallest such enclosing statement, that is, to the end of the loop. More precisely, in each of the statements
label: while (foo) {
  {
    // ...
  }
contin: ;
}

label: do {
  {
    // ...
  }
contin: ;
} while (foo);

label: for (;;) {
  {
    // ...
  }
contin: ;
}
a continue not contained in an an enclosed iteration statement is equivalent to goto contin. the following are equivalent to goto contin:
  • A continue not contained in an an enclosed iteration statement.
  • A continue label not contained in an enclosed iteration statement labeled label:.

Update [stmt.goto] paragraph 1 as follows:

The goto statement unconditionally transfers control to the a statement labeled ([stmt.label]) by the identifier a label in the current function containing identifier, but not to a case label . The identifier shall be a label located in the current function. There shall be exactly one such label.

References

Normative References

[N5001]
Thomas Köppe. Working Draft, Programming Languages — C++. 17 December 2024. URL: https://wg21.link/n5001

Informative References

[N3355]
Alex Celeste. N3355: Named loops, v3. 2024-09-18. URL: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3355.htm
[N3377]
Erich Keane. N3377: Named Loops Should Name Their Loops: An Improved Syntax For N3355. URL: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3377.pdf
[N3879]
Andrew Tomazos. Explicit Flow Control: break label, goto case and explicit switch. 16 January 2014. URL: https://wg21.link/n3879
[N4327]
Ville Voutilanen. C++ Standard Evolution Closed Issues List (Revision R10). 21 November 2014. URL: https://wg21.link/n4327
[P3568R0]
Jan Schultke, Sarah Quiñones. break label; and continue label;. 12 January 2025. URL: https://wg21.link/p3568r0