6

Tôi đã gặp khó khăn với vấn đề biên dịch và có thể thu hẹp sự cố xuống một đoạn mã nhỏ.Hành vi đặc biệt đối với người khai thác cuộc gọi đối với các loại không đầy đủ

Để đặt giai đoạn, tôi đang cố gắng thực hiện CRTP, trong đó phương thức cơ sở gọi một phương thức khác trong lớp dẫn xuất. Biến chứng là, tôi muốn sử dụng các kiểu trả về theo sau để có được kiểu chuyển tiếp trực tiếp đến phương thức của lớp Derived. Điều này luôn không thể biên dịch trừ khi Tôi chuyển tiếp đến toán tử cuộc gọi trong lớp dẫn xuất.

này biên dịch:

#include <utility> 

struct Incomplete; 

template <typename Blah> 
struct Base 
{ 
    template <typename... Args> 
    auto entry(Args&&... args) 
     -> decltype(std::declval<Blah&>()(std::declval<Args&&>()...)); 
}; 

void example() 
{ 
    Base<Incomplete> derived; 
} 

Trong khi điều này không: (lưu ý Viết bình luận cho sự khác biệt duy)

#include <utility> 

struct Incomplete; 

template <typename Blah> 
struct Base 
{ 
    template <typename... Args> 
    auto entry(Args&&... args) 
     -> decltype(std::declval<Blah&>().operator()(std::declval<Args&&>()...)); 
     //    I only added this ^^^^^^^^^^^ 
}; 

void example() 
{ 
    Base<Incomplete> derived; 
} 

Các lỗi tôi nhận được:

<source>: In instantiation of 'struct Base<Incomplete>': 
15 : <source>:15:22: required from here 
10 : <source>:10:58: error: invalid use of incomplete type 'struct Incomplete' 
     -> decltype(std::declval<Blah&>().operator()(std::declval<Args&&>()...)); 
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ 

Có vẻ là một số hành vi đặc biệt xảy ra trong khi giải quyết các decltype trong lớp Derived. Có cái gì đó trong tiêu chuẩn sẽ giải thích điều này?

EDIT: Đã thực hiện một việc đơn giản hóa thậm chí lớn hơn

PS: biên soạn dụ trên godbolt: https://godbolt.org/g/St2gYC

+0

Tại sao bạn không viết 'std :: declval ()'? Tôi đoán rằng khối thứ hai là một sử dụng odr của 'Incomplete' trong khi lần đầu tiên là không. –

+0

@PasserBy Dù bằng cách nào, đó cũng là vấn đề tương tự. Tôi đã thay đổi nó thành gợi ý của bạn để có ít nhầm lẫn hơn. –

+0

Bạn đang tìm kiếm điều này có thể? https://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda –

Trả lời

4

Instantiating một lớp mẫu instantiates tờ khai mẫu hàm thành viên của nó ([temp.inst]/2). I E. chúng tôi đang xem xét việc kê khai

template <typename... Args> 
auto entry(Args&&... args) 
    -> decltype(std::declval<Incomplete&>().operator()(std::declval<Args&&>()...)); 

Bây giờ xem xét [temp.res]/10:

Nếu một tên không phụ thuộc vào một mẫu tham số (theo quy định tại 14.6.2), một tuyên bố (hay tập hợp các tờ khai) cho tên đó phải nằm trong phạm vi tại điểm tên xuất hiện trong định nghĩa mẫu;

Và thực tế, tên operator() không phụ thuộc vào thông số mẫu. Nó không phải là loại hay cũng không phụ thuộc vào giá trị, và nó cũng không phải là tên phụ thuộc. Rõ ràng, không có tuyên bố trong phạm vi, do đó tuyên bố là hình thành bệnh, không cần chẩn đoán.

Ngược lại, đoạn mã đầu tiên của bạn không cần tra cứu tên trong phạm vi Incomplete. Sự biến đổi của x(...), nơi x là loại lớp, để x.operator()(...) chỉ xảy ra sau khi operator() được ngẩng đầu lên trong vòng x — [over.call]:

Do đó, một cuộc gọi x(arg1,...) được hiểu như là x.operator() (arg1, .. .) đối với đối tượng lớp x của loại T nếu T​::​operator()(T1, T2, T3) tồn tại và nếu toán tử được chọn làm chức năng đối sánh tốt nhất bằng cách quá tải cơ chế phân giải ([over.match.best]).

Điều này khác với đoạn mã khiến mã thứ hai của bạn không được định dạng: [temp.res]/10 nói rằng một số (các) tuyên bố phải nằm trong phạm vi và rằng tên đó được ràng buộc với các khai báo đó. Việc chuyển đổi trên đòi hỏi rằng các kiểu đối số (và cả số ...) được biết đến như vậy mà chúng ta có thể xác định duy nhất một số operator() để được gọi; có nghĩa là, chúng tôi không chỉ chèn .operator(), nhưng luôn luôn đồng thời xác định hàm toán tử nào được gọi. Chúng tôi có thể tìm thêm xác nhận về cách giải thích này trong [temp.dep]:

Nếu toán hạng của toán tử là biểu thức phụ thuộc vào loại, toán tử cũng biểu thị tên phụ thuộc. Các tên như vậy không bị ràng buộc và được tra cứu tại thời điểm tạo mẫu [...]

Toán hạng đối số của operator() rõ ràng là loại phụ thuộc.

+0

Tuyệt vời! Lời giải thích hoàn hảo. Như một lưu ý phụ, cách tôi nhận được xung quanh vấn đề này là buộc toán tử '()' phụ thuộc vào tham số mẫu. Đầu tiên giới thiệu một từ khác 'template T mydeclval();' và sau đó sử dụng nó như vậy 'decltype (mydeclval (). Operator (std :: declval () ...)) ' –

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