Question

Pour un std::map<std::string, std::string> variables, j'aimerais faire ceci:

BOOST_CHECK_EQUAL(variables["a"], "b");

Le seul problème est que, dans ce contexte, variables est const, donc operator[] ne fonctionnera pas: (

Maintenant, il existe plusieurs solutions de contournement à cela; jeter le variables.count("a") ? variables.find("a")->second : std::string(), utiliser <=> ou même créer une fonction qui le recouvre. Rien de tout cela ne me semble aussi beau que <=>. Que devrais-je faire? Existe-t-il un moyen standard de le faire (magnifiquement)?

Modifier: Juste pour énoncer la réponse qu'aucun de vous ne veut donner: non, il n'y a pas de moyen simple, beau et classique de faire cela en C ++. Je vais devoir mettre en place une fonction de support.

Était-ce utile?

La solution

template <typename K, typename V>
V get(std::map<K, V> const& map, K const& key)
{
    std::map<K, V>::const_iterator iter(map.find(key));
    return iter != map.end() ? iter->second : V();
}

Implémentation améliorée basée sur les commentaires:

template <typename T>
typename T::mapped_type get(T const& map, typename T::key_type const& key)
{
    typename T::const_iterator iter(map.find(key));
    return iter != map.end() ? iter->second : typename T::mapped_type();
}

Autres conseils

Renvoyer const est incorrect, car l'opérateur [] sur la carte < > créera l'entrée si elle n'est pas présente avec une chaîne construite par défaut. Si la carte est réellement dans un stockage immuable, elle échouera. Cela doit être le cas, car l'opérateur [] renvoie une référence non-const pour permettre l'affectation. (par exemple, m [1] = 2)

Une fonction libre rapide pour implémenter la comparaison:

template<typename CONT>
bool check_equal(const CONT& m, const typename CONT::key_type& k,
                    const typename CONT::mapped_type& v)
{
    CONT::const_iterator i(m.find(k));
    if (i == m.end()) return false;
    return i->second == v;
}

Je vais réfléchir au sucre syntaxique et le mettre à jour si je pense à quelque chose.

...

Le sucre syntaxique immédiat impliquait une fonction libre qui crée une carte < > :: find () et retourne une classe spéciale qui englobe la carte < > :: const_iterator, puis a surchargé l'opérateur == () et l'opérateur! = () pour permettre la comparaison avec le type mappé. Donc, vous pouvez faire quelque chose comme:

if (nonmutating_get(m, "key") == "value") { ... }

Je ne suis pas convaincu que c'est beaucoup mieux que:

if (check_equal(m, "key", "value")) { ... }

Et c'est certainement beaucoup plus complexe et ce qui se passe est beaucoup moins évident.

L'objet enveloppant l'itérateur a pour but de ne plus avoir d'objets de données construits par défaut. Si vous ne vous en souciez pas, utilisez simplement le & "Obtenir &"; répondre.

En réponse au commentaire sur le fait d’être préféré à une comparaison dans l’espoir de trouver une utilisation future, j’ai ces commentaires:

  • Dites ce que vous voulez dire: appeler une fonction appelée " check_equal " indique clairement que vous effectuez une comparaison d’égalité sans création d’objet.

  • Je recommande de n'implémenter la fonctionnalité que lorsque vous en avez besoin. Faire quelque chose avant est souvent une erreur.

  • Selon la situation, un constructeur par défaut peut avoir des effets secondaires. Si vous comparez, pourquoi faire autre chose?

  • L'argument SQL: NULL n'est pas équivalent à une chaîne vide. L’absence de clé de votre conteneur est-elle vraiment la même chose que la clé présente dans votre conteneur avec une valeur construite par défaut?

Cela dit, un objet construit par défaut revient à utiliser map < > :: operator [] sur un conteneur non const. Et peut-être avez-vous actuellement besoin d'une fonction get qui renvoie un objet construit par défaut; Je sais que j'ai eu cette exigence dans le passé.

find est la forme idiomatique. Rejeter const est presque toujours une mauvaise idée. Vous devez vous assurer qu'aucune opération d'écriture n'est effectuée. Bien que cela puisse être raisonnablement attendu d’un accès en lecture sur une carte, la spécification n’en dit rien.

Si vous savez que la valeur existe, vous pouvez bien entendu renoncer au test avec count (ce qui est tout à fait inefficace, car cela implique de parcourir deux fois la carte. savoir si l'élément existe, je ne l'utiliserais pas. Utilisez plutôt le texte suivant:

T const& item(map<TKey, T> const& m, TKey const& key, T const& def = T()) {
    map<TKey, T>::const_iterator i = m.find(key);
    return i == m.end() ? def : i->second;
}

/ EDIT: Comme Chris l’a bien souligné, la construction par défaut des objets de type T peut être coûteuse, d’autant plus que cela est fait même si cet objet n’est pas réellement nécessaire (car l'entrée existe). Si tel est le cas, n'utilisez pas la valeur par défaut pour l'argument def dans le cas ci-dessus.

Autre particularité intéressante, la découverte du type de modèle dans l'implémentation get acceptée a été acceptée (celle qui récupère la valeur ou retourne un objet construit par défaut). Premièrement, vous pouvez faire ce qui a été accepté et avoir:

template <typename K, typename V>
V get1(const std::map<K, V>& theMap, const K const key)
{
    std::map<K, V>::const_iterator iter(theMap.find(key));
    return iter != theMap.end() ? iter->second : V();
}

ou vous pouvez utiliser le type de carte et en extraire les types:

template<typename T>
typename T::mapped_type
get2(const T& theMap, const typename T::key_type& key)
{
    typename T::const_iterator itr = theMap.find(key);
    return itr != theMap.end() ? itr->second : typename T::mapped_type();
}

L'avantage de ceci est que le type de la clé en cours de transmission ne joue pas dans la découverte du type et qu'il peut s'agir de quelque chose qui peut être implicitement converti en une clé. Par exemple:

std::map<std::string, int> data;
get1(data, "hey"); // doesn't compile because the key type is ambiguous
get2(data, "hey"); // just fine, a const char* can be converted to a string

En effet, l'opérateur [] est un opérateur non-constant sur std :: map, car il insère automatiquement une paire clé-valeur dans la carte si elle n'y était pas. (Oooh effets secondaires!)

La bonne façon consiste à utiliser map::find et, si l'itérateur renvoyé est valide (!= map.end()), à renvoyer le second, comme vous l'avez indiqué.

map<int, int> m;
m[1]=5; m[2]=6; // fill in some couples
...
map<int,int>::const_iterator it = m.find( 3 );
if( it != m.end() ) {
    int value = it->second;
    // ... do stuff with value
}

Vous pouvez ajouter un map::operator[]( const key_type& key ) const dans une sous-classe de std :: map que vous utilisez, et définir la clé à rechercher, après quoi vous retournez le it->second.

std::map<std::string, std::string>::const_iterator it( m.find("a") );
BOOST_CHECK_EQUAL( 
                     ( it == m.end() ? std::string("") : it->second ), 
                     "b" 
                 );

Cela ne me semble pas trop mal… Je n'écrirais probablement pas de fonction pour cela.

Suivant l’idée de xtofl de spécialiser le conteneur de cartes. Ce qui suit fonctionnera-t-il bien?

template <typename K,typename V>  
struct Dictionary:public std::map<K,V>  
{  
  const V& operator[] (const K& key) const  
  {  
    std::map<K,V>::const_iterator iter(this->find(key));  
    BOOST_VERIFY(iter!=this->end());  
    return iter->second;  
  }  
};  
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top