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
-
Proposed syntax (except label relaxations) is identical to [N3355], accepted into C2y.
Decisions required:
-
Do we want
at all?break label -
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:
-
C2y acceptance.
-
much more common now, butconstexpr
workaround doesn’t work.goto -
staple of new languages; see Rust, Kotlin, Cpp2.break label
2. Motivation
-
andbreak label
label useful for controlling nested loops/switches.continue -
Early
not always viable alternative:return -
not viable in every function
-
MISRA-C++:2008 Rule 6-6-5 "A function shall have a single point of exit at the end of the function"
-
does not replace
continue
-
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" );
-
works really well, very readable.continue process_files -
would skipbreak ;
.std :: println
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:
-
cannot cross (non-vacuous) initialization (problematic if we add variable beforegoto
)std :: println -
not
constexpr -
Discouraged heavily (CppCoreGuidelines, MISRA, etc.)
-
No community consensus when exactly
is OK.goto -
Meaning of
unclear until seeing label location.goto -
Bad label name = confusing control flow.
-
Super controversial.
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:
-
Can no longer
outer loop.break -
More indentation level.
-
Cost in constant evaluation, debug builds.
-
expresses intent poorly.return ;
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:
-
More mutable state to keep track of.
-
Way longer, extra
.if -
Needs one
peer loop, doesn’t scale.bool
2.2. Argumentum ad populum
-
andbreak label
very popular in other languages.continue label -
This proposal: positive reception on Reddit.
-
See poll below.
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 s
| Labeled s
| Σ
| s
|
Java |
| 424K files | 152K files | 576K files | N/A |
JavaScript |
| 53.8K files | 68.7K files | 122.5K files | N/A |
Perl |
| 34.9K files | 31.7K files | 66.6K files | 16.9K files |
Rust |
| 30.6K files | 29.1K files | 59.7K files | N/A |
TypeScript |
| 11.6K files | 9K files | 20.6K files | N/A |
Swift |
| 12.6K files | 5.6K files | 18.2K files | N/A |
Kotlin |
| 8.7K files | 7.6K files | 16.3K files | N/A |
D |
| 3.5K files | 2.6K files | 6.1K files | 12.3K files |
Go |
| 270 files | 252 files | 522 | 1.5K files |
Cpp2 (cppfront) |
| N/A | N/A | N/A | N/A |
C |
| N/A | N/A | N/A | 7.8M files |
3. Design Considerations
3.1. Alternative break
and continue
forms
-
(e.g.break N
)break 2 ; -
(i.e. use keyword as label)break while -
(e.g.break statement
)break break ;
No, thanks!
Received poorly, too exotic.
WG14 wants
.
3.2. constexpr
break label ;
and continue label ;
Yes, absolutely!
See motivation; easier to implement than
because almost exists already:
while ( /* ... */ ) { if ( /* ... */ ) { { { { { { break ; } } } } } } }
-
Arbitrarily deeply nested
already implemented.break -
just skips some statements and breaks more, easy.break label
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
-
block-names don’t have to be unique, and this is good for macros.
-
Counterpoint: I relax labels instead, no need for [N3377] syntax.
-
-
Disambiguation between
targets andgoto
targets.break -
Counterpoint: Impact may be overstated, could disambiguate with naming convention, linter scripts.
-
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:
gotoLINE ; 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 ;
-
andbreak label
has decades of precedent in other languages.continue label -
C++ often used in conjunction with other languages, ergo common syntax = good.
3.3.2.2. Repetition
[N3377] is bad when loop is targeted by
and
:
goto outer ; // ... outer : while outer ( true) { while ( true) { break outer ; } }
-
Option A: copy/paste name, but DRY!
-
Option B: two different names for same loop?!
3.3.2.3. Extendability
label : { // OK in Java, JS, TS break label ; }
-
Not proposed, but who knows next 40 years?
-
[N3377] syntax generally harder to apply to new features, more existing ones.
3.3.2.4. Blocking contextual keywords
while parallel ( /* ... */ )
-
cannot be contextual keyword if taken by user.parallel -
Precedent:
.if constexpr -
To be fair, [N3377] floats idea
.while : parallel : () -
But then why not put label in front?
-
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
-
OK in Java and JavaScript.
-
With
'
andouter '
instead, this code is OK Rust.inner
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 } }
-
OK in Rust
'
, but not in JS, Java.l -
Motivation: macros.
3.4.3. New labels - direct duplicates?
l : l : l : l : for (); // OK
-
Motivation:
-
Why not?
-
Macros.
-
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
-
Tons of other languages have this; it works.
-
A GCC implementation of [N3355]
-
An LLVM implementation is W.I.P.
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:The optional attribute-specifier-seq appertains to the label.
attribute-specifier-seqopt identifierlabeled-statement:
:
attribute-specifier-seqoptconstant-expression
case
:
attribute-specifier-seqopt
default
:
label statementThe only use of a label with an identifier is as the target of aA label can be used in a. No two labels in a function shall have the same identifier.
goto statement ([stmt.goto]) before its introduction.
goto
[ Note: Multiple identical labels within the same function are permitted, but such duplicate labels cannot be used in astatement. — end note ]
goto
In [stmt.label] insert a new paragraph after paragraph 1:
A label L of the form attribute-specifier-seqopt identifierlabels a statement S if
:
- L is the label and S is the statement of a labeled-statement X, or
- L labels X (recursively).
[ Example:— end 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 } }
Note: This defines the term (to) label, which is used extensively below.
We also don’t want
or
labels to label statements, since this would inadvertently
permit
given
, considering how we word [stmt.break].
Update [stmt.label] paragraph 3 as follows:
A control-flow-limited statement is a statement S for which:
- a
or
case label appearing within S shall be associated with a
default statement ([stmt.switch]) within S, and
switch - a label declared in S shall only be referred to by a statement
([stmt.goto])in S.
Note: While the restriction still primarily applies to
(preventing the user from e.g. jumping into an
statement),
if other statements can also refer to labels, it is misleading to say
"statement ([stmt.goto])" as if
was the only relevant statement.
Update [stmt.jump.general] paragraph 1 as follows:
Jump statements unconditionally transfer control.jump-statement:identifier
goto
;
identifieropt
break
;
identifieropt
continue
;
expr-or-braced-init-listopt
return
;
identifier
goto
;
Note:
is being relocated to the top so that all the jump statements with an identifier are grouped together.
Of these three,
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
(in the case of
).
Update [stmt.break] paragraph 1 as follows:
A breakable statement is an iteration-statement ([stmt.iter]) or astatement ([stmt.switch]). A
switch statement shall be enclosed by ([stmt.pre]) a breakable statement
break an iteration-statement ([stmt.iter]) or a. If present, the identifier shall be part of a label L which labels ([stmt.label]) an enclosing breakable statement. Thestatement ([stmt.switch])
switch statement causes termination of :
break the smallest such enclosing statement;
- if an identifier is present, the smallest enclosing breakable statement labeled by L,
- otherwise, the smallest enclosing breakable statement.
controlControl passes to the statement following the terminated statement, if any.
[ Example:— end 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
Update [stmt.cont] paragraph 1 as follows:
Astatement 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 :
continue the smallest such enclosing statement, that is, to the end of the loop.More precisely, in each of the statements
- if an identifier is present, the smallest enclosing iteration-statement labeled by L,
- otherwise, the smallest enclosing iteration-statement.
label : while ( foo ) { { // ... } contin : ; } label : do { { // ... } contin : ; } while ( foo ); label : for (;;) { { // ... } contin : ; } athe following are equivalent tonot contained in an an enclosed iteration statement is equivalent to
continue .
goto contin :
goto contin
- A
not contained in an an enclosed iteration statement.
continue - A
not contained in an enclosed iteration statement labeled
continue label .
label :
Update [stmt.goto] paragraph 1 as follows:
Thestatement unconditionally transfers control to
goto thea statement labeled ([stmt.label]) bythe identifiera label in the current function containing identifier, but not to alabel .
case The identifier shall be a label located in the current function.There shall be exactly one such label.