TL; DR
Dấu ngoặc đơn thay đổi ý nghĩa của chương trình C ++ trong các ngữ cảnh sau:
- ngăn chặn tra cứu tên phụ thuộc vào đối số
- bật toán tử dấu phẩy trong ngữ cảnh danh sách
- giải quyết sự mơ hồ của các phân đoạn khó chịu
- suy ra tham chiếu trong
decltype
biểu thức
- ngăn chặn lỗi macro bộ xử lý trước
Ngăn tra cứu tên phụ thuộc vào đối số
Như được trình bày chi tiết trong Phụ lục A của Tiêu chuẩn, a post-fix expression
có dạng (expression)
là a primary expression
, nhưng không phải id-expression
và do đó không phải là unqualified-id
. Điều này có nghĩa là tra cứu tên phụ thuộc đối số bị ngăn chặn trong các lệnh gọi hàm của biểu mẫu (fun)(arg)
so với biểu mẫu thông thường fun(arg)
.
3.4.2 Tra cứu tên phụ thuộc vào đối số [basic.lookup.argdep]
1 Khi biểu thức hậu tố trong một lệnh gọi hàm (5.2.2) là một id không đủ tiêu chuẩn , các không gian tên khác không được xem xét trong quá trình tra cứu thông thường không đủ tiêu chuẩn (3.4.1) có thể được tìm kiếm và trong các không gian tên đó, hàm bạn của không gian tên phạm vi hoặc Có thể tìm thấy các khai báo mẫu hàm (11.3) không hiển thị. Những sửa đổi này đối với tìm kiếm phụ thuộc vào loại đối số (và đối với đối số mẫu mẫu, không gian tên của đối số mẫu). [ Thí dụ:
namespace N {
struct S { };
void f(S);
}
void g() {
N::S s;
f(s); // OK: calls N::f
(f)(s); // error: N::f not considered; parentheses
// prevent argument-dependent lookup
}
—Gửi ví dụ]
Bật toán tử dấu phẩy trong ngữ cảnh danh sách
Toán tử dấu phẩy có một ý nghĩa đặc biệt trong hầu hết các ngữ cảnh giống như danh sách (đối số hàm và mẫu, danh sách trình khởi tạo, v.v.). Dấu ngoặc đơn của biểu mẫu a, (b, c), d
trong các ngữ cảnh như vậy có thể cho phép toán tử dấu phẩy so với biểu mẫu thông thường a, b, c, d
không áp dụng toán tử dấu phẩy.
5.18 Toán tử dấu phẩy [expr.comma]
2 Trong các ngữ cảnh mà dấu phẩy có ý nghĩa đặc biệt, [Ví dụ: trong danh sách các đối số cho các hàm (5.2.2) và danh sách các bộ khởi tạo (8.5) - ví dụ thêm] toán tử dấu phẩy như được mô tả trong Điều 5 chỉ có thể xuất hiện trong dấu ngoặc đơn. [ Thí dụ:
f(a, (t=3, t+2), c);
có ba đối số, đối số thứ hai có giá trị 5. —end ví dụ]
Giải quyết sự mơ hồ của các phân đoạn khó chịu
Khả năng tương thích ngược với C và cú pháp khai báo hàm phức tạp của nó có thể dẫn đến sự mơ hồ phân tích cú pháp đáng ngạc nhiên, được gọi là phân tích cú pháp khó chịu. Về cơ bản, bất kỳ thứ gì có thể được phân tích cú pháp như một khai báo sẽ được phân tích cú pháp thành một , mặc dù một phân tích cú pháp cạnh tranh cũng sẽ được áp dụng.
6.8 Giải quyết sự mơ hồ [stmt.ambig]
1 Có sự không rõ ràng trong ngữ pháp liên quan đến các câu lệnh và khai báo biểu thức: Một câu lệnh biểu thức có kiểu chuyển đổi kiểu hàm rõ ràng (5.2.3) là biểu thức con ngoài cùng bên trái của nó có thể không thể phân biệt được với một khai báo trong đó bộ khai báo đầu tiên bắt đầu bằng ( . Trong những trường hợp báo cáo kết quả là một bản tuyên bố .
8.2 Giải quyết sự mơ hồ [dcl.ambig.res]
1 Sự không rõ ràng phát sinh do sự giống nhau giữa một kiểu hàm và một khai báo được đề cập trong 6.8 cũng có thể xảy ra trong ngữ cảnh của một khai báo . Trong bối cảnh đó, sự lựa chọn là giữa một khai báo hàm với một tập hợp các dấu ngoặc đơn dự phòng xung quanh tên tham số và một khai báo đối tượng với một kiểu hàm kiểu làm bộ khởi tạo. Cũng như đối với những điểm không rõ ràng được đề cập trong 6.8, giải pháp là xem xét bất kỳ cấu trúc nào có thể là một khai báo . [Lưu ý: Một khai báo có thể được phân định rõ ràng bằng kiểu ép kiểu phi chức năng, bằng dấu = để chỉ ra sự khởi tạo hoặc bằng cách xóa dấu ngoặc đơn thừa xung quanh tên tham số. —Gửi ghi chú] [Ví dụ:
struct S {
S(int);
};
void foo(double a) {
S w(int(a)); // function declaration
S x(int()); // function declaration
S y((int)a); // object declaration
S z = int(a); // object declaration
}
—Gửi ví dụ]
Một ví dụ nổi tiếng về vấn đề này là Most Vexing Parse , một cái tên được Scott Meyers phổ biến trong Mục 6 của cuốn sách STL Hiệu quả của ông :
ifstream dataFile("ints.dat");
list<int> data(istream_iterator<int>(dataFile), // warning! this doesn't do
istream_iterator<int>()); // what you think it does
Điều này khai báo một hàm data
, có kiểu trả về là list<int>
. Dữ liệu hàm nhận hai tham số:
- Tham số đầu tiên được đặt tên
dataFile
. Đó là loại istream_iterator<int>
. Các dấu ngoặc đơn xung quanh dataFile
là thừa và được bỏ qua.
- Tham số thứ hai không có tên. Kiểu của nó là con trỏ đến hàm không lấy gì và trả về một
istream_iterator<int>
.
Đặt thêm dấu ngoặc đơn xung quanh đối số hàm đầu tiên (dấu ngoặc quanh đối số thứ hai là bất hợp pháp) sẽ giải quyết sự mơ hồ
list<int> data((istream_iterator<int>(dataFile)), // note new parens
istream_iterator<int>()); // around first argument
// to list's constructor
C ++ 11 có cú pháp khởi tạo dấu ngoặc nhọn cho phép loại bỏ các vấn đề phân tích cú pháp như vậy trong nhiều ngữ cảnh.
Khấu trừ tham chiếu trong decltype
biểu thức
Ngược lại với auto
suy luận kiểu, decltype
cho phép suy ra tham chiếu (tham chiếu giá trị và giá trị). Các quy tắc phân biệt giữa decltype(e)
và decltype((e))
biểu thức:
7.1.6.2 Bộ chỉ định loại đơn giản [dcl.type.simple]
4 Đối với một biểu thức e
, kiểu được biểu thị bằngdecltype(e)
được định nghĩa như sau:
- if e
là một biểu thức id không có dấu ngoặc đơn hoặc quyền truy cập thành viên lớp không có dấu ngoặc đơn (5.2.5), decltype(e)
là kiểu thực thể được đặt tên bởi e
. Nếu không có thực thể như vậy, hoặc nếu e
đặt tên cho một tập hợp các hàm quá tải, chương trình không được định hình;
- ngược lại, if e
là một xvalue, decltype(e)
is T&&
, ở đâu T
là loại e
;
- nếu không, nếu e
là giá trị, decltype(e)
là T&
, ở đâu T
là loại e
;
- nếu không, decltype(e)
là loại e
.
Toán hạng của trình xác định kiểu khai báo là một toán hạng không được đánh giá (Điều 5). [ Thí dụ:
const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = 0; // type is const int&&
decltype(i) x2; // type is int
decltype(a->x) x3; // type is double
decltype((a->x)) x4 = x3; // type is const double&
—End example] [Lưu ý: Các quy tắc để xác định các loại liên quan
decltype(auto)
được quy định trong 7.1.6.4. —Gửi ghi chú]
Các quy tắc cho decltype(auto)
có ý nghĩa tương tự đối với các dấu ngoặc đơn trong RHS của biểu thức khởi tạo. Đây là một ví dụ từ Câu hỏi thường gặp về C ++ và Câu hỏi & Đáp liên quan này
decltype(auto) look_up_a_string_1() { auto str = lookup1(); return str; } //A
decltype(auto) look_up_a_string_2() { auto str = lookup1(); return(str); } //B
Trả về đầu tiên string
, trả về thứ hai string &
, là một tham chiếu đến biến cục bộ str
.
Ngăn chặn các lỗi liên quan đến macro bộ xử lý trước
Có một loạt các điểm tinh vi với các macro tiền xử lý trong tương tác của chúng với ngôn ngữ C ++ thích hợp, trong đó phổ biến nhất được liệt kê dưới đây
- sử dụng dấu ngoặc đơn xung quanh các tham số macro bên trong định nghĩa macro
#define TIMES(A, B) (A) * (B);
để tránh ưu tiên toán tử không mong muốn (ví dụ: trong TIMES(1 + 2, 2 + 1)
đó kết quả là 9 nhưng sẽ mang lại 6 mà không có dấu ngoặc đơn xung quanh (A)
và(B)
- sử dụng dấu ngoặc đơn xung quanh đối số macro có dấu phẩy bên trong:
assert((std::is_same<int, int>::value));
nếu không sẽ không biên dịch
- sử dụng dấu ngoặc đơn xung quanh một hàm để bảo vệ khỏi việc mở rộng macro trong các tiêu đề được bao gồm:
(min)(a, b)
(với tác dụng phụ không mong muốn là vô hiệu hóa ADL)
&(C::f)
, toán hạng của&
vẫnC::f
là, phải không?