Introduction
The C++ preprocessor is probably the most ubiquitous text transformation tool out there. However, it has its drawbacks. For one, it does not let you
iterate.
I often wish for a #for(...)
directive to complement the #if(..)
. This is specially useful for writing repetitive code that follows a pattern.
Fortunately, there is a way around this. I discovered this when I accidentally #include
d a file from within the same file. The compiler complained of infinite recursion.
Voila! The preprocessor supports recursive includes! The only other piece to this puzzle is an index variable which can be modified.
Here is where it gets ugly. Preprocessor variables can not be incremented/decremented using the usual arithmetic operators. In other words, the following is illegal:
#define ITER 0
ITER = ITER+1
However, a similar result can be obtained using brute force:
#if(ITER == 0)
#undef ITER
#define ITER 1
#elif(ITER == 1)
#undef ITER
#define ITER 2
#endif
This can be continued for as many iterations as will be needed.
Putting it all together
The source code for this is hosted on github:
https://github.com/debdattabasu/pp_Iteration
Basically, the code defines an iterator that is incremented as discussed above. The file
loop.h keeps including itself until the iterator reaches a specified value.
Make sure to check out the code sample for details.
Use Cases
- Loop Unrolling: Most modern compilers can do this for standard loops. However, some low-end embedded compilers can't.
A good example of this is the GLSL compiler found in many embedded GPU drivers.
- Emulated Arrays: This is another issue I had to face while writing GLSL. Older versions of OpenGL do not support arrays of textures.
Hence, I had to do things like this:
uniform sampler2D Tex_01 ;
uniform sampler2D Tex_02 ;
uniform sampler2D Tex_03 ;
uniform sampler2D Tex_04 ;
It is much easier to automate this with preprocessor iterations.
Using
the Code
To demonstrate how preprocessor iterations work, we create code to print numbers less than 10. The looping is done at compile time, and there is no runtime overhead.
int main()
{
#include "loopstart.h"
#define LOOP_END 10
#define MACRO(x) printf("%d\n", x);
#include "loop.h"
return 0;
}
Basically, we have to do the following:
- Include loopstart.h
- Define
LOOP_END
to be the number of required iterations - Define
MACRO(x)
to be the code fragment that is repeated. X is the loop counter. - Include Loop.h
We could, of course, add more bells and whistles to this, like a starting point for the iterator other than the default
0
.
This is pretty easy to do once you get a hang of the general concept, and is left as an exercise for the reader.