5-6 常量表达式变量

在上一节5.5——常量表达式中,我们定义了常量表达式的含义,探讨了使用常量表达式的优势,并总结了常量表达式在编译时实际求值的时机。

本节课我们将深入探讨如何在现代C++中创建可用于常量表达式的变量,同时探索确保代码在编译时实际执行的第一种方法。


编译时常量挑战

在上一课中,我们提到创建可用于常量表达式的变量的一种方法是使用 const 关键字。具有整数类型且初始化器为常量表达式的 const 变量可用于常量表达式。其他所有 const 变量均不能用于常量表达式。

然而,使用 const 创建可用于常量表达式的变量存在若干挑战。

首先,使用 const 并不能立即明确该变量是否适用于常量表达式。某些情况下我们能较容易地判断:

int a { 5 };       // not const at all
const int b { a }; // clearly not a constant expression (since initializer is non-const)
const int c { 5 }; // clearly a constant expression (since initializer is a constant expression)

在其他情况下,这可能相当困难:

const int d { someVar };    // not obvious whether d is usable in a constant expression or not
const int e { getValue() }; // not obvious whether e is usable in a constant expression or not

在上例中,变量 d 和 e 能否用于常量表达式取决于 someVar 和 getValue() 的定义方式。这意味着我们必须检查这些初始化的定义来推断具体情况。即便如此仍可能不够——若 someVar 是 const 类型且通过变量或函数调用初始化,我们还需检查其初始化的定义!

其次,const的使用无法告知编译器我们需要一个可在常量表达式中使用的变量(若无法满足则应终止编译)。相反,它只会默默创建一个仅限于运行时表达式使用的变量。

第三,使用 const 创建编译时常量变量并不适用于非整型变量。而实际中存在大量场景需要将非整型变量也作为编译时常量处理。


constexpr 关键字

幸运的是,我们可以借助编译器的帮助,确保在需要时获得编译时常量变量。为此,我们在变量声明中使用 constexpr 关键字(即“常量表达式”的缩写)代替 const。constexpr变量始终是编译时常量。因此,constexpr 变量必须使用常量表达式初始化,否则将导致编译错误。

例如:

#include <iostream>

// The return value of a non-constexpr function is not constexpr
int five()
{
    return 5;
}

int main()
{
    constexpr double gravity { 9.8 }; // ok: 9.8 is a constant expression
    constexpr int sum { 4 + 5 };      // ok: 4 + 5 is a constant expression
    constexpr int something { sum };  // ok: sum is a constant expression

    std::cout << "Enter your age: ";
    int age{};
    std::cin >> age;

    constexpr int myAge { age };      // compile error: age is not a constant expression
    constexpr int f { five() };       // compile error: return value of five() is not constexpr

    return 0;
}

image

由于函数通常在运行时执行,其返回值并非常量表达式(即使返回表达式本身是常量表达式)。因此 five() 不能作为 constexpr int f 的合法初始化值。

相关内容
我们在 F.1 节——常量表达式函数中讨论了返回值可用于常量表达式的函数。

此外,constexpr 同样适用于非整数类型的变量:

constexpr double d { 1.2 }; // d can be used in constant expressions!

const 与 constexpr 在变量中的含义

对于变量:

  • const 表示对象的值在初始化后不可更改。初始化器的值可能在编译时或运行时已知。const 对象可在运行时求值。
  • constexpr 表示该对象可用于常量表达式。初始化器的值必须在编译时已知。constexpr 对象可在运行时或编译时求值。

constexpr 变量隐式具有 const 属性。const 变量不隐式具有 constexpr 属性(除初始化表达式为常量的 const 整型变量外)。虽然变量可同时定义为 constexpr 和 const,但在多数情况下这是冗余的,只需使用其中一种即可。

与const不同,constexpr不属于对象的类型属性。因此声明为constexpr int的变量实际类型为const int(因constexpr为对象隐式添加const属性)。

最佳实践

初始化表达式为常量表达式的常量变量应声明为constexpr。

初始化表达式非常量表达式(即运行时常量)的常量变量应声明为const。

注意事项:后续将讨论部分与 constexpr 不完全兼容的类型(包括 std::string、std::vector 及其他使用动态内存分配的类型)。对于此类型的常量对象,请使用 const 替代 constexpr,或选择兼容 constexpr 的替代类型(如 std::string_view 或 std::array)。

术语说明

