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 <iostream>

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.