c++ stl language-lawyer rvalue-reference pass-by-rvalue-reference

¿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 con std::forward<Args>(args)... si y solo si no hay ningún elemento en el contenedor con clave equivalente a la clave de t .

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 de t .

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 de emplace , que requiere los argumentos para construir un value_type (es decir, un std::pair value_type