Cppreference имеет этот пример кода для 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; });
Но это также говорит:
std::transform
не гарантирует применение в порядкеunary_op
илиbinary_op
. Чтобы применить функцию к последовательности по порядку или применить функцию, которая изменяет элементы последовательности, используйтеstd::for_each
.
Предположительно, это допускает параллельные реализации. Однако третий параметр std::transform
представляет собой LegacyOutputIterator
следующее условие ++r
:
После этой операции
r
нет необходимости увеличивать ее, и любые копии предыдущего значенияr
больше не должны быть разыменованными или увеличиваемыми.
Поэтому мне кажется, что назначение вывода должно происходить по порядку. Означают ли они просто, что приложение unary_op
может быть не в порядке и сохранено во временном местоположении, но скопировано в выходные данные по порядку? Это не похоже на то, что вы когда-либо хотели бы сделать.
Большинство библиотек C ++ на самом деле еще не реализовали параллельных исполнителей, но Microsoft имеет. Я почти уверен, что это релевантный код, и я думаю, что он вызывает эту populate()
функцию для записи итераторов в порции вывода, что, безусловно, не является допустимым действием, поскольку его LegacyOutputIterator
можно сделать недействительным, увеличив его копии.
Что мне не хватает?
источник
transform
версией, которая решает, использовать ли паралелизм или нет. Дляtransform
больших векторов не удается.s
, что делает итераторы недействительными.std::transform
политику exaction, то необходим итератор произвольного доступа, которыйback_inserter
не может быть выполнен. ИМО цитирует часть документации, относящейся к этому сценарию. Обратите внимание на пример использования документацииstd::back_inserter
.Ответы:
1) Требования к выходному итератору в стандарте полностью нарушены. Смотри LWG2035 .
2) Если вы используете чисто выходной итератор и чисто исходный диапазон входных данных, то на практике алгоритм мало что может сделать; у него нет другого выбора, кроме как писать по порядку. (Тем не менее, гипотетическая реализация может выбрать особый случай для своих собственных типов, например
std::back_insert_iterator<std::vector<size_t>>
; я не понимаю, почему любая реализация захотела бы сделать это здесь, но это разрешено делать.)3) Ничто в стандарте не гарантирует, что
transform
применения преобразований по порядку. Мы смотрим на детали реализации.Это
std::transform
требует только выходных итераторов, но это не значит, что он не может обнаружить более сильные итераторы и переупорядочить операции в таких случаях. Действительно, алгоритмы отправки на итератора силы все время , и они имеют специальную обработку для специальных типов итераторов (например , указатели или векторных итераторов) все время .Когда стандарт хочет гарантировать определенный заказ, он знает, как это сказать (см.
std::copy
«Начиная сfirst
и переходя кlast
»).источник
От
n4385
:§25.6.4 Преобразование :
§23.5.2.1.2 back_inserter
§23.5.2.1 Шаблон класса back_insert_iterator
Поэтому
std::back_inserter
нельзя использовать с параллельными версиямиstd::transform
. Версии, которые поддерживают выходные итераторы, читают из их источника входные итераторы. Поскольку входные итераторы могут быть только до и после приращения (§23.3.5.2 Входные итераторы) и существует только последовательное ( то есть непараллельное) выполнение, порядок между ними и выходным итератором должен быть сохранен.источник
std::advance
имеет только одно определение, которое принимает итераторы ввода , но libstdc ++ предоставляет дополнительные версии для двунаправленных итераторов и итераторов с произвольным доступом . Затем конкретная версия выполняется в зависимости от типа переданного итератора .ForwardIterator
это не значит, что вы должны делать все по порядку. Но вы подчеркнули то, что я пропустил - для параллельных версий ониForwardIterator
не пользуютсяOutputIterator
.Поэтому я упустил то, что параллельные версии берут
LegacyForwardIterator
s, а неLegacyOutputIterator
. Значение ALegacyForwardIterator
можно увеличивать, не аннулируя его копии, поэтому его легко использовать для реализации неупорядоченной параллелиstd::transform
.Я думаю, что непараллельные версии
std::transform
должны быть выполнены в порядке. Либо cppreference ошибочен, либо, возможно, стандарт просто оставляет это требование неявным, потому что другого способа его реализации нет. (Дробовик не пробирается через стандарт, чтобы узнать!)источник
transform
должна быть в порядке.LegacyOutputIterator
заставляет вас использовать его по порядку.std::back_insert_iterator<std::vector<T>>
иstd::vector<T>::iterator
. Первое должно быть в порядке. Второй не имеет такого ограниченияLegacyForwardIterator
в непараллельtransform
, это может иметь специализацию для того, что делает это не по порядку. Хорошая точка зрения.Я считаю, что преобразование гарантированно будет обработано по порядку .
std::back_inserter_iterator
является выходным итератором (егоiterator_category
тип члена является псевдонимом дляstd::output_iterator_tag
) в соответствии с [back.insert.iterator] .Следовательно,
std::transform
не имеет никакого другого выбора , как перейти к следующей итерации , чем член вызоваoperator++
поresult
параметру.Конечно, это действительно только для перегрузок без политики выполнения, где
std::back_inserter_iterator
их нельзя использовать (это не итератор пересылки ).Кстати, я бы не стал спорить с цитатами из cppreference. Утверждения там часто неточны или упрощены. В таких случаях лучше взглянуть на стандарт C ++. Где, относительно
std::transform
, нет цитаты о порядке операций.источник