Fairly often a for loop will run a number of iterations based on some integral expression. To express this common pattern clearly, we can create a macro that helps us build the for loop. We'll call it "loop_n_times", and have it take a single argument that is converted to an integer for the count. It will be used like this:
for ( loop_n_times( 10 ); ) {
// ...
}
void memset( void* vp, char c, std::size_t n ) {
char* p = static_cast<char*> (vp);
for ( loop_n_times( n ); ++p )
*p = c;
}
The expression can be checked during debugging to ensure it is non-negative. Checking for a non-negative count manually is quite verbose. Also, the macro can be tailored to the compiler to produce the most efficient for loop structure. Finally, there is no room to mess up the loop structure, i.e. to introduce an off-by-one error.
Here is one version of the macro that allows the compiler I use to compile loops to use native looping instructions of the target CPU:
#define loop_n_times( iter ) \
/* for ( */ unsigned long count_ = (iter); (count_--) /*; ... ) */
The macro can be written so that extra looping conditions can be added after it. For example:
for ( loop_n_times( num_items ) && !past_end(); ) {
// ...
}
As with macros, this isn't without its problems. I will try to use this as an example of the problems and tradeoffs involved.
First, we have the common vocabulary problem. If different developers use different names/semantics for this construct, we end up with many slightly-different looping constructs. This is a reason to standardize the construct (on as large a scale as possible).
As with all macros, this can clash with identifiers in the source text with the same name. This macro, for example, prevents us from having a function named loop_n_times. Also, the "semantics" of it are very specialized. Developers have to learn its semantics. They may be more familiar with writing normal for loops manually.
Use of this ties the code to the macro. To use the code one must have the definition of the macro. There is a significant tradeoff here - we are sacrificing some reusability of the code so that we can more conveniently/safely express this common construct. In some cases, the cost of the macro may be too great to justify its use.
:-)