constexpr 是“常量表达式”的组合词。该名称的选用源于 constexpr 对象(及函数)可用于常量表达式中。

严格而言,constexpr 关键字仅适用于对象和函数。惯例上,constexpr 一词常作为任何常量表达式(如 1 + 2)的简称。

作者注
本站部分示例编写于 constexpr 最佳实践出台之前,因此您会发现某些示例未遵循上述规范。我们正在逐步更新不符合规范的示例。

对于进阶读者

在 C 和 C++ 中,数组对象(可存储多个值的对象)的声明要求数组长度(可容纳的值的数量)在编译时已知(以便编译器确保为数组对象分配正确内存)。

由于字面量在编译时已知,因此可将其用作数组长度:

int arr[5]; // an array of 5 int values, length of 5 is known at compile-time

在许多情况下,使用符号常量作为数组长度更为理想(例如避免魔数,并在数组长度被多处使用时便于修改)。在C语言中,这可通过预处理器宏或枚举器实现,但无法使用常量变量(排除具有其他弊端的可变长度数组)。C++为改进这一情况,试图允许使用const变量替代宏。但变量值通常被认为仅在运行时已知,这使得它们无法充当数组长度。

为解决此问题,C++语言标准新增了例外条款:当const整数类型使用常量表达式初始化时,其值将被视为编译时已知,从而可用于数组长度:

const int arrLen = 5;
int arr[arrLen]; // ok: array of 5 ints

当C++11引入常量表达式时,将常量表达式初始化的const int纳入该定义具有合理性。委员会曾讨论是否应将其他类型也纳入其中,但最终决定不予采纳。


常量(Const)和常量表达式函数形参(constexpr function parameters)

常规函数调用在运行时求值,传入的实参用于初始化函数形参。由于函数形参的初始化发生在运行时,这导致两个后果:

  1. 常量函数形参const function parameters被视为运行时常量runtime constants(即使传入的实参是编译时常量)。
  2. 函数形参无法声明为 constexpr,因为其初始化值直至运行时才确定。

相关内容

我们将在下文讨论可在编译时求值(从而用于常量表达式)的函数。
C++ 还支持向函数传递编译时常量的方法,相关内容详见第 11.9 节——非类型模板参数。


命名法回顾

Term Definition
Compile-time constant A value or non-modifiable object whose value must be known at compile time (e.g. literals and constexpr variables).
Constexpr Keyword that declares objects as compile-time constants (and functions that can be evaluated at compile-time). Informally, shorthand for “constant expression”.
Constant expression An expression that contains only compile-time constants and operators/functions that support compile-time evaluation.
Runtime expression An expression that is not a constant expression.
Runtime constant A value or non-modifiable object that is not a compile-time constant.

constexpr 函数简介

constexpr 函数是指可在常量表达式中调用的函数。当包含该函数的常量表达式必须在编译时求值时(例如在 constexpr 变量的初始化器中),该函数必须在编译时完成求值。否则,constexpr 函数可在编译时(若符合条件)或运行时求值。要满足编译时执行的条件,所有参数必须是常量表达式。

定义 constexpr 函数时,需在函数声明的返回类型前添加 constexpr 关键字:

#include <iostream>

int max(int x, int y) // this is a non-constexpr function
{
    if (x > y)
        return x;
    else
        return y;
}

constexpr int cmax(int x, int y) // this is a constexpr function
{
    if (x > y)
        return x;
    else
        return y;
}

int main()
{
    int m1 { max(5, 6) };            // ok
    const int m2 { max(5, 6) };      // ok
    constexpr int m3 { max(5, 6) };  // compile error: max(5, 6) not a constant expression

    int m4 { cmax(5, 6) };           // ok: may evaluate at compile-time or runtime
    const int m5 { cmax(5, 6) };     // ok: may evaluate at compile-time or runtime
    constexpr int m6 { cmax(5, 6) }; // okay: must evaluate at compile-time

    return 0;
}

image

作者注

本章原计划详细讨论constexpr函数,但读者反馈表明该主题内容过长且涉及细节,不宜在本教程系列早期呈现。因此,我们已将完整讨论移至F.1课——constexpr函数。

本节导论的核心要点在于:constexpr函数可在常量表达式中被调用。

后续示例中将适时出现 constexpr 函数的应用,但在正式讲解该主题前,我们不会要求您深入理解或自行编写此类函数。

posted @ 2026-02-17 06:48  游翔  阅读(0)  评论(0)    收藏  举报