The case for (or against) 'continue'

A

Alf P. Steinbach

Given

template < typename InputIterator1, typename InputIterator2 >
bool disjoint ( InputIterator1 from1, InputIterator1 to1,
InputIterator2 from2, InputIterator2 to2 ) {
while ( from1 != to1 && from2 != to2 ) {
if ( *from1 < *from2 ) {
++ from1;
continue;
}
if ( *from2 < *from1 ) {
++ from2;
continue;
}
return ( false );
}
return ( true );
}

and as an alternative, by some other programmer:

template <typename InputIter1, typename InputIter2>
bool disjoint(InputIter1 first1, InputIter1 last1,
InputIter2 first2, InputIter2 last2)
{
while (first1 != last1 && first2 != last2)
if (*first1 < *first2)
++first1;
else if (*first2 < *first1)
++first2;
else // *first1 == *first2
return false;
return true;
}

Is the first version a case for 'continue', an example where it's natural/good?

Neither code snippet is mine (I'd do the second version but with curly braces).


Cheers, & hoping for some enlightenment,

- Alf
 
J

joecook

Given

    template < typename InputIterator1, typename InputIterator2 >
    bool disjoint ( InputIterator1 from1, InputIterator1 to1,
            InputIterator2 from2, InputIterator2 to2 ) {
        while ( from1 != to1 && from2 != to2 ) {
            if ( *from1 < *from2 ) {
                ++ from1;
                continue;
            }
            if ( *from2 < *from1 ) {
                ++ from2;
                continue;
            }
            return ( false );
        }
        return ( true );
    }

and as an alternative, by some other programmer:

    template <typename InputIter1, typename InputIter2>
    bool disjoint(InputIter1 first1, InputIter1 last1,
              InputIter2 first2, InputIter2 last2)
    {
        while (first1 != last1 && first2 != last2)
            if (*first1 < *first2)
                ++first1;
            else if (*first2 < *first1)
                ++first2;
            else // *first1 == *first2
                return false;
        return true;
    }

Is the first version a case for 'continue', an example where it's natural/good?

Neither code snippet is mine (I'd do the second version but with curly braces).

Cheers, & hoping for some enlightenment,

- Alf

Of course many structure purists along with calling your continues
"glorified gotos" will say that there should be one entrance and one
exit to a given function (your two returns being a slap on the
wrist). I'm not in that camp (I'll break any rule if there is a
cogent reason to do so), but would agree to ask why continues
(unnatural loop exists) are used unless there is a reason to do so?
Usually the reason is clearer code. I don't think code version #1 is
any clearer than code version #2. In fact, version #2 wins on
clarity, not for structure issues, but because of the comment
"(*first1 == *first2)", which may be obvious, but the comment helps
read it quickly. I also think the if/else structure is easier to read
in this case, but that could be a personal preference.

If I were writing this code, and wanted to disregard a clean structure
for efficiency, (perhaps a premature optimization) you could rewrite
it to not check both iterators for reaching the end every time (since
you are only changing one of them per loop iteration)

template <typename InputIter1, typename InputIter2>
bool disjoint(InputIter1 first1, InputIter1 last1,
InputIter2 first2, InputIter2 last2)
{
for(;;)
{
if(*first1 < *first2)
{
if(++first1 == last1) { return true; }
}
else if(*first2 < *first1)
{
if(++first2 == last2) { return true; }
}
else // *first1 == *first2
{
return false;
}
}
}
 
A

Andrew Koenig

If you treat executing a continue statement as an assertion that you have
re-established the invariant(s) of the surrounding loop while ensuring
progress toward termination, I don't see anything wrong with them.

On the other hand, I wonder how many people think of continue statements in
this way.
 
J

James Kanze

If you treat executing a continue statement as an assertion
that you have re-established the invariant(s) of the
surrounding loop while ensuring progress toward termination, I
don't see anything wrong with them.
On the other hand, I wonder how many people think of continue
statements in this way.

The problem is that they break visible program flow in an
unexpected way. Alf's else's, for example, make it clear where
the code continues; the continue hides this fact.

Alf's return from within the loop, of course, has the same
problem---it hides the actual code flow (and would be banned in
just about any serious coding guidelines).
 
A

anon

Alf said:
Given

template < typename InputIterator1, typename InputIterator2 >
bool disjoint ( InputIterator1 from1, InputIterator1 to1,
InputIterator2 from2, InputIterator2 to2 ) {
while ( from1 != to1 && from2 != to2 ) {
if ( *from1 < *from2 ) {
++ from1;
continue;
}
if ( *from2 < *from1 ) {
++ from2;
continue;
}
return ( false );
}
return ( true );
}

[cut the normal version]
Is the first version a case for 'continue', an example where it's
natural/good?

How is it different then this:

template < typename InputIterator1, typename InputIterator2 >
bool disjoint ( InputIterator1 from1, InputIterator1 to1,
InputIterator2 from2, InputIterator2 to2 ) {
while ( from1 != to1 && from2 != to2 ) {
loop_start:
if ( *from1 < *from2 ) {
++ from1;
goto loop_start;
}
if ( *from2 < *from1 ) {
++ from2;
goto loop_start;
}
return ( false );
}
return ( true );
}
?

'continue' is always unnatural way of jumping in loops, but that's my
personal opinion.
 
B

Bart van Ingen Schenau

The problem is that they break visible program flow in an
unexpected way.  Alf's else's, for example, make it clear where
the code continues; the continue hides this fact.

I disagree on this.
If it is unclear where the top of the innermost loop is, then there is
no hope for the code without major refactoring as it is overly
complicated regardless of the control structure used.
Only a goto statement can leave it unclear where the code continues.
All the other jump statements have a clearly identifiable destination
(top of loop, after loop/switch or at call-site in the caller).

On the other hand, I find the version with continue less readable
because
- it makes you wonder why part of the loopis skipped
- when skimming the code, you might miss that part of the loop is
skipped with a continue.
The version with else does not have these problems.
Alf's return from within the loop, of course, has the same
problem---it hides the actual code flow (and would be banned in
just about any serious coding guidelines).

If any serious coding standard forbids multiple exits from a control
structure, then those serious coding standards must also forbid any
use of exceptions.

Somehow, the proponent of 'single entry, single exit' holler loudly
about jump statements that are clearly visible in the code, but are
seemingly not concerned about exceptions, which can alter the control
flow in much more drastic ways without a clear signal in the code.
--
James Kanze (GABI Software)             email:[email protected]
Conseils en informatique orientée objet/
                   Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Bart v Ingen Schenau
 
A

Alf P. Steinbach

* anon:
Alf said:
Given

template < typename InputIterator1, typename InputIterator2 >
bool disjoint ( InputIterator1 from1, InputIterator1 to1,
InputIterator2 from2, InputIterator2 to2 ) {
while ( from1 != to1 && from2 != to2 ) {
if ( *from1 < *from2 ) {
++ from1;
continue;
}
if ( *from2 < *from1 ) {
++ from2;
continue;
}
return ( false );
}
return ( true );
}

[cut the normal version]
Is the first version a case for 'continue', an example where it's
natural/good?

How is it different then this:

template < typename InputIterator1, typename InputIterator2 >
bool disjoint ( InputIterator1 from1, InputIterator1 to1,
InputIterator2 from2, InputIterator2 to2 ) {
while ( from1 != to1 && from2 != to2 ) {
loop_start:
if ( *from1 < *from2 ) {
++ from1;
goto loop_start;
}
if ( *from2 < *from1 ) {
++ from2;
goto loop_start;
}
return ( false );
}
return ( true );
}
?

The placement of the label. :)

