Sandor Dargo's Blog 关于 C++、软件开发和书籍

C++26:标准库中更多的 constexpr 特性

Sandor Dargo 发表于 2025年4月29日 2025-04-30T00:00:00+02:00,预计阅读时间 5 分钟

上周,我们讨论了C++26 中变为 constexpr 的语言特性。今天,让我们关注标准库中即将可以在编译时使用的特性。有一个主题缺失了:异常。由于它们需要核心语言和库的更改,我认为它们值得单独写一篇文章。

P2562R1constexpr 稳定排序

该提案建议使 std::stable_sortstd::stable_partitionstd::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 unions (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/cpp26-constexpr-library-changes)](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/>) 许可发布。