
How it works
The interesting part in the hello-world.cpp example is the conditional compilation based on the preprocessor definitions IS_WINDOWS, IS_LINUX, or IS_MACOS:
std::string say_hello() {
#ifdef IS_WINDOWS
return std::string("Hello from Windows!");
#elif IS_LINUX
return std::string("Hello from Linux!");
#elif IS_MACOS
return std::string("Hello from macOS!");
#else
return std::string("Hello from an unknown system!");
#endif
}
These definitions are defined at configure time by CMake in CMakeLists.txt by using target_compile_definitions before being passed on to the preprocessor. We could have achieved a more compact expression without repeating if-endif statements and we will demonstrate this refactoring in the next recipe. We could also have joined the if-endif statements into one if-elseif-elseif-endif statement.
At this stage, we should point out that we could have set the definitions using add_definitions(-DIS_LINUX) (of course, adjusting the definition according to the platform in question) instead of using target_compile_definitions. The disadvantage of using add_definitions is that it modifies compile definitions for the entire project, whereas target_compile_definitions gives us the possibility to restrict both the scope of the definitions to a specific target, as well as to restrict visibility of these definitions by using the PRIVATE, PUBLIC, or INTERFACE qualifiers. These qualifiers have the same meaning they had for compiler flags, as we have seen already in Chapter 1, From a Simple Executable to Libraries, Recipe 8, Controlling compiler flags:
- With the PRIVATE qualifier, compile definitions will only be applied to the given target and not by other targets consuming it.
- With the INTERFACE qualifier, compile definitions on a given target will only be applied to targets consuming it.
- With the PUBLIC qualifier, compile definitions will be applied to the given target and all other targets consuming it.