Comme beaucoup de gens ces jours-ci, j'ai essayé les différentes fonctionnalités qu'apporte C ++ 11. Un de mes favoris est le "basé sur la gamme pour les boucles".
Je comprends que:
for(Type& v : a) { ... }
Est équivalent à:
for(auto iv = begin(a); iv != end(a); ++iv)
{
Type& v = *iv;
...
}
Et cela begin()
revient simplement a.begin()
pour des conteneurs standard.
Mais que se passe-t-il si je veux rendre mon type personnalisé "basé sur une plage pour la boucle" ?
Dois-je juste me spécialiser begin()
et end()
?
Si mon type personnalisé appartient à l'espace de noms xml
, dois-je définir xml::begin()
ou std::begin()
?
En bref, quelles sont les lignes directrices pour ce faire?
c++
for-loop
c++11
customization
ereOn
la source
la source
begin/end
ou un ami, statique ou librebegin/end
.for( auto x : range<float>(0,TWO_PI, 0.1F) ) { ... }
. Je suis curieux de savoir comment vous contournez le fait que `` opérateur! = () '' Est difficile à définir. Et qu'en est-il du déréférencement (*__begin
) dans ce cas? Je pense que ce serait une grande contribution si quelqu'un nous montrait comment cela se faisait!Réponses:
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 typeX
est désormais l'une des deux façons suivantes:Créer un membre
X::begin()
etX::end()
renvoyer quelque chose qui agit comme un itérateurCréez une fonction gratuite
begin(X&)
etend(X&)
qui retourne quelque chose qui agit comme un itérateur, dans le même espace de nom que votre typeX
.¹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 à :devient:
où les variables commençant par
__
sont uniquement destinées à l'exposition, etbegin_expr
etend_expr
est la magie qui appellebegin
/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 celuibegin_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 uneauto&&
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 gratuitebegin
/end
, ou magic pour la prise en charge des tableaux de style C. Notez qu'ilstd::begin
n'est pas appelé sauf s'ilrange_expression
renvoie un objet de type dansnamespace std
ou dépendant de celui-ci.Dans c ++ 17 l'expression range-for a été mise à jour
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 unchar*
. Cela permet à une expression d'intervalle C ++ de générer du code optimal lors de l'itération sur unchar*
tampon terminé par null .exemple en direct dans un compilateur sans prise en charge complète de C ++ 17;
for
boucle développée manuellement.la source
begin
et desend
fonctions différentes de celles disponibles dans le code normal. Peut-être qu'ils pourraient alors être très spécialisés pour se comporter différemment (c'est-à-dire plus rapidement en ignorant l'argument de fin pour obtenir les optimisations maximales possibles.) Mais je ne suis pas assez bon avec les espaces de noms pour être sûr de savoir comment faire.begin(X&&)
. Le temporaire est suspendu dans les airs parauto&&
une plage basée sur, etbegin
est toujours appelé avec un lvalue (__range
).J'écris ma réponse parce que certaines personnes pourraient être plus satisfaites d'un simple exemple de la vie réelle sans STL inclus.
J'ai ma propre implémentation de tableau de données uniquement pour une raison quelconque, et je voulais utiliser la plage basée sur la boucle. Voici ma solution:
Ensuite, l'exemple d'utilisation:
la source
const
qualificatif de retour pourconst DataType& operator*()
, et de laisser l'utilisateur choisir d'utiliserconst auto&
ouauto&
? Merci quand même, bonne réponse;)La partie pertinente de la norme est 6.5.4 / 1:
Vous pouvez donc effectuer l'une des opérations suivantes:
begin
etend
membres fonctionsbegin
etend
libérer des fonctions qui seront trouvées par ADL (version simplifiée: les mettre dans le même espace de noms que la classe)std::begin
etstd::end
std::begin
appelle debegin()
toute façon la fonction membre, donc si vous implémentez uniquement l'une des options ci-dessus, les résultats devraient être les mêmes, peu importe celui que vous choisissez. Ce sont les mêmes résultats pour les boucles à distance, et aussi le même résultat pour le simple code mortel qui n'a pas ses propres règles de résolution de noms magiques, justeusing std::begin;
suivi d'un appel sans réserve àbegin(a)
.Si vous implémentez les fonctions membres et les fonctions ADL, les boucles for basées sur la plage doivent appeler les fonctions membres, tandis que les simples mortels appellent les fonctions ADL. Assurez-vous qu'ils font la même chose dans ce cas!
Si la chose que vous écrivez implémente l'interface de conteneur, il aura
begin()
etend()
fonctions membres déjà, ce qui devrait être suffisant. S'il s'agit d'une gamme qui n'est pas un conteneur (ce qui serait une bonne idée si elle est immuable ou si vous ne connaissez pas la taille à l'avance), vous êtes libre de choisir.Parmi les options que vous disposez, notez que vous ne devez pas surcharger
std::begin()
. Vous êtes autorisé à spécialiser les modèles standard pour un type défini par l'utilisateur, mais à part cela, l'ajout de définitions à l'espace de noms std est un comportement non défini. Mais de toute façon, la spécialisation des fonctions standard est un mauvais choix, ne serait-ce que parce que le manque de spécialisation partielle des fonctions signifie que vous ne pouvez le faire que pour une seule classe, pas pour un modèle de classe.la source
!=
, préfixe++
et unaire*
. Il est probablement sage de mettre en œuvrebegin()
et lesend()
fonctions membres ou des fonctions non membres ADL que tout retour autre qu'un iterator, mais je pense que c'est légal.std::begin
Je pense que la spécialisation pour renvoyer un non-itérateur est UB.Pour autant que je sache, cela suffit. Vous devez également vous assurer que l'incrémentation du pointeur se produira du début à la fin.
L'exemple suivant (il manque la version const de début et de fin) se compile et fonctionne correctement.
Voici un autre exemple avec des fonctions de début / fin. Ils doivent être dans le même espace de noms que la classe, à cause d'ADL:
la source
return v + 10
.&v[10]
déréférence l'emplacement de mémoire juste après le tableau.Dans le cas où vous souhaitez sauvegarder l'itération d'une classe directement avec son membre
std::vector
oustd::map
, voici le code pour cela:la source
const_iterator
peut également être consulté dans unauto
(C ++ 11) manière via compatiblecbegin
,cend
etc.Ici, je partage l'exemple le plus simple de création de type personnalisé, qui fonctionnera avec " boucle basée sur une plage ":
J'espère que ce sera utile pour un développeur novice comme moi: p :)
Merci.
la source
end()
toute évidence, la fonction elle-même ne déréférence pas un emplacement de mémoire incorrect, car elle ne prend que «l'adresse» de cet emplacement de mémoire. L'ajout d'un élément supplémentaire signifierait que vous auriez besoin de plus de mémoire et que l'utilisationyour_iterator::end()
d'une manière qui déréférencerait cette valeur ne fonctionnerait de toute façon pas avec d'autres itérateurs car ils sont construits de la même manière.return &data[sizeofarray]
il devrait simplement renvoyer les données d'adresse + sizeofarray mais que sais-je,data + sizeofarray
serait la bonne façon d'écrire ceci.La réponse de Chris Redford fonctionne également pour les conteneurs Qt (bien sûr). Voici une adaptation (remarquez que je retourne a
constBegin()
, respectivement àconstEnd()
partir des méthodes const_iterator):la source
Je voudrais élaborer certaines parties de la réponse de @Steve Jessop, pour lesquelles au début je ne comprenais pas. J'espère que ça aide.
https://en.cppreference.com/w/cpp/language/range-for :
Pour une boucle basée sur une plage, les fonctions membres sont sélectionnées en premier.
Mais pour
Les fonctions ADL sont sélectionnées en premier.
Exemple:
la source