antipattern++: using the C preprocessor to improve your antipattern

the scenario

For some reason, you're implementing the for-case/loop-switch sequence!

For unknowable reasons, you have a switch statement inside of a loop, and you want to iterate through and hit every case before moving onto something else.

the details

You're using C++. Maybe it's in a function that gets called repeatedly, maybe it's a for loop, maybe it's some other godforesaken state machine. For this example, we'll just use a for loop.

Here's what we're starting with:

int assets_to_load = 8; for (int i = 0; i < assets_to_load; i++) { switch(i) { case 0: loadRegularAsset("/path/to/fileA"); break; case 1: loadRegularAsset("/path/to/fileB"); break; case 2: loadRegularAsset("/path/to/fileD"); break; case 3: loadRegularAsset("/path/to/fileE"); break; case 4: loadRegularAsset("/path/to/fileF"); break; case 5: loadParticularAsset("/path/to/fileV", 255, 255); break; case 6: loadParticularAsset("/path/to/fileW", 128, 128); break; case 7: loadParticularAsset("/path/to/fileX", 0, 255); break; case 8: loadParticularAsset("/path/to/fileZ", 0, 127); break; } cout << "Loaded asset " << i << endl; }

Clearly this is a problem whose only possible solution is a for-case loop! But what if you want to add a case fileC in between fileB and fileD, or a case FileY in between fileX and fileZ? You would have to update all of the indices, which would be a huge pain, or have your indices out of order, which would not be very nice! Plus you would have to update the assets_to_load since you now have more assets to load...

You don't care about the values of the indices. They are not used for anything beyond incrementation. And yet, you have to manage them!

And you can't just use a new integer j and write case (j++): because C and C++ only allow contsants as cases in a switch statement.

This is a seemingly unsurmountable problem! How will we solve it while maintaining this seemingly unmaintanable code?

C preprocessor to the rescue!

The C preprocessor allows you to solve all sorts of problems you probably shouldn't, including this! The __COUNTER__ macro is a macro that, at compile time, increments by 1 every time you call it! It's a non-standard extension, but it is present in GCC, clang, and Microsoft's C and C++ compilers, so therefore you will never have any problem using it!

the solution


/* counter_offset is set to guarantee we start at 0. It is not defined as a macro because we want it to evaluate only once. */ const int counter_offset = __COUNTER__; /* At compile time, every instance of CASE will expand to its expression. Because every element of the expression will be a constant, the expression can be used as a switch case. */ #define CASE __COUNTER__-(counter_offset+1) /* assets_to_load will be recalculated later by CASE. It must be initialized to a value greater than 0 to start the loop, but it will never need to be updated manually. */ int assets_to_load = 1; // A for-case where you can add, remove, or rearrange things freely! for (int i = 0; i < assets_to_load; i++) { switch(i) { case CASE: loadRegularAsset("/path/to/fileA"); break; case CASE: loadRegularAsset("/path/to/fileB"); break; case CASE: // easily added out of order without changing anything loadRegularAsset("/path/to/fileC"); break; case CASE: loadRegularAsset("/path/to/fileD"); break; case CASE: loadRegularAsset("/path/to/fileE"); break; case CASE: loadRegularAsset("/path/to/fileF"); break; case CASE: loadParticularAsset("/path/to/fileV", 255, 255); break; case CASE: loadParticularAsset("/path/to/fileW", 128, 128); break; case CASE: loadParticularAsset("/path/to/fileX", 0, 255); break; case CASE: // easily added out of order without changing anything loadParticularAsset("/path/to/fileY", 196, 255); break; case CASE: loadParticularAsset("/path/to/fileZ", 0, 127); break; } cout << "Loaded asset " << i << endl; assets_to_load = CASE; // The for loop now knows the number of cases. }

why?

Why NOT?

you shouldn't do this

YOU shouldn't do this!