C++26:标准库中更多的 `constexpr` 特性
Sandor Dargo's Blog 关于 C++、软件开发和书籍
C++26:标准库中更多的 constexpr
特性
Sandor Dargo 发表于 2025年4月29日 2025-04-30T00:00:00+02:00,预计阅读时间 5 分钟
上周,我们讨论了C++26 中变为 constexpr
的语言特性。今天,让我们关注标准库中即将可以在编译时使用的特性。有一个主题缺失了:异常。由于它们需要核心语言和库的更改,我认为它们值得单独写一篇文章。
P2562R1:constexpr
稳定排序
该提案建议使 std::stable_sort
、std::stable_partition
、std::inplace_merge
及其 ranges
版本可以在常量表达式中使用。 虽然多年来许多算法已经变为 constexpr
,但与稳定排序相关的这一系列算法一直是个例外——直到现在。
最近引入的 constexpr
容器为该提案提供了额外的动力。 如果你可以在编译时构造一个容器,那么很自然地也希望在那里对其进行排序。 更重要的是,一个 constexpr std::vector
现在可以支持高效、稳定的排序算法。
一个关键问题是,该算法是否能在常量求值(constant evaluation)的约束下满足其计算复杂度要求。 幸运的是,std::is_constant_evaluated()
为实现提供了一个转义出口。 更多细节,请查看提案本身。
P1383R2:<cmath>
和 <complex>
中更多的 constexpr
虽然 P0533 使许多 <cmath>
和 <cstdlib>
函数在 C++23 中对 constexpr
更加友好,但它只解决了行为简单的函数——那些不比基本算术运算符更复杂的函数。
浮点计算可能会根据编译器设置、优化级别和硬件平台产生不同的结果。 例如,由于在此类规模下浮点运算的复杂性,计算 std::sin(1e100)
可能会产生不同的结果。 该论文讨论了这些挑战,并认为考虑到浮点计算的性质,结果中的一些可变性是可以接受的。
该提案 接受了严格确定性和实际灵活性之间需要平衡。 它建议,虽然某些函数应该在不同平台上产生一致的结果,但其他函数可能本质上允许一定的可变性。
P3074R7:trivial union
s (was std::uninitialized<T>
)
要实现静态的、就地的、对 constexpr
友好的容器(如非分配向量),你通常需要未初始化的存储空间——通常通过 union
。 但是,union 的特殊成员的默认行为受到了限制:如果不是所有备选项都是 trivial 的,则会删除该特殊成员。 这对于 constexpr
代码来说是个问题,因为 no-op 析构函数与 trivial 析构函数并不完全相同。
解决这个问题的道路并不短:P3074R7 经历了七次修订,并考虑了五种可能的解决方案——包括基于库的方法、新的注解,甚至是新的 union 类型。 最终,委员会决定通过对用户体验进行最小的更改来_使其工作_。
但是如何做到的呢?
对于 unions,默认构造函数(如果没有默认成员初始化器)始终是 trivial 的。 如果第一个备选项是隐式生命周期类型,则它会开始其生命周期并成为活动成员。
如果 union 具有用户提供的默认构造函数,或者存在具有默认成员初始化器的变体备选项,并且该成员的析构函数被删除或无法访问,则默认析构函数将被删除。 否则,析构函数是 trivial 的。
以下是提案中的摘录,很好地展示了这些更改。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
```
| ```
// trivial default constructor (does not start lifetime of s)
// trivial destructor
// (status quo: deleted default constructor and destructor)
union U1 { string s; };
// non-trivial default constructor
// deleted destructor
// (status quo: deleted destructor)
union U2 { string s = "hello"; }
// trivial default constructor
// starts lifetime of s
// trivial destructor
// (status quo: deleted default constructor and destructor)
union U3 { string s[10]; }
// non-trivial default constructor (initializes next)
// trivial destructor
// (status quo: deleted destructor)
union U4 { string s; U4* next = nullptr; };
```
---|---
`
## [P3372R2](https://www.sandordargo.com/blog/2025/04/30/<https:/www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3372r2.html>):`constexpr` 容器和适配器
Hana Dusíková 撰写了[一份庞大的提案](https://www.sandordargo.com/blog/2025/04/30/<https:/www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3372r2.html>),可以归结为一个简单的目标:使(几乎)所有容器和适配器都为 `constexpr`。
到目前为止,只有少数几个是 `constexpr` 友好的(`std::vector`、`std::span`、`std::mdspan`、`std::basic_string` 和 `std::basic_string_view`)。 从现在开始,情况将发生转变。 几乎所有东西都将是 `constexpr` 友好的。 有一个例外和一个约束:
* 不包括 `std::hive`,因为它还没有稳定的措辞
* 如果你想在编译时使用无序容器,你必须提供你自己的哈希设施,因为由于其要求,`std::hash` 不能成为 `constexpr` 友好的。 它的结果仅保证在程序的持续时间内保持一致。
美好的一天!
## [P3508R0](https://www.sandordargo.com/blog/2025/04/30/<www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3508r0.html>):为“`constexpr` for specialized memory algorithms”的措辞
如此奇怪的标题,不是吗? _某些内容_的措辞……
事实证明,已经有一篇论文被接受([P2283R2](https://www.sandordargo.com/blog/2025/04/30/<https:/www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2283r2.pdf>)),该论文使专用内存算法成为 `constexpr` 友好的。 这些算法对于实现 `constexpr` 容器支持至关重要,但在 C++20 中被遗忘了。
这些算法是(在 `std` 和 `std::ranges` 命名空间中):
* `uninitialized_value_construct`
* `uninitialized_value_construct_n`
* `uninitialized_copy`
* `uninitialized_copy_result`
* `uninitialized_copy_n`
* `uninitialized_copy_n_result`
* `uninitialized_move`
* `uninitialized_move_result`
* `uninitialized_move_n`
* `uninitialized_move_n_result`
* `uninitialized_fill`
* `uninitialized_fill_n`
当这篇论文发表时,必要的实现更改是使用 `std::construct_at` 代替 _placement new_,因为 `std::consturct_at` 已经是 `constexpr`。 但与此同时,[P2747R2](https://www.sandordargo.com/blog/2025/04/30/<https:/www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2747r2.html>) 被接受,并且核心语言中的 _placement new_ 也变成了 `constexpr`。 因此,不必更改上述函数的实现,只需更新其签名以支持 `constexpr` 即可。 因此,需要更改措辞。
## [P3369R0](https://www.sandordargo.com/blog/2025/04/30/<www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3369r0.html>):`constexpr` for `uninitialized_default_construct`
我们看到 `constexpr` _placement new_ 影响了 [P2283R2](https://www.sandordargo.com/blog/2025/04/30/<https:/www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2283r2.pdf>) 并引发了在 [P3508R0](https://www.sandordargo.com/blog/2025/04/30/<www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3508r0.html>) 中执行的措辞更改的需要。 但这并不是它唯一的副作用。 从上面列出的算法系列中,缺少一个:`uninitialized_default_construct`。 原因是 `uninitialized_default_construct` 无法使用 `std::construct_at` 实现,因为它总是执行值初始化,默认初始化是不可能的。
但是有了 `constexpr` _placement new_,这不再是一个问题,因此 `uninitialized_default_construct` 也可以转换为 `constexpr`。
## 结论
C++26 标志着标准库中 `constexpr` 支持向前迈出了一大步。 从稳定排序算法到容器,从棘手的 union 规则到专用内存函数,编译时编程正变得越来越受支持。
在下一篇文章中,我们将介绍编译时异常!
## 深入连接
如果你喜欢这篇文章,请
* 点击喜欢按钮,
* [订阅我的新闻通讯](https://www.sandordargo.com/blog/2025/04/30/<http:/eepurl.com/gvcv1j>)
* 并在 [Twitter](https://www.sandordargo.com/blog/2025/04/30/<https:/twitter.com/SandorDargo>) 上与我联系!
[](https://www.sandordargo.com/blog/2025/04/30/<https:/www.patreon.com/sandordargo>)
[dev](https://www.sandordargo.com/blog/2025/04/30/</categories/dev/>)
[cpp](https://www.sandordargo.com/blog/2025/04/30/</tags/cpp/>) [cpp26](https://www.sandordargo.com/blog/2025/04/30/</tags/cpp26/>) [constexpr](https://www.sandordargo.com/blog/2025/04/30/</tags/constexpr/>) [standardlibrary](https://www.sandordargo.com/blog/2025/04/30/</tags/standardlibrary/>)
本文由作者根据 [CC BY 4.0](https://www.sandordargo.com/blog/2025/04/30/<https:/creativecommons.org/licenses/by/4.0/>) 许可发布。