Introduction
This question follows from this one : The named loop idiom : dangerous?. For people who don't want to read the original question, it was about doing things like that :
named(label1) for(int i = 0 ; i < 10 ; i++) {
for(int j = 0 ; j < 10 ; j++) {
if(some_condition)
break(label1); // exit outer loop
}
}
This new question is about an improved version of the "named loop" idiom. If you're too lazy to read this entire post, you can directly go to the "Example" section of this post to clearly understand what I'm talking about.
Design flaws
Unfortunately, the question was closed very quickly (and later has been re-opened) because it was more a pros/cons debate than a pure technical question. It seems that it was not fitting the SO Q&A format. Moreover, the code I presented had several flaws :
- The keyword
break
was redefined by a macro - The macro were written using lowercase letters
It made some horrible things compilable (at least using MSVC) :
int foo() { named(label1) for(int i = 0 ; i < 10; i++) { if(some_condition) { break(label1); // here it's ok, the behavior is obvious } } break(label1); // it compiles fine without warning... but the behavior is pretty obscur! }
It could break some good-looking code. For instance, the following is not compiling because of a scoping problem.
int foo() { named(label1) for(int i = 0 ; i < 10 ; i++) named(label2) for(int j = 0 ; j < 10 ; j++) if(i*j<15) cout << i*j << endl; else break(label2); }
A safer implementation
I tried to fix all these problems in order to get a safe version of named loops. More generally, it could also be called breakable scopes because it can be used to exit prematurely any scope, not only loops.
Here is the definition of the two macros NAMED
and BREAK
:
#define NAMED(bn) if(const bool _YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_##bn##_ = true)\
goto _named_loop_identifier__##bn##_;\
else\
_break_the_loop_labelled_##bn##_:\
if(true)\
{}\
else\
if(! _YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_##bn##_)\
goto _break_the_loop_labelled_##bn##_;\
else\
_named_loop_identifier__##bn##_:\
#define BREAK(bn) if(!_YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_##bn##_){} \
else goto _break_the_loop_labelled_##bn##_
It looks ugly & cumbersome because it also avoids some warnings that could be generated by MSVC or GCC like 'unused variable', 'unreferenced label' or 'suggest explicit braces'. Moreover, it should not compile if used incorrectly, and in that case the error message will be understandable. For instance :
NAMED(loop1) for(int i = 0 ; i < 10; i++) {
NAMED(loop2) for(int j = 0 ; j < i ; j++) {
cout << i << "," << j << endl;
if(j == 5) {
BREAK(loop1); // this one is okay, we exit the outer loop
}
}
BREAK(loop2); // we're outside loop2, this is an error
}
The previous code will not compile, and the compiler error message for the second BREAK
: '_YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_loop2_` which is very explicit.
Example
Before asking my question, I provide two examples to illustrate the (relative) usefulness of those constructs :
break
an outer loop :
NAMED(myloop) for(int i = 0 ; i < rows; i++) {
for(int j = 0 ; j < cols ; j++) {
if(some_condition) {
BREAK(myloop);
}
}
}
Exit a specific scope :
NAMED(myscope1) {
cout<< "a";
NAMED(myscope2)
{
cout << "b";
NAMED(myscope3)
{
cout << "c";
BREAK(myscope2);
cout << "d";
}
cout << "e";
}
cout <<"f";
}
This code prints : abcf
.
My question
Before defining what my question is, I must define what it isn't since I don't want to see my topic closed in 10 minutes.
It is not "Is it a good idea?", nor "What do you think?" or even "Is it useful?", since stackoverflow doesn't seem to be a discussion place. Anyway, I already know the answers : "Macro are evils", "Goto's are evil" & "Use lambdas instead".
Instead, my question is about technical correctness & robustness against programming errors. I want this construct to be as safe as possible.
Is it possible for an user to misuse it and still be able to compile ? I tried to fix the obvious problems of the original implementation, but C++ is very complex and maybe I missed something ?
Could it silently break some good looking code ? This is my main concern. Can it interfere with the other C++ features (exception handling, destructor calls, others conditionals statements, or anything else...) ?
My objective is to demonstrate that this construct is not intrinsically dangerous. I already know that it would be a very bad idea to use it in real code since others programmers may not clearly understand it, but is it safe-enough to be used in a personal project ?
EDIT : The boolean variable is now const
(thanks to Jens Gustedt). I'll try to replace the if
s by for
s later to check it can removes spurious warning when used like that :
if(true)
BREAK(label);
EDIT2: As noticed by JensGustedt, variable declaration in an if
statement is not allowed in C (C++ only). Another reason to replace it by a loop.