C/C++ loops and macros

In C/C++ you often break from the middle of a loop instead of from the top/bottom. There are arguments against this — it doesn’t respect block structure and it isn’t a functional style. A break is just a goto. But it’s common, easy to understand, and a very useful pattern.

...
    // General loop micro-pattern:
    for ( ; ; ) {
        .. do some stuff ..
        if ( .. test .. ) break;
        .. do some stuff ..
        if ( .. test .. ) break;
        .. do some stuff ..
    }
...
    
// I've seen programmers define LOOP and LOOP_forever macros.
# define LOOP          for(;;)
# define LOOP_forever  for(;;)

...
    LOOP {
        if ( .. ) break;
        .. etc ..
    }
...

You should be careful when defining keyword and structure macros like LOOP — they interfere with editors and other tools and they can hurt readability. But if you like LOOP, you can go further:

// Getting carried away with macros.
# define LOOP             for(;;)
# define LOOP_exit        break
# define LOOP_next        continue
# define LOOP_while( x )  if ( ! x ) break
# define LOOP_until( x )  if ( x ) break

...
    LOOP {
        LOOP_until( test );
        .. do something ..
        LOOP_while( test );
        .. do something ..
        if ( .. ) {
            LOOP_next;
        }
        if ( .. ) {
            .. do something ..
            LOOP_exit;
        }
        .. do something ..
    }
...

Finally, let’s imagine taking the loop macro to the limit. The following isn’t real code so don’t try to compile it.

    // Fantasy loop macro.
    ...
    loop named top_level {
        int how_many_times = 0;
        int my_sum = 0;
        int my_sum2 = 0;
        std::vector<int> my_vector;
        std::stack<int> my_stack;
        ...
        loop {
            for x in { 1, 2, 4, 8 };
            for e in 1..7;
            for y = e * e; /* eval'd each time */
            init g = x * x; /* eval'd once */
            for z = 1 to 10 by 2;
            for a = 10 downto 1;
            for b = 2 inc by 2;
            for c = 2 dec by 2;
            for d = 2 then 6;
            while my_test( );
            until my_test( ) do (is_special = true) exit top_level;
            until my_test( ) exit 2 levels;
            exit;
            next;
            next 2 levels;
            next top_level;
            collect x in my_vector;
            collect x in my_stack push;
            sum x in my_sum;
            reduce x in my_sum2 using plus;
            count my_test( ) in how_many_times;
            if ( ! LOOP_first_time ) {
                std::cout << LOOP_count << std::endl;
            }
            finally {
                std::cout << "leaving loop" << std::endl;
            }
        }
        ...
    }
    ...

Of course this is no longer C/C++ — you can’t do this with #define, you’d have to use a more powerful pre-processor. One that can manipulate code blocks and define nested macros in an enclosing parent. The standard #define macros are not even meant to take blocks as params.

// I've seen this but it may not be portable and I don't recommend it.
# define STANDARD_CATCH( block_ )           \
    try block_                              \
    catch( my::error::warning* w ) {        \
        my::error::log( w);                 \
    }                                       \
    catch ( ... ) {                         \
        my::error::log_unknown( );          \
        throw;                              \
    }

void some_fn( )
{
    .. stuff ..
    STANDARD_CATCH( {
		// Does this macro expand correctly if a comma appears in the block?
        .. stuff ..
    } )
}

Macros like this are usually a bad idea in C/C++, but in Lisp they are common and a very powerful feature of the language. C/C++ has a lot of syntax that communicates structure and keeps the source compact. Lisp has a very simple syntax, which makes it wordy and full of deeply nested parentheses but also makes it easy to extend. Lisp has a programming model that makes it harder to write tight, efficient, close-to-the-metal code, so C and C++ are probably better choices for realtime and systems engineering. But you don’t have to have Lisp to get something like the Lisp macro facility — you could define a pre-processing facility for C/C++ that worked on the block level and added a lot of power. A simpler C++ sytax would help, but it’s not vital.

But this post is getting too long. I’ll talk about what these macros might look like later.