Cppreference a cet exemple de code pour std::transform
:
std::vector<std::size_t> ordinals;
std::transform(s.begin(), s.end(), std::back_inserter(ordinals),
[](unsigned char c) -> std::size_t { return c; });
Mais cela dit aussi:
std::transform
ne garantit pas l'application dans l'ordre deunary_op
oubinary_op
. Pour appliquer une fonction à une séquence dans l'ordre ou pour appliquer une fonction qui modifie les éléments d'une séquence, utilisezstd::for_each
.
C'est probablement pour permettre des implémentations parallèles. Cependant, le troisième paramètre de std::transform
est un LegacyOutputIterator
qui a la postcondition suivante pour ++r
:
Après cette opération, il
r
n'est pas nécessaire d'être incrémentable et aucune copie de la valeur précédente der
ne doit plus être déréférencable ou incrémentable.
Il me semble donc que l'affectation de la sortie doit se faire dans l'ordre. Signifient-ils simplement que l'application de unary_op
peut être hors service et stockée dans un emplacement temporaire, mais copiée dans l'ordre de sortie? Cela ne ressemble pas à quelque chose que vous voudriez faire.
La plupart des bibliothèques C ++ n'ont pas encore implémenté d'exécuteurs parallèles, mais Microsoft l'a fait. Je suis sûr que c'est le code correspondant, et je pense qu'il appelle cette fonction pour enregistrer des morceaux de itérateurs à la sortie, ce qui est certainement pas une chose valable de le faire parce que peut être invalidée par incrémenter copies.populate()
LegacyOutputIterator
Qu'est-ce que je rate?
la source
transform
version qui décide d'utiliser ou non le paralélisme. Letransform
pour les grands vecteurs échoue.s
, ce qui invalide les itérateurs.std::transform
avec la politique d'exaction, un itérateur d'accès aléatoire est requis, ce quiback_inserter
ne peut pas être réalisé. La documentation des pièces citées par l'OMI fait référence à ce scénario. Notez l'exemple dans la documentation utilisestd::back_inserter
.Réponses:
1) Les exigences de l'itérateur de sortie dans la norme sont complètement rompues. Voir LWG2035 .
2) Si vous utilisez un itérateur de sortie purement et une plage de source d'entrée purement, l'algorithme ne peut pas faire grand-chose d'autre en pratique; il n'a d'autre choix que d'écrire dans l'ordre. (Cependant, une implémentation hypothétique peut choisir de mettre en cas particulier ses propres types, comme
std::back_insert_iterator<std::vector<size_t>>
; je ne vois pas pourquoi une implémentation voudrait le faire ici, mais elle est autorisée à le faire.)3) Rien dans la norme ne garantit l'
transform
application des transformations dans l'ordre. Nous examinons un détail d'implémentation.Cela
std::transform
ne nécessite que des itérateurs de sortie ne signifie pas qu'il ne peut pas détecter des forces d'itérateur plus élevées et réorganiser les opérations dans de tels cas. En effet, les algorithmes distribuent tout le temps la force des itérateurs , et ils ont un traitement spécial pour les types d'itérateurs spéciaux (comme les pointeurs ou les itérateurs vectoriels) tout le temps .Lorsque la norme veut garantir une commande particulière, elle sait comment la dire (voir
std::copy
«partir defirst
et continuer verslast
»).la source
De
n4385
:§25.6.4 Transformer :
§23.5.2.1.2 back_inserter
§23.5.2.1 Modèle de classe back_insert_iterator
std::back_inserter
Ne peut donc pas être utilisé avec des versions parallèles destd::transform
. Les versions qui prennent en charge les itérateurs de sortie lisent à partir de leur source avec les itérateurs d'entrée. Comme les itérateurs d'entrée ne peuvent être pré- et post-incrémentés (§23.3.5.2 Itérateurs d'entrée) et qu'il n'y a qu'une exécution séquentielle ( c'est -à- dire non parallèle), l'ordre doit être préservé entre eux et l'itérateur de sortie.la source
std::advance
n'a qu'une seule définition qui prend d' entrée-itérateurs , mais libstdc ++ fournit des versions supplémentaires pour bidirectionnelle itérateurs et -accès-itérateurs aléatoires . La version particulière est ensuite exécutée en fonction du type d'itérateur passé .ForwardIterator
cela ne signifie pas que vous devez faire les choses dans l'ordre. Mais vous avez souligné ce que j'ai manqué - pour les versions parallèles qu'ils n'utilisentForwardIterator
pasOutputIterator
.Donc, ce qui m'a manqué, c'est que les versions parallèles prennent
LegacyForwardIterator
s, nonLegacyOutputIterator
. ALegacyForwardIterator
peut être incrémenté sans invalider des copies de celui-ci, il est donc facile de l'utiliser pour implémenter un parallèle hors servicestd::transform
.Je pense que les versions non parallèles de
std::transform
doivent être exécutées dans l'ordre. Soit cppreference se trompe, soit la norme laisse simplement cette exigence implicite car il n'y a pas d'autre moyen de l'implémenter. (Le fusil de chasse ne parcourt pas la norme pour le savoir!)la source
transform
doit être en ordre.LegacyOutputIterator
vous oblige à l'utiliser dans l'ordre.std::back_insert_iterator<std::vector<T>>
etstd::vector<T>::iterator
. Le premier doit être en ordre. Le second n'a pas une telle restrictionLegacyForwardIterator
dans le non parallèletransform
, il pourrait avoir une spécialisation pour ce qui le fait dans le désordre. Bon point.Je crois que la transformation est garantie d'être traitée dans l'ordre .
std::back_inserter_iterator
est un itérateur de sortie (soniterator_category
type de membre est un alias pourstd::output_iterator_tag
) selon [back.insert.iterator] .Par conséquent,
std::transform
n'a pas d'autre option sur la façon de procéder à la prochaine itération que d'appeler membreoperator++
sur leresult
paramètre.Bien sûr, cela n'est valable que pour les surcharges sans politique d'exécution, où
std::back_inserter_iterator
ne peut pas être utilisé (ce n'est pas un itérateur de transfert ).BTW, je ne voudrais pas argumenter avec des citations de cppreference. Les déclarations y sont souvent imprécises ou simplifiées. Dans de tels cas, il est préférable de regarder la norme C ++. Où, en ce qui concerne
std::transform
, il n'y a aucune citation sur l'ordre des opérations.la source