¿El operador std :: unordered_map [] realiza una inicialización cero para la clave no existente?

25

Según cppreference.com, std::map::operator[]para el valor no existente, la inicialización cero.

Sin embargo, el mismo sitio no menciona la inicialización cero para std::unordered_map::operator[], excepto que tiene un ejemplo que se basa en esto.

Por supuesto, este es solo un sitio de referencia, no el estándar. Entonces, ¿el código a continuación está bien o no?

#include <unordered_map>
int main() {
    std::unordered_map<int, int> map;
    return map[42];     // is this guaranteed to return 0?
}
Hyde
fuente
13
@ Ælex no puede probar de forma confiable si algo se inicializa
idclev 463035818
2
@ Ælex Realmente no entiendo, ¿cómo puede tener un no inicializado std::optional?
idclev 463035818
2
@ Ælex no hay forma de probar si un objeto se inicializa o no porque cualquier operación en un objeto no inicializado que no sea la inicialización da como resultado un comportamiento indefinido. Un std::optionalobjeto que no contiene ningún valor contenido sigue siendo un objeto inicializado.
bolov
2
El objeto de valor tiene valor inicializado, no cero inicializado. Para los tipos escalares son los mismos, pero para los tipos de clase son diferentes.
aschepler
@bolov Intenté probarlo ayer usando gnu 17 y std 17, y extrañamente todo lo que obtuve fue cero inicialización. Pensé en std::optional has_valueprobarlo, pero falla, así que supongo que tienes razón.
Ælex

Respuestas:

13

Dependiendo de qué sobrecarga estamos hablando, std::unordered_map::operator[]es equivalente a [unord.map.elem]

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

(la sobrecarga de tomar un valor p-referencia sólo se mueve ken try_emplacey es por lo demás idéntico)

Si existe un elemento bajo la clave ken el mapa, entonces try_emplacedevuelve un iterador a ese elemento y false. De lo contrario, try_emplaceinserta un nuevo elemento debajo de la clave ky devuelve un iterador a eso y true [unord.map.modifiers] :

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

Interesante para nosotros es el caso de que todavía no haya ningún elemento [unord.map.modifiers] / 6 :

De lo contrario, inserta un objeto de tipo value_­typeconstruido conpiecewise_­construct, forward_­as_­tuple(k), forward_­as_­tuple(std​::​forward<Args>(args)...)

(la sobrecarga de tomar un valor p-referencia sólo se mueve ken forward_­as_­tupley, de nuevo, es por lo demás idéntico)

Como value_typees un pair<const Key, T> [unord.map.overview] / 2 , esto nos dice que el nuevo elemento del mapa se construirá como:

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

Como argsestá vacío cuando viene de operator[], esto se reduce a nuestro nuevo valor que se construye como miembro de los pairargumentos sin par [pair.pair] / 14, que es la inicialización directa [class.base.init] / 7 de un valor de tipo Tusando ()como inicializador que se reduce a inicialización de valor [dcl.init] /17.4 . La inicialización del valor de un intes inicialización cero [dcl.init] / 8 . Y la inicialización cero de un intnaturalmente inicializa eso inta 0 [dcl.init] / 6 .

Entonces sí, su código está garantizado para devolver 0 ...

Michael Kenzel
fuente
21

En el sitio que vinculaste dice:

Cuando se utiliza el asignador predeterminado, esto da como resultado que la clave se copie construida a partir de la clave y que el valor asignado se inicialice con el valor.

Entonces el valorint está inicializado :

Los efectos de la inicialización del valor son:

[...]

4) de lo contrario, el objeto está inicializado en cero

Por eso es que el resultado es 0.

Resplandor
fuente