¿El estándar C++ garantiza que una inserción fallida en un contenedor asociativo no modificará el argumento rvalue-reference?
stl language-lawyer (3)
#include <set>
#include <string>
#include <cassert>
using namespace std::literals;
int main()
{
auto coll = std::set{ "hello"s };
auto s = "hello"s;
coll.insert(std::move(s));
assert("hello"s == s); // Always OK?
}
¿El estándar C ++ garantiza que una inserción fallida en un contenedor asociativo no modificará el argumento rvalue-reference?
(Respuesta solo para C ++ 17)
Creo que la respuesta correcta está en algún lugar entre la respuesta de NathanOliver (ahora eliminada) y la respuesta de AndyG.
Como señala AndyG, dicha garantía
no puede
existir en general: a veces, la biblioteca
debe
realizar una construcción de movimiento solo para determinar si la inserción puede realizarse o no.
Este será el caso de la función
emplace
, cuyo comportamiento se especifica en el estándar como:
Efectos: Inserta un objeto
value_type
t
construido constd::forward<Args>(args)...
si y solo si no hay ningún elemento en el contenedor con clave equivalente a la clave det
.
Podemos interpretar esto como diciendo que el objeto
t
se construye sin importar qué, y luego se elimina si la inserción no puede ocurrir porque el valor
t
o
t
t.first
Ya existe en el conjunto o mapa, respectivamente.
Y dado que la
template <class P> pair<iterator, bool> insert(P&&)
método
template <class P> pair<iterator, bool> insert(P&&)
de
std::map
se especifica en términos de
emplace
, como señala AndyG, tiene el mismo comportamiento.
Como SergeyA señala, los métodos
try_emplace
están diseñados para evitar este problema.
Sin embargo, en el ejemplo específico dado por el OP, el valor que se inserta es
exactamente del mismo tipo que el tipo de valor del contenedor
.
El comportamiento de una llamada de
insert
se especifica en el párrafo de requisitos generales dado anteriormente por NathanOliver:
Efectos: inserta
t
si y solo si no hay ningún elemento en el contenedor con una clave equivalente a la clave det
.
En este caso, no se otorga licencia para que la biblioteca modifique el argumento en el caso en el que no se realiza la inserción.
Creo que llamar a una función de biblioteca no debe tener efectos secundarios observables además de lo que el estándar permite explícitamente.
Por lo tanto, este caso,
t
no debe modificarse.
No.
Si bien @NathanOliver señala que un elemento no se insertará si y solo si no hay una clave equivalente, no garantiza que los argumentos no se modifiquen.
De hecho, [map.modifiers] dice lo siguiente
template <class P>
pair<iterator, bool> insert(P&& x);
equivalente a
return emplace(std::forward<P>(x)).
Donde el
emplace
puede reenviar perfectamente los argumentos para construir otra
P
, dejando a
x
en un estado válido pero indeterminado.
Aquí hay un ejemplo que también demuestra (no prueba) que con
std::map
(un contenedor asociativo), un valor se mueve un poco:
#include <iostream>
#include <utility>
#include <string>
#include <map>
struct my_class
{
my_class() = default;
my_class(my_class&& other)
{
std::cout << "move constructing my_class/n";
val = other.val;
}
my_class(const my_class& other)
{
std::cout << "copy constructing my_class/n";
val = other.val;
}
my_class& operator=(const my_class& other)
{
std::cout << "copy assigning my_class/n";
val = other.val;
return *this;
}
my_class& operator=(my_class& other)
{
std::cout << "move assigning my_class/n";
val = other.val;
return *this;
}
bool operator<(const my_class& other) const
{
return val < other.val;
}
int val = 0;
};
int main()
{
std::map<my_class, int> my_map;
my_class a;
my_map[a] = 1;
std::pair<my_class, int> b = std::make_pair(my_class{}, 2);
my_map.insert(std::move(b)); // will print that the move ctor was called
}
NO explícito e inequívoco. Standard no tiene esta garantía, y es por eso que try_emplace existe.
Ver notas:
A diferencia de insertar o colocar, estas funciones no se mueven desde los argumentos de valor si la inserción no ocurre , lo que facilita la manipulación de mapas cuyos valores son tipos de solo movimiento, como
std::map<std::string, std::unique_ptr<foo>>
. Además,try_emplace
trata la clave y los argumentos para mapped_type por separado, a diferencia deemplace
, que requiere los argumentos para construir unvalue_type
(es decir, unstd::pair
value_type