In project development, we usually use conditional compilation to trim code and selectively exclude code that is not needed, for example, if a feature is not supported at all under a certain platform, then that feature should not be compiled.

Generally we use macros to determine the code, selectively pick the parts that need to be compiled, and turn on such conditions in the build system.

 1 2 3 4 5  #ifdef XXXXXXXXXX std::cout << "hello world!" << std::endl; #else std::cout << "good bye!" << std::endl; #endif 

Such behavior is normal in C projects, and even in C++ projects, we choose to use macros for conditional judgments.

However, if more macros are defined, it is easy to fragment the code and not visualize the workflow.

For example, code like this:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26  #define KWIN_VERSION_CHECK(major, minor, patch, build) ((major<<24)|(minor<<16)|(patch<<8)|build) #ifdef KWIN_VERSION_STR #define KWIN_VERSION KWIN_VERSION_CHECK(KWIN_VERSION_MAJ, KWIN_VERSION_MIN, KWIN_VERSION_PAT, KWIN_VERSION_BUI) #endif #if defined(KWIN_VERSION) && KWIN_VERSION < KWIN_VERSION_CHECK(5, 21, 3, 0) typedef int TimeArgType; #else typedef std::chrono::milliseconds TimeArgType; #endif #if defined(Q_OS_LINUX) && !defined(QT_NO_DYNAMIC_LIBRARY) && !defined(QT_NO_LIBRARY) QT_BEGIN_NAMESPACE QFunctionPointer qt_linux_find_symbol_sys(const char *symbol); QT_END_NAMESPACE QFunctionPointer KWinUtils::resolve(const char *symbol) { return QT_PREPEND_NAMESPACE(qt_linux_find_symbol_sys)(symbol); #else QFunctionPointer KWinUtils::resolve(const char *symbol) { static QString lib_name = "kwin.so." + qApp->applicationVersion(); return QLibrary::resolve(lib_name, symbol); #endif } 

As you can see from the above example, once there are a lot of repeated judgment conditions in the code, the code is very unintuitive and split into many parts by macros.

By chance, I saw an article introducing the use of if constexpr in C++ 17, which can perform some calculations during the compilation period. Although I knew about the use of constexpr a long time ago, the examples people gave were basically numerical calculations, allowing the compiler to calculate the values during compilation, thus reducing the runtime consumption, and I never thought of other uses I never thought of other uses for constexpr, so I never used it in my project.

If the code is small and very intuitive, such as the example often given to calculate the Fibonacci sequence during compilation, the compiler will help us open the optimization and give the result directly, even if we don’t use constexpr to explicitly require it.

But if the code is very complex, the compiler may not do this optimization for us, and we need to manually mark where we can compute and ask the compiler to evaluate and optimize during compilation.

What I envision is to use cmake to make a file of the switch values at build time, and then use if constexpr to make conditional judgments where they are needed, and during compilation, the compiler will find a branch that is determined not to be executed (equivalent to if(false) {}), and then that branch will not be compiled and The branch will not be compiled and will be rejected.

Some work needs to be done in CMakeLists.txt to add the compilation parameters to the build system.

  1 2 3 4 5 6 7 8 9 10 11 12  option (ENABLE_MODULE "Enable Module" ON) if(ENABLE_MODULE) set(ENABLE_MODULE "1") else() set(ENABLE_MODULE "0") endif(ENABLE_MODULE) configure_file ( "${CMAKE_CURRENT_SOURCE_DIR}/options/options.h.in" "${CMAKE_CURRENT_BINARY_DIR}/options/options.h" ) 

In the options/options.h.in file, import the variables into the file as required by cmake, and replace the contents.

 1 2 3  #pragma once #cmakedefine01 ENABLE_MODULE 

Here I am still using macro definitions, which can also be written directly in the following form.

  1 2 3 4 5 6 7 8 9 10 11 12  option (ENABLE_MODULE "Enable Module" ON) if(ENABLE_MODULE) set(ENABLE_MODULE "true") else() set(ENABLE_MODULE "false") endif(ENABLE_MODULE) configure_file ( "${CMAKE_CURRENT_SOURCE_DIR}/options/options.h.in" "${CMAKE_CURRENT_BINARY_DIR}/options/options.h" ) 
 1 2 3  #pragma once const bool ENABLE_MODULE{@ENABLE_MODULE@}; 

Write a test code in main.cpp:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  #include "options/options.h" #include int main() { if constexpr (ENABLE_MODULE) { std::cout << "Now Enable Module" << std::endl; } if constexpr (!ENABLE_MODULE) { std::cout << "Now Disable Module" << std::endl; } return 0; } 

