An Alternative Conditional Compilation Approach with Modern C++
Conditional compilation using macros is a well-known approach among C/C++ programmers. Library developer provides a library like FreeRTOS, WolfMQTT, and Little File System. To use such a library, we have to provide a configuration file, like FreeRTOSConfig.h. In this configuration, we command our requests to the library. “I will use mutexes, enable timers for me, disable X” and so on…
What is wrong with this old-school (but rock star) approach?
The very first problem is config file dependency. To use a config file-required library, you must provide a config file. Why? The reason is simple: It is a config file-required library. In this case, you must open a new x_config.h header file in your editor and must provide necessary macros in it.
In this case we have a such kind of file dependency,
The library needs something from the application space. This is annoying when you want to build the library as a static library.
Secondly, we don’t have an extensive compile-time configuration checking facility.
To understand whether a configuration parameter is defined correctly or not, we have limited options. One of the most well-known approaches is to provide a brand new header that consists of other checkers or injecting those checkers into related source files.
Additionally, each macro must be defined before its use. For example, such a macro definition is not valid,
This means that you can not control the conditional compilation context from where you write your actual application code. Dull? Not dull but think the situation you are writing code, you need to change a config parameter, you open the config file, change the parameter, close the file and return back your actual code. Otherwise, you simply may want to check what the actual config parameter is, the same cycle comes to play…
The last one I have in my mind: how to test-drive a conditionally compiled code?
I have just tried to design a simple module.
The dependency hierarchy is set as shown in Figure-1. We have two files for module: module.h and module.c,
module.c file depends on a module_config.h, as we discussed earlier.
In my main.cpp file, I just tried to write a piece of test code for the module below,
It seems, it does not work for me,
If you know how to compile this module conditionally on the way, please let me know via a PM or a public comment.
Assume we have enough resources, enthusiastic developers, a brand new C++ compiler that supports C++20 and, we are supposed to design a single instance module. The requirements are the same: module shall consist of a function that is called func and will be compiled conditionally according to a configuration…
We have a module that takes only one template argument. I improved the design reliability with a concept. What it means is “the config template parameter must have at least a static function named model_number.” If it does not, the compiler will generate an error and stop compilation at that moment. We will see it later.
Let’s have a closer look into the function. The module is supposed to be a single instance. In this case, the function definition is static.
In the code above, we have an if constexpr. This feature comes to the scene with C++17 and it has “at least” the same effect as the code below,
Let’s provide configurations and use the module,
The syntax may look a bit ugly. We can improve it by using “using.”
Let’s have a look, what the compiler generates for such a code, with zero optimization,
1- The actual code looks a bit messy. Therefore a typical non-template-metaprogramming developer may think this code is not for her. That is a myth. It generates what you intend, no more than it.
2- We solved the messy file dependency problem. The actual application code is dependent only on the module.hpp. The actual library code is only now dependent on itself. We provide the necessary configuration where we write the actual application code by defining structs like config1 or config2.
3- We can check configuration parameters on the way. If a developer passes that does not look like what the library expects, the compiler will stop her. We have concepts, we have intuitive constexpr utilities. You can command to the compiler like “configuration shall have a function like that…” You should dive into the world of the concepts.
4- We control the library configuration from the actual application code context.
5- We have a better syntax, at least for me,
I prefer this,
Instead of this,
6- We can test drive the code. Let’s remember the pseudo test code,
In the code above, we are configuring our module on the way. Assume we have a specific test scenario that requires very specific configuration. We can do it using local classes. So the code is cleaner, clean code(!) is better code. Thereby we do not need to jump between a config file and the actual code.