O operador std :: unordered_map [] faz a inicialização zero para a chave não existente?

25

De acordo com cppreference.com, std::map::operator[]para valor inexistente, a inicialização é zero.

No entanto, o mesmo site não menciona inicialização zero std::unordered_map::operator[], exceto que possui um exemplo que depende disso.

Claro que este é apenas um site de referência, não o padrão. Então, o código abaixo está ok ou não?

#include <unordered_map>
int main() {
    std::unordered_map<int, int> map;
    return map[42];     // is this guaranteed to return 0?
}
hyde
fonte
13
@ Ælex você não pode testar de forma confiável se algo é inicializado
idclev 463035818
2
@ Alex Eu realmente não entendo, como você pode ter um não inicializado std::optional?
Idclev 463035818
2
@ Lex não há como testar se um objeto foi inicializado ou não, porque qualquer operação em um objeto não inicializado que não seja a inicialização resulta em Comportamento Indefinido. Um std::optionalobjeto que não contém valor contido ainda é um objeto inicializado.
Bolov #
2
O objeto de valor é inicializado por valor, não inicializado por zero. Para tipos escalares, eles são iguais, mas para tipos de classes são diferentes.
aschepler 6/12/19
@bolov Tentei testá-lo ontem usando o gnu 17 e o std 17, e estranhamente tudo que consegui foi a inicialização zero. Eu pensei que std::optional has_valueiria testá-lo, mas ele falha, então acho que você está correto.
Lexlex

Respostas:

13

Dependendo de qual sobrecarga estamos falando, std::unordered_map::operator[]é equivalente a [unord.map.elem]

T& operator[](const key_type& k)
{
    return try_­emplace(k).first->second;
}

(a sobrecarga tendo uma referência rvalue apenas se move kpara try_emplacee é de outro modo idêntica)

Se um elemento existir sob a chave kno mapa, try_emplaceretornará um iterador para esse elemento e false. Caso contrário, try_emplaceinsere um novo elemento sob a chave ke retorna um iterador para isso e true [unord.map.modifiers] :

template <class... Args>
pair<iterator, bool> try_emplace(const key_type& k, Args&&... args);

Interessante para nós é o caso de ainda não haver nenhum elemento [unord.map.modifiers] / 6 :

Caso contrário, insere um objeto do tipo value_­typeconstruído compiecewise_­construct, forward_­as_­tuple(k), forward_­as_­tuple(std​::​forward<Args>(args)...)

(a sobrecarga que toma uma referência rvalue apenas se move kpara forward_­as_­tuplee, novamente, é idêntica)

Como value_typeé um pair<const Key, T> [unord.map.overview] / 2 , isso nos diz que o novo elemento do mapa será construído como:

pair<const Key, T>(piecewise_­construct, forward_­as_­tuple(k), forward_­as_­tuple(std​::​forward<Args>(args)...));

Como argsestá vazio quando vem de operator[], isso se resume ao nosso novo valor sendo construído como membro dos pairargumentos from from [pairs.pair] / 14, que é a inicialização direta [class.base.init] / 7 de um valor do tipo Tusing ()como inicializador, que se resume à inicialização de valor [dcl.init] /17.4 . A inicialização do valor de um inté inicialização zero [dcl.init] / 8 . E a inicialização zero de um intnaturalmente inicializa isso intpara 0 [dcl.init] / 6 .

Então, sim, seu código está garantido para retornar 0…

Michael Kenzel
fonte
21

No site que você vinculou, diz:

Quando o alocador padrão é usado, isso resulta na cópia da chave construída a partir da chave e no valor mapeado sendo inicializado pelo valor.

Portanto, o valorint é inicializado :

Os efeitos da inicialização do valor são:

[...]

4) caso contrário, o objeto será inicializado com zero

É por isso que o resultado é 0.

Chama
fonte