โดยการใช้auto&& var = <initializer>
ที่คุณกำลังพูดว่า: ฉันจะยอมรับการเริ่มต้นใด ๆ ไม่ว่าจะเป็น lvalue หรือ rvalue แสดงออกและเราจะรักษา constness โดยทั่วไปจะใช้สำหรับการส่งต่อ (โดยปกติจะมีT&&
) ด้วยเหตุนี้การทำงานเป็นเพราะ "การอ้างอิงสากล" auto&&
หรือT&&
จะผูกกับอะไร
คุณอาจจะบอกว่าดีทำไมไม่เพียงแค่ใช้const auto&
เพราะที่จะยังผูกกับอะไร? ปัญหาของการใช้การconst
อ้างอิงคือมันเป็นconst
! คุณจะไม่สามารถที่จะต่อมาผูกใด ๆ ที่ไม่ใช่การอ้างอิง const หรือเรียกใช้ฟังก์ชันสมาชิกใด ๆ const
ที่ไม่ได้ทำเครื่องหมาย
ยกตัวอย่างเช่นลองนึกภาพว่าคุณต้องการได้รับstd::vector
เอา iterator ไปที่องค์ประกอบแรกและแก้ไขค่าที่ iterator ชี้ไปในทางใดทางหนึ่ง:
auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;
รหัสนี้จะรวบรวมได้ดีโดยไม่คำนึงถึงการแสดงออกเริ่มต้น ทางเลือกที่จะauto&&
ล้มเหลวด้วยวิธีการดังต่อไปนี้:
auto => will copy the vector, but we wanted a reference
auto& => will only bind to modifiable lvalues
const auto& => will bind to anything but make it const, giving us const_iterator
const auto&& => will bind only to rvalues
ดังนั้นสำหรับสิ่งนี้auto&&
ทำงานได้อย่างสมบูรณ์แบบ! ตัวอย่างของการใช้auto&&
สิ่งนี้อยู่ในfor
ลูปที่อิงตามช่วง ดูคำถามอื่นของฉันสำหรับรายละเอียดเพิ่มเติม
หากคุณใช้std::forward
ในauto&&
การอ้างอิงของคุณเพื่อรักษาความจริงที่ว่าเดิมเป็น lvalue หรือ rvalue รหัสของคุณบอกว่า: ตอนนี้ฉันได้รับวัตถุของคุณจากนิพจน์ lvalue หรือ rvalue ทั้งสองฉันต้องการที่จะรักษาคุณค่าอันใดอันหนึ่งไว้ มีดังนั้นฉันสามารถใช้มันได้อย่างมีประสิทธิภาพมากที่สุด - นี่อาจเป็นโมฆะ ในขณะที่:
auto&& var = some_expression_that_may_be_rvalue_or_lvalue;
// var was initialized with either an lvalue or rvalue, but var itself
// is an lvalue because named rvalues are lvalues
use_it_elsewhere(std::forward<decltype(var)>(var));
สิ่งนี้ทำให้use_it_elsewhere
สามารถดึงความกล้าออกมาเพื่อผลการปฏิบัติงาน (หลีกเลี่ยงการทำสำเนา) เมื่อ initializer ดั้งเดิมเป็น rvalue ที่แก้ไขได้
สิ่งนี้หมายความว่าเราสามารถหรือเมื่อเราสามารถขโมยทรัพยากรได้var
หรือไม่ เนื่องจากความauto&&
ต้องการจะผูกมัดกับสิ่งใดก็ตามเราไม่สามารถพยายามที่จะดึงvar
ความกล้าออกมาได้ - มันอาจจะเป็น lvalue หรือ const อย่างไรก็ตามเราสามารถstd::forward
ใช้ฟังก์ชั่นอื่น ๆ ที่อาจทำลายภายในของมันโดยสิ้นเชิง ทันทีที่เราทำเช่นนี้เราควรพิจารณาvar
ให้อยู่ในสถานะที่ไม่ถูกต้อง
ทีนี้เราจะนำสิ่งนี้ไปใช้กับกรณีของauto&& var = foo();
ตามที่กำหนดไว้ในคำถามของคุณโดยที่ foo คืนT
ค่าโดย ในกรณีนี้เราทราบว่าประเภทของจะอนุมานได้ว่าเป็นvar
T&&
เนื่องจากเราทราบได้อย่างแน่นอนว่ามันเป็นค่าใช้จ่ายเราจึงไม่ต้องการการstd::forward
อนุญาตให้ขโมยทรัพยากร ในกรณีเฉพาะนี้โดยรู้ว่าfoo
ผลตอบแทนตามมูลค่าผู้อ่านควรอ่านมันเป็น: ฉันกำลังอ้างอิงค่า rvalue กับค่าที่ส่งคืนจากชั่วคราวfoo
ดังนั้นฉันจึงสามารถย้ายจากมันได้อย่างมีความสุข
ในฐานะที่เป็นภาคผนวกฉันคิดว่ามันคุ้มค่าที่จะกล่าวถึงเมื่อนิพจน์เช่นsome_expression_that_may_be_rvalue_or_lvalue
อาจเปิดขึ้นนอกเหนือจากสถานการณ์ "รหัสของคุณอาจมีการเปลี่ยนแปลง" ดังนั้นนี่คือตัวอย่างที่วางแผนไว้:
std::vector<int> global_vec{1, 2, 3, 4};
template <typename T>
T get_vector()
{
return global_vec;
}
template <typename T>
void foo()
{
auto&& vec = get_vector<T>();
auto i = std::begin(vec);
(*i)++;
std::cout << vec[0] << std::endl;
}
นี่get_vector<T>()
คือการแสดงออกที่น่ารักที่สามารถเป็นได้ทั้ง lvalue หรือ rvalue T
ขึ้นอยู่กับประเภททั่วไป เราเป็นหลักเปลี่ยนประเภทการกลับมาของผ่านพารามิเตอร์แม่แบบของget_vector
foo
เมื่อเราเรียกfoo<std::vector<int>>
มันget_vector
จะส่งคืนglobal_vec
ค่าที่ให้นิพจน์ rvalue อีกวิธีหนึ่งคือเมื่อเราเรียกfoo<std::vector<int>&>
, get_vector
จะกลับglobal_vec
โดยการอ้างอิงผลในการแสดงออก lvalue
ถ้าเราทำ:
foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;
เราได้ผลลัพธ์ต่อไปนี้ตามที่คาดไว้:
2
1
2
2
หากคุณกำลังจะเปลี่ยนauto&&
ในรหัสใด ๆauto
, auto&
, const auto&
หรือconst auto&&
แล้วเราจะไม่ได้รับผลที่เราต้องการ
อีกทางเลือกหนึ่งในการเปลี่ยนตรรกะของโปรแกรมโดยอ้างอิงว่าauto&&
การอ้างอิงของคุณถูกกำหนดค่าเริ่มต้นด้วยการแสดงออก lvalue หรือ rvalue คือการใช้ลักษณะของประเภท:
if (std::is_lvalue_reference<decltype(var)>::value) {
// var was initialised with an lvalue expression
} else if (std::is_rvalue_reference<decltype(var)>::value) {
// var was initialised with an rvalue expression
}
auto&&
? ฉันเคยคิดถึงว่าทำไมช่วงที่ใช้ลูปเพื่อขยายจึงใช้auto&&
เป็นตัวอย่าง แต่ไม่ได้รับรอบ บางทีใครก็ตามที่คำตอบสามารถอธิบายได้