2010-02-25 8 views
52

Existe-t-il un moyen de spécifier la valeur par défaut std::map renvoie operator[] lorsqu'une clé n'existe pas?std :: map valeur par défaut

+0

cette construction commune est très élégante en Perl: 'my $ val = carte $ { « clé » }: "NAN" ' –

Répondre

34

Non, il n'y en a pas. La solution la plus simple consiste à écrire votre propre fonction de modèle gratuit pour ce faire. Quelque chose comme:

#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); 
} 

C++ 11 Mise à jour

Objet: compte pour les conteneurs associatifs génériques, ainsi que des paramètres de comparaison et allocateur en option.

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; 
} 
+1

Belle solution. Vous pouvez ajouter quelques arguments de modèle afin que le modèle de fonction fonctionne avec des mappes qui n'utilisent pas les paramètres de modèle par défaut pour le comparateur et l'allocateur. – sbi

+3

+1, mais pour fournir le même comportement que l'opérateur '[]' avec la valeur par défaut, la valeur par défaut doit être insérée dans la carte dans le bloc 'if (it == m.end())' –

+12

@David Je suppose que le PO ne veut pas vraiment ce comportement. J'utilise un schéma similaire pour lire les configurations, mais je ne veux pas que la configuration soit mise à jour si une clé manque. –

3

Il est impossible de spécifier la valeur par défaut - elle est toujours construite par défaut (constructeur de paramètre zéro). En fait, operator[] fait probablement plus que ce à quoi vous vous attendez, comme si une valeur n'existait pas pour la clé donnée dans la carte, elle en insère une nouvelle avec la valeur du constructeur par défaut.

+2

A droite, pour éviter d'ajouter de nouvelles entrées, vous pouvez utiliser' find' qui renvoie l'itérateur de fin si aucun élément n'existe pour une clé donnée. –

1

Peut-être que vous pouvez donner un allocateur personnalisé qui allouer avec une valeur par défaut que vous voulez.

template < class Key, class T, class Compare = less<Key>, 
     class Allocator = allocator<pair<const Key,T> > > class map; 
+4

'operator []' renvoie un objet créé en invoquant 'T()', peu importe ce que fait l'allocateur. – sbi

+1

@sbi: La carte n'appelle-t-elle pas la méthode allocator 'construct'? Il serait possible de changer cela, je pense. Je soupçonne qu'une fonction 'construct' qui ne fait pas autre chose que' new (p) T (t); 'n'est pas bien formée, cependant. EDIT: En rétrospective c'était idiot, sinon toutes les valeurs seraient les mêmes: P Où est mon café ... – GManNickG

+1

@GMan: ma copie de C++ 03 dit (en 23.3.1.2) que 'operator []' renvoie ' (* ((insert (make_pair (x, T()))). premier)). second'. Donc, à moins de manquer quelque chose, cette réponse est fausse. – sbi

10

La norme C++ (23.3.1.2) indique que la valeur nouvellement insérée est construit par défaut, de sorte que map lui-même ne fournit pas un moyen de le faire. Vos choix sont:

  • Donnez la valeur de type un constructeur par défaut qui initialise à la valeur que vous voulez, ou
  • Enveloppez la carte dans votre propre classe qui fournit une valeur par défaut et met en œuvre operator[] pour insérer cette valeur par défaut.
+6

Eh bien, pour être précis, la valeur nouvellement insérée est la valeur initialisée (8.5.5): - si T est un type de classe avec un constructeur déclaré par l'utilisateur (12.1), le constructeur par défaut de T est appelé (et le l'initialisation est mal formée si T n'a pas de constructeur par défaut accessible); - si T est un type de classe non-union sans constructeur déclaré par l'utilisateur, chaque élément de données non-statique et composant de base de T est initialisé en valeur; - si T est un type de tableau, chaque élément est initialisé en valeur; - sinon, l'objet est initialisé à zéro –

4
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; 
+0

Essayez avec une chaîne, et littéral. ça ne marche pas. –

+2

Puis modifiez-le pour que cela fonctionne. Je ne vais pas prendre la peine de réparer un cas que ce code n'a pas été conçu pour accomplir. –

2

La valeur est initialisé à l'aide du constructeur par défaut, comme disent les autres réponses. Cependant, il est utile d'ajouter que dans le cas de types simples (types entiers tels que int, float, pointeur ou POD (plan old data)), les valeurs sont initialisées à zéro (ou initialisées par initialisation de valeur (qui est effectivement la même chose), selon la version de C++ utilisée). Quoi qu'il en soit, la ligne de fond est que les cartes avec des types simples initialiseront automatiquement les nouveaux éléments automatiquement. Ainsi, dans certains cas, il n'est pas nécessaire de spécifier explicitement la valeur initiale par défaut.

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 

Voir Do the parentheses after the type name make a difference with new? pour plus de détails sur la question.

4

version plus générale, le soutien C de 98/03 et Plus de conteneurs

Fonctionne avec des conteneurs associatifs génériques, le seul paramètre de modèle est le type de conteneur lui-même.

conteneurs pris en charge: 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; 
} 

Utilisation:

std::map<int, std::string> t; 
t[1] = "one"; 
string s = get_with_default(t, 2, "unknown"); 

Voici une implémentation similaire en utilisant une classe wrapper, qui est plus similaire à la méthode get() de type dict 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); 
} 

Utilisation:

std::map<int, std::string> t; 
t[1] = "one"; 
string s = wrap_map(t).get(2, "unknown"); 
13

Bien que cela ne répond pas exactement à la question, j'ai contourné le problème avec le code comme ceci:

struct IntDefaultedToMinusOne 
{ 
    int i = -1; 
}; 

std::map<std::string, IntDefaultedToMinusOne > mymap;