In this version the loop continuation condition is checked only for the first
iteration.

But with proper placement of the label there is that question, yes.

'continue' is always unnatural way of jumping in loops, but that's my
personal opinion.


Cheers,

- Alf
 
A

anon

Alf said:
* anon:
Alf said:
Given

template < typename InputIterator1, typename InputIterator2 >
bool disjoint ( InputIterator1 from1, InputIterator1 to1,
InputIterator2 from2, InputIterator2 to2 ) {
while ( from1 != to1 && from2 != to2 ) {
if ( *from1 < *from2 ) {
++ from1;
continue;
}
if ( *from2 < *from1 ) {
++ from2;
continue;
}
return ( false );
}
return ( true );
}

[cut the normal version]
Is the first version a case for 'continue', an example where it's
natural/good?

How is it different then this:

template < typename InputIterator1, typename InputIterator2 >
bool disjoint ( InputIterator1 from1, InputIterator1 to1,
InputIterator2 from2, InputIterator2 to2 ) {
while ( from1 != to1 && from2 != to2 ) {
loop_start:
if ( *from1 < *from2 ) {
++ from1;
goto loop_start;
}
if ( *from2 < *from1 ) {
++ from2;
goto loop_start;
}
return ( false );
}
return ( true );
}
?

The placement of the label. :)

