《constexpr 变量》中,我们引入了关键字 constexpr
,并用其创建编译期(符号)常量。我们还介绍了“常量表达式”(constant expressions),即可以在编译期而非运行期求值的表达式。
常量表达式的一大限制是:不允许在其中调用普通函数。这意味着任何需要常量表达式的地方都不能出现普通函数调用。
请看以下程序:
#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;
}
输出:
Our circle has circumference 18.8496
然而,circumference
的初始化略显复杂,并且需要额外定义 radius
与 pi
两个辅助变量。于是,我们尝试将其改写成函数:
#include <iostream>
double calcCircumference(double radius)
{
constexpr double pi{ 3.14159265359 };
return 2.0 * pi * radius;
}
int main()
{
constexpr double circumference{ calcCircumference(3.0) }; // 编译错误
std::cout << "Our circle has circumference " << circumference << "\n";
return 0;
}
代码更简洁,却无法通过编译。原因在于 circumference
为 constexpr
变量,其初始化器必须是常量表达式,而 calcCircumference()
的调用不是常量表达式。
在此例中,我们可以把 circumference
改为非 constexpr
,程序即可编译。虽然失去了常量表达式的优势,至少程序能够运行。
然而,C++ 中还存在某些场景(后续课程会介绍)别无选择,只能用常量表达式。此时,我们迫切需要能在常量表达式中调用的函数,而普通函数调用无法满足。怎么办?
constexpr 函数可用于常量表达式
constexpr
函数是允许在常量表达式中被调用的函数。
只需在函数返回类型前加上关键字 constexpr
即可将其声明为 constexpr
函数。
关键要点
constexpr
关键字向编译器及开发者表明:该函数可在常量表达式中使用。
将上例改写为 constexpr
函数:
#include <iostream>
constexpr double calcCircumference(double radius) // 现在是 constexpr 函数
{
constexpr double pi{ 3.14159265359 };
return 2.0 * pi * radius;
}
int main()
{
constexpr double circumference{ calcCircumference(3.0) }; // 可编译
std::cout << "Our circle has circumference " << circumference << "\n";
return 0;
}
由于 calcCircumference()
为 constexpr
函数,它可以在常量表达式中使用,如 circumference
的初始化。
constexpr 函数可在编译期求值
在 5.5 课《常量表达式》中我们指出:凡是要求常量表达式的上下文(例如 constexpr
变量的初始化),常量表达式必须在编译期求值。若所需常量表达式中包含 constexpr
函数调用,则该函数调用必须在编译期完成求值。
当函数调用在编译期求值时,编译器会计算其返回值并用该值替换函数调用。
因此,上例中的 calcCircumference(3.0)
被替换为 18.8496
。编译器实际生成的代码等效于:
constexpr double circumference{ 18.8496 };
要在编译期求值,还须满足两个条件:
- 函数调用的实参必须在编译期已知(即均为常量表达式);
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 }; // 非 constexpr
int y{ 6 }; // 非 constexpr
std::cout << greater(x, y) << " is greater!\n"; // 运行期求值
return 0;
}
此处 x
与 y
不是常量表达式,函数无法在编译期求值,但仍可在运行期正确返回 int
结果。
关键要点
当 constexpr
函数在运行期求值时,其行为与普通函数完全一致;此时 constexpr
关键字不产生额外效果。
关键要点
允许带 constexpr
返回类型的函数同时在编译期或运行期求值,使得一个函数即可服务于两种场景。否则必须编写两份几乎相同的代码,并且还要取不同的名字!
为何关心函数是否在编译期执行?
此时正是回顾“编译期编程优势”的好时机,详见 5.5 课《常量表达式》。