F-1 constexpr 函数
在第5.6课——constexpr 变量中,我们介绍了 constexpr 关键字,用于创建编译时(符号)常量。同时介绍了常量表达式,这类表达式可在编译时而非运行时求值。
常量表达式面临的挑战在于:普通函数调用不被允许出现在常量表达式中。这意味着在需要常量表达式的位置无法使用此类函数调用。
请看以下程序:
#include <iostream>
int main()
{
constexpr double radius { 3.0 };
constexpr double pi { 3.14159265359 };
constexpr double circumference { 2.0 * radius * pi };
std::cout << "Our circle has circumference " << circumference << "\n";
return 0;
}
运行结果为:

为周长变量设置复杂初始化器并不理想(且需额外实例化半径和π两个辅助变量)。因此我们改用函数实现:
#include <iostream>
double calcCircumference(double radius)
{
constexpr double pi { 3.14159265359 };
return 2.0 * pi * radius;
}
int main()
{
constexpr double circumference { calcCircumference(3.0) }; // compile error
std::cout << "Our circle has circumference " << circumference << "\n";
return 0;
}

此代码更简洁,但同样无法编译。constexpr变量circumference要求其初始化器为常量表达式,而calcCircumference()调用不符合该要求。
在此特殊情况下,可将周长设为非常量表达式(non-constexpr)使程序编译通过。虽然会失去常量表达式的优势,但至少程序能运行。
然而C++中存在其他情形(后续将介绍),此时我们别无选择,唯有使用常量表达式。此时我们虽渴望使用函数,但普通函数调用却行不通。该如何解决?
常量表达式中可使用 constexpr 函数
constexpr 函数constexpr function是允许在常量表达式中调用的函数。
只需在函数返回类型前添加 constexpr 关键字,即可使函数成为 constexpr 函数。
核心要点
constexpr 关键字用于向编译器及其他开发者表明该函数可在常量表达式中使用。
以下是使用 constexpr 函数的同等示例:
#include <iostream>
constexpr double calcCircumference(double radius) // now a constexpr function
{
constexpr double pi { 3.14159265359 };
return 2.0 * pi * radius;
}
int main()
{
constexpr double circumference { calcCircumference(3.0) }; // now compiles
std::cout << "Our circle has circumference " << circumference << "\n";
return 0;
}

由于 calcCircumference() 现已成为 constexpr 函数,它可用于常量表达式中,例如周长变量的初始化表达式。
constexpr 函数可在编译时求值
在第 5.5 节 常量表达式 中我们提到:当需要常量表达式(如初始化 constexpr 变量时),该常量表达式必须在编译时求值。若所需常量表达式包含 constexpr 函数调用,则该函数调用必须在编译时完成求值。
在上例中,变量 circumference 具有 constexpr 属性,因此需要常量表达式初始化器。由于 calcCircumference() 是该常量表达式的一部分,故必须在编译时进行求值。
当函数调用在编译时求值时,编译器会计算函数调用的返回值,然后用该返回值替换函数调用。
因此在我们的示例中,对 calcCircumference(3.0) 的调用被替换为函数调用的结果 18.8496。换言之,编译器将编译以下内容:
#include <iostream>
constexpr double calcCircumference(double radius)
{
constexpr double pi { 3.14159265359 };
return 2.0 * pi * radius;
}
int main()
{
constexpr double circumference { 18.8496 };
std::cout << "Our circle has circumference " << circumference << "\n";
return 0;
}
要在编译时进行求值,还必须满足另外两个条件:
- 对 constexpr 函数的调用必须具有在编译时已知的实参(例如常量表达式)。
- constexpr函数内的所有语句和表达式必须可在编译时求值。
当constexpr(或consteval)函数在编译时被求值时,其调用的任何其他函数也必须在编译时求值(否则初始函数将无法在编译时返回结果)。
对于进阶读者
还有一些较少遇到的其他条件。这些条件可在此处查阅。
constexpr 函数也可在运行时求值
constexpr 函数同样可在运行时求值,此时将返回非 constexpr 结果。例如:
#include <iostream>
constexpr int greater(int x, int y)
{
return (x > y ? x : y);
}
int main()
{
int x{ 5 }; // not constexpr
int y{ 6 }; // not constexpr
std::cout << greater(x, y) << " is greater!\n"; // will be evaluated at runtime
return 0;
}

在此示例中,由于实参 x 和 y 并非常量表达式,函数无法在编译时解析。但该函数仍将在运行时解析,并以非 constexpr int 类型返回预期值。
关键洞察
当constexpr 函数在运行时求值时,其行为与普通(non-constexpr)函数完全一致。换言之,此时常量表达式constexpr修饰符不产生任何影响。
关键洞察
允许返回类型为 constexpr 的函数在编译时或运行时求值,是为了使单个函数能同时满足两种场景。
否则就需要分别定义两个函数(一个返回 constexpr 类型的函数,一个返回非 constexpr 类型的函数)。这不仅会导致代码冗余,两个函数还必须使用不同名称!
请再次提醒我们为何要关注函数在编译时是否执行?
现在正是回顾编译时编程技术优势的好时机:5.5节——常量表达式。

浙公网安备 33010602011771号