คำถามนี้ไม่สามารถตอบได้อย่างสมบูรณ์ในรหัส คุณอาจจะสามารถเขียนรหัส "เทียบเท่า" ได้บ้าง แต่ไม่ได้ระบุมาตรฐานดังกล่าว
เมื่อออกนอกเส้นทางมา[expr.prim.lambda]
เริ่มกันเลย สิ่งแรกที่ควรทราบคือตัวสร้างจะกล่าวถึงเฉพาะใน[expr.prim.lambda.closure]/13
:
ประเภทการปิดที่สัมพันธ์กับlambda-expressionไม่มี constructor เริ่มต้นหากlambda-expressionมีlambda-captureและ default constructor เริ่มต้นเป็นอย่างอื่น มันมีตัวสร้างการคัดลอกเริ่มต้นและตัวสร้างการย้ายเริ่มต้น ([class.copy.ctor]) มีตัวดำเนินการกำหนดค่าการคัดลอกที่ถูกลบหากlambda-expressionมีlambda-captureและตัวดำเนินการคัดลอกและย้ายที่เป็นค่าเริ่มต้นเป็นอย่างอื่น ([class.copy.assign]) [ หมายเหตุ:ฟังก์ชั่นสมาชิกพิเศษเหล่านี้มีการกำหนดโดยนัยตามปกติและอาจถูกกำหนดเป็นลบ - บันทึกท้าย ]
ดังนั้นทันทีที่ค้างคาวควรมีความชัดเจนว่าคอนสตรัคเตอร์ไม่ได้เป็นทางการตามที่กำหนดไว้ในการถ่ายภาพวัตถุ คุณสามารถเข้าใกล้ได้ (ดูคำตอบ cppinsights.io) แต่รายละเอียดแตกต่างกัน (โปรดสังเกตว่ารหัสในคำตอบนั้นสำหรับกรณีที่ 4 ไม่ได้รวบรวม)
นี่เป็นมาตราฐานหลักที่จำเป็นในการหารือกรณีที่ 1:
[expr.prim.lambda.capture]/10
[... ]
สำหรับแต่ละเอนทิตีที่บันทึกโดยการคัดลอกสมาชิกข้อมูลที่ไม่คงที่ที่ไม่มีชื่อจะถูกประกาศในประเภทการปิด คำสั่งประกาศของสมาชิกเหล่านี้ไม่ได้ระบุไว้ ประเภทของสมาชิกข้อมูลดังกล่าวเป็นประเภทอ้างอิงถ้ากิจการเป็นการอ้างอิงไปยังวัตถุการอ้างอิง lvalue ไปยังประเภทฟังก์ชั่นอ้างอิงถ้ากิจการเป็นการอ้างอิงไปยังฟังก์ชั่นหรือประเภทของกิจการที่ถูกจับที่สอดคล้องกันเป็นอย่างอื่น สมาชิกของสหภาพที่ไม่ระบุชื่อจะไม่ถูกคัดลอก
[expr.prim.lambda.capture]/11
idทุกนิพจน์ภายในคำสั่งผสมของแลมบ์ดา - นิพจน์ที่เป็นการใช้ประโยชน์จากเอนทิตีที่ดักจับโดยการคัดลอกจะถูกแปลงเป็นการเข้าถึงสมาชิกข้อมูลที่ไม่มีชื่อที่สอดคล้องกันของประเภทการปิด [ ... ]
[expr.prim.lambda.capture]/15
เมื่อประเมินแลมบ์ดานิพจน์เอนทิตีที่ถูกจับโดยการคัดลอกจะถูกใช้เพื่อกำหนดค่าเริ่มต้นให้กับสมาชิกข้อมูลที่ไม่คงที่ที่สอดคล้องกันของวัตถุปิดที่เกิดขึ้นและสมาชิกข้อมูลที่ไม่คงที่ที่สอดคล้องกับการเริ่มต้น ระบุโดย initializer ที่สอดคล้องกัน (ซึ่งอาจเป็นการคัดลอกหรือการกำหนดค่าเริ่มต้นโดยตรง) [ ... ]
ลองนำสิ่งนี้ไปใช้กับกรณีของคุณ 1:
กรณีที่ 1: การดักจับตามค่า / การดักจับเริ่มต้นตามค่า
int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };
ประเภทปิดแลมบ์ดานี้จะมีชื่อไม่คงที่ข้อมูลสมาชิก (ขอเรียกว่า__x
) ประเภทint
(ตั้งแต่x
จะไม่อ้างอิงหรือฟังก์ชั่น) และการเข้าถึงภายในร่างกายแลมบ์ดาจะเปลี่ยนการเข้าถึงx
__x
เมื่อเราประเมินการแสดงออกแลมบ์ดา (เช่นเมื่อกำหนดไปlambda
) เราโดยตรงการเริ่มต้น ด้วย__x
x
กล่าวโดยย่อจะมีเพียงหนึ่งสำเนาเท่านั้น Constructor ของประเภทการปิดไม่เกี่ยวข้องและเป็นไปไม่ได้ที่จะแสดงสิ่งนี้ใน "ปกติ" C ++ (โปรดทราบว่าประเภทการปิดไม่ได้เป็นประเภทรวม )
การอ้างอิงที่เกี่ยวข้องกับ[expr.prim.lambda.capture]/12
:
เอนทิตีถูกจับโดยการอ้างอิงถ้ามันถูกจับโดยปริยายหรืออย่างชัดเจน แต่ไม่ถูกจับโดยการคัดลอก มันไม่ได้ระบุว่าสมาชิกข้อมูลที่ไม่คงที่ไม่มีชื่อเพิ่มเติมจะประกาศในประเภทการปิดสำหรับหน่วยงานที่บันทึกโดยอ้างอิง [ ... ]
มีอีกย่อหน้าเกี่ยวกับการอ้างอิงการอ้างอิง แต่เราไม่ได้ทำอย่างนั้น
ดังนั้นสำหรับกรณีที่ 2:
กรณีที่ 2: การดักจับโดยการอ้างอิง / การดักจับเริ่มต้นโดยการอ้างอิง
int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };
เราไม่ทราบว่ามีการเพิ่มสมาชิกประเภทการปิดหรือไม่ x
ในร่างกายแลมบ์ดาอาจจะหมายถึงx
ภายนอกโดยตรง นี่ขึ้นอยู่กับคอมไพเลอร์เพื่อคิดออกและมันจะทำในภาษากลางบางรูปแบบ (ซึ่งแตกต่างจากคอมไพเลอร์เป็นคอมไพเลอร์) ไม่ใช่การแปลงซอร์สของรหัส C ++
เริ่มการดักจับมีรายละเอียดใน[expr.prim.lambda.capture]/6
:
การเริ่มต้นการจับภาพจะทำงานราวกับว่ามันประกาศและจับตัวแปรของรูปแบบauto init-capture ;
ที่มีภูมิภาคการประกาศเป็นคำสั่งผสมของแลมบ์ดานิพจน์ยกเว้นว่า:
- (6.1) หากการจับภาพเกิดจากการคัดลอก (ดูด้านล่าง) สมาชิกข้อมูลที่ไม่คงที่ที่ประกาศไว้สำหรับการดักจับและตัวแปรจะถือว่าเป็นสองวิธีที่แตกต่างกันในการอ้างถึงวัตถุเดียวกันซึ่งมีอายุการใช้งานของข้อมูลไม่คงที่ สมาชิกและไม่มีการคัดลอกและทำลายเพิ่มเติมและ
- (6.2) หากการดักจับเป็นการอ้างอิงอายุการใช้งานของตัวแปรจะสิ้นสุดลงเมื่ออายุการใช้งานของวัตถุปิดสิ้นสุดลง
ให้ดูที่กรณีที่ 3:
กรณีที่ 3: การจับภาพเริ่มต้นทั่วไป
auto lambda = [x = 33]() { std::cout << x << std::endl; };
ตามที่ระบุไว้ลองจินตนาการว่านี่เป็นตัวแปรที่ถูกสร้างขึ้นauto x = 33;
และคัดลอกโดยชัดเจน ตัวแปรนี้เป็นเพียง "มองเห็นได้" ภายในร่างกายแลมบ์ดา ดังที่กล่าวไว้[expr.prim.lambda.capture]/15
ก่อนหน้านี้การกำหนดค่าเริ่มต้นของสมาชิกประเภทปิด ( __x
สำหรับลูกหลาน) ที่สอดคล้องกันนั้นเริ่มต้นจากการประเมินค่าของการแสดงออกแลมบ์ดา
สำหรับการหลีกเลี่ยงข้อสงสัย: นี่ไม่ได้หมายความว่าจะมีการเตรียมใช้งานสองครั้งที่นี่ auto x = 33;
เป็น "ราวกับว่า" จะได้รับมรดกความหมายของการจับภาพที่เรียบง่ายและการเริ่มต้นอธิบายคือการปรับเปลี่ยนความหมายเหล่านั้น การเริ่มต้นเพียงครั้งเดียวเกิดขึ้น
นอกจากนี้ยังครอบคลุมถึงกรณีที่ 4:
auto l = [p = std::move(unique_ptr_var)]() {
// do something with unique_ptr_var
};
สมาชิกประเภทการปิดจะเริ่มต้นได้โดย__p = std::move(unique_ptr_var)
เมื่อมีการประเมินการแสดงออกของแลมบ์ดา (เช่นเมื่อl
ถูกกำหนดให้) การเข้าถึงในร่างกายแลมบ์ดาจะกลายเป็นเข้าถึงp
__p
TL; DR: เฉพาะการคัดลอก / การกำหนดค่าเริ่มต้น / การเคลื่อนย้ายที่น้อยที่สุดเท่านั้นที่ทำได้ (ดังที่คาดหวัง / คาดหวัง) ฉันจะสมมติว่า lambdas ไม่ได้ระบุไว้ในแง่ของการแปลงแหล่งที่มา (ต่างจากน้ำตาล syntactic อื่น ๆ ) อย่างแน่นอนเพราะการแสดงสิ่งต่าง ๆ ในแง่ของการก่อสร้างจะทำให้การดำเนินการฟุ่มเฟือย
ฉันหวังว่าสิ่งนี้จะทำให้เกิดความกลัวในคำถาม :)