(Xem thêm ở đây để biết câu trả lời C ++ 11 của tôi )
Để phân tích một chương trình C ++, trình biên dịch cần biết một số tên nhất định có phải là loại hay không. Ví dụ sau đây chứng minh rằng:
t * f;
Làm thế nào điều này nên được phân tích cú pháp? Đối với nhiều ngôn ngữ, trình biên dịch không cần biết ý nghĩa của tên để phân tích cú pháp và về cơ bản biết hành động của một dòng mã. Trong C ++, tuy nhiên, ở trên có thể mang lại nhiều cách hiểu khác nhau tùy thuộc vào t
phương tiện. Nếu đó là một loại, thì nó sẽ là một khai báo của một con trỏ f
. Tuy nhiên, nếu nó không phải là một loại, nó sẽ là một phép nhân. Vì vậy, Tiêu chuẩn C ++ nói tại đoạn (3/7):
Một số tên biểu thị các loại hoặc mẫu. Nói chung, bất cứ khi nào một tên gặp phải, cần phải xác định xem tên đó biểu thị một trong những thực thể này trước khi tiếp tục phân tích chương trình có chứa nó. Quá trình xác định điều này được gọi là tra cứu tên.
Làm thế nào trình biên dịch sẽ tìm ra những gì một tên t::x
đề cập đến, nếu t
đề cập đến một tham số loại mẫu? x
có thể là một thành viên dữ liệu int tĩnh có thể được nhân lên hoặc cũng có thể là một lớp lồng nhau hoặc typedef có thể mang lại một khai báo. Nếu một tên có thuộc tính này - rằng nó không thể được tra cứu cho đến khi biết các đối số mẫu thực tế - thì nó được gọi là tên phụ thuộc (nó "phụ thuộc" vào các tham số mẫu).
Bạn có thể khuyên bạn chỉ nên đợi cho đến khi người dùng khởi tạo mẫu:
Chúng ta hãy đợi cho đến khi người dùng khởi tạo mẫu và sau đó tìm hiểu ý nghĩa thực sự của nó t::x * f;
.
Điều này sẽ hoạt động và thực sự được Tiêu chuẩn cho phép như một cách tiếp cận thực hiện có thể. Các trình biên dịch này về cơ bản sao chép văn bản của mẫu vào bộ đệm bên trong và chỉ khi cần khởi tạo, chúng mới phân tích mẫu và có thể phát hiện lỗi trong định nghĩa. Nhưng thay vì làm phiền người dùng của mẫu (đồng nghiệp nghèo!) Với các lỗi do tác giả của mẫu tạo ra, các triển khai khác chọn kiểm tra mẫu sớm và đưa ra lỗi trong định nghĩa càng sớm càng tốt, trước khi việc khởi tạo thậm chí diễn ra.
Vì vậy, phải có một cách để nói với trình biên dịch rằng một số tên nhất định là các loại và các tên nhất định không có.
Từ khóa "typename"
Câu trả lời là: Chúng tôi quyết định cách trình biên dịch nên phân tích cú pháp này. Nếu t::x
là một tên phụ thuộc, thì chúng ta cần phải thêm tiền tố vào nó typename
để báo cho trình biên dịch phân tích cú pháp theo một cách nhất định. Tiêu chuẩn cho biết tại (14.6 / 2):
Tên được sử dụng trong khai báo hoặc định nghĩa mẫu và phụ thuộc vào tham số mẫu được giả sử không đặt tên loại trừ khi tra cứu tên áp dụng tìm thấy tên loại hoặc tên đủ điều kiện theo tên kiểu từ khóa.
Có nhiều tên typename
không cần thiết, bởi vì trình biên dịch có thể, với tra cứu tên có thể áp dụng trong định nghĩa mẫu, tìm ra cách phân tích một cấu trúc - ví dụ như T *f;
, khi nào T
là tham số mẫu kiểu. Nhưng t::x * f;
để trở thành một tuyên bố, nó phải được viết như typename t::x *f;
. Nếu bạn bỏ qua từ khóa và tên được coi là không phải loại, nhưng khi khởi tạo tìm thấy nó biểu thị một loại, các thông báo lỗi thông thường được trình biên dịch phát ra. Đôi khi, lỗi do đó được đưa ra tại thời điểm định nghĩa:
// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;
Cú pháp typename
chỉ cho phép trước các tên đủ điều kiện - do đó được coi là các tên không đủ tiêu chuẩn luôn được biết là tham chiếu đến các loại nếu chúng làm như vậy.
Một gotcha tương tự tồn tại cho các tên biểu thị các mẫu, như được gợi ý bởi văn bản giới thiệu.
Từ khóa "mẫu"
Ghi nhớ trích dẫn ban đầu ở trên và làm thế nào Tiêu chuẩn yêu cầu xử lý đặc biệt cho các mẫu? Hãy lấy ví dụ trông ngây thơ sau đây:
boost::function< int() > f;
Nó có thể trông rõ ràng đối với một người đọc con người. Không phải như vậy cho trình biên dịch. Hãy tưởng tượng định nghĩa tùy ý sau đây về boost::function
và f
:
namespace boost { int function = 0; }
int main() {
int f = 0;
boost::function< int() > f;
}
Đó thực sự là một biểu thức hợp lệ ! Nó sử dụng toán tử nhỏ hơn để so sánh boost::function
với zero ( int()
) và sau đó sử dụng toán tử lớn hơn để so sánh kết quả bool
với f
. Tuy nhiên, như bạn có thể biết, boost::function
trong cuộc sống thực là một khuôn mẫu, vì vậy trình biên dịch biết (14.2 / 3):
Sau khi tra cứu tên (3.4) thấy rằng tên là tên mẫu, nếu tên này được theo sau bởi <, <luôn được lấy làm phần đầu của danh sách đối số mẫu và không bao giờ là tên được theo sau bởi ít hơn hơn nhà điều hành.
Bây giờ chúng tôi trở lại vấn đề tương tự như với typename
. Điều gì xảy ra nếu chúng ta chưa biết liệu tên có phải là mẫu khi phân tích mã không? Chúng tôi sẽ cần phải chèn template
ngay trước tên mẫu, như được chỉ định bởi 14.2/4
. Điều này trông giống như:
t::template f<int>(); // call a function template
Tên mẫu không chỉ có thể xảy ra sau một ::
nhưng cũng sau một ->
hoặc .
trong một truy cập thành viên lớp. Bạn cũng cần chèn từ khóa vào đó:
this->template f<int>(); // call a function template
Phụ thuộc
Đối với những người có sách Standardese dày trên kệ của họ và muốn biết chính xác những gì tôi đang nói, tôi sẽ nói một chút về cách thức này được quy định trong Tiêu chuẩn.
Trong khai báo mẫu, một số cấu trúc có ý nghĩa khác nhau tùy thuộc vào đối số mẫu bạn sử dụng để khởi tạo mẫu: Biểu thức có thể có các loại hoặc giá trị khác nhau, các biến có thể có các kiểu gọi hoặc hàm khác nhau có thể gọi các hàm khác nhau. Các cấu trúc như vậy thường được cho là phụ thuộc vào các tham số mẫu.
Tiêu chuẩn xác định chính xác các quy tắc bằng cách xây dựng có phụ thuộc hay không. Nó phân tách chúng thành các nhóm khác nhau về mặt logic: Một loại bắt các loại, một loại khác bắt các biểu thức. Biểu thức có thể phụ thuộc bởi giá trị của chúng và / hoặc loại của chúng. Vì vậy, chúng tôi có, với các ví dụ điển hình được nối thêm:
- Các loại phụ thuộc (ví dụ: tham số mẫu loại
T
)
- Biểu thức phụ thuộc giá trị (ví dụ: tham số mẫu không phải kiểu
N
)
- Các biểu thức phụ thuộc vào loại (ví dụ: truyền vào tham số mẫu loại
(T)0
)
Hầu hết các quy tắc là trực quan và được xây dựng đệ quy: Ví dụ: một loại được xây dựng dưới T[N]
dạng loại phụ thuộc nếu N
là biểu thức phụ thuộc giá trị hoặc T
là loại phụ thuộc. Các chi tiết của điều này có thể được đọc trong phần (14.6.2/1
) cho các loại phụ thuộc, (14.6.2.2)
cho các biểu thức phụ thuộc loại và (14.6.2.3)
cho các biểu thức phụ thuộc giá trị.
Tên người phụ thuộc
Tiêu chuẩn là một chút không rõ ràng về những gì chính xác là một tên phụ thuộc . Trong một lần đọc đơn giản (bạn biết, nguyên tắc ít bất ngờ nhất), tất cả những gì nó định nghĩa là một tên phụ thuộc là trường hợp đặc biệt cho các tên hàm bên dưới. Nhưng vì rõ ràng T::x
cũng cần phải được tra cứu trong bối cảnh khởi tạo, nên nó cũng cần phải là một tên phụ thuộc (may mắn thay, kể từ giữa C ++ 14, ủy ban đã bắt đầu xem xét cách khắc phục định nghĩa khó hiểu này).
Để tránh vấn đề này, tôi đã sử dụng một cách giải thích đơn giản về văn bản Tiêu chuẩn. Trong tất cả các cấu trúc biểu thị các loại hoặc biểu thức phụ thuộc, một tập hợp con của chúng đại diện cho các tên. Những cái tên đó là "tên phụ thuộc". Một tên có thể có các hình thức khác nhau - Tiêu chuẩn nói:
Tên là việc sử dụng mã định danh (2.11), toán tử-hàm-id (13,5), hàm chuyển đổi-id (12.3.2) hoặc id-id (14.2) biểu thị một thực thể hoặc nhãn (6.6.4, 6.1)
Một định danh chỉ là một chuỗi ký tự / chữ số đơn giản, trong khi hai ký tự tiếp theo là operator +
và operator type
biểu mẫu. Hình thức cuối cùng là template-name <argument list>
. Tất cả đều là tên và bằng cách sử dụng thông thường trong Tiêu chuẩn, một tên cũng có thể bao gồm các vòng loại cho biết không gian tên hoặc lớp nào mà tên cần được tra cứu.
Một biểu thức phụ thuộc giá trị 1 + N
không phải là một tên, nhưng N
là. Tập hợp con của tất cả các cấu trúc phụ thuộc là các tên được gọi là tên phụ thuộc . Tuy nhiên, tên hàm có thể có ý nghĩa khác nhau trong các lần xuất hiện khác nhau của mẫu, nhưng không may là không bị quy tắc chung này bắt.
Tên hàm phụ thuộc
Không phải chủ yếu là một mối quan tâm của bài viết này, nhưng vẫn đáng được đề cập: Tên hàm là một ngoại lệ được xử lý riêng. Tên hàm định danh phụ thuộc không phải bởi chính nó, mà bởi các biểu thức đối số phụ thuộc loại được sử dụng trong một cuộc gọi. Trong ví dụ f((T)0)
, f
là một tên phụ thuộc. Trong Tiêu chuẩn, điều này được chỉ định tại (14.6.2/1)
.
Ghi chú và ví dụ bổ sung
Trong trường hợp đủ, chúng ta cần cả typename
và template
. Mã của bạn sẽ trông như sau
template <typename T, typename Tail>
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
typedef typename Tail::template inUnion<U> dummy;
};
// ...
};
Từ khóa template
không phải luôn luôn xuất hiện trong phần cuối của tên. Nó có thể xuất hiện ở giữa trước một tên lớp được sử dụng làm phạm vi, như trong ví dụ sau
typename t::template iterator<int>::value_type v;
Trong một số trường hợp, các từ khóa bị cấm, như chi tiết dưới đây
Về tên của một lớp cơ sở phụ thuộc, bạn không được phép viết typename
. Nó giả định rằng tên được đưa ra là một loại tên lớp. Điều này đúng cho cả hai tên trong danh sách lớp cơ sở và danh sách trình khởi tạo hàm tạo:
template <typename T>
struct derive_from_Has_type : /* typename */ SomeBase<T>::type
{ };
Trong các khai báo sử dụng, không thể sử dụng template
sau lần cuối ::
và ủy ban C ++ nói không hoạt động trên một giải pháp.
template <typename T>
struct derive_from_Has_type : SomeBase<T> {
using SomeBase<T>::template type; // error
using typename SomeBase<T>::type; // typename *is* allowed
};