1. Vấn đề nó giải quyết
decltype(value) và decltype((value)) có thể cho hai kiểu khác nhau. decltype dùng kiểu đã khai báo cho tên thông thường không bọc ngoặc; với biểu thức khác, nó xét thêm nhóm giá trị của biểu thức (value category).
2. Kiến thức cần có
Biết
T,T&,T&&vàconst.Nhận diện lvalue như biến có tên, xvalue như
std::move(x), và prvalue như42. Value category là tính chất của biểu thức, không chỉ của kiểu biến.
3. Ý tưởng cốt lõi
Với biến thông thường int value, decltype(value) cho kiểu khai báo int. Dấu ngoặc trong decltype((value)) khiến quy tắc biểu thức được áp dụng: (value) là lvalue, nên kết quả là int&.
Tương tự, truy cập thành viên không bọc ngoặc như object.member dùng kiểu khai báo của thành viên. Structured binding và tham số template hằng có quy tắc riêng. Bản thảo C++ quy định các trường hợp này.
4. Cú pháp tối thiểu
int value = 0;
decltype(value) copy = value;
decltype((value)) alias = value;
alias = 5;copy là một int riêng vẫn bằng 0; alias là int&, nên phép gán đổi value thành 5.
5. Cách nó hoạt động
Với biểu thức e có kiểu T không thuộc các trường hợp tên đặc biệt:
Nếu
elà lvalue,decltype(e)làT&.Nếu
elà xvalue,decltype(e)làT&&.Nếu
elà prvalue,decltype(e)làT.
Toán hạng của decltype không được chạy. Chẳng hạn, decltype(++value) là int& nhưng không tăng value. Tuy vậy, biểu thức vẫn phải hợp lệ về kiểu.
Một biến int&& r = 1; có kiểu khai báo int&&, nhưng biểu thức có tên r là lvalue: decltype(r) là int&&, còn decltype((r)) là int&.
6. Lỗi thường gặp
Bỏ qua ngoặc quanh tên biến khi đọc
decltype.Nghĩ rằng dùng tên một biến kiểu
T&&luôn tạo xvalue.Dùng
decltype(auto)để trả về tham chiếu tới biến cục bộ. Suy luận đúng kiểu không làm đối tượng sống lâu hơn.
7. Khi nào nên dùng
Dùng decltype để lấy kiểu của biểu thức trong template, khai báo kiểu trả về hoặc kiểm tra kiểu bằng static_assert. Khi cần giữ chính xác giá trị/tham chiếu từ biểu thức khởi tạo, decltype(auto) dùng quy tắc decltype; hãy kiểm tra vòng đời của đối tượng được tham chiếu.
8. Ví dụ đơn giản
Ba kiểm tra chính trong mã mẫu C++20:
int value = 0;
static_assert(std::is_same_v<decltype(value), int>);
static_assert(std::is_same_v<decltype((value)), int&>);
static_assert(std::is_same_v<decltype(std::move(value)), int&&>);Cần <type_traits> và <utility>. Chương trình biên dịch mà không in gì. std::move(value) trong decltype không được thực thi.
Mã mẫu hoàn chỉnh
Tệp mã nguồn
dailycppinterview/006_decltype/main.cpp
// Real-World C++ Interviews Q006: Explain decltype!
// Key: For an unparenthesized name or member-access expression, `decltype` generally reports
// the declared type of the named entity; structured bindings and constant template parameters
// have special rules. For other expressions of type `T`, it reflects value category: lvalue
// gives `T&`, xvalue gives `T&&`, and prvalue gives `T`. Thus `decltype(object.member)` gives
// the member's declared type, while `decltype((object.member))` gives an lvalue reference when
// `object` is an lvalue.
#include <type_traits>
#include <utility>
int main() {
int value = 0;
static_assert(std::is_same_v<decltype(value), int>);
static_assert(std::is_same_v<decltype((value)), int&>);
static_assert(std::is_same_v<decltype(std::move(value)), int&&>);
}
9. Điều cần nhớ
Đọc tên và dấu ngoặc trước, rồi mới xét lvalue/xvalue/prvalue. decltype có thể tạo ra một kiểu tham chiếu dù bạn không viết & trực tiếp.
10. Câu hỏi tự kiểm tra
Đề đầy đủ: Giải thích decltype!
Với const int x = 3;, decltype(x), decltype((x)) và decltype(x + 1) là gì? decltype(++value) có làm tăng value không?