In this version the loop continuation condition is checked only for the
first iteration.

But with proper placement of the label there is that question, yes.

Sorry, haven't tried :(
Here is corrected :

template < typename InputIterator1, typename InputIterator2 >
bool disjoint ( InputIterator1 from1, InputIterator1 to1,
InputIterator2 from2, InputIterator2 to2 ) {

loop_start:
while ( from1 != to1 && from2 != to2 ) {
if ( *from1 < *from2 ) {
++ from1;
goto loop_start;
}
if ( *from2 < *from1 ) {
++ from2;
goto loop_start;
}
return ( false );
}
return ( true );
}
 
G

Gernot Frisch

IMO both are good solutions and should provide the same output.

Why don't you like the continue?
 
N

Noah Roberts

Alf said:
Is the first version a case for 'continue', an example where it's
natural/good?

I don't find the first version particularly natural. Mostly, of course,
that's due to the fact that I never see code resembling it. I did see
some similarly unnatural code at one time though:

void f()
{
if (x)
goto endf;

... do some stuff ...

endf:
return;
}

No, it's not significantly different from "if (x) return" but why go to
all that trouble? Natural to me means what one normally sees and can
get used to. The use of goto and continue in these manners is not
particularly natural.

I'd probably have a tantrum if I saw code like that in a review.
 
J

Juha Nieminen

James said:
The problem is that they break visible program flow in an
unexpected way.

Sometimes the alternative to 'continue' is a set of deeply-nested
conditionals. In some cases the version with the 'continues' may
actually be cleaner and easier to read then the nested conditionals.

Something like:

for(...)
{
some_stuff_1;
if(condition_1)
{
do_stuff_1;
continue;
}

some_stuff_2;
if(condition_2)
{
do_stuff_2;
continue;
}

some_stuff_3;
if(condition_3)
{
do_stuff_3;
continue;
}

some_stuff_4;
}

vs.

for(...)
{
some_stuff_1;
if(condition_1)
{
do_stuff_1;
}
else
{
some_stuff_2;
if(condition_2)
{
do_stuff_2;
}
else
{
some_stuff_3;
if(condition_3)
{
do_stuff_3;
}
else
{
some_stuff_4;
}
}
}
}
 
A

Andrew Koenig

Here's another way to think about it.

In his 1968 letter, "goto Considered Harmful," Dijkstra argued that the
problem with goto statements was not the goto, specifically, but rather the
label. He gave the reason that in order to determine what conditions hold
at a label, it is necessary to inspect the entire program in order to find
every place in that program that might contain a goto statement that refers
to that label.

A continue statement is essentially a goto without a label. In order to
determine whether a loop that contains continue statement is correct, there
is no need to inspect code outside the loop -- it is necessary only to
verify that the right conditions hold at every continue statement as well as
at the end of the loop. This is a much less onerous requirement than the
one that results from a goto statement, which necessarily entails a label.
 
V

Vidar Hasfjord

[...]
Of course many structure purists along with calling your continues
"glorified gotos" will say that there should be one entrance and one
exit to a given function (your two returns being a slap on the
wrist).   I'm not in that camp (I'll break any rule if there is a
cogent reason to do so), but would agree to ask why continues
(unnatural loop exists) are used unless there is a reason to do so?
Usually the reason is clearer code.  I don't think code version #1 is
any clearer than code version #2.  In fact, version #2 wins on
clarity, not for structure issues, but because of the comment
"(*first1 == *first2)", which may be obvious, but the comment helps
read it quickly. I also think the if/else structure is easier to read
in this case, but that could be a personal preference.

If I were writing this code, and wanted to disregard a clean structure
for efficiency, (perhaps a premature optimization) you could rewrite
it to not check both iterators for reaching the end every time (since
you are only changing one of them per loop iteration)

    template <typename InputIter1, typename InputIter2>
    bool disjoint(InputIter1 first1, InputIter1 last1,
              InputIter2 first2, InputIter2 last2)
    {
        for(;;)
         {
          if(*first1 < *first2)
            {
               if(++first1 == last1) { return true; }
            }
          else if(*first2 < *first1)
            {
                if(++first2 == last2) { return true; }
            }
          else  // *first1 == *first2
            {
            return false;
             }
           }
    }

