Quero criar uma função clear
tal que, ao chamá-la assim clear(v)
, substitua v
por um valor construído padrão do mesmo tipo (que é uma inicialização zero para tipos internos). A solução mais simples v = {}
não funciona para arrays porque você não pode atribuir a ela, e então acabo fazendo isso:
template<class T>
void clear(T& v) noexcept
{
if constexpr (std::is_array_v<T>) {
using base_t = std::remove_all_extents_t<T>;
if constexpr (std::is_trivially_default_constructible_v<base_t>)
std::memset(v, 0, sizeof(v));
else {
// To deal with multidimensional arrays, I make sure to get pointers
// the first and last actual value of the array.
base_t* it = reinterpret_cast<base_t*>(v);
base_t* end = it + sizeof(v) / sizeof(base_t);
for (; it != end; ++it)
clear(*it);
}
} else
v = {};
}
Gostaria de saber sobre potenciais armadilhas sobre essa implementação, especificamente em relação a regras de aliasing e alinhamento. Acho que a implementação está OK, mas não tenho certeza. A verificação para o tipo base do construtor trivial é porque tenho que ter certeza de que o construtor padrão do tipo não adiciona valores de inicialização personalizados para seu membro na lista de inicialização ou por meio de um inicializador padrão. Em outras palavras, que para esse tipo inicializá-lo com zero e inicializá-lo com valor padrão são sinônimos.
Mas a implementação parece muito mais complicada do que deveria, e também prefiro que o compilador cuide de tudo isso. Só quero consertar o problema de arrays não serem atribuíveis, então acabei fazendo isso:
template<class T>
void clear(T& v) noexcept
{
struct helper { T v; };
*reinterpret_cast<helper*>(&v) = {};
}
e quero saber também sobre as potenciais armadilhas dessa abordagem.
A primeira implementação é pedantemente UB para matrizes multidimensionais, a
T[M][N]
não é acessível como aT[M * N]
.A segunda implementação tem comportamento indefinido. Não há um
helper
naquele endereço, então você violou aliasing estrito.Uma maneira definida de fazer o que você quer (pós C++20) é
Isso ainda tem ressalvas. O tipo deve ser nothrow default constructable, nothrow destructible para não cair em conflito com o
noexcept
, e o objeto não deve ser potencialmente sobreposto (por exemplo, um subobjeto base). Ponteiros e referências ao objeto anterior agora são inválidos, você só pode acessá-lo por meio da referência retornada.Antes disso, você pode fazer backport deles. Nb C++17 tem
std::destroy_at
, mas é indefinido para arrays.Alternativamente, você pode simplesmente copiar a estrutura do
destroy_at
backportIsso não é exatamente o mesmo que a versão
destroy_at
/construct_at
, pois requeroperator=
, que pode não existir para um tipo, ou pode fazer algo diferente do construtor padrão.