¿Hay una manera de especificar el valor predeterminado std::map
's operator[]
de retorno cuando no existe una clave?
Respuestas:
No, no lo hay. La solución más simple es escribir su propia función de plantilla gratuita para hacer esto. Algo como:
#include <string>
#include <map>
using namespace std;
template <typename K, typename V>
V GetWithDef(const std::map <K,V> & m, const K & key, const V & defval ) {
typename std::map<K,V>::const_iterator it = m.find( key );
if ( it == m.end() ) {
return defval;
}
else {
return it->second;
}
}
int main() {
map <string,int> x;
...
int i = GetWithDef( x, string("foo"), 42 );
}
Actualización de C ++ 11
Propósito: Tener en cuenta los contenedores asociativos genéricos, así como los parámetros opcionales de comparador y asignador.
template <template<class,class,class...> class C, typename K, typename V, typename... Args>
V GetWithDef(const C<K,V,Args...>& m, K const& key, const V & defval)
{
typename C<K,V,Args...>::const_iterator it = m.find( key );
if (it == m.end())
return defval;
return it->second;
}
operator[]
con el valor predeterminado, el valor predeterminado debe insertarse en el mapa dentro del if ( it == m.end() )
bloque
Si bien esto no responde exactamente a la pregunta, he eludido el problema con un código como este:
struct IntDefaultedToMinusOne
{
int i = -1;
};
std::map<std::string, IntDefaultedToMinusOne > mymap;
El estándar C ++ (23.3.1.2) especifica que el valor recién insertado se construye por defecto, por lo que en map
sí mismo no proporciona una forma de hacerlo. Tus opciones son:
operator[]
para insertar ese valor predeterminado.Versión más general, compatible con C ++ 98/03 y más contenedores
Funciona con contenedores asociativos genéricos, el único parámetro de plantilla es el tipo de contenedor en sí.
Contenedores compatibles: std::map
, std::multimap
, std::unordered_map
, std::unordered_multimap
, wxHashMap
, QMap
, QMultiMap
, QHash
, QMultiHash
, etc.
template<typename MAP>
const typename MAP::mapped_type& get_with_default(const MAP& m,
const typename MAP::key_type& key,
const typename MAP::mapped_type& defval)
{
typename MAP::const_iterator it = m.find(key);
if (it == m.end())
return defval;
return it->second;
}
Uso:
std::map<int, std::string> t;
t[1] = "one";
string s = get_with_default(t, 2, "unknown");
Aquí hay una implementación similar mediante el uso de una clase contenedora, que es más similar al método get()
de dict
tipo en Python: https://github.com/hltj/wxMEdit/blob/master/src/xm/xm_utils.hpp
template<typename MAP>
struct map_wrapper
{
typedef typename MAP::key_type K;
typedef typename MAP::mapped_type V;
typedef typename MAP::const_iterator CIT;
map_wrapper(const MAP& m) :m_map(m) {}
const V& get(const K& key, const V& default_val) const
{
CIT it = m_map.find(key);
if (it == m_map.end())
return default_val;
return it->second;
}
private:
const MAP& m_map;
};
template<typename MAP>
map_wrapper<MAP> wrap_map(const MAP& m)
{
return map_wrapper<MAP>(m);
}
Uso:
std::map<int, std::string> t;
t[1] = "one";
string s = wrap_map(t).get(2, "unknown");
C ++ 17 proporciona try_emplace
cuál hace exactamente esto. Toma una clave y una lista de argumentos para el constructor de valor y devuelve un par: an iterator
y a bool
.: Http://en.cppreference.com/w/cpp/container/map/try_emplace
No hay forma de especificar el valor predeterminado; siempre es un valor construido por defecto (constructor de parámetro cero).
De hecho, operator[]
probablemente hace más de lo que espera, ya que si no existe un valor para la clave dada en el mapa, insertará uno nuevo con el valor del constructor predeterminado.
find
que devuelve el iterador final si no existe ningún elemento para una clave determinada.
find
en ese caso?
template<typename T, T X>
struct Default {
Default () : val(T(X)) {}
Default (T const & val) : val(val) {}
operator T & () { return val; }
operator T const & () const { return val; }
T val;
};
<...>
std::map<KeyType, Default<ValueType, DefaultValue> > mapping;
El valor se inicializa usando el constructor predeterminado, como dicen las otras respuestas. Sin embargo, es útil agregar que en el caso de tipos simples (tipos integrales como int, float, pointer o tipos POD (plan old data)), los valores se inicializan a cero (o se ponen a cero mediante la inicialización de valor (que es efectivamente lo mismo), dependiendo de la versión de C ++ que se utilice).
De todos modos, la conclusión es que los mapas con tipos simples inicializarán a cero los nuevos elementos automáticamente. Entonces, en algunos casos, no hay necesidad de preocuparse por especificar explícitamente el valor inicial predeterminado.
std::map<int, char*> map;
typedef char *P;
char *p = map[123],
*p1 = P(); // map uses the same construct inside, causes zero-initialization
assert(!p && !p1); // both will be 0
Consulte ¿Los paréntesis después del nombre del tipo marcan la diferencia con new? para más detalles al respecto.
Una solución es utilizar en map::at()
lugar de []
. Si no existe una clave, at
lanza una excepción. Aún mejor, esto también funciona para vectores y, por lo tanto, es adecuado para programación genérica donde puede intercambiar el mapa con un vector.
Usar un valor personalizado para una clave no registrada puede ser peligroso ya que ese valor personalizado (como -1) puede procesarse más abajo en el código. Con excepciones, es más fácil detectar errores.
Tal vez pueda dar un asignador personalizado que asigne un valor predeterminado que desee.
template < class Key, class T, class Compare = less<Key>,
class Allocator = allocator<pair<const Key,T> > > class map;
operator[]
devuelve un objeto creado mediante la invocación T()
, sin importar lo que haga el asignador.
construct
método de asignadores ? Creo que sería posible cambiar eso. Sin embargo, sospecho que hay una construct
función que no new(p) T(t);
está bien formada. EDITAR: En retrospectiva, eso fue una tontería, de lo contrario, todos los valores serían los mismos: P ¿Dónde está mi café ...?
operator[]
devuelve (*((insert(make_pair(x, T()))).first)).second
. Entonces, a menos que me falte algo, esta respuesta es incorrecta.
insert
con a T()
, pero dentro de la inserción es cuando usará el asignador obtener memoria para una nueva y T
luego llamar construct
a esa memoria con el parámetro que se le dio, que es T()
. Entonces, de hecho, es posible cambiar el comportamiento de operator[]
para que devuelva algo más, pero el asignador no puede diferenciar por qué se llama. Entonces, incluso si hiciéramos construct
ignorar su parámetro y usamos nuestro valor especial, eso significaría que cada elemento construido tiene ese valor, lo cual es malo.
Ampliando la respuesta https://stackoverflow.com/a/2333816/272642 , esta plantilla función utiliza std::map
s' key_type
y mapped_type
typedefs para deducir el tipo de key
e def
. Esto no funciona con contenedores sin estos typedefs.
template <typename C>
typename C::mapped_type getWithDefault(const C& m, const typename C::key_type& key, const typename C::mapped_type& def) {
typename C::const_iterator it = m.find(key);
if (it == m.end())
return def;
return it->second;
}
Esto le permite usar
std::map<std::string, int*> m;
int* v = getWithDefault(m, "a", NULL);
sin necesidad de lanzar los argumentos como std::string("a"), (int*) NULL
.
Utilizar std::map::insert()
.
Al darme cuenta de que llego bastante tarde a esta fiesta, pero si está interesado en el comportamiento de operator[]
los valores predeterminados personalizados (es decir, busque el elemento con la clave dada, si no está presente, inserte un elemento en el mapa con un escogido valor por defecto y devolver una referencia a cualquiera de los dos el valor que acaba de insertar o el valor existente), ya hay una función disponible para usted pre C ++ 17: std::map::insert()
. insert
no insertará realmente si la clave ya existe, sino que devolverá un iterador al valor existente.
Digamos que quería un mapa de cadena a int e inserta un valor predeterminado de 42 si la clave aún no estaba presente:
std::map<std::string, int> answers;
int count_answers( const std::string &question)
{
auto &value = answers.insert( {question, 42}).first->second;
return value++;
}
int main() {
std::cout << count_answers( "Life, the universe and everything") << '\n';
std::cout << count_answers( "Life, the universe and everything") << '\n';
std::cout << count_answers( "Life, the universe and everything") << '\n';
return 0;
}
que debería generar 42, 43 y 44.
Si el costo de construir el valor del mapa es alto (si copiar / mover la clave o el tipo de valor es costoso), esto conlleva una penalización de rendimiento significativa, que creo que se evitaría con C ++ 17 try_emplace
.