Your rewrite changes preconditions; the function no longer works for
empty sequences. Here's a corrected version for the conciseness crowd:

template <typename II1, typename II2>
bool disjoint (II1 f1, II1 l1, II2 f2, II2 l2)
{
if (f1 != l1 && f2 != l2)
do if (*f1 == *f2) return false;
while ((*f1 < *f2) ? ++f1 != l1 : ++f2 != l2);
return true;
}

Regards,
Vidar Hasfjord
 
K

Kai-Uwe Bux

Bart said:
I disagree on this.
If it is unclear where the top of the innermost loop is, then there is
no hope for the code without major refactoring as it is overly
complicated regardless of the control structure used.
Only a goto statement can leave it unclear where the code continues.
All the other jump statements have a clearly identifiable destination
(top of loop, after loop/switch or at call-site in the caller).

On the other hand, I find the version with continue less readable
because
- it makes you wonder why part of the loopis skipped
- when skimming the code, you might miss that part of the loop is
skipped with a continue.
The version with else does not have these problems.

I find the continue version easier to read. The continue statement
occurs precisely when the right kind to progress has been made. In the
if-version, I have to skip behind the if-statement just to realize that no
code is there. That makes me wonder why I haven't been told to start over
right away.


Here, there is a reason for the early return: the loop invariant is easier
to formulate that way. In

while ( from1 != to1 && from2 != to2 ) {
if ( *from1 < *from2 ) {
++ from1;
continue;
}
if ( *from2 < *from1 ) {
++ from2;
continue;
}
// ***
return ( false );
}

the loop invariant is that the given input ranges are disjoint except
possibly in the remaining tails [from1,to1) x [from2,to2). At the point ***
inside the loop, it is impossible to (a) increment from1 or from2 and (b)
keep the invariant, i.e., we can neither start over (since we did not make
progress) nor break (since the invariant cannot be established).
Fortunately there is an alternative: flow control can return with an answer
(false).

If you rewrite the loop avoiding the early return, the loop invariant
becomes (slightly) more involved and arguing correctness becomes a little
less clear.

If any serious coding standard forbids multiple exits from a control
structure, then those serious coding standards must also forbid any
use of exceptions.

Somehow, the proponent of 'single entry, single exit' holler loudly
about jump statements that are clearly visible in the code, but are
seemingly not concerned about exceptions, which can alter the control
flow in much more drastic ways without a clear signal in the code.

And I agree with this: exceptions are huge jumps and you _always_ have to be
ready for them. They are very tricky.


Best

Kai-Uwe Bux
 
J

Jerry Coffin

Given

template < typename InputIterator1, typename InputIterator2 >
bool disjoint ( InputIterator1 from1, InputIterator1 to1,
InputIterator2 from2, InputIterator2 to2 ) {
while ( from1 != to1 && from2 != to2 ) {
if ( *from1 < *from2 ) {
++ from1;
continue;
}
if ( *from2 < *from1 ) {
++ from2;
continue;
}
return ( false );
}
return ( true );
}

and as an alternative, by some other programmer:

template <typename InputIter1, typename InputIter2>
bool disjoint(InputIter1 first1, InputIter1 last1,
InputIter2 first2, InputIter2 last2)
{
while (first1 != last1 && first2 != last2)
if (*first1 < *first2)
++first1;
else if (*first2 < *first1)
++first2;
else // *first1 == *first2
return false;
return true;
}

Is the first version a case for 'continue', an example where it's natural/good?

Neither code snippet is mine (I'd do the second version but with curly braces).

In this case, it seems to me that the first version is more difficult to
understand, which clearly isn't a good thing.

At the same time, I don't think it's a particularly strong argument
against continue in general. Just for example, in processing a list of
files (following the Unix-based convention that names starting with '.'
are hidden), I've written loops like this:

while (there's another file) {
if (filename[0] == '.')
continue; // ignore hidden file
process file
}

IMO, this is quite reasonable and understandable. For the job above, I'd
probably do things a bit differently (than anybody else on earth),
something like this:

// warning: untested code.

namespace {

enum returns {FIRST, SECOND, EQUAL};

template <class T>
int lesser(T const &a, T const &b) {
if (a<b)
return FIRST;
if (b<a)
return SECOND;
return EQUAL;
}
}

template <class inputIter>
bool disjoint(inputIter first1, inputIter last1,
inputIter first2, inputIter last2) {

inputIter iters[2] = {first1, first2};
returns ret;

while (EQUAL != (ret=lesser(*first1, *first2))
&& iters[0] != last1
&& iters[1] != last2)
{
++iters[ret];
}

return ret!=EQUAL;
}

The more I program, the more my code comes full circle to resemble
ancient Fortran -- flow control based only on +, 0 or -, and everything
else is subscripting! :)
 
