Por que `std :: string :: find ()` não retorna o iterador final em caso de falhas?

29

Acho que o comportamento std::string::findé inconsistente com os contêineres C ++ padrão.

Por exemplo

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

Mas para uma corda,

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

Por que a falha não deveria myStr.find('!')retornar em myStr.end()vez de std::string::npos?

Como o std::stringé um pouco especial quando comparado com outros contêineres, estou me perguntando se há alguma razão real por trás disso. (Surpreendentemente, eu não consegui encontrar ninguém questionando isso em lugar nenhum).

Sumudu
fonte
5
Acho que apenas uma resposta razoável está próxima da resposta à pergunta: 'Por que cachorros-quentes são embalados em 4 e bolos de cachorro-quente em 6?' Bem, é assim que o mundo acontece.
bartop
Verifique isso
NutCracker 17/10/19
IMHO, uma razão para esse comportamento seria que std::stringinternamente consiste em caracteres que são elementos baratos (em relação à memória). Além disso, o caractere é o único tipo que std::stringpode conter. Por outro lado, std::mapconsiste em elementos mais complexos. Além disso, a especificação de std::map::finddiz que é suposto encontrar um elemento, e a especificação de std::string::finddiz que sua tarefa é encontrar a posição.
Nutcracker
Para o mapa, você não pode ter um npos iterator, portanto o iterador final é usado. Para corda, podemos usar npos, então por que não :)
LF

Respostas:

28

Para começar, std::stringsabe-se que a interface é inchada e inconsistente, consulte Gotw84 de Herb Sutter sobre este tópico. Mas, no entanto, há um raciocínio por trás std::string::findretornando um índice: std::string::substr. Esta função de membro de conveniência opera em índices, por exemplo

const std::string src = "abcdefghijk";

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

Você pode implementar de substrforma que ele aceite iteradores na cadeia de caracteres, mas não precisaremos esperar muito tempo por reclamações altas que std::stringsão inutilizáveis ​​e contra-intuitivas. Então, considerando que std::string::substraceita índices, como você encontraria o índice da primeira ocorrência 'd'na string de entrada acima para imprimir tudo a partir dessa substring?

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

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

Isso também pode não ser o que você deseja. Portanto, podemos deixar std::string::findretornar um índice, e aqui estamos:

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

Se você deseja trabalhar com iteradores, use <algorithm>. Eles permitem que você acima

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

std::copy(it, src.cend(), std::ostream_iterator<char>(std::cout));
lubgr
fonte
4
Bom ponto. No entanto, em vez de retornar um iterador, std::string::findainda pode retornar size(), em vez de nposmanter a compatibilidade com substr, além de evitar várias ramificações extras.
erenon 17/10/19
11
@erenon Talvez, mas std::string::substrjá cubra o caso "comece aqui até o fim" com um parâmetro padrão para o segundo índice ( npos). Eu acho que voltar size()também seria confuso e ter uma sentinela literal como npospode ser a melhor escolha ?!
lubgr
@lubgr Mas se std::string::findretornar um iterador, std::string::substrprovavelmente também aceitaria um iterador para a posição inicial. Seu exemplo com find seria o mesmo nos dois casos neste mundo alternativo.
Mattias Wallin
@MattiasWallin Good point. Mas, std::string::substrcom um argumento de iterador, abre a porta para mais um caso de UB (além do cenário passado que pode igualmente acontecer com índices ou iteradores): passar um iterador que se refere a outra sequência.
lubgr
3

Isso ocorre porque std::stringhá duas interfaces:

  • A interface geral baseada em iterador encontrada em todos os contêineres
  • A interface std::stringespecífica baseada em índice

std::string::findfaz parte da interface baseada em índice e, portanto, retorna índices.

Use std::findpara usar a interface geral baseada no iterador.

Use std::vector<char>se você não quiser a interface baseada em índice (não faça isso).

Mattias Wallin
fonte