É legal escrever algo assim?
#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);
Aqui, i
o inicializador -member primeiro inicializa j
o membro usando std::construct_at
e depois lê seu valor em j-1
.
Na prática, vejo que todos os GCC, MSVC e Clang aceitam o programa. Demonstração online: https://gcc.godbolt.org/z/YzEoPPj96
Mas Clang emite o aviso:
<source>:5:50: warning: field 'j' is uninitialized when used here [-Wuninitialized]
5 | constexpr A() : i((std::construct_at(&j, 2), j-1)) {}
| ^
Isso parece contraditório, já que a leitura de valores não inicializados em expressões constantes deve resultar em falha grave. O programa está bem formado e o diagnóstico está simplesmente errado?
E graças ao @TedLyngmo, aqui está um exemplo mais complicado com alocações de heap:
#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 );
Demonstração online: https://gcc.godbolt.org/z/zcb4hbhY3
De acordo com [specialized.construct] ,
std::construct_at(&j, 2)
é ±equivalente a::new (&j) int(2)
.this->j
O objeto que contém o objeto (a
) não está dentro do tempo de vida , portanto, o objeto recém-criado não éa
um subobjeto de , reutilizandoa
o armazenamento de sem estar aninhado nele. Eu diria que isso atualmente é " UB implícito ", portanto, pode-se afirmar que "Uma expressão E é uma expressão constante central, a menos que a avaliação de E ... avalie um dos seguintes: ... uma operação que teria comportamento indefinido, conforme especificado em [intro] a [cpp]" não se aplica.O CWG2757 visa tornar essa UB explícita. Pelo menos, sua intenção é clara. No entanto, sua resolução proposta (revisada pelo CWG em 20/10/2023) afirma erroneamente que a criação de um objeto aninhado dentro de algum objeto o não reutiliza o armazenamento de o , embora não termine seu tempo de vida. A criação de um objeto sempre reutiliza o armazenamento se ele foi usado no momento da criação.