
文章目录
-
- [1. constexpr 函数中使用非字面量变量、标号和 goto (P2242R3)](#1. constexpr 函数中使用非字面量变量、标号和 goto (P2242R3))
- [2. 允许 constexpr 函数中的常量表达式中使用 static 和 thread_local 变量 (P2647R1)](#2. 允许 constexpr 函数中的常量表达式中使用 static 和 thread_local 变量 (P2647R1))
- [3. constexpr 函数的返回类型和形参类型不必为字面类型 (P2448R2)](#3. constexpr 函数的返回类型和形参类型不必为字面类型 (P2448R2))
- [4. 不存在满足核心常量表达式要求的调用的 constexpr 函数 (P2448R2)](#4. 不存在满足核心常量表达式要求的调用的 constexpr 函数 (P2448R2))
- 总结表格
在 C++23 标准中, constexpr
特性迎来了一系列令人瞩目的改动,这些改动进一步提升了 C++ 的编译时计算能力和代码的灵活性。下面我们将详细介绍这些改动,并通过表格的形式进行总结。
1. constexpr 函数中使用非字面量变量、标号和 goto (P2242R3)
在 C++23 之前,constexpr
函数的使用受到较多限制,不能在其中使用非字面量变量、标号和 goto
语句。但在 C++23 中,这些限制被放宽了。这意味着在 constexpr
函数里,我们可以更自由地编写代码,利用非字面量变量进行计算,使用标号和 goto
语句实现复杂的控制流。
在过去,由于这些限制,一些看似合理的代码可能会被编译器拒绝。例如下面的代码:
cpp
template<typename T> constexpr bool f() {
if (std::is_constant_evaluated()) {
// ...
return true;
} else {
T t;
// ...
return true;
}
}
struct nonliteral { nonliteral(); };
static_assert(f<nonliteral>());
在之前的标准中,这段代码可能会因为 nonliteral
是一个非字面类型而导致编译失败,尽管导致失败的那一行代码并不在常量求值的上下文中。而在 C++23 中,这样的代码是有效的。
从编译器支持情况来看,GCC 12 和 Clang 15 开始支持这一改动。这一改动的原理是,只要这些非字面量变量、标号和 goto
语句在编译时不被求值,它们在函数中的存在就不会有问题。因为 constexpr
函数可能在编译时求值,也可能在运行时求值。如果我们想在 constexpr
函数中调用一段保证在编译时求值的代码,需要将这段代码放在 if consteval
或 if (std::is_constant_evaluated())
条件下的代码块中。
示例代码
cpp
#include <iostream>
constexpr int func(int x) {
int result = 0;
if (x > 0) {
result = x * 2;
} else {
// 使用标号和 goto
label:
result = -x;
}
return result;
}
int main() {
constexpr int value = func(5);
std::cout << "Result: " << value << std::endl;
return 0;
}
2. 允许 constexpr 函数中的常量表达式中使用 static 和 thread_local 变量 (P2647R1)
在 C++23 之前,constexpr
函数的常量表达式中不允许使用 static
和 thread_local
变量。C++23 打破了这个限制,允许在 constexpr
函数的常量表达式中使用这两种变量。这为编译时计算提供了更多的可能性,例如可以在编译时初始化一些静态变量或线程局部变量。
最初,constexpr
函数中根本不允许声明任何 static
变量。后来在 [P2242R3] 中有所放宽,规则改为控制流不能经过 static
变量的初始化。对于 static
(或者更糟糕的 thread_local
)变量,其初始化器可能会运行任意代码,所以之前有这样的限制是合理的。但对于 static constexpr
变量,根据定义,它必须是常量初始化的,不存在何时运行初始化的问题,它就是一个常量。
下面我们来看一个例子:
cpp
char xdigit(int n) {
static constexpr char digits[] = "0123456789abcdef";
return digits[n];
}
这个函数原本是完全没问题的,但当我们尝试将其扩展为在编译时也能工作时,就会遇到问题:
cpp
constexpr char xdigit(int n) {
static constexpr char digits[] = "0123456789abcdef";
return digits[n];
}
在之前的标准中,这段代码是格式错误的,但在 C++23 中,它是有效的。
在之前为了实现类似的功能,有几种变通方法,但都有各自的缺点。比如可以完全避开 static
变量,直接索引字面量,但这只在我们只需要使用一次时才有效:
cpp
constexpr char xdigit(int n) {
return "0123456789abcdef"[n];
}
也可以将 static
变量移到非局部作用域,但我们希望将其设为局部变量是有原因的,它只与这个特定的函数相关:
cpp
static constexpr char digits[] = "0123456789abcdef";
constexpr char xdigit(int n) {
return digits[n];
}
还可以将变量设为非 static
,但编译器很难对其进行优化,会导致代码生成效果变差:
cpp
constexpr char xdigit(int n) {
constexpr char digits[] = "0123456789abcdef";
return digits[n];
}
示例代码
cpp
#include <iostream>
constexpr int func() {
static int counter = 0;
counter++;
return counter;
}
int main() {
constexpr int value = func();
std::cout << "Counter value: " << value << std::endl;
return 0;
}
3. constexpr 函数的返回类型和形参类型不必为字面类型 (P2448R2)
在 C++23 之前,constexpr
函数的返回类型和形参类型必须是字面类型。C++23 放宽了这一要求,允许 constexpr
函数的返回类型和形参类型不必为字面类型。这使得 constexpr
函数的使用更加灵活,可以处理更多类型的数据。
示例代码
cpp
#include <iostream>
#include <string>
constexpr std::string func(const std::string& str) {
return str + " appended";
}
int main() {
constexpr std::string result = func("Hello");
std::cout << "Result: " << result << std::endl;
return 0;
}
4. 不存在满足核心常量表达式要求的调用的 constexpr 函数 (P2448R2)
在 C++23 中,对于那些不存在满足核心常量表达式要求的调用的 constexpr
函数,也有了新的处理方式。这使得在某些情况下,即使函数的调用不满足核心常量表达式的要求,函数仍然可以作为 constexpr
函数存在。
示例代码
cpp
#include <iostream>
constexpr int func(int x) {
if (x > 0) {
return x * 2;
} else {
// 这里的调用可能不满足核心常量表达式要求
return -x;
}
}
int main() {
int value = 5;
// 这里的调用可能不是常量表达式
int result = func(value);
std::cout << "Result: " << result << std::endl;
return 0;
}
总结表格
改动内容 | 提案编号 | 说明 |
---|---|---|
constexpr 函数中使用非字面量变量、标号和 goto | P2242R3 | 放宽了 constexpr 函数的使用限制,允许使用非字面量变量、标号和 goto 语句,只要在编译时不被求值即可,GCC 12 和 Clang 15 开始支持 |
允许 constexpr 函数中的常量表达式中使用 static 和 thread_local 变量 | P2647R1 | 打破了 constexpr 函数常量表达式中对 static 和 thread_local 变量的限制,之前 static 变量相关规则在 [P2242R3] 中有所调整,现在 static constexpr 变量在 constexpr 函数中使用更合理 |
constexpr 函数的返回类型和形参类型不必为字面类型 | P2448R2 | 使 constexpr 函数的使用更加灵活,可处理更多类型的数据 |
不存在满足核心常量表达式要求的调用的 constexpr 函数 | P2448R2 | 对于不满足核心常量表达式要求的调用的 constexpr 函数有了新的处理方式 |