ใช้กับ std :: transform กับ std :: back_inserter ได้ไหม


20

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_opstd::for_each

นี่น่าจะอนุญาตให้มีการใช้งานแบบขนาน อย่างไรก็ตามพารามิเตอร์ที่สามของstd::transformคือLegacyOutputIteratorซึ่งมี postcondition ต่อไปนี้สำหรับ++r:

หลังจากการดำเนินการrนี้ไม่จำเป็นต้องเพิ่มและสำเนาใด ๆ ของค่าก่อนหน้าของrไม่จำเป็นต้องถูกยกเลิกหรือเพิ่มเติม

สำหรับฉันแล้วดูเหมือนว่าการกำหนดผลลัพธ์จะต้องเกิดขึ้นตามลำดับ พวกเขาหมายถึงว่าแอปพลิเคชันunary_opอาจไม่เป็นระเบียบและจัดเก็บในตำแหน่งชั่วคราว แต่คัดลอกไปยังเอาต์พุตตามลำดับหรือไม่ ไม่ได้ดูเหมือนสิ่งที่คุณอยากจะทำ

ไลบรารี C ++ ส่วนใหญ่ยังไม่ได้ใช้ตัวจัดการแบบขนานจริง ๆ แต่ Microsoft มี ผมค่อนข้างมั่นใจว่านี้เป็นรหัสที่เกี่ยวข้องและผมคิดว่ามันสายนี้populate()ฟังก์ชั่นการบันทึกเพื่อ iterators ชิ้นของการส่งออกซึ่งก็ไม่ได้เป็นสิ่งที่ถูกต้องที่จะทำเพราะ LegacyOutputIteratorสามารถจะถูกยกเลิกโดยการเพิ่มสำเนาของมัน

ฉันกำลังคิดถึงอะไร


การทดสอบง่ายๆในgodboltแสดงให้เห็นว่านี่เป็นปัญหา ด้วย C ++ 20 และtransformเวอร์ชันที่ตัดสินใจว่าจะใช้ paralelism หรือไม่ transformสำหรับเวกเตอร์ขนาดใหญ่ล้มเหลว
Croolman

6
@Cololman รหัสของคุณไม่ถูกต้องเนื่องจากคุณใส่กลับเข้าไปsซึ่งจะทำให้การทำซ้ำเป็นโมฆะ
Daniel Langr

@DanielsaysreinstateMonica โอ้ schnitzel คุณพูดถูก กำลังปรับเปลี่ยนและปล่อยให้อยู่ในสถานะที่ไม่ถูกต้อง ฉันนำความคิดเห็นของฉันกลับมา
Croolman

หากคุณใช้std::transformกับนโยบาย exaction จำเป็นต้องใช้ตัววนซ้ำการเข้าถึงแบบสุ่มซึ่งback_inserterไม่สามารถทำได้ เอกสารประกอบชิ้นส่วน IMO ที่ยกมาอ้างถึงสถานการณ์นั้น std::back_inserterตัวอย่างเช่นหมายเหตุในการใช้เอกสาร
Marek R

@Cololman ตัดสินใจใช้ parallelism โดยอัตโนมัติหรือไม่
curiousguy

คำตอบ:


9

1) ข้อกำหนดของตัววนซ้ำเอาต์พุตในมาตรฐานจะแตกหักอย่างสมบูรณ์ ดูLWG2035

2) หากคุณใช้ตัววนซ้ำเอาต์พุตแบบหมดจดและช่วงแหล่งอินพุตอย่างหมดจดมีวิธีอื่นที่สามารถทำได้ในทางปฏิบัติ มันไม่มีทางเลือกนอกจากเขียนตามลำดับ (อย่างไรก็ตามการดำเนินการตามสมมุติฐานสามารถเลือกประเภทของตัวเองเป็นกรณีพิเศษเช่นstd::back_insert_iterator<std::vector<size_t>>ฉันไม่เห็นว่าทำไมการดำเนินการใด ๆ ที่ต้องการทำที่นี่ แต่ได้รับอนุญาตให้ทำเช่นนั้น)

3) ไม่มีอะไรในการรับประกันมาตรฐานที่transformใช้การเปลี่ยนแปลงตามลำดับ เรากำลังดูรายละเอียดการใช้งาน

