Bạn phải hiểu vấn đề chuyển tiếp. Bạn có thể đọc toàn bộ vấn đề một cách chi tiết , nhưng tôi sẽ tóm tắt.
Về cơ bản, với biểu thức E(a, b, ... , c)
, chúng tôi muốn biểu thức f(a, b, ... , c)
tương đương. Trong C ++ 03, điều này là không thể. Có nhiều cố gắng, nhưng tất cả đều thất bại trong một số vấn đề.
Đơn giản nhất là sử dụng tham chiếu lvalue:
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
E(a, b, c);
}
Nhưng điều này không thể xử lý các giá trị tạm thời: f(1, 2, 3);
vì chúng không thể bị ràng buộc với tham chiếu giá trị.
Nỗ lực tiếp theo có thể là:
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(a, b, c);
}
Mà sửa chữa vấn đề trên, nhưng flops flops. Bây giờ không cho phép E
có các đối số không phải là const:
int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these
Nỗ lực thứ ba chấp nhận tham chiếu const, nhưng sau đó const_cast
là 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));
}
Điều này chấp nhận tất cả các giá trị, có thể truyền vào tất cả các giá trị, nhưng có khả năng dẫn đến hành vi không xác định:
const int i = 1, j = 2, k = 3;
E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!
Một giải pháp cuối cùng xử lý mọi thứ một cách chính xác ... với chi phí không thể duy trì. Bạn cung cấp quá tải f
, với tất cả sự kết hợp của const và non-const:
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);
Đối số N yêu cầu 2 kết hợp N , một cơn ác mộng. Chúng tôi muốn làm điều này tự động.
(Đây thực sự là những gì chúng tôi có được trình biên dịch để làm cho chúng tôi trong C ++ 11.)
Trong C ++ 11, chúng tôi có cơ hội sửa lỗi này. Một giải pháp sửa đổi các quy tắc khấu trừ mẫu trên các loại hiện có, nhưng điều này có khả năng phá vỡ rất nhiều mã. Vì vậy, chúng ta phải tìm một cách khác.
Giải pháp là thay vào đó sử dụng các tham chiếu rvalue mới được thêm vào ; chúng tôi có thể giới thiệu các quy tắc mới khi suy ra các loại tham chiếu giá trị và tạo ra bất kỳ kết quả mong muốn nào. Rốt cuộc, chúng ta không thể phá mã ngay bây giờ.
Nếu được cung cấp một tham chiếu đến một tham chiếu (tham chiếu ghi chú là một thuật ngữ bao gồm cả hai T&
và T&&
), chúng tôi sử dụng quy tắc sau để tìm ra loại kết quả:
"[đã cho] một loại TR là tham chiếu đến loại T, một nỗ lực để tạo tham chiếu giá trị kiểu Lvue cho cv TR, tạo ra tham chiếu kiểu lvalue cho Típ, trong khi cố gắng tạo tham chiếu rvalue loại cv TRv tạo ra loại TR. "
Hoặc ở dạng bảng:
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)
Tiếp theo, với suy luận đối số mẫu: nếu đối số là giá trị A, chúng tôi cung cấp đối số mẫu với tham chiếu giá trị cho A. Mặt khác, chúng tôi suy luận bình thường. Điều này cung cấp cái gọi là tài liệu tham khảo phổ quát (thuật ngữ tham chiếu chuyển tiếp hiện là tài liệu tham khảo chính thức).
Tại sao điều này hữu ích? Bởi vì kết hợp chúng tôi duy trì khả năng theo dõi danh mục giá trị của một loại: nếu đó là giá trị, chúng tôi có tham số tham chiếu giá trị, nếu không, chúng tôi có tham số tham chiếu rvalue.
Trong mã:
template <typename T>
void deduce(T&& x);
int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)
Điều cuối cùng là "chuyển tiếp" danh mục giá trị của biến. Hãy ghi nhớ, một khi bên trong hàm, tham số có thể được truyền dưới dạng giá trị cho bất kỳ thứ gì:
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
Điêu đo không tôt. E cần phải có cùng loại giá trị mà chúng tôi có! Giải pháp là đây:
static_cast<T&&>(x);
Cái này làm gì Hãy xem xét chúng tôi bên trong deduce
chức năng và chúng tôi đã được thông qua một giá trị. Điều này có nghĩa T
là một A&
, và do đó, loại mục tiêu cho các diễn viên tĩnh là A& &&
, hoặc chỉ A&
. Vì x
đã là một A&
, chúng tôi không làm gì cả và chỉ còn lại một tham chiếu giá trị.
Khi chúng tôi đã được thông qua một rvalue, T
là A
, vì vậy các loại mục tiêu cho các diễn viên tĩnh là A&&
. Các kết quả đúc trong một biểu thức giá trị, không còn có thể được chuyển đến một tham chiếu giá trị . Chúng tôi đã duy trì loại giá trị của tham số.
Đặt những thứ này lại với nhau mang lại cho chúng ta "chuyển tiếp hoàn hảo":
template <typename A>
void f(A&& a)
{
E(static_cast<A&&>(a));
}
Khi f
nhận được một giá trị, E
nhận được một giá trị. Khi f
nhận được một giá trị, E
nhận được một giá trị. Hoàn hảo.
Và tất nhiên, chúng tôi muốn thoát khỏi sự xấu xí. static_cast<T&&>
là khó hiểu và kỳ lạ để nhớ; thay vào đó, hãy tạo một hàm tiện ích được gọi forward
, thực hiện điều tương tự:
std::forward<A>(a);
// is the same as
static_cast<A&&>(a);
f
là một chức năng, và không phải là một biểu thức?