Existem muitas funções úteis <algorithm>
, mas todas elas operam em "sequências" - pares de iteradores. Por exemplo, se eu tenho um container e gostaria de rodar std::accumulate
nele, preciso escrever:
std::vector<int> myContainer = ...;
int sum = std::accumulate(myContainer.begin(), myContainer.end(), 0);
Quando tudo que pretendo fazer é:
int sum = std::accumulate(myContainer, 0);
O que é um pouco mais legível e mais claro aos meus olhos.
Agora eu vejo que pode haver casos em que você deseja operar apenas em partes de um contêiner, por isso é definitivamente útil ter a opção de passar por intervalos. Mas pelo menos na minha experiência, esse é um caso especial raro. Normalmente, eu quero operar em contêineres inteiros.
É fácil escrever uma função wrapper que leva um recipiente e chamadas begin()
e end()
sobre ele, mas tais funções de conveniência não estão incluídos na biblioteca padrão.
Gostaria de saber o raciocínio por trás dessa opção de design da STL.
fonte
boost::accumulate
Respostas:
Pode ser um caso especial raro em sua experiência , mas, na realidade, todo o contêiner é o caso especial e o intervalo arbitrário é o caso geral.
Você já percebeu que é possível implementar todo o caso do contêiner usando a interface atual, mas não pode fazer o inverso.
Portanto, o escritor da biblioteca teve a opção de implementar duas interfaces antecipadamente ou apenas uma que ainda abrange todos os casos.
Verdade, especialmente desde as funções gratuitas
std::begin
estd::end
agora estão incluídas.Então, digamos que a biblioteca ofereça a sobrecarga de conveniência:
agora ele também precisa fornecer a sobrecarga equivalente usando um functor de comparação, e precisamos fornecer os equivalentes para todos os outros algoritmos.
Mas pelo menos cobrimos todos os casos em que queremos operar em um contêiner cheio, certo? Bem, não exatamente. Considerar
Se queremos lidar com a operação reversa em contêineres, precisamos de outro método (ou par de métodos) por algoritmo existente.
Portanto, a abordagem baseada em faixa é mais geral no sentido simples de que:
Há outro motivo válido, é claro, que já era muito trabalhoso padronizar o STL e inflá-lo com invólucros de conveniência antes que ele fosse amplamente utilizado não seria um ótimo uso do tempo limitado do comitê. Se você estiver interessado, pode encontrar o relatório técnico de Stepanov & Lee aqui
Conforme mencionado nos comentários, o Boost.Range fornece uma abordagem mais nova sem exigir alterações no padrão.
fonte
f(c.begin(), c.end(), ...)
, e talvez apenas a sobrecarga mais comumente usada (como você determinar) para evitar duplicar o número de sobrecargas. Além disso, os adaptadores do iterador são completamente ortogonais (como você observa, eles funcionam bem no Python, cujos iteradores funcionam de maneira muito diferente e não têm a maior parte do poder sobre o qual você fala).std::sort(std::range(start, stop))
.#define MAKE_RANGE(container) (container).begin(), (container).end()
</jk>Acontece que há um artigo de Herb Sutter sobre esse assunto. Basicamente, o problema é a ambiguidade da sobrecarga. Dado o seguinte:
E adicionando o seguinte:
Tornará difícil distinguir
4
e1
adequadamente.Os conceitos, conforme propostos, mas não incluídos no C ++ 0x, teriam resolvido isso e também é possível contorná-lo usando
enable_if
. Para alguns dos algoritmos, não há problema algum. Mas eles decidiram contra.Agora, depois de ler todos os comentários e respostas aqui, acho que os
range
objetos seriam a melhor solução. Acho que vou dar uma olhadaBoost.Range
.fonte
typename Iter
parece ser muito tipificado para uma linguagem estrita. Eu preferiria, por exemplotemplate<typename Container> void sort(typename Container::iterator, typename Container::iterator); // 1
etemplate<template<class> Container, typename T> void sort( Container<T>&, std::function<bool(const T&)> ); // 4
etc. (o que talvez resolver o problema da ambiguidade)T[]::iterator
disponível. Além disso, o iterador adequado não é obrigado a ser um tipo aninhado de qualquer coleção, basta definirstd::iterator_traits
.Basicamente, uma decisão herdada. O conceito de iterador é modelado em ponteiros, mas os contêineres não são modelados em matrizes. Além disso, como as matrizes são difíceis de passar (precisam de um parâmetro de modelo não-tipo para comprimento, em geral), muitas vezes uma função tem apenas ponteiros disponíveis.
Mas sim, em retrospectiva, a decisão está errada. Teríamos ficado melhor com um objeto de intervalo construtível a partir de
begin/end
oubegin/length
; agora temos vários_n
algoritmos com sufixo.fonte
Adicionando-se você ganhar nenhum poder (você já pode fazer todo o recipiente chamando
.begin()
e.end()
você mesmo), e gostaria de acrescentar mais uma coisa para a biblioteca que tem de ser devidamente especificado, adicionadas às bibliotecas pelos vendedores, testado, mantido, etc etc.Em suma, provavelmente não está lá porque não vale a pena manter um conjunto de modelos extras apenas para evitar que usuários de contêineres inteiros digitem um parâmetro de chamada de função extra.
fonte
std::getline
, e ainda está na biblioteca. Pode-se chegar ao ponto de dizer que estruturas de controle estendidas não me ganham poder, pois eu poderia fazer tudo usando apenasif
egoto
. Sim, comparação injusta, eu sei;) Eu acho que posso entender a carga de especificação / implementação / manutenção de alguma forma, mas é apenas um pequeno invólucro de que estamos falando aqui, então ..Até agora, http://en.wikipedia.org/wiki/C++11#Range-based_for_loop é uma boa alternativa para
std::for_each
. Observe, nenhum iterador explícito:(Inspirado em https://stackoverflow.com/a/694534/2097284 .)
fonte
<algorithm>
, não todos os algos reais que precisambegin
eend
iteradores - mas o benefício não pode ser exagerado! Quando experimentei o C ++ 03 pela primeira vez em 2009, evitei os iteradores devido ao clichê de loop e, felizmente ou não, meus projetos na época permitiam isso. Reiniciando em C ++ 11 em 2014, foi um upgrade incrível, a linguagem C ++ sempre deveria ter sido, e agora eu não posso viver semauto &it: them
:)