ที่std::transformต้องมีตัววนซ้ำเอาต์พุตเท่านั้นไม่ได้หมายความว่ามันไม่สามารถตรวจจับความแรงของตัววนซ้ำที่สูงขึ้นและเรียงลำดับการดำเนินการใหม่ในกรณีดังกล่าว อันที่จริงขั้นตอนวิธีการจัดส่งกำลังมาแรง iterator ตลอดเวลาและพวกเขามีการจัดการพิเศษประเภท iterator พิเศษ (เช่นตัวชี้หรือ iterators เวกเตอร์) ตลอดเวลา

เมื่อมาตรฐานต้องการรับประกันคำสั่งซื้อเฉพาะจะรู้วิธีพูด (ดูstd::copy"เริ่มต้นfirstและดำเนินการต่อไปlast")


5

จากn4385:

§25.6.4 Transform :

template<class InputIterator, class OutputIterator, class UnaryOperation>
constexpr OutputIterator
transform(InputIterator first1, InputIterator last1, OutputIterator result, UnaryOperation op);

template<class ExecutionPolicy, class ForwardIterator1, class ForwardIterator2, class UnaryOperation>
ForwardIterator2
transform(ExecutionPolicy&& exec, ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 result, UnaryOperation op);

template<class InputIterator1, class InputIterator2, class OutputIterator, class BinaryOperation>
constexpr OutputIterator
transform(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, OutputIterator result, BinaryOperation binary_op);

template<class ExecutionPolicy, class ForwardIterator1, class ForwardIterator2, class ForwardIterator, class BinaryOperation>
ForwardIterator
transform(ExecutionPolicy&& exec, ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2, ForwardIterator result, BinaryOperation binary_op);

§23.5.2.1.2 back_inserter

template<class Container>
constexpr back_insert_iterator<Container> back_inserter(Container& x);

คืนค่า: back_insert_iterator (x)

§23.5.2.1 แม่แบบเรียน back_insert_iterator

using iterator_category = output_iterator_tag;

ดังนั้นไม่สามารถใช้กับรุ่นขนานstd::back_inserter std::transformเวอร์ชันที่รองรับตัววนซ้ำเอาต์พุตอ่านจากแหล่งที่มาด้วยอินพุตตัววนซ้ำ เนื่องจากอินพุตตัววนซ้ำสามารถทำได้ก่อนและหลังเพิ่มขึ้น (§23.3.5.2อินพุตตัววนซ้ำ) และมีการดำเนินการตามลำดับเท่านั้น ( เช่นไม่ใช่แบบขนาน) คำสั่งจะต้องเก็บรักษาไว้ระหว่างพวกเขากับตัวประมวลผลเอาต์พุต


2
โปรดทราบว่าคำจำกัดความเหล่านี้จากมาตรฐาน C ++ ไม่ได้หลีกเลี่ยงการใช้งานเพื่อให้อัลกอริทึมรุ่นพิเศษที่เลือกสำหรับตัววนซ้ำประเภทเพิ่มเติม ตัวอย่างเช่นstd::advanceมีเพียงหนึ่งคำนิยามที่ใช้เวลานำเข้า iteratorsแต่ libstdc ++ ให้รุ่นเพิ่มเติมสำหรับสองทิศทาง-iteratorsและrandom-access-iterators รุ่นโดยเฉพาะอย่างยิ่งก็จะดำเนินการตามประเภทของ iterator ผ่าน
Daniel Langr

ฉันไม่คิดว่าความคิดเห็นของคุณถูกต้อง - ForwardIteratorไม่ได้หมายความว่าคุณต้องทำสิ่งต่าง ๆ ตามลำดับ แต่คุณได้เน้นสิ่งที่ฉันพลาด - สำหรับรุ่นขนานที่พวกเขาใช้ไม่ได้ForwardIterator OutputIterator
Timmmm

1
อ่าใช่ฉันคิดว่าเราเห็นด้วย
Timmmm

1
คำตอบนี้อาจได้รับประโยชน์จากการเพิ่มคำบางคำเพื่ออธิบายความหมายที่แท้จริง
Barry

