¿Por qué `std :: string :: find ()` no devuelve el iterador final en caso de fallas?

29

Encuentro el comportamiento de std::string::findser inconsistente con los contenedores estándar de C ++.

P.ej

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

Pero por una cuerda,

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

¿Por qué no debería la myStr.find('!')devolución fallida en myStr.end()lugar de std::string::npos?

Dado que std::stringes algo especial en comparación con otros contenedores, me pregunto si hay alguna razón real detrás de esto. (Sorprendentemente, no pude encontrar a nadie cuestionando esto en ninguna parte).

Sumudu
fuente
55
Creo que solo una respuesta razonable está cerca de responder a la pregunta: '¿Por qué los perritos calientes se empaquetan en 4 y los hot dogs en 6?' Bueno, así es como será el mundo
bartop el
Mira esto
NutCracker
En mi humilde opinión, una razón para este comportamiento sería que std::stringinternamente se compone de personajes que son elementos de bajo costo (en lo que respecta a la memoria). Y, además, el carácter es el único tipo que std::stringpuede contener. Por otro lado, std::mapconsta de elementos más complejos. Además, la especificación de std::map::finddice que se supone que debe encontrar un elemento, y la especificación de std::string::finddice que su tarea es encontrar la posición.
NutCracker
Para el mapa, no puede tener un iterador npos, por lo que se utiliza el iterador final. Para la cadena, podemos usar npos, entonces ¿por qué no :)
LF

Respuestas:

28

Para empezar, std::stringse sabe que la interfaz es hinchada e inconsistente, vea Gotw84 de Herb Sutter sobre este tema. Pero, sin embargo, hay un razonamiento detrás de std::string::finddevolver un índice: std::string::substr. Esta función de miembro de conveniencia opera en índices, p. Ej.

const std::string src = "abcdefghijk";

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

Puede implementar de substrmodo que acepte iteradores en la cadena, pero no tendríamos que esperar mucho tiempo para recibir quejas fuertes que no std::stringse pueden usar y que no son intuitivas. Entonces, dado que std::string::substracepta índices, ¿cómo encontraría el índice de la primera aparición de 'd'en la cadena de entrada anterior para imprimir todo a partir de esta subcadena?

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

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

Esto también podría no ser lo que quieres. Por lo tanto, podemos dejar que std::string::finddevuelva un índice, y aquí estamos:

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

Si desea trabajar con iteradores, use <algorithm>. Te permiten lo anterior como

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

std::copy(it, src.cend(), std::ostream_iterator<char>(std::cout));
lubgr
fuente
44
Buen punto. Sin embargo, en lugar de devolver un iterador, std::string::findaún podría devolver size(), en lugar de nposmantener la compatibilidad con substr, evitando al mismo tiempo varias ramificaciones adicionales.
Erenon
1
@erenon Quizás, pero std::string::substrya cubre el caso "comenzar aquí hasta el final" con un parámetro predeterminado para el segundo índice ( npos). Supongo que regresar size()también sería confuso y tener un centinela literal como npospodría ser la mejor opción.
lubgr
@lubgr Pero si std::string::finddevuelve un iterador, std::string::substrprobablemente también aceptaría un iterador para la posición de inicio. Su ejemplo con find se vería igual en ambos casos en este mundo alternativo.
Mattias Wallin el
@MattiasWallin Buen punto. Pero std::string::substrcon un argumento iterador se abre la puerta para otro caso de UB (además del escenario pasado-final que igualmente puede suceder con índices o iteradores): pasar un iterador que se refiere a otra cadena.
lubgr
3

Esto se debe a que std::stringtiene dos interfaces:

  • La interfaz general basada en iterador que se encuentra en todos los contenedores
  • La interfaz basada en índicesstd::string específicos

std::string::findforma parte de la interfaz basada en índices y, por lo tanto, devuelve índices.

Se usa std::findpara usar la interfaz general basada en iterador.

Úselo std::vector<char>si no desea la interfaz basada en índice (no haga esto).

Mattias Wallin
fuente