C++26:核心语言中更多 constexpr
C++26: more constexpr in the core language

原始链接: https://www.sandordargo.com/blog/2025/04/23/cpp26-constexpr-language-changes

C++26 对 `constexpr` 做了若干增强,扩展了其在核心语言中的能力。P2738R1 允许在常量表达式中将 `void*` 转换为 `T` 类型指针,前提是该地址上的对象恰好是 `T` 类型。这增强了标准库组件对 `constexpr` 的支持,这些组件使用了 `void*` 编译防火墙技术。P2747R2 引入了 `constexpr` placement new,解决了 `std::construct_at` 的限制,并利用了新的 `void*` 转换能力。P2686R5 允许 `constexpr` 结构化绑定,并放宽了 `constexpr` 引用上的限制。`constexpr` 引用以前仅限于具有静态存储期的变量,现在如果它们的地址相对于栈帧是常量,则可以绑定到自动存储期变量。这些变化共同扩展了 C++26 中 `constexpr` 的范围,实现了更多编译时计算,并改进了标准库的支持。

Hacker News 的讨论围绕着 C++ 标准的快速发展及其对编译器实现和采用的影响展开。许多评论者担心标准发展速度过快,C++20 尚未在主要编译器中完全实现,而 C++23 更是基本未实现。一些人建议回归到标准化现有实践或要求在标准化之前完成实现。 一个关键点是 C++ 标准委员会 WG21 的转变,更多成员推动新特性加入。一些评论者指出,虽然像 `constexpr` 和反射这样的新特性受到欢迎,但可移植的并行 STL 或跨编译器的稳定支持等基本方面仍然缺乏。D 语言等替代语言被提及,它们拥有 C++ 难以实现的特性。普遍认为,目前的努力过于关注具体的实现,而不是整体的改进。一些帖子表达了对编译器开发由于投资减少而放缓的担忧。还有一些讨论集中在不断修改是否是为了修复先前版本引入的问题。

原文

Since constexpr was added to the language in C++11, its scope has been gradually expanded. In the beginning, we couldn’t even use if, else or loops, which were changed in C++14. C++17 added support for constexpr lambdas. C++20 added the ability to use allocation and use std::vector and std::string in constant expressions. In this article, let’s see how constexpr evolves with C++26. To be more punctual, let’s see what language features become more constexpr-friendly. We’ll discuss library changes in a separate article, as well as constexpr exceptions, which need both language and library changes.

P2738R1: constexpr cast from void*

Thanks to the acceptance of P2738R1, starting from C++26, one can cast from void* to a pointer of type T in constant expressions, if the type of the object at that adress is exactly the type of T.

Note that conversions to interconvertible - including pointers to base classes - or not related types are not permitted.

The motivation behind this change is to make several standard library functions or types work at compile time. To name a few examples: std::format, std::function, std::function_ref, std::any. The reason why this change will allow many more for more constexpr in the standard library is that storing void* is a commonly used compilation firewall technique to reduce template instantiations and the number of symbols in compiled binaries.

P2747R2: constexpr placement new

As std::construct_at is a limited tool that only allows to perform value initialization but not others such as default or list initialization, there has been a need to make placement new usable in constant expressions.

At the same time, placement new is a very, maybe even too flexible tool and to use it in a safe way requires casting to void* and then back to T*. This faced some issues, but the acceptance of P2738R1 and the ability of casting from void* in constant expressions made the impossible possible.

If you are looking for more details, check P2747R2.

P2686R5: constexpr structured bindings and references to constexpr variables

This is a rather long (20 pages) proposal and I found it not particularly easy to read. That’s not the fault of the authors, the problem is hard to address. The paper which is based on another, went through 5 revisions, discusses various solutions, and lists the wording changes on more than 10 pages.

Long story short, you’ll be able to declare structured bindings constexpr.

As structured bindings behave like references, the same restrictions apply as to constexpr references. Those restrictions become more relaxed. Before, a constexpr reference had to bind to a variable with static storage duration, so that the address doesn’t change from one evaluation to another. With C++26, in addition, variables with automatic storage duration are also accepted if and only if the address is constant relative to the stack frame in which the reference or the structured binding lives.

In practice, this means that you cannot have a constexpr reference in a lambda to bind to an enclosing function. The reason is that in order to access that variable, the expression is something like this->__x where __x represents the captured address of x. As we don’t know at compile time what object this points to, it’s not a constant expression.

Conclusion

In this article, we reviewed how constexpr evolves in the C++26 core language. We are getting constexpr cast from void*, placement new, structured bindings and even exceptions (not discussed today). In the next article, we’ll see how the standard library’s constexpr support evolves.

Connect deeper

If you liked this article, please

联系我们 contact @ memedata.com