The implementation results are as expected.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33  # lxz @ lxz-MacBook-Pro in ~/Develop/constexpr-demo/build on git:master o [13:28:39] $cmake ../ -G Ninja -DENABLE_MODULE=ON -- The CXX compiler identification is AppleClang 13.0.0.13000029 -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ - skipped -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /Users/lxz/Develop/constexpr-demo/build # lxz @ lxz-MacBook-Pro in ~/Develop/constexpr-demo/build on git:master o [13:28:45]$ ninja [2/2] Linking CXX executable src/constexpr # lxz @ lxz-MacBook-Pro in ~/Develop/constexpr-demo/build on git:master o [13:28:48] $./src/constexpr Now Enable Module # lxz @ lxz-MacBook-Pro in ~/Develop/constexpr-demo/build on git:master o [13:28:52]$ cmake ../ -G Ninja -DENABLE_MODULE=OFF -- Configuring done -- Generating done -- Build files have been written to: /Users/lxz/Develop/constexpr-demo/build # lxz @ lxz-MacBook-Pro in ~/Develop/constexpr-demo/build on git:master o [13:28:58] $ninja [2/2] Linking CXX executable src/constexpr # lxz @ lxz-MacBook-Pro in ~/Develop/constexpr-demo/build on git:master o [13:29:00]$ ./src/constexpr Now Disable Module 

Although the result matches, we are actually not sure if the code culling is actually done during compilation, so we use the command to do an assembly to see if the assembly contains a judgment instruction and two output strings.

 1  clang -S main.cpp -o main.s -I./ 

main.s

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106   .text .file "main.cpp" .globl main // -- Begin function main .p2align 2 .type main,@function main: // @main .cfi_startproc // %bb.0: stp x29, x30, [sp, #-32]! // 16-byte Folded Spill str x19, [sp, #16] // 8-byte Folded Spill mov x29, sp .cfi_def_cfa w29, 32 .cfi_offset w19, -16 .cfi_offset w30, -24 .cfi_offset w29, -32 adrp x19, :got:_ZSt4cout ldr x19, [x19, :got_lo12:_ZSt4cout] adrp x1, .L.str add x1, x1, :lo12:.L.str mov w2, #18 mov x0, x19 bl _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l ldr x8, [x19] ldur x8, [x8, #-24] add x8, x8, x19 ldr x19, [x8, #240] cbz x19, .LBB0_5 // %bb.1: ldrb w8, [x19, #56] cbz w8, .LBB0_3 // %bb.2: ldrb w1, [x19, #67] b .LBB0_4 .LBB0_3: mov x0, x19 bl _ZNKSt5ctypeIcE13_M_widen_initEv ldr x8, [x19] mov w1, #10 mov x0, x19 ldr x8, [x8, #48] blr x8 mov w1, w0 .LBB0_4: adrp x0, :got:_ZSt4cout ldr x0, [x0, :got_lo12:_ZSt4cout] bl _ZNSo3putEc bl _ZNSo5flushEv ldr x19, [sp, #16] // 8-byte Folded Reload mov w0, wzr ldp x29, x30, [sp], #32 // 16-byte Folded Reload ret .LBB0_5: bl _ZSt16__throw_bad_castv .Lfunc_end0: .size main, .Lfunc_end0-main .cfi_endproc // -- End function .section .text.startup,"ax",@progbits .p2align 2 // -- Begin function _GLOBAL__sub_I_main.cpp .type _GLOBAL__sub_I_main.cpp,@function _GLOBAL__sub_I_main.cpp: // @_GLOBAL__sub_I_main.cpp .cfi_startproc // %bb.0: stp x29, x30, [sp, #-32]! // 16-byte Folded Spill str x19, [sp, #16] // 8-byte Folded Spill mov x29, sp .cfi_def_cfa w29, 32 .cfi_offset w19, -16 .cfi_offset w30, -24 .cfi_offset w29, -32 adrp x19, _ZStL8__ioinit add x19, x19, :lo12:_ZStL8__ioinit mov x0, x19 bl _ZNSt8ios_base4InitC1Ev adrp x0, :got:_ZNSt8ios_base4InitD1Ev ldr x0, [x0, :got_lo12:_ZNSt8ios_base4InitD1Ev] mov x1, x19 ldr x19, [sp, #16] // 8-byte Folded Reload adrp x2, __dso_handle add x2, x2, :lo12:__dso_handle ldp x29, x30, [sp], #32 // 16-byte Folded Reload b __cxa_atexit .Lfunc_end1: .size _GLOBAL__sub_I_main.cpp, .Lfunc_end1-_GLOBAL__sub_I_main.cpp .cfi_endproc // -- End function .type _ZStL8__ioinit,@object // @_ZStL8__ioinit .local _ZStL8__ioinit .comm _ZStL8__ioinit,1,1 .hidden __dso_handle .type .L.str,@object // @.str .section .rodata.str1.1,"aMS",@progbits,1 .L.str: .asciz "Now Disable Module" // 关键在这里 .size .L.str, 19 .section .init_array,"aw",@init_array .p2align 3 .xword _GLOBAL__sub_I_main.cpp .ident "clang version 13.0.1" .section ".note.GNU-stack","",@progbits .addrsig .addrsig_sym _GLOBAL__sub_I_main.cpp .addrsig_sym _ZStL8__ioinit .addrsig_sym __dso_handle .addrsig_sym _ZSt4cout 

Looking at the entire main.s assembly, we find that only the expected text string is present in the .L.str segment, and we can conclude that the code was done during compilation to reject it, which meets our requirements.