web-dev-qa-db-fra.com

Est-il valide d'utiliser std :: transform avec std :: back_inserter?

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 de unary_op Ou binary_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, utilisez std::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 n'est pas nécessaire que r soit incrémentable et aucune copie de la valeur précédente de r ne doit plus être déréférencable ou incrémentable.

Il me semble donc que l'affectation de la sortie doit se produire 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 la sortie dans l'ordre? 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 assez sûr this est le code pertinent, et je pense qu'il appelle cette fonction populate() function pour enregistrer des itérateurs en morceaux de la sortie, ce qui n'est sûrement pas une chose valide à faire car LegacyOutputIterator peut être invalidé en incrémentant des copies de celui-ci.

Qu'est-ce que je rate?

20
Timmmm

Je crois que la transformation est garantie d'être traitée dans l'ordre . std::back_inserter_iterator est un itérateur de sortie (son iterator_category le type de membre est un alias pour std::output_iterator_tag) selon [back.insert.iterator] .

Par conséquent, std::transform n'a aucune autre option sur la façon de procéder à l'itération suivante que d'appeler le membre operator++ sur le paramètre result.

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 pas de citation sur l'ordre des opérations.

0
Daniel Langr

Donc, ce que j'ai manqué, c'est que les versions parallèles prennent LegacyForwardIterator s, pas LegacyOutputIterator. Un LegacyForwardIterator peut être incrémenté sans invalider des copies de celui-ci, il est donc facile de l'utiliser pour implémenter un parallèle désordonné std::transform.

Je pense que les versions non parallèles de std::transform doit être exécuté dans l'ordre. Soit cppreference est erroné, 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!)

0
Timmmm