2011-08-26 16 views
14

Tôi rất ngạc nhiên khi thấy trên các phiên bản khác nhau mẫu của g ++, các biên dịch sau đây mà không có lỗi hoặc cảnh báo:sử dụng lớp do không đủ số mẫu

// Adapted from boost::checked_delete() 
template <class T> inline void assert_complete() 
{ 
    typedef char type_must_be_complete[ sizeof(T) ? 1 : -1 ]; 
    (void) sizeof(type_must_be_complete); 
} 

class X; 

void f() 
{ 
    assert_complete<X>(); 
} 

class X {}; 

int main() {} 

Nếu định nghĩa của X bị thiếu hoặc trong một đơn vị dịch thuật khác nhau, tôi nhận được lỗi.

Nhưng trong chương trình như trên, không phải là định nghĩa của f điểm khởi tạo đơn lẻ trong mẫu của tôi? Và không phải là sự thiếu hoàn toàn của X tại điểm khởi tạo đó có phải là lỗi ngữ nghĩa không?

Chuẩn (C++ 03 và/hoặc C++ 11) có gọi chương trình này được định dạng tốt, không đúng định dạng, không đúng định dạng nhưng không bắt buộc hoặc không được xác định?

Chỉnh sửa: @David Rodriguez - dribeas báo cáo rằng clang ++, comeau và Visual Studio 2010 cũng chấp nhận mã tương tự.

+0

Thật thú vị khi tôi thêm một 'int' vào định nghĩa của' X', 'sizeof (T)' là 4. Các mẫu đã được biết trong quá khứ để có khả năng nhận thức. –

Trả lời

12

(Tôi đã chờ đợi để Alf Steinbach để gửi câu trả lời, nhưng kể từ khi ông không được làm việc đó, tôi sẽ gửi tài liệu tham khảo mà ông đề cập trong Lounge C++ trò chuyện):

Tiêu chuẩn chỉ ra rằng mẫu các phiên âm được thực hiện sau đơn vị dịch đã được dịch, để kịp thời, các bản mẫu tức thời xảy ra sau tất cả các phần tử không được tạo mẫu đã được xử lý. Này được mô tả trong phần 2.2 Các giai đoạn của dịch:

Đoạn 1-6 xác định công việc tiền xử lý và cơ bản văn bản hoạt động (chuyển đổi của bộ ký tự, nối các chữ ...)

7/Ký tự trắng tách ký tự không còn quan trọng nữa. Mỗi mã thông báo tiền xử lý được chuyển đổi thành mã thông báo. (2.7). Các mã thông báo kết quả được phân tích cú pháp và ngữ nghĩa và được dịch như một đơn vị dịch thuật.

8/Đơn vị dịch thuật và đơn vị instantiation được kết hợp như sau: Mỗi đơn vị dịch được dịch sẽ được kiểm tra để tạo danh sách các instantiation cần thiết. Các định nghĩa của các mẫu được yêu cầu được đặt. Nó được thực hiện xác định liệu nguồn gốc của các đơn vị dịch có chứa các định nghĩa này là cần thiết để có sẵn. Tất cả các instantiation cần thiết được thực hiện để tạo ra các đơn vị instantiation. [Lưu ý: Các tệp này tương tự như các đơn vị dịch đã dịch, nhưng không chứa tham chiếu đến các mẫu không được phân tích và không có định nghĩa mẫu. - lưu ý kết thúc] Chương trình bị hỏng nếu xảy ra bất kỳ sự kiện nào.

Tôi đã xóa một số ghi chú ngắn gọn. Bây giờ bit quan trọng có vẻ là mã được dịch mà không kích hoạt các mẫu instantiations trong một bước, và sau đó trong một bước sau các mẫu được khởi tạo. Điều này có nghĩa là nếu loại là hoàn thành ở bất kỳ vị trí nào trong đơn vị dịch, nó sẽ được xử lý bởi thời gian trình biên dịch được đưa vào phiên bản.

Disclaimer: Điều này có vẻ giống như một lý do chính đáng cho tất cả các trình biên dịch mà tôi đã cố gắng thể hiện hành vi chính xác cùng (gcc, kêu vang, comeau, VS 2010), nhưng điều này chỉ tiểu bang khi trong thời gian instantiation được thực hiện, nó không tuyên bố rõ ràng rằng loại có thể không đầy đủ tại thời điểm tạo mẫu.

+0

Câu trả lời và nhận xét của spencercw dường như chỉ ra rằng nó được cho phép rõ ràng ở những nơi khác trong tiêu chuẩn. –

+0

@MatthewWalton: câu trả lời của spencercw không liên quan đến vấn đề này. Nếu thay vì một mẫu hàm, nó không phải là mẫu, thì mã sẽ không biên dịch được, như ashepler trỏ trong nhận xét của anh ta cho câu trả lời của spencercw, thực tế là định danh chỉ cùng loại trong tất cả TU không có nghĩa là không có giới hạn về những gì bạn có thể làm hoặc không làm * trước * định nghĩa kiểu được gặp phải (nghĩa là trong khi vẫn chưa hoàn thành). –

+0

Câu hỏi cụ thể là về lý do tại sao 'int size1() {return sizeof (X); } 'sẽ không biên dịch nếu' X' không hoàn thành * tại thời điểm này *, trong khi 'template int size2() {return sizeof (T)}' sẽ biên dịch ngay cả khi loại không hoàn thành tại * điểm instantiation *, và Câu trả lời của spencercw không đối phó với sự khác biệt này. –

6

Dòng này hoàn thành các loại:

class X {}; 

Chừng nào loại được hoàn thành ở đâu đó trong các đơn vị dịch sau đó bất kỳ sớm hơn trường hợp không đầy đủ sẽ được hoàn thành.

Dưới đây là phần có liên quan từ tiêu chuẩn [basic.types] (3.9 khoản 7):

Một kiểu lớp (chẳng hạn như “lớp X”) có thể không đầy đủ tại một thời điểm trong một đơn vị dịch thuật và hoàn thành sau này; loại “class X” là cùng loại ở cả hai điểm. Kiểu khai báo của một đối tượng mảng có thể là một mảng kiểu lớp không đầy đủ và do đó không đầy đủ; nếu loại lớp được hoàn thành sau này trong đơn vị dịch, kiểu mảng trở nên hoàn chỉnh; loại mảng ở hai điểm đó là cùng loại. Kiểu khai báo của một đối tượng mảng có thể là một mảng có kích thước không xác định và do đó không đầy đủ tại một điểm trong một đơn vị dịch và hoàn thành sau này; các kiểu mảng tại hai điểm đó (“mảng không xác định của T” và “mảng của N T”) là các kiểu khác nhau. Loại của một con trỏ đến mảng có kích thước không xác định, hoặc của một kiểu được xác định bởi một khai báo typedef là một mảng có kích thước không xác định, không thể hoàn thành.

+1

Nhưng có rất nhiều điều bạn không thể làm với một loại chưa hoàn thành, ngay cả khi nó được hoàn thành sau này trong TU. Hãy thử lấy mẫu ra khỏi hình: 'class X; void f() {(void) sizeof (X); } class X {}; 'chắc chắn sẽ gây ra lỗi. – aschepler

+0

Ah vâng, tôi thấy quan điểm của bạn; có vẻ như tôi đã hiểu sai thông số. Tuy nhiên, [temp.arg.type] tuyên bố "một đối số kiểu mẫu có thể là một kiểu không hoàn chỉnh", mà tôi cho phép nó sử dụng kiểu đã hoàn thành được định nghĩa sau trong đơn vị dịch. – spencercw