คุณต้องเข้าใจปัญหาการส่งต่อ คุณสามารถอ่านรายละเอียดปัญหาทั้งหมดแต่ฉันจะสรุป
โดยพื้นฐานแล้วเมื่อได้รับนิพจน์E(a, b, ... , c)
เราต้องการให้นิพจน์f(a, b, ... , c)
นั้นเทียบเท่า ใน C ++ 03 สิ่งนี้เป็นไปไม่ได้ มีหลายครั้ง แต่พวกเขาล้มเหลวในบางเรื่อง
วิธีที่ง่ายที่สุดคือใช้การอ้างอิง lvalue:
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
E(a, b, c);
}
แต่สิ่งนี้ล้มเหลวในการจัดการค่าชั่วคราว: f(1, 2, 3);
เนื่องจากไม่สามารถผูกกับการอ้างอิง lvalue ได้
ความพยายามครั้งต่อไปอาจเป็น:
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(a, b, c);
}
ซึ่งแก้ไขปัญหาข้างต้น แต่พลิก flops ตอนนี้ไม่อนุญาตให้E
มีอาร์กิวเมนต์ที่ไม่ใช่ const:
int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these
ความพยายามที่สามยอมรับ const อ้างอิง แต่แล้วconst_cast
's const
ห่าง:
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c));
}
สิ่งนี้ยอมรับค่าทั้งหมดสามารถส่งผ่านค่าทั้งหมด แต่อาจนำไปสู่พฤติกรรมที่ไม่ได้กำหนด:
const int i = 1, j = 2, k = 3;
E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!
ทางออกสุดท้ายจะจัดการทุกสิ่งอย่างถูกต้อง ... ด้วยราคาที่ไม่สามารถรักษาได้ คุณให้การโอเวอร์โหลดของf
พร้อมชุดค่าผสมทั้งหมดและไม่ใช่ค่าคงที่:
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);
ไม่มีข้อโต้แย้งต้องมี 2 ชุดNเป็นฝันร้าย เราต้องการทำสิ่งนี้โดยอัตโนมัติ
(นี่คือสิ่งที่เราจะได้รับคอมไพเลอร์อย่างมีประสิทธิภาพสำหรับเราใน C ++ 11)
ใน C ++ 11 เราได้รับโอกาสแก้ไข วิธีการหนึ่งแก้ไขกฎการหักเงินของเทมเพลตสำหรับประเภทที่มีอยู่ แต่สิ่งนี้อาจทำลายรหัสได้มาก ดังนั้นเราต้องหาวิธีอื่น
การแก้ปัญหาคือแทนที่จะใช้ที่เพิ่มใหม่rvalue อ้างอิง ; เราสามารถนำกฎใหม่มาใช้เมื่อลดประเภทการอ้างอิงค่า rvalue และสร้างผลลัพธ์ที่ต้องการ ท้ายที่สุดเราไม่สามารถทำลายรหัสได้ในขณะนี้
หากได้รับการอ้างอิงถึงการอ้างอิง (การอ้างอิงโน้ตเป็นคำที่ครอบคลุมทั้งความหมายT&
และT&&
) เราจะใช้กฎต่อไปนี้เพื่อค้นหาประเภทผลลัพธ์:
"[รับ] ประเภท TR ที่อ้างอิงถึงประเภท T ความพยายามในการสร้างประเภท“ อ้างอิง lvalue กับ cv TR” สร้างประเภท“ อ้างอิง lvalue กับ T” ในขณะที่ความพยายามในการสร้างประเภท“ อ้างอิง rvalue เพื่อ cv TR” สร้างประเภท TR "
หรือในรูปแบบตาราง:
TR R
T& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T& && -> T& // rvalue reference to cv TR -> TR (lvalue reference to T)
T&& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T&& && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)
ถัดไปด้วยการหักล้างอาร์กิวเมนต์เทมเพลต: หากอาร์กิวเมนต์เป็น lvalue A เราจะจัดหาอาร์กิวเมนต์เท็มเพลตที่มีการอ้างอิง lvalue ไปยัง A มิฉะนั้นเราจะอนุมานตามปกติ สิ่งนี้ให้การอ้างอิงสากลที่เรียกว่า( การอ้างอิงการส่งต่อคำว่าขณะนี้เป็นทางการ)
ทำไมถึงมีประโยชน์ เนื่องจากการรวมกันเรารักษาความสามารถในการติดตามหมวดหมู่ค่าของประเภท: ถ้ามันเป็น lvalue เรามีพารามิเตอร์ lvalue-reference มิฉะนั้นเราจะมีพารามิเตอร์ rvalue-reference
ในรหัส:
template <typename T>
void deduce(T&& x);
int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)
สิ่งสุดท้ายคือ "ส่งต่อ" หมวดหมู่ค่าของตัวแปร โปรดจำไว้ว่าเมื่อมีการใช้งานฟังก์ชั่นแล้วพารามิเตอร์สามารถส่งผ่านเป็น lvalue ไปยังสิ่งใดก็ได้:
void foo(int&);
template <typename T>
void deduce(T&& x)
{
foo(x); // fine, foo can refer to x
}
deduce(1); // okay, foo operates on x which has a value of 1
นั่นไม่ดีเลย E ต้องได้รับหมวดหมู่มูลค่าแบบเดียวกันกับที่เราได้รับ! ทางออกคือ:
static_cast<T&&>(x);
สิ่งนี้ทำอะไร พิจารณาว่าเราอยู่ในdeduce
ฟังก์ชั่นและเราได้ผ่าน lvalue วิธีนี้T
เป็นA&
และอื่น ๆ ประเภทเป้าหมายสำหรับหล่อคงเป็นหรือเพียงแค่A& &&
A&
เนื่องจากx
เป็นแล้วA&
เราจึงไม่ทำอะไรเลยและมีการอ้างอิง lvalue อยู่
เมื่อเราได้รับการผ่านการ rvalue, T
เป็นดังนั้นประเภทเป้าหมายสำหรับการหล่อแบบคงที่คือA
A&&
ผลการโยนในการแสดงออก rvalue, ซึ่งไม่สามารถส่งผ่านไปยังอ้างอิง lvalue เราได้รักษาหมวดหมู่ค่าของพารามิเตอร์ไว้
การรวมสิ่งเหล่านี้เข้าด้วยกันทำให้เรา "การส่งต่อที่สมบูรณ์แบบ":
template <typename A>
void f(A&& a)
{
E(static_cast<A&&>(a));
}
เมื่อf
ได้รับ lvalue ให้E
รับ lvalue เมื่อf
รับค่า rvalue E
จะได้ค่า rvalue สมบูรณ์
และแน่นอนเราต้องการกำจัดความน่าเกลียด static_cast<T&&>
เป็นความลับและแปลกที่จะจำ; ลองสร้างฟังก์ชั่นยูทิลิตี้ที่เรียกว่าforward
ซึ่งทำสิ่งเดียวกัน:
std::forward<A>(a);
// is the same as
static_cast<A&&>(a);
f
เป็นหน้าที่และไม่ใช่การแสดงออกใช่มั้ย