Cppreference memiliki kode contoh ini untuk 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; });
Tetapi juga dikatakan:
std::transform
tidak menjamin aplikasiunary_op
ataubinary_op
. Untuk menerapkan fungsi ke urutan secara berurutan atau menerapkan fungsi yang memodifikasi elemen urutan, gunakanstd::for_each
.
Ini mungkin untuk memungkinkan implementasi paralel. Namun parameter ketiga std::transform
adalah LegacyOutputIterator
yang memiliki postcondition berikut untuk ++r
:
Setelah operasi
r
ini tidak perlu bertambah dan salinan dari nilai sebelumnyar
tidak lagi diperlukan untuk dereferensi atau bertambah.
Jadi menurut saya penugasan output harus dilakukan secara berurutan. Apakah mereka hanya berarti bahwa aplikasi unary_op
mungkin rusak, dan disimpan ke lokasi sementara, tetapi disalin ke output secara berurutan? Itu tidak terdengar seperti sesuatu yang ingin Anda lakukan.
Kebanyakan pustaka C ++ belum benar-benar mengimplementasikan eksekutor paralel, tetapi Microsoft telah melakukannya. Saya cukup yakin ini adalah kode yang relevan, dan saya pikir ini memanggil fungsi inipopulate()
untuk merekam iterator ke potongan output, yang tentunya bukan hal yang valid untuk dilakukan karena LegacyOutputIterator
dapat dibatalkan dengan menambah salinannya.
Apa yang saya lewatkan?
sumber
transform
versi yang memutuskan apakah akan menggunakan paralelisme atau tidak. Thetransform
untuk vektor besar gagal.s
, yang membatalkan iterator.std::transform
dengan kebijakan exaction, diperlukan iterator akses acak yangback_inserter
tidak dapat dipenuhi. Dokumentasi bagian yang dikutip IMO mengacu pada skenario itu. Perhatikan contoh dalam penggunaan dokumentasistd::back_inserter
.Jawaban:
1) Persyaratan iterator keluaran dalam standar benar-benar rusak. Lihat LWG2035 .
2) Jika Anda menggunakan iterator keluaran murni dan rentang sumber input murni, maka tidak banyak lagi yang dapat dilakukan algoritma dalam praktiknya; ia tidak punya pilihan selain menulis secara berurutan. (Namun, implementasi hipotetis dapat memilih untuk tipe kasus khusus sendiri, seperti
std::back_insert_iterator<std::vector<size_t>>
; Saya tidak melihat mengapa implementasi apa pun ingin melakukannya di sini, tetapi diizinkan untuk melakukannya.)3) Tidak ada dalam jaminan standar yang
transform
menerapkan transformasi secara berurutan. Kami melihat detail implementasi.Itu
std::transform
hanya membutuhkan iterator keluaran tidak berarti tidak dapat mendeteksi kekuatan iterator yang lebih tinggi dan menyusun ulang operasi dalam kasus tersebut. Memang, algoritma mengirimkan kekuatan iterator sepanjang waktu , dan mereka memiliki penanganan khusus untuk tipe iterator khusus (seperti pointer atau iterator vektor) sepanjang waktu .Ketika standar ingin menjamin pesanan tertentu, ia tahu bagaimana mengatakannya (lihat
std::copy
"mulai darifirst
dan melanjutkan kelast
").sumber
Dari
n4385
:§25.6.4 Transformasi :
§23.5.2.1.2 back_inserter
§23.5.2.1 Template kelas back_insert_iterator
Jadi
std::back_inserter
tidak bisa digunakan dengan versi paralelstd::transform
. Versi yang mendukung iterator keluaran membaca dari sumbernya dengan iterator input. Karena iterator input hanya dapat sebelum dan sesudah kenaikan (§23.3.5.2 iterator input) dan hanya ada eksekusi berurutan ( yaitu non-paralel), urutan harus dipertahankan antara mereka dan iterator output.sumber
std::advance
hanya memiliki satu definisi yang mengambil input-iterator , tetapi libstdc ++ menyediakan versi tambahan untuk bidirectional-iterators dan random-access-iterators . Versi tertentu kemudian dieksekusi berdasarkan jenis iterator yang diteruskan .ForwardIterator
bukan berarti Anda harus melakukan sesuatu secara berurutan. Tetapi Anda telah menyoroti hal yang saya lewatkan - untuk versi paralel yang mereka gunakanForwardIterator
tidakOutputIterator
.Jadi hal yang saya lewatkan adalah bahwa versi paralel mengambil
LegacyForwardIterator
s, bukanLegacyOutputIterator
. ALegacyForwardIterator
dapat ditambahkan tanpa membuat salinannya tidak valid, sehingga mudah digunakan untuk mengimplementasikan paralel yang tidak sesuai pesananstd::transform
.Saya pikir versi non-paralel
std::transform
harus dijalankan secara berurutan. Entah cppreference salah tentangnya, atau mungkin standar membiarkan persyaratan ini implisit karena tidak ada cara lain untuk mengimplementasikannya. (Shotgun tidak mengarungi standar untuk mencari tahu!)sumber
transform
harus di-order.LegacyOutputIterator
memaksa Anda untuk menggunakannya secara berurutan.std::back_insert_iterator<std::vector<T>>
danstd::vector<T>::iterator
. Yang pertama harus berurutan. Yang kedua tidak memiliki batasan seperti ituLegacyForwardIterator
non-paraleltransform
, itu bisa memiliki spesialisasi untuk hal yang tidak sesuai aturan . Poin bagus.Saya percaya transformasi dijamin diproses secara berurutan .
std::back_inserter_iterator
adalah output iterator (iterator_category
tipe anggotanya adalah alias untukstd::output_iterator_tag
) menurut [back.insert.iterator] .Akibatnya,
std::transform
memiliki pilihan lain tentang bagaimana untuk melanjutkan ke iterasi berikutnya daripada anggota panggilanoperator++
padaresult
parameter.Tentu saja, ini hanya berlaku untuk kelebihan beban tanpa kebijakan eksekusi, di mana
std::back_inserter_iterator
mungkin tidak digunakan (ini bukan iterator penerusan ).BTW, saya tidak akan berdebat dengan kutipan dari cppreference. Pernyataan di sana sering tidak tepat atau disederhanakan. Dalam hal ini, lebih baik untuk melihat Standar C ++. Di mana, berkenaan dengan
std::transform
, tidak ada kutipan tentang urutan operasi.sumber