Lỗi mẫu khó hiểu


91

Tôi đã chơi với clang một thời gian và tôi tình cờ gặp "test / SemaTemplate / depend-template-recovery.cpp" (trong bản phân phối clang) được cho là cung cấp các gợi ý để khôi phục sau lỗi mẫu.

Toàn bộ sự việc có thể dễ dàng rút gọn thành một ví dụ nhỏ nhất:

template<typename T, typename U, int N> struct X {
    void f(T* t)
    {
        // expected-error{{use 'template' keyword to treat 'f0' as a dependent template name}}
        t->f0<U>();
    }
};

Thông báo lỗi do clang mang lại:

tpl.cpp:6:13: error: use 'template' keyword to treat 'f0' as a dependent template name
         t->f0<U>();
            ^
            template 
1 error generated.

... Nhưng tôi rất khó hiểu nơi chính xác người ta phải chèn templatetừ khóa để có mã chính xác về mặt cú pháp?


11
Bạn đã thử chèn nó vào vị trí mũi tên đang trỏ chưa?
Mike Seymour,

3
Tương tự như nàynày
Prasoon Saurav

Câu trả lời:


104

ISO C ++ 03 14.2 / 4:

Khi tên của chuyên ngành mẫu thành viên xuất hiện sau. hoặc -> trong biểu thức hậu tố hoặc sau mã định danh-tên được lồng trong id đủ điều kiện và biểu thức hậu tố hoặc id đủ điều kiện phụ thuộc rõ ràng vào tham số mẫu (14.6.2), tên mẫu thành viên phải là được đặt trước bởi mẫu từ khóa . Nếu không, tên này được coi là tên của một mẫu không phải.

Trong t->f0<U>(); f0<U>là một chuyên môn mẫu thành viên xuất hiện sau ->và phụ thuộc rõ ràng vào tham số mẫu U, vì vậy chuyên môn mẫu thành viên phải được bắt đầu bằng templatetừ khóa.

Vì vậy, hãy thay đổi t->f0<U>()thành t->template f0<U>().


Điều thú vị là, tôi nghĩ rằng việc đưa biểu thức trong ngoặc đơn: t->(f0<U>())sẽ đã sửa lỗi này, như tôi nghĩ rằng sẽ đưa f0<U>()vào biểu hiện độc lập ... tốt, tôi nghĩ sai, có vẻ như ...

24
Bạn có thể bình luận về lý do tại sao trường hợp này xảy ra? Tại sao C ++ yêu cầu loại cú pháp này?
Tò mò

2
Yeah, điều này thật kỳ lạ. Ngôn ngữ có thể "phát hiện" rằng từ khóa mẫu cần phải có mặt. Nếu nó có thể làm được điều đó thì nó chỉ nên "chèn" từ khóa vào đó.
Enrico Borba

26

Ngoài những điểm mà những người khác đã đưa ra, hãy lưu ý rằng đôi khi trình biên dịch không thể quyết định và cả hai cách diễn giải đều có thể mang lại các chương trình hợp lệ thay thế khi khởi tạo

#include <iostream>

template<typename T>
struct A {
  typedef int R();

  template<typename U>
  static U *f(int) { 
    return 0; 
  }

  static int f() { 
    return 0;
  }
};

template<typename T>
bool g() {
  A<T> a;
  return !(typename A<T>::R*)a.f<int()>(0);
}


int main() {
  std::cout << g<void>() << std::endl;
}

Điều này in ra 0khi bỏ qua templatetrước f<int()>nhưng 1khi chèn nó vào. Tôi để nó như một bài tập để tìm ra mã hoạt động.


3
Bây giờ đó là một ví dụ quỷ quái!
Matthieu M.

1
Tôi không thể tái tạo hành vi bạn đang mô tả trong Visual Studio 2013. Nó luôn gọi f<U>và luôn in 1, điều này hoàn toàn hợp lý với tôi. Tôi vẫn không hiểu tại sao templatetừ khóa lại được yêu cầu và nó tạo ra sự khác biệt gì.
Violet Giraffe

