std :: valor predeterminado del mapa

86

¿Hay una manera de especificar el valor predeterminado std::map's operator[]de retorno cuando no existe una clave?

luego
fuente

Respuestas:

56

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;
}
WhozCraig
fuente
1
Buena solucion. Es posible que desee agregar algunos argumentos de plantilla para que la plantilla de función funcione con mapas que no utilicen los parámetros de plantilla predeterminados para comparador y asignador.
sbi
3
+1, pero para proporcionar exactamente el mismo comportamiento que operator[]con el valor predeterminado, el valor predeterminado debe insertarse en el mapa dentro del if ( it == m.end() )bloque
David Rodríguez - dribeas
12
@David Asumo que el OP no quiere ese comportamiento. Utilizo un esquema similar para leer configuraciones, pero no quiero que se actualice la configuración si falta una clave.
2
Algunos piensan que los parámetros de @GMan bool son de mal estilo porque no se puede saber al mirar la llamada (a diferencia de la declaración) qué hacen; en este caso, ¿"verdadero" significa "usar predeterminado" o "don t usar default "(o algo completamente diferente)? Una enumeración siempre es más clara pero, por supuesto, es más código. Estoy en dos mentes sobre el tema, yo mismo.
2
Esta respuesta no funciona si el valor predeterminado es nullptr, pero stackoverflow.com/a/26958878/297451 sí.
Jon
44

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;
SupervivenciaMáquina
fuente
1
Esta es la mejor solución para mí. Fácil de implementar, muy flexible y genérico.
acegs
11

El estándar C ++ (23.3.1.2) especifica que el valor recién insertado se construye por defecto, por lo que en mapsí mismo no proporciona una forma de hacerlo. Tus opciones son:

  • Asigne al tipo de valor un constructor predeterminado que lo inicialice con el valor que desee, o
  • Envuelva el mapa en su propia clase que proporciona un valor predeterminado y lo implementa operator[]para insertar ese valor predeterminado.
Mike Seymour
fuente
8
Bueno, para ser precisos, el valor recién insertado es el valor inicializado (8.5.5) por lo que: - si T es un tipo de clase con un constructor declarado por el usuario (12.1), entonces se llama al constructor predeterminado para T (y la inicialización está mal -formado si T no tiene un constructor predeterminado accesible); - si T es un tipo de clase sin unión sin un constructor declarado por el usuario, entonces cada miembro de datos no estáticos y componente de clase base de T se inicializa con valor; - si T es un tipo de matriz, entonces cada elemento tiene valor inicializado; - de lo contrario, el objeto se inicializa a cero
Tadeusz Kopec
6

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 dicttipo 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");
jyw
fuente
MAP :: mapped_type & no es seguro para devolver ya que typename MAP :: mapped_type & defval pueden estar fuera de alcance.
Joe C
5

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.

Michael Anderson
fuente
2
Correcto, para evitar agregar nuevas entradas, puede usar findque devuelve el iterador final si no existe ningún elemento para una clave determinada.
Thomas Schaub
@ThomasSchaub ¿Cuál es la complejidad del tiempo finden ese caso?
ajaysinghnegi
5
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;
Thomas Eding
fuente
3
Luego modifíquelo para que funcione. No me voy a molestar en arreglar un caso para el que este código no fue diseñado.
Thomas Eding
3

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.

los cerdos
fuente
2

Una solución es utilizar en map::at()lugar de []. Si no existe una clave, atlanza 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.

Decano
fuente
1

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;
VDVLeon
fuente
4
operator[]devuelve un objeto creado mediante la invocación T(), sin importar lo que haga el asignador.
sbi
1
@sbi: ¿El mapa no llama al constructmétodo de asignadores ? Creo que sería posible cambiar eso. Sin embargo, sospecho que hay una constructfunció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é ...?
GManNickG
1
@GMan: mi copia de C ++ 03 dice (en 23.3.1.2) que operator[]devuelve (*((insert(make_pair(x, T()))).first)).second. Entonces, a menos que me falte algo, esta respuesta es incorrecta.
sbi
Tienes razón. Pero eso me parece mal. ¿Por qué no usan la función de asignación para insertar?
VDVLeon
2
@sbi: No, estoy de acuerdo en que esta respuesta es incorrecta, pero por una razón diferente. El compilador de hecho lo hace insertcon a T(), pero dentro de la inserción es cuando usará el asignador obtener memoria para una nueva y Tluego llamar constructa 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 constructignorar su parámetro y usamos nuestro valor especial, eso significaría que cada elemento construido tiene ese valor, lo cual es malo.
GManNickG
1

Ampliando la respuesta https://stackoverflow.com/a/2333816/272642 , esta plantilla función utiliza std::maps' key_typey mapped_typetypedefs para deducir el tipo de keye 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.

Vortico
fuente
1

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(). insertno 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.

Dhavenith
fuente