Bằng cách sử dụng auto&& var = <initializer>
bạn đang nói: Tôi sẽ chấp nhận bất kỳ trình khởi tạo nào bất kể đó là biểu thức lvalue hay rvalue và tôi sẽ giữ nguyên hằng số của nó . Điều này thường được sử dụng để chuyển tiếp (thường là với T&&
). Lý do điều này hoạt động là vì một "tài liệu tham khảo phổ quát", auto&&
hoặc T&&
, sẽ liên kết với bất cứ điều gì .
Bạn có thể nói, tại sao không chỉ sử dụng một const auto&
vì điều đó cũng sẽ liên kết với bất cứ điều gì? Vấn đề với việc sử dụng một const
tài liệu tham khảo là nó const
! Sau này, bạn sẽ không thể liên kết nó với bất kỳ tham chiếu không phải nào hoặc gọi bất kỳ hàm thành viên nào không được đánh dấu const
.
Ví dụ, hãy tưởng tượng rằng bạn muốn lấy a std::vector
, đưa một trình vòng lặp đến phần tử đầu tiên của nó và sửa đổi giá trị được chỉ ra bởi trình vòng lặp đó theo một cách nào đó:
auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;
Mã này sẽ biên dịch tốt bất kể biểu thức khởi tạo. Các lựa chọn thay thế auto&&
thất bại theo các cách sau:
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
Vì vậy, đối với điều này, auto&&
hoạt động hoàn hảo! Một ví dụ về việc sử dụng auto&&
như thế này là trong một for
vòng lặp dựa trên phạm vi . Xem câu hỏi khác của tôi để biết thêm chi tiết.
Nếu sau đó bạn sử dụng std::forward
trên auto&&
tài liệu tham khảo của mình để bảo vệ thực tế rằng ban đầu nó là giá trị hoặc giá trị, mã của bạn nói: Bây giờ tôi đã có đối tượng của mình từ biểu thức giá trị hoặc giá trị, tôi muốn giữ nguyên giá trị ban đầu vì vậy tôi có thể sử dụng nó một cách hiệu quả nhất - điều này có thể làm mất hiệu lực của nó. Như trong:
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));
Điều này cho phép use_it_elsewhere
tách ruột của nó ra vì mục đích hiệu suất (tránh các bản sao) khi trình khởi tạo ban đầu là một giá trị có thể sửa đổi.
Điều này có nghĩa là gì nếu chúng ta có thể hoặc khi chúng ta có thể đánh cắp tài nguyên từ var
đâu? Vì ý auto&&
chí sẽ liên kết với bất cứ điều gì, chúng ta không thể cố gắng tự xé var
ruột mình - nó rất có thể là một giá trị hoặc thậm chí là const. Tuy nhiên, chúng ta có thể làm std::forward
điều đó với các chức năng khác có thể tàn phá hoàn toàn bên trong của nó. Ngay khi chúng tôi làm điều này, chúng tôi nên xem xét var
ở trạng thái không hợp lệ.
Bây giờ, hãy áp dụng điều này cho trường hợp auto&& var = foo();
, như được đưa ra trong câu hỏi của bạn, nơi foo trả về một T
giá trị. Trong trường hợp này, chúng tôi biết chắc chắn rằng loại var
sẽ được suy ra là T&&
. Vì chúng tôi biết chắc chắn rằng đó là một giá trị, chúng tôi không cần std::forward
sự cho phép để đánh cắp tài nguyên của nó. Trong trường hợp cụ thể này, biết rằng foo
trả về theo giá trị , người đọc chỉ nên đọc nó như sau: Tôi đang lấy một tham chiếu giá trị cho trả về tạm thời từ đó foo
, vì vậy tôi có thể vui vẻ di chuyển từ nó.
Là một phụ lục, tôi nghĩ rằng đáng để đề cập đến khi một biểu thức như some_expression_that_may_be_rvalue_or_lvalue
có thể xuất hiện, ngoài trường hợp "mã của bạn có thể thay đổi". Vì vậy, đây là một ví dụ giả định:
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;
}
Ở đây, get_vector<T>()
biểu hiện đáng yêu đó có thể là giá trị hoặc giá trị tùy thuộc vào loại chung T
. Về cơ bản chúng tôi thay đổi kiểu trả về get_vector
thông qua mẫu tham số của foo
.
Khi chúng ta gọi foo<std::vector<int>>
, get_vector
sẽ trả về global_vec
theo giá trị, đưa ra biểu thức giá trị. Ngoài ra, khi chúng tôi gọi foo<std::vector<int>&>
, get_vector
sẽ trả về global_vec
bằng tham chiếu, dẫn đến biểu thức giá trị.
Nếu chúng ta làm:
foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;
Chúng tôi nhận được đầu ra như mong đợi:
2
1
2
2
Nếu bạn đã thay đổi auto&&
trong mã bất kỳ auto
, auto&
, const auto&
, hoặc const auto&&
sau đó chúng tôi sẽ không có được kết quả chúng ta muốn.
Một cách khác để thay đổi logic chương trình dựa trên việc auto&&
tham chiếu của bạn được khởi tạo với biểu thức giá trị hay giá trị là sử dụng các đặc điểm loại:
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&&
? Tôi đã suy nghĩ về việc xem tại sao một vòng lặp dựa trên phạm vi mở rộng để sử dụngauto&&
làm ví dụ, nhưng vẫn chưa hoàn thành nó. Có lẽ bất cứ ai trả lời có thể giải thích nó.