13

Gần đây tôi đã tạo mã ví dụ này để minh họa cho việc sử dụng hàm C++ 11 variadic template.Sự cần thiết của các hàm mẫu khai báo chuyển tiếp

template <typename Head, typename... Tail> void foo (Head, Tail...); 
template <typename... Tail> void foo (int, Tail...); 
void foo() {} 

template <typename... Tail> 
void foo (int x, Tail... tail) 
{ 
    std :: cout << "int:" << x; 
    foo (tail...); 
} 

template <typename Head, typename... Tail> 
void foo (Head x, Tail... tail) 
{ 
    std :: cout << " ?:" << x; 
    foo (tail...); 
} 

foo (int (123), float (123)); // Prints "int:123 ?:123.0" 

Nếu hai dòng đầu tiên mà phía trước-tuyên bố foo bị bỏ qua thì đây sẽ in int:123int:123 để thay thế. Điều này làm ngạc nhiên một lập trình viên C++ có kinh nghiệm và hiểu biết nhất định.

Ông đã thuyết phục rằng các tuyên bố chuyển tiếp không cần thiết vì cơ thể sẽ không được khởi tạo cho đến giai đoạn hai của tra cứu hai pha. Ông nghĩ rằng trình biên dịch (gcc 4.6) có một lỗi.

Tôi tin trình biên dịch là đúng vì hai foodifferent base template functions và lựa chọn mẫu cơ bản cần phải được khóa trong giai đoạn đầu hoặc nếu không bạn có thể vi phạm quy tắc một định nghĩa bằng cách instantiating foo trước tất cả các phiên bản của nó đã được xác định và sau đó một lần nữa sau đó (xem xét cách trình liên kết giả định rằng các định nghĩa hàm mẫu thừa là giống hệt nhau, có thể hoán đổi cho nhau và loại bỏ được).

Vì vậy, ai là đúng?


Các GOTW trên liên kết độc đáo giải thích như thế nào và tại sao chức năng mẫu không một phần chuyên, nhưng sự tồn tại của chức năng template variadic dường như để thêm vào sự nhầm lẫn - trực giác rằng foo<int,Tail...> phải là một đặc tả từng phần của foo<Head,Tail...> mạnh hơn trực giác đối với các hàm phi-variadic, ít nhất là đối với tôi.

+0

FWIW, clang ++ tạo ra kết quả tương tự như gcc. – Cubbi

+0

Tôi không chắc chắn, nhưng có vẻ như khi foo() được gọi, nó sẽ khởi tạo foo (int, Tail ...) mà sau đó cố gắng foo (đuôi ...) và khi tuyên bố về phía trước là không có , nó sẽ không thấy foo (Head, Tail ...) và chỉ có thể chọn foo (int, Tail ...) một ... Bạn cũng có thể thêm một cuộc gọi đến bar (x); vào foo (int, Tail ...) và sau đó khai báo một thanh() sau khi tất cả các hàm foo() ... nó sẽ không được tìm thấy quá – PlasmaHH

Trả lời

8

GCC (và Clang) là đúng. MSVC sẽ hiểu sai vì nó không thực hiện tra cứu chính xác.

Có vẻ như là sự hiểu lầm từ đồng nghiệp của bạn. Các quy tắc để nhìn lên là:

  • cơ sở mẫu chức năng cần được khai báo trước khi nó được gọi từ một định nghĩa
  • chức năng template chuyên ngành cần được khai báo trước khi nó được khởi tạo

Note : các quy tắc đó áp dụng cho các chức năng miễn phí, trong một lớp không cần khai báo trước

Lưu ý rằng vì định nghĩa cũng hoạt động như một tuyên bố không cần thiết, trong ví dụ của bạn, để chuyển tiếp tuyên bố phiên bản int.

Đúng dụ:

template <typename T> void foo(T);    // declare foo<T> 

template <typename T> void bar(T t) { foo(t); }// call foo<T> (dependent context) 

template <> void foo<int>(int);    // declare specialiaztion foo<int> 

void bar(int i) { foo(i); }     // instantiate foo<T> with int 
               // which is the specialization 

Nếu có mẫu cơ sở có sẵn, đây là một lỗi. Nếu chuyên môn không được khai báo trước khi instantiation, nó sẽ không được sử dụng, và điều này có thể, sau đó, có nghĩa là vi phạm quy tắc ODR (nếu một instantiation sử dụng chuyên môn).

Từ Standard (C++ 0x FDI):

14.6.4.2

1. Đối với một cuộc gọi chức năng mà phụ thuộc vào một tham số mẫu, các chức năng ứng cử viên được tìm thấy sử dụng các quy tắc tra cứu thông thường (3.4.1, 3.4.2, 3.4.3) ngoại trừ:

- Đối với phần tra cứu sử dụng tra cứu tên không đủ tiêu chuẩn (3.4.1) hoặc tra cứu tên đủ điều kiện (3.4.3) , chỉ các khai báo hàm từ ngữ cảnh định nghĩa mẫu được tìm thấy.

- Đối với phần tra cứu bằng cách sử dụng các không gian tên được liên kết (3.4.2), chỉ có các khai báo chức năng được tìm thấy trong ngữ cảnh định nghĩa mẫu hoặc ngữ cảnh instantiation mẫu được tìm thấy.

Nếu tên hàm là id không đủ tiêu chuẩn và cuộc gọi sẽ bị hỏng hoặc sẽ tìm thấy kết quả phù hợp hơn, hãy tra cứu trong các không gian tên được kết hợp xem tất cả các khai báo hàm có liên kết bên ngoài được giới thiệu trong các không gian tên đó trong tất cả các đơn vị dịch , không chỉ xem xét những tuyên bố được tìm thấy trong định nghĩa mẫu và bối cảnh instantiation mẫu, sau đó chương trình có hành vi không xác định.

Lưu ý rằng các đoạn được đề cập là dành cho các chức năng thông thường.

+1

Tôi không chắc làm thế nào ví dụ của bạn có liên quan đến câu hỏi. Trong câu hỏi không có chuyên môn rõ ràng, chỉ các mẫu chức năng quá tải. –

+0

@Charles: Tôi đã có cơ hội đánh hai con chim bằng đá. –

6

Hai pha tra cứu sẽ tìm thấy:

  • chức năng mà có thể nhìn thấy tại thời điểm định nghĩa, và
  • chức năng có thể được tìm thấy bằng cách ADL tại điểm instantiation.

template <typename Head, typename... Tail> void foo (Head x, Tail... tail) không thể được tìm thấy bởi ADL, vì vậy nếu không hiển thị ở điểm định nghĩa, nó sẽ không được tìm thấy ở tất cả.

Nói cách khác, GCC là đúng.

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