La norme a été modifiée depuis que la question (et la plupart des réponses) ont été publiées dans la résolution de ce rapport d'anomalie .
La façon de faire fonctionner une for(:)
boucle sur votre type X
est désormais l'une des deux façons suivantes:
Créer un membre X::begin()
et X::end()
renvoyer quelque chose qui agit comme un itérateur
Créez une fonction gratuite begin(X&)
et end(X&)
qui retourne quelque chose qui agit comme un itérateur, dans le même espace de nom que votre type X
.¹
Et similaire pour les const
variations. Cela fonctionnera à la fois sur les compilateurs qui implémentent les modifications de rapport de défaut et sur les compilateurs qui ne le font pas.
Les objets retournés ne doivent pas nécessairement être des itérateurs. La for(:)
boucle, contrairement à la plupart des parties de la norme C ++, est spécifiée pour s'étendre à quelque chose d'équivalent à :
for( range_declaration : range_expression )
devient:
{
auto && __range = range_expression ;
for (auto __begin = begin_expr,
__end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
où les variables commençant par __
sont uniquement destinées à l'exposition, et begin_expr
et end_expr
est la magie qui appelle begin
/ end
.²
Les exigences sur la valeur de retour de début / fin sont simples: vous devez surcharger la pré- ++
, vous assurer que les expressions d'initialisation sont valides, binaires !=
qui peuvent être utilisés dans un contexte booléen, unaire *
qui renvoie quelque chose que vous pouvez assigner-initialiserrange_declaration
et exposer un public destructeur.
Le faire d'une manière qui n'est pas compatible avec un itérateur est probablement une mauvaise idée, car les futures itérations de C ++ pourraient être relativement cavalières sur la rupture de votre code si vous le faites.
Soit dit en passant, il est raisonnablement probable qu'une future révision de la norme permettra end_expr
de renvoyer un type différent de celui begin_expr
. Ceci est utile car il permet une évaluation "paresseuse" (comme la détection de terminaison nulle) qui est facile à optimiser pour être aussi efficace qu'une boucle C manuscrite, et d'autres avantages similaires.
¹ Notez que les for(:)
boucles stockent tout temporaire dans une auto&&
variable et vous le transmettent en tant que valeur l. Vous ne pouvez pas détecter si vous parcourez une valeur temporaire (ou une autre valeur); une telle surcharge ne sera pas appelée par unfor(:)
boucle. Voir [stmt.ranged] 1.2-1.3 de n4527.
² Appelez la méthode begin
/ end
, ou la recherche ADL uniquement de la fonction gratuite begin
/ end
, ou magic pour la prise en charge des tableaux de style C. Notez qu'il std::begin
n'est pas appelé sauf s'il range_expression
renvoie un objet de type dans namespace std
ou dépendant de celui-ci.
Dans c ++ 17 l'expression range-for a été mise à jour
{
auto && __range = range_expression ;
auto __begin = begin_expr;
auto __end = end_expr;
for (;__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
avec les types de __begin
et __end
ont été découplés.
Cela permet à l'itérateur final de ne pas être du même type que begin. Votre type d'itérateur de fin peut être une "sentinelle" qui ne prend !=
en charge qu'avec le type d'itérateur de début.
Un exemple pratique de la raison pour laquelle cela est utile est que votre itérateur final peut lire "vérifiez votre char*
pour voir s'il pointe vers '0'
" lorsqu'il est ==
avec un char*
. Cela permet à une expression d'intervalle C ++ de générer du code optimal lors de l'itération sur un char*
tampon terminé par null .
struct null_sentinal_t {
template<class Rhs,
std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0
>
friend bool operator==(Rhs const& ptr, null_sentinal_t) {
return !*ptr;
}
template<class Rhs,
std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0
>
friend bool operator!=(Rhs const& ptr, null_sentinal_t) {
return !(ptr==null_sentinal_t{});
}
template<class Lhs,
std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0
>
friend bool operator==(null_sentinal_t, Lhs const& ptr) {
return !*ptr;
}
template<class Lhs,
std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0
>
friend bool operator!=(null_sentinal_t, Lhs const& ptr) {
return !(null_sentinal_t{}==ptr);
}
friend bool operator==(null_sentinal_t, null_sentinal_t) {
return true;
}
friend bool operator!=(null_sentinal_t, null_sentinal_t) {
return false;
}
};
exemple en direct dans un compilateur sans prise en charge complète de C ++ 17; for
boucle développée manuellement.
begin/end
ou un ami, statique ou librebegin/end
.