web-dev-qa-db-fra.com

Pourquoi std :: set n'a pas de fonction membre "contient"?

J'utilise beaucoup std::set<int> et souvent, je dois simplement vérifier si un tel ensemble contient un nombre ou non.

Je trouverais naturel d'écrire:

if (myset.contains(number))
   ...

Mais à cause de l'absence d'un membre contains, je dois écrire le lourd manuel:

if (myset.find(number) != myset.end())
  ..

ou le pas aussi évident:

if (myset.count(element) > 0) 
  ..

Y a-t-il une raison pour cette décision de conception?

92
Jabberwocky

Je pense que c'était probablement parce qu'ils essayaient de rendre std::set et std::multiset aussi semblables que possible. (Et évidemment, count a une signification parfaitement sensée pour std::multiset.)

Personnellement, je pense que c'était une erreur.

Cela n'a pas l'air si grave de prétendre que count est juste une faute de frappe de contains et d'écrire le test comme suit:

if (myset.count(element)) 
   ...

C'est quand même dommage.

142
Martin Bonner

Pour pouvoir écrire if (s.contains()), contains() doit renvoyer une bool (ou un type convertible en bool, qui est une autre histoire), comme le fait binary_search

La raison fondamentale à l'origine de la décision de conception not de procéder ainsi est que contains(), qui renvoie bool, perdrait des informations précieuses sur l'emplacement de l'élément dans la collection. find() préserve et renvoie ces informations sous la forme d'un itérateur. Il s'agit donc d'un meilleur choix pour une bibliothèque générique telle que STL. Tel a toujours été le principe directeur pour Alex Stepanov (par exemple, ici ).

En ce qui concerne l’approche count() en général, bien que ce soit souvent une solution de contournement satisfaisante, le problème est que cela fait plus de travail qu’uncontains()devrait faire.

Cela ne veut pas dire qu'une bool contains() n'est pas très agréable à avoir ou même nécessaire. Il y a quelque temps, nous avons eu une { longue discussion } sur ce même problème dans le groupe Norme ISO C++ - Propositions futures.

36
Leo Heinsaar

Cela manque parce que personne ne l'a ajouté. Personne ne l'a ajouté parce que les conteneurs du STL que la bibliothèque std incorporait étaient conçus pour avoir une interface minimale. (Notez que std::string ne vient pas de la STL de la même manière).

Si une syntaxe étrange ne vous dérange pas, vous pouvez la simuler:

template<class K>
struct contains_t {
  K&& k;
  template<class C>
  friend bool operator->*( C&& c, contains_t&& ) {
    auto range = std::forward<C>(c).equal_range(std::forward<K>(k));
    return range.first != range.second;
    // faster than:
    // return std::forward<C>(c).count( std::forward<K>(k) ) != 0;
    // for multi-meows with lots of duplicates
  }
};
template<class K>
containts_t<K> contains( K&& k ) {
  return {std::forward<K>(k)};
}

utilisation:

if (some_set->*contains(some_element)) {
}

Fondamentalement, vous pouvez écrire des méthodes d'extension pour la plupart des types C++ std à l'aide de cette technique.

Il est beaucoup plus logique de faire ceci:

if (some_set.count(some_element)) {
}

mais je suis amusé par la méthode de la méthode d'extension.

Ce qui est vraiment triste, c’est qu’écrire un contains efficace pourrait être plus rapide sur un multimap ou multiset, puisqu’ils doivent simplement trouver un élément, tandis que count doit trouver chacun d’eux et les compter .

Un multiset contenant 1 milliard de copies de 7 (vous savez, au cas où vous en auriez une) peut avoir une .count(7) très lente, mais peut avoir une contains(7) très rapide.

Avec la méthode d'extension ci-dessus, nous pourrions accélérer le processus en utilisant lower_bound, en comparant à end, puis en comparant à l'élément. Faire cela pour un miaou non ordonné ainsi que pour un miaou ordonné nécessiterait cependant des surcharges sophistiquées de SFINAE ou spécifiques à un conteneur.

21

Vous examinez un cas particulier sans voir la situation dans son ensemble. Comme indiqué dans documentationstd::set répond aux exigences de AssociativeContainer concept. Pour ce concept, il n’a aucun sens d’avoir une méthode contains, car elle est pratiquement inutile pour std::multiset et std::multimap, mais count fonctionne très bien pour chacun d’eux. Bien que la méthode contains puisse être ajoutée comme alias pour count pour std::set, std::map et leurs versions hachées (comme length pour size() dans std::string), il semble que les créateurs de bibliothèques n'en aient pas vraiment besoin.

12
Slava

Bien que je ne sache pas pourquoi std::set n'a pas de contains mais count qui ne retourne jamais que 0 ou 1, .__, vous pouvez écrire une fonction d'assistance contains basée sur un modèle comme ceci:

template<class Container, class T>
auto contains(const Container& v, const T& x)
-> decltype(v.find(x) != v.end())
{
    return v.find(x) != v.end();
}

Et utilisez-le comme ceci:

    if (contains(myset, element)) ...
10
rustyx

La vraie raison de set est un mystère pour moi, mais une explication possible de cette même conception dans map pourrait être d'empêcher les gens d'écrire du code inefficace par accident:

if (myMap.contains("Meaning of universe"))
{
    myMap["Meaning of universe"] = 42;
}

Ce qui donnerait lieu à deux recherches map.

Au lieu de cela, vous êtes obligé d'obtenir un itérateur. Cela vous donne une idée mentale que vous devriez réutiliser l'itérateur:

auto position = myMap.find("Meaning of universe");
if (position != myMap.cend())
{
    position->second = 42;
}

qui consomme une seule recherche map.

Lorsque nous réalisons que set et map sont fabriqués à partir de la même chair, nous pouvons appliquer ce principe également à set. C'est-à-dire que si nous voulons agir sur un élément de la set uniquement s'il est présent dans la set, cette conception peut nous empêcher d'écrire du code comme ceci:

struct Dog
{
    std::string name;
    void bark();
}

operator <(Dog left, Dog right)
{
    return left.name < right.name;
}

std::set<Dog> dogs;
...
if (dogs.contain("Husky"))
{
    dogs.find("Husky")->bark();
}

Bien sûr, tout ceci n’est qu’une pure spéculation.

7
Martin Drozdik

Qu'en est-il de binary_search?

 set <int> set1;
 set1.insert(10);
 set1.insert(40);
 set1.insert(30);
 if(std::binary_search(set1.begin(),set1.end(),30))
     bool found=true;
0
Massimiliano D