constexpr 是 c++11 引入的关键字,用于编译时常量和常量表达式。而 c++17 将这一特性做了增强,引入了 constexpr if , 使得编译器在编译时(compile time)能够做分支判断,从而有条件的编译代码。

下面可以通过一个简单的例子来看看constexpr if的用法:

 1#include <iostream>
 2#include <type_traits>
 3
 4template<typename T> auto getValue(T t)
 5{
 6    if constexpr (std::is_pointer<T>::value) {
 7        return *t;
 8    } else {
 9        return t;
10    }
11}
12
13int main(int argc, char* argv[])
14{
15    int  a = 10;
16    int* b = &a;
17    getValue(a);
18    getValue(b);
19    return 0;
20}

其实和普通的条件判断区别不大,只不过constexpr if中的条件是常量表达式,可以在编译时确定条件表达式的结果,从而选择编译对应的分支代码。 我们可以将上述代码编译成汇编来进一步分析:

 1auto getValue<int>(int):
 2        push    rbp
 3        mov     rbp, rsp
 4        mov     DWORD PTR [rbp-4], edi
 5        mov     eax, DWORD PTR [rbp-4]
 6        pop     rbp
 7        ret
 8auto getValue<int*>(int*):
 9        push    rbp
10        mov     rbp, rsp
11        mov     QWORD PTR [rbp-8], rdi
12        mov     rax, QWORD PTR [rbp-8]
13        mov     eax, DWORD PTR [rax]
14        pop     rbp
15        ret
16......

这里可以看到,生成的getValue<int>getValue<int*>两个版本的函数分别保留了对应类型的分支逻辑,而没有了条件判断。

至此我们对constexpr if的用法有了初步的认知,下面来通过元编程来加深对其的理解。

constexpr if 在元编程中的应用

说到元编程,我们就从元编程的"hello world"程序——计算阶乘开始,我们先写出一个不使用constexpr if的阶乘:

 1#include <iostream>
 2
 3template<int N> struct Factorial
 4{
 5    static constexpr int value = N * Factorial<N - 1>::value;
 6};
 7
 8template<> struct Factorial<1>
 9{
10    static constexpr int value = 1;
11};
12
13template<int N> inline constexpr int Factorial_v = Factorial<N>::value;
14
15int main(int argc, char* argv[])
16{
17    std::cout << Factorial_v<1> << std::endl;
18    std::cout << Factorial_v<2> << std::endl;
19    std::cout << Factorial_v<3> << std::endl;
20    std::cout << Factorial_v<4> << std::endl;
21    return 0;
22}

这段代码非常简单,没什么可解释的,这里之所以拿出来是为了将其使用constexpr if进行重写:

 1#include <iostream>
 2
 3template<int N> constexpr int factorial()
 4{
 5    if constexpr (N >= 2) {
 6        return N * factorial<N - 1>();
 7    } else {
 8        return N;
 9    }
10}
11
12int main(int argc, char* argv[])
13{
14    std::cout << factorial<1>() << std::endl;
15    std::cout << factorial<2>() << std::endl;
16    std::cout << factorial<3>() << std::endl;
17    std::cout << factorial<4>() << std::endl;
18    return 0;
19}

通过上面的改写,可以很容易发现,在不使用constexpr if的时候,我们需要额外的对Factorial模板类做特例化,来定义递归的结束位置。而有了constexpr if我们可以像写正常的函数那样写出能具有常量特性的函数,在编译期计算阶乘。

同样地,我们也可以很容易将fibonacci函数改造成constexpr if版本,这里就不再赘述了。