写这样的内容合法吗?
#include <memory>
struct A {
int i, j;
constexpr A() : i((std::construct_at(&j, 2), j-1)) {}
};
constexpr A a{};
static_assert(a.i == 1);
static_assert(a.j == 2);
这里,i
-member 初始化器首先j
使用 初始化成员std::construct_at
,然后在 中读取其值j-1
。
实践中我发现 GCC、MSVC 和 Clang 都能接受这个程序。在线演示:https://gcc.godbolt.org/z/YzEoPPj96
但 Clang 发出警告:
<source>:5:50: warning: field 'j' is uninitialized when used here [-Wuninitialized]
5 | constexpr A() : i((std::construct_at(&j, 2), j-1)) {}
| ^
这看起来自相矛盾,因为读取常量表达式中未初始化的值必然会导致硬失败。程序是否格式正确,只是诊断错误?
感谢@TedLyngmo,这里有一个更复杂的堆分配示例:
#include <string>
struct A {
std::string i, j;
constexpr A()
: i(((void)std::construct_at(&j,
"Hello world, this is very funny indeed "
"and this is a long string"),
j + " with some exta in it"))
, j([k=std::move(j)]()mutable { return std::move(k); }()) {}
};
static_assert( A{}.i.length() == 85 );
static_assert( A{}.j.length() == 64 );
根据[specialized.construct],
std::construct_at(&j, 2)
±等价于::new (&j) int(2)
。this->j
的包含对象 (a
) 不在生命周期 内,因此新创建的对象不是a
的子对象,因此它会重用a
的存储空间,而不会嵌套在其中。我认为这目前是“隐式不明代码”,因此可以断言“表达式E是核心常量表达式,除非E的求值……会得出以下结果之一:……根据[intro] 至 [cpp] 中的说明,操作将具有未定义行为”,但这并不适用。CWG2757旨在明确此类 UB。至少,其意图是明确的。尽管其拟议解决方案(由 CWG 2023-10-20 审核)错误地声称,创建嵌套在某个对象o中的对象不会重用o的存储空间,而只是不会终止其生命周期。如果在创建时使用了存储空间,则创建对象始终会重用存储空间。