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

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

#include <iostream>
#include <type_traits>

template<typename T> auto getValue(T t)
{
    if constexpr (std::is_pointer<T>::value) {
        return *t;
    } else {
        return t;
    }
}

int main(int argc, char* argv[])
{
    int  a = 10;
    int* b = &a;
    getValue(a);
    getValue(b);
    return 0;
}

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

auto getValue<int>(int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     eax, DWORD PTR [rbp-4]
        pop     rbp
        ret
auto getValue<int*>(int*):
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     rax, QWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rax]
        pop     rbp
        ret
......

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

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

constexpr if 在元编程中的应用

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

#include <iostream>

template<int N> struct Factorial
{
    static constexpr int value = N * Factorial<N - 1>::value;
};

template<> struct Factorial<1>
{
    static constexpr int value = 1;
};

template<int N> inline constexpr int Factorial_v = Factorial<N>::value;

int main(int argc, char* argv[])
{
    std::cout << Factorial_v<1> << std::endl;
    std::cout << Factorial_v<2> << std::endl;
    std::cout << Factorial_v<3> << std::endl;
    std::cout << Factorial_v<4> << std::endl;
    return 0;
}

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

#include <iostream>

template<int N> constexpr int factorial()
{
    if constexpr (N >= 2) {
        return N * factorial<N - 1>();
    } else {
        return N;
    }
}

int main(int argc, char* argv[])
{
    std::cout << factorial<1>() << std::endl;
    std::cout << factorial<2>() << std::endl;
    std::cout << factorial<3>() << std::endl;
    std::cout << factorial<4>() << std::endl;
    return 0;
}

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

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