Pourquoi `std :: string :: find ()` ne retourne pas l'itérateur de fin en cas d'échec?

29

Je trouve que le comportement de std::string::findn'est pas cohérent avec les conteneurs C ++ standard.

Par exemple

std::map<int, int> myMap = {{1, 2}};
auto it = myMap.find(10);  // it == myMap.end()

Mais pour une chaîne,

std::string myStr = "hello";
auto it = myStr.find('!');  // it == std::string::npos

Pourquoi le myStr.find('!')retour échoué ne devrait-il pas myStr.end()plutôt std::string::npos?

Étant donné que le std::stringest quelque peu spécial par rapport à d'autres conteneurs, je me demande s'il y a une vraie raison derrière cela. (Étonnamment, je n'ai trouvé personne qui remette cela en question).

Sumudu
la source
5
Je pense que seule une réponse raisonnable est proche de la réponse à la question: «Pourquoi les hot-dogs sont-ils emballés en 4 et les petits pains en 6?» Eh bien, c'est comme ça que le monde est
arrivé
Vérifiez ceci
NutCracker
À mon humble avis, une raison de ce comportement serait que, en std::stringinterne, se compose de caractères qui sont des éléments peu coûteux (en ce qui concerne la mémoire). Et, en outre, le caractère est le seul type std::stringpouvant contenir. D'autre part, se std::mapcompose d'éléments plus complexes. De plus, la spécification de std::map::finddit qu'il est censé trouver un élément, et la spécification de std::string::finddit que sa tâche est de trouver la position.
NutCracker
Pour la carte, vous ne pouvez pas avoir d'itérateur npos, donc l'itérateur de fin est utilisé. Pour la chaîne, nous pouvons utiliser npos, alors pourquoi pas :)
LF

Réponses:

28

Pour commencer, l' std::stringinterface est bien connue pour être gonflée et incohérente, voir Gotw84 de Herb Sutter sur ce sujet. Mais néanmoins, il y a un raisonnement derrière le std::string::findretour d' un indice: std::string::substr. Cette fonction de membre de commodité fonctionne sur des indices, par exemple

const std::string src = "abcdefghijk";

std::cout << src.substr(2, 5) << "\n";

Vous pouvez implémenter de substrtelle sorte qu'il accepte les itérateurs dans la chaîne, mais nous n'aurions pas besoin d'attendre longtemps pour les plaintes bruyantes std::stringinutilisables et contre-intuitives. Donc, étant donné que cela std::string::substraccepte les indices, comment trouveriez-vous l'indice de la première occurrence de 'd'dans la chaîne d'entrée ci-dessus afin d'imprimer tout à partir de cette sous-chaîne?

const auto it = src.find('d'); // imagine this returns an iterator

std::cout << src.substr(std::distance(src.cbegin(), it));

Ce n'est peut-être pas non plus ce que vous voulez. On peut donc laisser std::string::findretourner un indice, et nous voici:

const std::string extracted = src.substr(src.find('d'));

Si vous souhaitez travailler avec des itérateurs, utilisez <algorithm>. Ils vous permettent de ce qui précède comme

auto it = std::find(src.cbegin(), src.cend(), 'd');

std::copy(it, src.cend(), std::ostream_iterator<char>(std::cout));
lubgr
la source
4
Bon point. Cependant, au lieu de renvoyer un itérateur, std::string::findpourrait toujours retourner size(), au lieu de npos, conserver la compatibilité avec substr, tout en évitant plusieurs brances supplémentaires.
erenon
1
@erenon Peut-être, mais std::string::substrcouvre déjà le cas "commencer ici jusqu'à la fin" avec un paramètre par défaut pour le deuxième index ( npos). Je suppose que revenir size()serait aussi déroutant et avoir une sentinelle littérale comme npospourrait être le meilleur choix?!
lubgr
@lubgr Mais si std::string::findrenvoie un itérateur, std::string::substraccepterait probablement aussi un itérateur pour la position de départ. Votre exemple avec find serait le même dans les deux cas dans ce monde alternatif.
Mattias Wallin
@MattiasWallin Bon point. Mais std::string::substravec un argument itérateur, cela ouvre la porte à un autre cas UB (en plus du scénario de fin de cycle qui peut tout aussi bien se produire avec des indices ou des itérateurs): passer un itérateur qui fait référence à une autre chaîne.
lubgr
3

En effet, std::stringavoir deux interfaces:

  • L' interface générale basée sur un itérateur trouvée sur tous les conteneurs
  • L' interface basée sur un indexstd::string spécifique

std::string::findfait partie de l' interface basée sur un index et renvoie donc des indices.

Utilisez std::findpour utiliser l'interface générale basée sur un itérateur.

À utiliser std::vector<char>si vous ne voulez pas l'interface basée sur l'index (ne faites pas cela).

Mattias Wallin
la source