@Violet trình biên dịch VSC ++ không phải là trình biên dịch C ++ tuân thủ. Một câu hỏi mới là cần thiết nếu bạn muốn biết tại sao VSC ++ luôn in 1.
Johannes Schaub - litb

1
Câu trả lời này giải thích tại sao lại templatecần: stackoverflow.com/questions/610245/… mà không chỉ dựa vào các thuật ngữ tiêu chuẩn khó hiểu. Vui lòng báo cáo nếu bất kỳ điều gì trong câu trả lời đó vẫn còn khó hiểu.
Johannes Schaub - litb

@ JohannesSchaub-litb: Cảm ơn, một câu trả lời tuyệt vời. Hóa ra, tôi đã đọc nó trước đây vì nó đã được tôi ủng hộ. Rõ ràng, trí nhớ của tôi là meh.
Violet Hươu cao cổ

12

Chèn nó ngay trước điểm có dấu mũ:

template<typename T, typename U, int N> struct X {
     void f(T* t)
     {
        t->template f0<U>();
     }
};

Chỉnh sửa: lý do cho quy tắc này trở nên rõ ràng hơn nếu bạn nghĩ giống như một trình biên dịch. Các trình biên dịch thường chỉ nhìn về phía trước một hoặc hai mã thông báo cùng một lúc và thường không "nhìn trước" phần còn lại của biểu thức. [Chỉnh sửa: xem nhận xét] Lý do cho từ khóa cũng giống như lý do bạn cần typenametừ khóa để chỉ ra tên loại phụ thuộc: nó nói với trình biên dịch "này, số nhận dạng bạn sắp thấy là tên của một mẫu, thay vì tên của một thành viên dữ liệu tĩnh theo sau bởi một dấu nhỏ hơn ".


1
Tôi sẽ không bao giờ có thể đoán được điều đó ... nhưng cảm ơn bạn ;-). rõ ràng luôn luôn có điều gì đó để tìm hiểu về C ++!

3
Ngay cả khi nhìn về phía trước vô hạn, bạn vẫn sẽ cần template. Có những trường hợp cả có và không có templatesẽ mang lại các chương trình hợp lệ với các hành vi khác nhau. Vì vậy, đây không chỉ là một vấn đề cú pháp ( t->f0<int()>(0)có giá trị về mặt cú pháp cho cả phiên bản danh sách đối số nhỏ hơn và mẫu).
Johannes Schaub - litb 24/09/10

@Johannes Schaub - litb: Đúng vậy, vấn đề là gán ý nghĩa ngữ nghĩa nhất quán cho biểu thức, hơn là nhìn về phía trước.
Doug

11

Trích từ C ++ Templates

Cấu trúc .template Một vấn đề rất tương tự đã được phát hiện sau sự ra đời của typename. Hãy xem xét ví dụ sau bằng cách sử dụng loại bitet tiêu chuẩn:

template<int N> 
void printBitset (std::bitset<N> const& bs) 
{ 
    std::cout << bs.template to_string<char,char_traits<char>, 
                                       allocator<char> >(); 
} 

Cấu trúc lạ trong ví dụ này là .template. Nếu không sử dụng thêm mẫu đó, trình biên dịch sẽ không biết rằng mã thông báo nhỏ hơn (<) theo sau không thực sự là "nhỏ hơn" mà là phần đầu của danh sách đối số mẫu. Lưu ý rằng đây chỉ là sự cố nếu cấu trúc trước khoảng thời gian phụ thuộc vào tham số mẫu. Trong ví dụ của chúng tôi, tham số bs phụ thuộc vào tham số mẫu N.

Tóm lại, ký hiệu .template (và các ký hiệu tương tự như -> template) chỉ nên được sử dụng bên trong các mẫu và chỉ khi chúng tuân theo thứ gì đó phụ thuộc vào tham số mẫu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.