Cppreference tiene este código de ejemplo para 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; });
Pero también dice:
std::transformno garantiza la aplicación en orden deunary_opobinary_op. Para aplicar una función a una secuencia en orden o para aplicar una función que modifique los elementos de una secuencia, usestd::for_each.
Esto es presumiblemente para permitir implementaciones paralelas. Sin embargo, el tercer parámetro de std::transformes un LegacyOutputIteratorque tiene la siguiente condición posterior para ++r:
Después de esta operación
rno se requiere que sea incrementable y ya no se requiere que las copias del valor anteriorrsean desreferenciables o incrementables
Entonces me parece que la asignación de la salida debe ocurrir en orden. ¿Simplemente significan que la aplicación de unary_oppuede estar fuera de servicio y almacenada en una ubicación temporal, pero copiada a la salida en orden? Eso no suena como algo que alguna vez quieras hacer.
La mayoría de las bibliotecas de C ++ aún no han implementado ejecutores paralelos, pero Microsoft sí. Estoy bastante seguro de que este es el código relevante, y creo que llama a esta populate()función para grabar iteradores en fragmentos de la salida, lo que seguramente no es algo válido porque LegacyOutputIteratorpuede invalidarse incrementando copias de él.
¿Qué me estoy perdiendo?
fuente

transformversión que decide si se usa o no el paralelismo. Eltransformpara vectores grandes falla.s, lo que invalida los iteradores.std::transformpolítica de exacción, se requiere un iterador de acceso aleatorio queback_inserterno puede cumplir. La documentación de la parte citada por la OMI se refiere a ese escenario. Ejemplo de nota en usos de documentaciónstd::back_inserter.Respuestas:
1) Los requisitos del iterador de salida en el estándar están completamente rotos. Ver LWG2035 .
2) Si usa un iterador puramente de salida y un rango de fuente de entrada pura, entonces hay poco más que el algoritmo puede hacer en la práctica; no tiene más remedio que escribir en orden. (Sin embargo, una implementación hipotética puede optar por casos especiales de sus propios tipos, como
std::back_insert_iterator<std::vector<size_t>>; no veo por qué una implementación querría hacerlo aquí, pero está permitido hacerlo).3) Nada en la norma garantiza que
transformaplica las transformaciones en orden. Estamos viendo un detalle de implementación.El hecho de
std::transformque solo requiera iteradores de salida no significa que no pueda detectar fortalezas de iterador más altas y reordenar las operaciones en tales casos. De hecho, los algoritmos se distribuyen en la fuerza del iterador todo el tiempo , y tienen un manejo especial para los tipos de iteradores especiales (como punteros o iteradores de vectores) todo el tiempo .Cuando el estándar quiere garantizar un pedido en particular, sabe cómo decirlo (vea
std::copy"comenzando desdefirsty pasando alast").fuente
De
n4385:§25.6.4 Transformación :
§23.5.2.1.2 back_inserter
§23.5.2.1 Plantilla de clase back_insert_iterator
Por
std::back_inserterlo tanto , no se puede usar con versiones paralelas destd::transform. Las versiones que admiten iteradores de salida leen desde su fuente con iteradores de entrada. Dado que los iteradores de entrada solo pueden incrementarse antes y después (§23.3.5.2 iteradores de entrada) y solo hay ejecución secuencial ( es decir, no paralela), el orden debe preservarse entre ellos y el iterador de salida.fuente
std::advancesólo tiene una definición que toma de entrada-iteradores , pero libstdc ++ proporciona versiones adicionales para bidireccionales-iteradores y de acceso aleatorio iteradores . La versión particular se ejecuta en función del tipo de iterador pasado .ForwardIteratorNo significa que tenga que hacer las cosas en orden. Pero ha resaltado lo que me perdí, por las versiones paralelas queForwardIteratorno usanOutputIterator.Entonces, lo que me perdí es que las versiones paralelas toman
LegacyForwardIterators, noLegacyOutputIterator. ALegacyForwardIteratorpuede incrementarse sin invalidar copias de él, por lo que es fácil de usar para implementar un paralelo fuera de ordenstd::transform.Creo que las versiones no paralelas de
std::transformdeben ejecutarse en orden. O bien cppreference está equivocado al respecto, o posiblemente el estándar simplemente deja implícito este requisito porque no hay otra forma de implementarlo. (¡La escopeta no vadea el estándar para descubrirlo!)fuente
transformdebe estar en orden.LegacyOutputIteratorte obliga a usarlo en orden.std::back_insert_iterator<std::vector<T>>ystd::vector<T>::iterator. El primero debe estar en orden. El segundo no tiene esa restricciónLegacyForwardIteratorno paralelotransform, podría tener una especialización para lo que lo hace fuera de servicio. Buen punto.Creo que la transformación está garantizada para ser procesada en orden .
std::back_inserter_iteratores un iterador de salida (suiterator_categorytipo de miembro es un alias parastd::output_iterator_tag) de acuerdo con [back.insert.iterator] .En consecuencia, no
std::transformtiene otra opción sobre cómo proceder a la siguiente iteración que llamar al miembrooperator++en elresultparámetro.Por supuesto, esto es válido solo para sobrecargas sin política de ejecución, donde
std::back_inserter_iteratorno se puede usar (no es un iterador de reenvío ).Por cierto, no discutiría con citas de cppreference. Las declaraciones allí a menudo son imprecisas o simplificadas. En casos como estos, es mejor mirar el Estándar C ++. Donde, con respecto a
std::transform, no hay una cita sobre el orden de las operaciones.fuente