9

Trong C++ 11, nó có vẻ như đó là pháp lý để khởi tạo một std::map<std::string, int> như sau:Quy tắc ngôn ngữ nào cho phép C++ 11 suy ra rằng đây là một initializer_list của các cặp?

std::map<std::string, int> myMap = { 
    { "One", 1 }, 
    { "Two", 2 }, 
    { "Three", 3 } 
}; 

trực giác, điều này làm cho tinh thần - initializer cú đúp kín là một danh sách các cặp của chuỗi, và std::map<std::string, int>::value_typestd::pair<std::string, int> (có thể với một số bằng cấp const.

Tuy nhiên, tôi không chắc mình hiểu cách gõ hoạt động ở đây. Nếu chúng ta loại bỏ khai báo biến ở đây và chỉ có trình khởi tạo kèm theo dấu ngoặc đơn, trình biên dịch sẽ không biết rằng đang nhìn vào một std::initializer_list<std::pair<std::string, int>> bởi vì nó sẽ không biết rằng các cặp braced lại trình bày std::pair s. Do đó, có vẻ như trình biên dịch bằng cách nào đó trì hoãn hành động gán kiểu cho bộ khởi tạo ngoặc kèm theo cho đến khi nó có đủ thông tin kiểu từ hàm tạo std::map để nhận ra rằng các dấu ngoặc lồng nhau là cho các cặp. Tôi không nhớ bất cứ điều gì như thế này xảy ra trong C++ 03; theo hiểu biết tốt nhất của tôi, loại biểu thức không bao giờ phụ thuộc vào ngữ cảnh của nó.

Quy tắc ngôn ngữ nào cho phép mã này biên dịch chính xác và để trình biên dịch xác định loại nào sẽ sử dụng cho danh sách bộ khởi tạo? Tôi đang hy vọng cho câu trả lời với các tài liệu tham khảo cụ thể để spec C++ 11, vì nó thực sự thú vị mà công trình này!

Cảm ơn!

+0

Các danh sách có ngoặc nhọn trong C++ 03 đã rất cụ thể đối với ngữ cảnh khởi tạo. Không có sự khác biệt đáng kể ở đây từ một tổng hợp lồng nhau C++ 03 (có thể 'std :: pair myArray []') được khởi tạo bằng cách sử dụng dấu ngoặc, ngoại trừ kiểu không được đặt tên trực tiếp, nó được suy ra từ các nhà thầu có sẵn. –

+0

BTW, toán tử chuyển đổi ngầm (toán tử 'thành viên T()') là nơi khác mà bối cảnh xác định loại biểu thức (và được sử dụng để phân giải quá tải). –

+2

'braced-init-list' không phải là biểu thức và không có loại. Không có khấu trừ, chỉ có độ phân giải quá tải giữa các nhà xây dựng danh sách khởi tạo (trong đó chỉ có một) – Cubbi

Trả lời

9

Trong biểu

std::map<std::string, int> myMap = { 
    { "One", 1 }, 
    { "Two", 2 }, 
    { "Three", 3 } 
}; 

ở bên phải bạn có một chuẩn bị tinh thần-init-list nơi mỗi phần tử cũng là một chuẩn bị tinh thần-init-list. Điều đầu tiên xảy ra là hàm tạo danh sách khởi tạo của std::map được xem xét.

map(initializer_list<value_type>, 
    const Compare& = Compare(), 
    const Allocator& = Allocator()); 

map<K, V>::value_type là một typedef cho pair<const K, V>, trong trường hợp này pair<const string, int>. Các dấu ngoặc đơn bên trong-init có thể được chuyển đổi thành công thành map::value_type bởi vì std::pair có một hàm tạo tham chiếu đến hai loại thành phần của nó và std::string có một hàm tạo chuyển đổi ngầm chiếm một số char const *.

Do đó, hàm tạo danh sách khởi tạo của std::map là khả thi và việc xây dựng có thể xảy ra từ các danh sách được lồng nhau-init-lồng nhau.

Các standardese có liên quan có mặt trong §13.3.1.7/1 [over.match.list]

Khi đối tượng của phi tổng hợp lớp loại T là danh sách khởi tạo (8.5.4), độ phân giải quá tải chọn hàm khởi tạo theo hai pha:
- Ban đầu, hàm ứng cử viên là các hàm tạo danh sách khởi tạo (8.5.4) của lớp T và danh sách đối số bao gồm danh sách trình khởi tạo làm đối số duy nhất.
- Nếu không tìm thấy hàm khởi tạo danh sách khởi tạo khả thi, độ phân giải quá tải được thực hiện lại, trong đó hàm ứng viên là tất cả các hàm tạo của lớp T và danh sách đối số bao gồm các phần tử của danh sách khởi tạo.

Viên đạn đầu tiên là những gì làm cho initializer_list constructor của map được chọn để bên ngoài chuẩn bị tinh thần-init-list, trong khi kết quả đạn thứ hai trong việc lựa chọn đúng pair constructor cho khu vực nội chuẩn bị tinh thần-init- danh sách.

+0

Tuyệt vời, cảm ơn! – templatetypedef

2

Đây là danh sách khởi tạo. Các quy tắc được tìm thấy trong §8.5.4 [dcl.init.list]/p3 của tiêu chuẩn:

List-khởi của một đối tượng hoặc tài liệu tham khảo của loại T được định nghĩa là sau:

  • Nếu danh sách khởi tạo không có phần tử và T là một kiểu lớp với một hàm tạo mặc định, đối tượng được khởi tạo giá trị.
  • Nếu không, nếu T là tổng hợp, tổng hợp khởi tạo được thực hiện (8.5.1). [Ví dụ bỏ qua]
  • Ngược lại, nếu T là một chuyên môn của std::initializer_list<E>, một đối tượng initializer_list được xây dựng như mô tả dưới đây và sử dụng để khởi tạo đối tượng theo các quy tắc cho khởi của một đối tượng từ một lớp cùng loại (8.5).
  • Nếu không, nếu T là một loại lớp, các nhà thầu sẽ được xem xét. Các nhà thầu được áp dụng được liệt kê và được lựa chọn tốt nhất thông qua độ phân giải quá tải (13.3, 13.3.1.7). Nếu cần thu hẹp chuyển đổi (xem bên dưới) để chuyển đổi bất kỳ đối số nào, chương trình bị lỗi.
  • [ví dụ và phần còn lại của các quy tắc bỏ qua]

Lưu ý rằng quá tải độ phân giải sẽ thích std::initializer_list nhà xây dựng trong những trường hợp này (§13.3.1.7 [over.match.list]). Do đó khi trình biên dịch thấy một danh sách được sử dụng để khởi tạo một đối tượng thuộc loại lớp không tổng hợp, không phải là std::initializer_list, nó sẽ thực hiện độ phân giải quá tải để chọn phương thức khởi tạo thích hợp, thích hàm khởi tạo initializer_list. như đối với std::map).

Các vấn đề liên quan