int myints[] = { 10,20,30,30,20,10,10,20 };
déclare un tableau contenant 8 éléments. myints
correspond à un pointeur pointant sur l'addresse du premier élément du tableau soit 10
.
std::vector<int> v(myints, myints + 8);
construit un containeur de type std::vector à partir d'une séquence d'élément en appelant le constructeur suivant :
template< class InputIt >
vector( InputIt first, InputIt last,
const Allocator& alloc = Allocator() );
Le compilateur identifie int[]
comme étant un int*
qui se respecte les exigences définies par input iterator. Comme le tableau contient 8 éléments, myints+8
définit un pointeur qui pointe sur l'élément qui se trouverait à l'indice 8
, soit myints[8]
. Cependant, cet élément n'existe pas ! C'est bien un élément situé après le dernier élément dans le tableau myints
qui est myints[8]
et qui est référencé par le pointeur myints+7
. En conséquence, myints+8
marque bien la fin de la séquence, puisque c'est l'itérateur qui suit le dernier itérateur valide de la séquence.
std::vector<int> v(
/* pointeur sur le premier de la séquence */myints,
/* pointeur sur l'élément qui serait situé après le dernier élément de la séquence */ myints + 8);
Donc nous initialisons ainsi un vecteur avec les valeurs provenant du tableau myints
. Nous voyons aussi au passage que les pointeurs sont un cas particulier des itérateurs et que les itérateurs respectent les exigences définies par random access iterator.
Une fois ceci effectué, les lignes suivantes n'appellent pas de remarques particulières :
std::vector<int>::iterator up = upper(v.begin(), v.end(), 20);
std::cout << "first value greater than 20 at position "
<< (up - v.begin()) << '\n';
à l'exception que l'itérateur up
ayant pour type std::vector<int>::iterator
doit supporter l'opération -
dite différence de position. Cette opération n'est pas disponible pour tous les types d'itérateurs . Elle n'est en fait que disponible pour les itérateurs qui respectent les exigences définies par random access iterator, ce qui est le cas pour l'itérateur de std::vector<int>::iterator
défini dans std::vector.
Si nous souhaitons utiliser un autre containeur comme par exemple le containeur std::set, nous pouvons écrire le code suivant :
std::set<int> intSet(v.begin(), v.end());
auto /* std::set<int>::iterator */upSet = upper(intSet.begin(), intSet.end(), 20);
Le mot-clé ''auto'' dit au compilateur d'inférer le type. Si nous ne mettons pas auto
, nous aurions du écrire le type de l'itérateur qui est exposé par std::set, soit std::set<int>::iterator
. Cependant, le compilateur n'est pas toujours en mesure d'inférer (de calculer) le type, dans ce cas, un message d'erreur sera généré et il vous appartiendra de soit fournir des informations complémentaires au compilateur permettant d'inférer le type ou au contraire de définir le type.
De plus, upSet
est un itérateur de type bidirectional iterator. De fait upSet
n'implante l'opération -
dite distance qui n'est implanter que par les itérateurs étant des random access iterator.
Ceci fait que le code suivant :
std::cout << "first value greater than 20 at position "
<< (setUp - intSet.begin()) << '\n';
ne compile pas puisque nous l'opération '-' n'est pas défini pour des objets de type std::set<int>::iterator
.
Si nous souhaitons connaitre la position, il faudra compter le nombre de pas que nous devons effectuer en partant du premier élément contenu dans l'ensemble jusqu'à l'élément qui est référencé par l'itérateur setUp
. Ce qui donne la boucle suivante :
int position = 0; //upSet - v.begin()
for(auto it = intSet.begin(); it != upSet; it++) { position ++; }
std::cout << "First value greater than 20 at position "
<< position << std::endl;
En fait, cette boucle peut-être effectuée par un appel à la fonction std::distance définie dans le fichie d'entête <algorithm>, ce qui réduit le code précédent à :
std::set<int> intSet(v.begin(), v.end());
auto upSet = upper(intSet.begin(), intSet.end(), 20);
std::cout << "First value greater than 20 at position "
<< std::distance(v.begin(), upSet) << std::endl;
L'intérêt de faire appel à la fonction std::distance qui a pour protopye :
template<class inputIterator>
typename std::iterator_traits<inputIterator>::difference_type
distance(inputIterator first, inputIterator last);
</code
est de laisser le compilateur sélectonner l'algorithme le plus efficace pour calculer le nombre d'itérations nécessaires pour aller du premier itérateur ''first'' au second itérateur ''last''. Si les itérateurs sont des itérateurs respectant les exigences relatives à un [[https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator|random access iterator]], dans ce cas, le code exécuté sera équivalent à :
<code cpp>
return last - first;
Par contre si l'itérateur n'est pas un random access iterator, l'itérateur est supposé être un input iterator et en conséquence le code exécuté est similaire au suivant :
std::iterator_traits<inputIterator>::difference_type position = 0; //upSet - v.begin()
for(auto it = intSet.begin(); it != upSet; it++) { position ++; }
return position.
En fait, la classe std::iterator_traits permet d'accéder plus simplement et de manière homogène aux champs des itérateurs ou des éléments assimilés à des itérateurs comme les pointeurs. Cette classe expose plusieurs définitions de type et notamment la définition de type qui s'avère être iterator_category
qui identifie le type de l'itérateur.
Ainsi le code de la fonction std::distance
devient :
namespace {
template<class inputIterator, class iteratorTag>
typename std::iterator_traits<inputIterator>::difference_type
distance(inputIterator first, inputIterator last, iteratorTag)
{
std::iterator_traits<inputIterator>::difference_type position = 0; //upSet - v.begin()
for(auto it = intSet.begin(); it != upSet; it++) { position ++; }
return position.
}
template<class inputIterator>
typename std::iterator_traits<inputIterator>::difference_type
distance(inputIterator first, inputIterator last, std::random_access_iterator_tag)
{
return last - first;
}
}
template<class inputIterator>
typename std::iterator_traits<inputIterator>::difference_type
distance(inputIterator first, inputIterator last)
{
return distance(first, last,
std::iterator_traits<inputIterator>::iterator_category());
}
Quand on appelle la fonction distance
, elle appelle la fonction distance qui prend trois arguments, le dernier étant un objet ayant un type identifiant le type d'itérateur. Deux fonctions distances
internes (ie. dans un espace de nom anoyme) sont définies:
template<class inputIterator, class iteratorTag>
typename std::iterator_traits<inputIterator>::difference_type
distance(inputIterator first, inputIterator last, iteratorTag)
Cette fonction est la fonction générique qui fonctionne pour l'intégralité des différents type d'itérateurs iteratorTag
.
template<class inputIterator>
typename std::iterator_traits<inputIterator>::difference_type
distance(inputIterator first, inputIterator last, std::random_access_iterator_tag)
Cette fonction est la version spécialisée pour le type iteratorTag
qui doit être égal à std::random_access_iterator_tag.
Comme la version spécialisée est prioritaire sur la version générique, si l'itérateur définit iterator_category
comme étant std::random_access_iterator_tag, c'est la version spécialisée de la fonction qui sera appelé, sinon la version générique, ce qui permet d'avoir recours à la méthode optimale pour calculer la distance.