constexpr 函数

《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 的初始化略显复杂,并且需要额外定义 radiuspi 两个辅助变量。于是,我们尝试将其改写成函数:

#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;
}

代码更简洁,却无法通过编译。原因在于 circumferenceconstexpr 变量,其初始化器必须是常量表达式,而 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 };

要在编译期求值,还须满足两个条件:

  1. 函数调用的实参必须在编译期已知(即均为常量表达式);
  2. 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;
}

此处 xy 不是常量表达式,函数无法在编译期求值,但仍可在运行期正确返回 int 结果。

关键要点 当 constexpr 函数在运行期求值时,其行为与普通函数完全一致;此时 constexpr 关键字不产生额外效果。

关键要点 允许带 constexpr 返回类型的函数同时在编译期或运行期求值,使得一个函数即可服务于两种场景。否则必须编写两份几乎相同的代码,并且还要取不同的名字!

为何关心函数是否在编译期执行?

此时正是回顾“编译期编程优势”的好时机,详见 5.5 课《常量表达式》。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"