Cppreference ma ten przykładowy kod dla 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; });
Ale mówi również:
std::transform
nie gwarantuje zastosowaniaunary_op
lubbinary_op
. Aby zastosować funkcję do sekwencji w kolejności lub zastosować funkcję, która modyfikuje elementy sekwencji, użyjstd::for_each
.
Ma to prawdopodobnie umożliwić równoległe wdrożenia. Jednak trzecim parametrem std::transform
jest LegacyOutputIterator
następujący warunek ++r
:
Po tej operacji
r
nie wymaga się, aby była inkrementowalna, a wszelkie kopie poprzedniej wartościr
nie muszą już być dereferencyjne ani inkrementowalne.
Wydaje mi się więc, że przyporządkowanie danych wyjściowych musi nastąpić po kolei. Czy oznaczają one po prostu, że aplikacja unary_op
może być nieczynna i przechowywana w tymczasowej lokalizacji, ale kopiowana na wyjście w odpowiedniej kolejności? To nie brzmi jak coś, co kiedykolwiek chciałbyś zrobić.
Większość bibliotek C ++ nie zaimplementowało jeszcze równoległych programów wykonawczych, ale Microsoft ma. Jestem prawie pewien, że jest to odpowiedni kod i myślę, że wywołuje tę populate()
funkcję, aby rejestrować iteratory w porcjach danych wyjściowych, co z pewnością nie jest prawidłową rzeczą, ponieważ LegacyOutputIterator
może zostać unieważnione przez zwiększenie jej kopii.
czego mi brakuje?
źródło
transform
wersją, która decyduje, czy użyć paralelizmu. Wtransform
przypadku dużych wektorów zawodzi.s
, co unieważnia iteratory.std::transform
z zasad exaction, wymagany jest iterator o dostępie swobodnym, któryback_inserter
nie może spełnić. Cytowana przez IMO dokumentacja części odnosi się do tego scenariusza. Uwaga przykład w zastosowaniach dokumentacjistd::back_inserter
.Odpowiedzi:
1) Wymagania iteratora wyjściowego w normie są całkowicie zepsute. Zobacz LWG2035 .
2) Jeśli używasz iteratora czysto wyjściowego i zakresu wyłącznie wejściowego źródła, algorytm może zrobić niewiele więcej; nie ma wyboru, musi pisać w kolejności. (Jednak hipotetyczna implementacja może wybrać specjalne przypadki własnych typów, na przykład
std::back_insert_iterator<std::vector<size_t>>
; Nie rozumiem, dlaczego jakakolwiek implementacja chciałaby to zrobić tutaj, ale jest to dozwolone).3) Nic w standardowych gwarancjach, które
transform
stosują transformacje w kolejności. Patrzymy na szczegół implementacji.To
std::transform
wymaga tylko iteratorów wyjściowych, co nie oznacza, że nie może wykryć wyższych sił iteratora i zmienić kolejność operacji w takich przypadkach. Rzeczywiście, algorytmy wysyłką na iteratora wytrzymałość przez cały czas , a oni mają specjalnego traktowania dla specjalnych typów iterator (jak wskazówki lub iteratory wektor) cały czas .Kiedy standard chce zagwarantować określone zamówienie, wie, jak to powiedzieć (patrz
std::copy
„zaczynając odfirst
i przechodząc dolast
”).źródło
Od
n4385
:§25.6.4 Przekształcenie :
§23.5.2.1.2 back_inserter
§23.5.2.1 Szablon klasy back_insert_iterator
Dlatego
std::back_inserter
nie można go używać z równoległymi wersjamistd::transform
. Wersje obsługujące iteratory wyjściowe czytane ze źródła za pomocą iteratorów wejściowych. Od iteratory wejściowe mogą być tylko przed i po zwiększany (§23.3.5.2 iteratory wejściowe) i jest tylko kolejny ( tj nierównoległe) wykonanie, zlecenie musi być zachowana między nimi a iteratora wyjściowego.źródło
std::advance
ma tylko jedną definicję, która pobiera input-iteratory , ale libstdc ++ zapewnia dodatkowe wersje dla dwukierunkowego iteratory i random-access-iteratorów . Konkretna wersja jest następnie wykonywana na podstawie typu przekazanego iteratora .ForwardIterator
nie oznacza to, że musisz robić rzeczy po kolei. Ale podkreśliły co mi brakowało - dla równoległych wersjach z których korzystająForwardIterator
nieOutputIterator
.Więc tęskniłem za tym, że wersje równoległe wymagają
LegacyForwardIterator
s, a nieLegacyOutputIterator
.LegacyForwardIterator
Może być zwiększany bez unieważniania kopie, więc łatwo jest wykorzystać do wdrożenia out-of-order równoleglestd::transform
.Myślę, że nierównoległe wersje
std::transform
muszą być wykonywane w kolejności. Albo cppreferencja jest w tym zakresie błędna, albo być może standard po prostu pozostawia to wymaganie niejawne, ponieważ nie ma innego sposobu na jego wdrożenie. (Strzelba nie przedziera się przez standard, żeby się dowiedzieć!)źródło
transform
musi być w prawidłowej kolejności.LegacyOutputIterator
zmusza cię do używania go w kolejności.std::back_insert_iterator<std::vector<T>>
istd::vector<T>::iterator
. Pierwszy musi być w porządku. Drugi nie ma takiego ograniczeniaLegacyForwardIterator
nierównoległośćtransform
, może ona mieć specjalizację dla tego, co robi to nie w porządku. Słuszna uwaga.Uważam, że transformacja jest gwarantowana w kolejności .
std::back_inserter_iterator
jest iteratorem wyjściowym (jegoiterator_category
typ elementu jest aliasemstd::output_iterator_tag
) zgodnie z [back.insert.iterator] .W związku z tym
std::transform
nie ma innej opcji, jak przejść do następnej iteracji, niż wywołać członkaoperator++
wresult
parametrze.Oczywiście dotyczy to tylko przeciążeń bez zasad wykonywania, w których
std::back_inserter_iterator
nie można ich używać (nie jest to iterator przekazywania ).BTW, nie argumentowałbym cytatami z cppreferencji. Stwierdzenia tam często są nieprecyzyjne lub uproszczone. W takich przypadkach lepiej spojrzeć na standard C ++. Tam, gdzie w odniesieniu do
std::transform
, nie ma cytatu o kolejności operacji.źródło