1
@Barry เพิ่มคำบางคำใด ๆ และฟีดกลับชื่นชมมาก
พอลอีแวนส์

0

ดังนั้นสิ่งที่ผมพลาดคือการที่รุ่นขนานใช้LegacyForwardIterators LegacyOutputIteratorไม่ LegacyForwardIterator สามารถstd::transformจะเพิ่มขึ้นโดยไม่ต้องต้นเหตุสำเนาของมันจึงเป็นเรื่องง่ายที่จะใช้ในการดำเนินการขนานออกจากการสั่งซื้อ

ฉันคิดว่ารุ่นที่ไม่ขนานstd::transform จะต้องดำเนินการตามลำดับ cppreference อย่างใดอย่างหนึ่งผิดเกี่ยวกับมันหรืออาจเป็นเพียงแค่มาตรฐานทิ้งข้อกำหนดนี้โดยปริยายเพราะไม่มีวิธีอื่นที่จะใช้มัน (ปืนลูกซองไม่ลุยตามมาตรฐานเพื่อค้นหา!)


การแปลงรูปแบบที่ไม่ขนานอาจกระทำได้ตามคำสั่งหากตัววนซ้ำทั้งหมดมีความแข็งแรงเพียงพอ ในตัวอย่างในคำถามที่พวกเขาไม่ได้ดังนั้นความเชี่ยวชาญของtransformจะต้องอยู่ในลำดับ
Caleth

ไม่อาจไม่ได้เพราะLegacyOutputIteratorบังคับให้คุณใช้มันตามลำดับ
Timmmm

มันสามารถมีความเชี่ยวชาญแตกต่างกันสำหรับและstd::back_insert_iterator<std::vector<T>> std::vector<T>::iteratorก่อนอื่นต้องอยู่ในลำดับ ข้อที่สองไม่มีข้อ จำกัด เช่นนี้
Caleth

โอ้รอฉันเห็นสิ่งที่คุณหมายถึง - ถ้าคุณผ่านLegacyForwardIteratorเข้าสู่แบบไม่ขนานtransformก็อาจมีความเชี่ยวชาญสำหรับสิ่งที่ไม่เป็นระเบียบ จุดดี.
Timmmm

0

ผมเชื่อว่าการเปลี่ยนแปลงมีการประกันเพื่อนำมาประมวลผลในการสั่งซื้อ std::back_inserter_iteratorเป็นiterator เอาท์พุท (ของiterator_categoryประเภทสมาชิกเป็นนามแฝงสำหรับstd::output_iterator_tag) ตาม[back.insert.iterator]

ดังนั้นstd::transformมีไม่มีทางเลือกอื่นในการที่จะดำเนินการต่อไปซ้ำไปกว่าสมาชิกโทรoperator++ในresultพารามิเตอร์

แน่นอนนี่ใช้ได้สำหรับโอเวอร์โหลดที่ไม่มีนโยบายการดำเนินการซึ่งstd::back_inserter_iteratorอาจไม่สามารถใช้งานได้ (ไม่ใช่ตัววนซ้ำส่งต่อ )


BTW ฉันจะไม่โต้แย้งด้วยคำพูดจาก cppreference ข้อความที่มักไม่ชัดเจนหรือทำให้เข้าใจง่าย ในกรณีเช่นนี้จะเป็นการดีกว่าที่จะดูมาตรฐาน C ++ ที่เกี่ยวกับstd::transformไม่มีคำพูดเกี่ยวกับคำสั่งของการดำเนินงาน


"มาตรฐาน C ++ ที่เกี่ยวกับ std :: transform ไม่มีคำพูดเกี่ยวกับคำสั่งของการดำเนินการ"เนื่องจากไม่มีการกล่าวถึงคำสั่งซื้อ
HolyBlackCat

@HolyBlackCat ไม่ได้ระบุอย่างชัดเจน แต่กำหนดโดยตัววนซ้ำเอาต์พุต โปรดทราบว่าด้วยตัววนซ้ำเอาต์พุตเมื่อคุณเพิ่มขึ้นคุณจะไม่สามารถตรวจสอบค่าตัววนซ้ำก่อนหน้านี้ได้อีก
Daniel Langr
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.