J

Jerry Coffin

[ ... ]
template <class inputIter>
bool disjoint(inputIter first1, inputIter last1,
inputIter first2, inputIter last2) {

inputIter iters[2] = {first1, first2};
returns ret;

while (EQUAL != (ret=lesser(*first1, *first2))
&& iters[0] != last1
&& iters[1] != last2)

Oops -- I screwed up the order there. It should be:

while (iters[0] != last1
&& iters[1]!=last2
&& EQUAL !=(ret=lesser(*first1, *first2))

You need to assure that the iterators are still in the range before
they're dereferenced...
 
I

Ian Collins

Noah said:
I don't find the first version particularly natural. Mostly, of course,
that's due to the fact that I never see code resembling it. I did see
some similarly unnatural code at one time though:

That's the argument I would have used. I've hardly ever seen continue
used in C or C++ and I still have to remind my self where the program
flow ends up.
 
J

James Kanze

Sometimes the alternative to 'continue' is a set of
deeply-nested conditionals.

Only in poorly written code. The alternative is to rewrite the
code cleanly, breaking it down into smaller functions.
 
J

James Kanze

[...]
At the same time, I don't think it's a particularly strong argument
against continue in general. Just for example, in processing a list of
files (following the Unix-based convention that names starting with '.'
are hidden), I've written loops like this:
while (there's another file) {
if (filename[0] == '.')
continue; // ignore hidden file
process file
}
IMO, this is quite reasonable and understandable.

Not really. You've introduced an invariant in a way which
doesn't make it clear that it is present. I'd write:

while ( there's another file ) {
// invariant introduced by the while: there is a file
if ( filename[ 0 ] != '.' ) {
// invariant introduced by the if: the filename
// doesn't start with '.'
}
}

Done this way, the nesting makes the scope of each invariant
very clear. Written using the continue, it's not at all clear
that the if actually establishes an invariant for the code which
follows.

And of course, if the code in the if (in my case) is more than
two or three lines, it really belongs broken out in another
function.

(There's another very elegant solution, of course---to arrange
for your iterator over the filenames to filter out the filenames
you don't want. But that's would make the discussion irrelevant
to the issue here.)
For the job above, I'd
probably do things a bit differently (than anybody else on earth),
something like this:

// warning: untested code.

namespace {

enum returns {FIRST, SECOND, EQUAL};

template <class T>
int lesser(T const &a, T const &b) {
if (a<b)
return FIRST;
if (b<a)
return SECOND;
return EQUAL;

That would be:

return a < b
? FIRST
: b < a
? SECOND
: EQUAL ;

of course. This is one case where the ?: operator definitely
makes the code a lot more readable. (It's clear at a glance
that the function really is a function, in the purest sense of
the word.)
template <class inputIter>
bool disjoint(inputIter first1, inputIter last1,
inputIter first2, inputIter last2) {

inputIter iters[2] = {first1, first2};
returns ret;

while (EQUAL != (ret=lesser(*first1, *first2))
&& iters[0] != last1
&& iters[1] != last2)
{
++iters[ret];
}
return ret!=EQUAL;
}
The more I program, the more my code comes full circle to
resemble ancient Fortran -- flow control based only on +, 0 or
-, and everything else is subscripting! :)

It depends on the domain, but there are more than a few cases
where a three way if would be nice. Your lesser template,
combined with a switch, could do the trick.
 
J

Juha Nieminen

James said:
Only in poorly written code. The alternative is to rewrite the
code cleanly, breaking it down into smaller functions.

If the code inside the loop requires access to variables local to the
function, it becomes complicated. Either you need to pass all the local
variables as references to each function, which is a hassle and makes
things only more complicated rather than simplifying them, or you need
to encapsulate the whole thing into a class. While the latter options
sounds like (and often is) a good idea, in some cases it may be more
work than it's worth.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,825
Latest member
VernonQuy6

Latest Threads

Top