Il existe de nombreuses fonctions utiles dans <algorithm>
, Mais toutes fonctionnent sur des "séquences" - des paires d'itérateurs. Par exemple, si j'ai un conteneur et que je veux y exécuter std::accumulate
, Je dois écrire:
std::vector<int> myContainer = ...;
int sum = std::accumulate(myContainer.begin(), myContainer.end(), 0);
Quand tout ce que j'ai l'intention de faire, c'est:
int sum = std::accumulate(myContainer, 0);
Ce qui est un peu plus lisible et plus clair à mes yeux.
Maintenant, je peux voir qu'il peut y avoir des cas où vous ne souhaitez opérer que sur des parties d'un conteneur, il est donc certainement utile d'avoir option de plages de passage. Mais au moins d'après mon expérience, c'est un cas spécial rare. Je veux généralement opérer sur des conteneurs entiers.
Il est facile d'écrire une fonction wrapper qui prend un conteneur et appelle begin()
et end()
, mais ces fonctions pratiques ne sont pas incluses dans la bibliothèque standard.
Je voudrais connaître le raisonnement derrière ce choix de conception STL.
... il est certainement utile d'avoir la possibilité de passer des plages. Mais au moins d'après mon expérience, c'est un cas spécial rare. Je veux généralement opérer sur des conteneurs entiers
Cela peut être un cas spécial rare dans votre expérience , mais en réalité le conteneur entier est le cas spécial et la plage arbitraire est le cas général.
Vous avez déjà remarqué que vous pouvez implémenter le cas du conteneur entier en utilisant l'interface actuelle, mais vous ne pouvez pas faire l'inverse.
Ainsi, l'auteur de la bibliothèque avait le choix entre implémenter deux interfaces à l'avance, ou n'en implémenter qu'une qui couvre toujours tous les cas.
Il est facile d'écrire une fonction wrapper qui prend un conteneur et y appelle begin () et end (), mais ces fonctions pratiques ne sont pas incluses dans la bibliothèque standard
Vrai, d'autant plus que les fonctions gratuites std::begin
et std::end
sont désormais inclus.
Supposons donc que la bibliothèque offre la surcharge pratique:
template <typename Container>
void sort(Container &c) {
sort(begin(c), end(c));
}
maintenant, il doit également fournir la surcharge équivalente en prenant un foncteur de comparaison, et nous devons fournir les équivalents pour tous les autres algorithmes.
Mais nous avons au moins couvert tous les cas où nous voulons opérer sur un conteneur plein, non? Enfin, pas tout à fait. Considérer
std::for_each(c.rbegin(), c.rend(), foo);
Si nous voulons gérer le fonctionnement vers l'arrière sur les conteneurs, nous avons besoin d'une autre méthode (ou paire de méthodes) par algorithme existant.
Ainsi, l'approche basée sur la plage est plus générale au sens simple que:
Il y a une autre raison valable, bien sûr, qui était déjà beaucoup de travail pour normaliser la STL et la gonfler avec des emballages pratiques avant avait été largement utilisé ne serait pas une grande utilisation du temps limité du comité. Si vous êtes intéressé, vous pouvez trouver le rapport technique de Stepanov & Lee ici
Comme mentionné dans les commentaires, Boost.Range fournit une approche plus récente sans nécessiter de modifications de la norme.
Il s'avère qu'il y a un article de Herb Sutter sur ce même sujet. Fondamentalement, le problème est l'ambiguïté de surcharge. Compte tenu des éléments suivants:
template<typename Iter>
void sort( Iter, Iter ); // 1
template<typename Iter, typename Pred>
void sort( Iter, Iter, Pred ); // 2
Et en ajoutant ce qui suit:
template<typename Container>
void sort( Container& ); // 3
template<typename Container, typename Pred>
void sort( Container&, Pred ); // 4
Il sera difficile de distinguer 4
et 1
correctement.
Les concepts, tels que proposés mais finalement non inclus dans C++ 0x, auraient résolu cela, et il est également possible de le contourner en utilisant enable_if
. Pour certains algorithmes, ce n'est pas un problème du tout. Mais ils ont décidé contre.
Maintenant, après avoir lu tous les commentaires et réponses ici, je pense que les objets range
seraient la meilleure solution. Je pense que je vais jeter un œil à Boost.Range
.
Fondamentalement, une décision héritée. Le concept d'itérateur est modélisé sur des pointeurs, mais les conteneurs ne sont pas modélisés sur des tableaux. De plus, étant donné que les tableaux sont difficiles à passer (ont besoin d'un paramètre de modèle non type pour la longueur, en général), bien souvent une fonction ne dispose que de pointeurs.
Mais oui, avec le recul, la décision est mauvaise. Nous aurions été mieux avec un objet range constructible à partir de begin/end
ou begin/length
; nous avons maintenant plusieurs _n
algorithmes suffixés à la place.
Les ajouter ne vous gagnerait aucun pouvoir (vous pouvez déjà faire tout le conteneur en appelant .begin()
et .end()
vous-même), et cela ajouterait encore une chose à la bibliothèque qui doit être correctement spécifié, ajouté aux bibliothèques par les fournisseurs, testé, maintenu, etc., etc.
En bref, il n'est probablement pas là car cela ne vaut pas la peine de maintenir un ensemble de modèles supplémentaires juste pour éviter aux utilisateurs du conteneur entier de taper un paramètre d'appel de fonction supplémentaire.