2016-02-04 17 views
9

Tôi đang cố gắng hiểu việc triển khai std::is_class. Tôi đã sao chép một số triển khai có thể và biên dịch chúng, hy vọng tìm ra cách chúng hoạt động. Điều đó được thực hiện, tôi thấy rằng tất cả các tính toán được thực hiện trong quá trình biên dịch (như tôi đã tìm ra sớm hơn, nhìn lại), vì vậy gdb có thể cho tôi biết thêm chi tiết về chính xác những gì đang diễn ra.Cách triển khai này của std :: is_class hoạt động như thế nào?

Việc thực hiện tôi đang đấu tranh để hiểu là một trong những điều này:

template<class T, T v> 
    struct integral_constant{ 
    static constexpr T value = v; 
    typedef T value_type; 
    typedef integral_constant type; 
    constexpr operator value_type() const noexcept { 
     return value; 
    } 
}; 

namespace detail { 
    template <class T> char test(int T::*); //this line 
    struct two{ 
     char c[2]; 
    }; 
    template <class T> two test(...);   //this line 
} 

//Not concerned about the is_union<T> implementation right now 
template <class T> 
struct is_class : std::integral_constant<bool, sizeof(detail::test<T>(0))==1 
                && !std::is_union<T>::value> {}; 

Tôi đang gặp rắc rối với hai nhận xét dòng. Dòng đầu tiên:

template<class T> char test(int T::*); 

T::* có nghĩa là gì? Ngoài ra, đây không phải là một tuyên bố chức năng? Nó trông giống như một, nhưng điều này biên dịch mà không xác định một cơ quan chức năng.

Dòng thứ hai tôi muốn hiểu là:

template<class T> two test(...); 

Một lần nữa, đây không phải là khai báo hàm không có cơ thể bao giờ được xác định? Ngoài ra, dấu ba chấm có ý nghĩa gì trong ngữ cảnh này? Tôi nghĩ rằng một dấu ba chấm như một đối số chức năng yêu cầu một đối số được xác định trước ...?

Tôi muốn hiểu mã này đang làm gì. Tôi biết tôi chỉ có thể sử dụng các chức năng đã được triển khai từ thư viện chuẩn, nhưng tôi muốn hiểu cách chúng hoạt động.

Tài liệu tham khảo:

+0

Chức năng * khai báo * không chứa nội dung. Đó là cho các định nghĩa. –

+0

Định nghĩa hàm là khai báo hàm, Karoly. Nit bạn chọn là tất cả các định nghĩa đều là khai báo, nhưng không phải tất cả các khai báo đều là các định nghĩa. – Peter

Trả lời

8

Điều bạn đang xem là một số kỹ thuật lập trình được gọi là "SFINAE" viết tắt của "Thất bại thay thế không phải là lỗi". Ý tưởng cơ bản là:

namespace detail { 
    template <class T> char test(int T::*); //this line 
    struct two{ 
    char c[2]; 
    }; 
    template <class T> two test(...);   //this line 
} 

Không gian tên này cung cấp 2 quá tải cho test(). Cả hai đều là các mẫu, được giải quyết tại thời gian biên dịch. Người đầu tiên nhận một đối số int T::*. Nó được gọi là một thành viên-con trỏ và là một con trỏ đến một int, nhưng đến một int thats một thành viên của lớp T. Đây chỉ là một biểu thức hợp lệ, nếu T là một lớp. Thứ hai là lấy bất kỳ số lượng đối số nào, hợp lệ trong mọi trường hợp.

Vậy nó được sử dụng như thế nào?

sizeof(detail::test<T>(0))==1 

Ok, chúng tôi chuyển hàm 0 - đây có thể là con trỏ và đặc biệt là con trỏ thành viên - không có thông tin nào được sử dụng từ quá tải này. Vì vậy, nếu T là một lớp, thì chúng ta có thể sử dụng cả số T::* và quá tải ... ở đây - và do quá tải T::* là quá tải cụ thể ở đây, nó được sử dụng. Nhưng nếu T không phải là một lớp, thì chúng tôi không thể có một cái gì đó như T::* và quá tải là hình thành không đúng. Nhưng một thất bại của nó đã xảy ra trong quá trình thay thế tham số mẫu. Và vì "lỗi thay thế không phải là lỗi", trình biên dịch sẽ tự động bỏ qua tình trạng quá tải này.

Sau đó, áp dụng sizeof(). Nhận thấy các loại trả lại khác nhau?Vì vậy, tùy thuộc vào T trình biên dịch chọn quá tải đúng và do đó là kiểu trả về đúng, dẫn đến kích thước là sizeof(char) hoặc sizeof(char[2]).

Và cuối cùng, vì chúng tôi chỉ sử dụng kích thước của chức năng này và không bao giờ thực sự gọi nó, chúng tôi không cần triển khai.

1

nào T::* nghĩa là gì? Ngoài ra, đây không phải là một tuyên bố chức năng? Nó trông giống như một, nhưng điều này biên dịch mà không xác định một cơ quan chức năng.

int T::*pointer to member object. Nó có thể được sử dụng như sau:

struct T { int x; } 
int main() { 
    int T::* ptr = &T::x; 

    T a {123}; 
    a.*ptr = 0; 
} 

Một lần nữa, điều này không phải là một lời tuyên bố chức năng không có cơ thể bao giờ được xác định? Ngoài ra, dấu ba chấm có ý nghĩa gì trong ngữ cảnh này?

Trong đầu dây bên kia:

template<class T> two test(...); 

ellipsis is a C construct để xác định rằng một hàm mất bất kỳ số lượng các đối số.

Tôi muốn hiểu mã này đang làm gì.

Về cơ bản nó kiểm tra nếu một loại cụ thể là một struct hoặc một class bằng cách kiểm tra nếu 0 có thể được hiểu như một con trỏ thành viên (trong trường hợp T mà là một kiểu lớp).

Cụ thể, trong mã này:

namespace detail { 
    template <class T> char test(int T::*); 
    struct two{ 
     char c[2]; 
    }; 
    template <class T> two test(...); 
} 

bạn có hai quá tải:

  • một trong đó là phù hợp chỉ khi một T là một loại lớp (trong trường hợp này là phù hợp nhất và "thắng" trên giây thứ hai)
  • trên đó khớp với mỗi lần

Trong kết quả đầu tiên, sizeof cho kết quả là 1 (kiểu trả về của hàm là char), kết quả khác 2 (một cấu trúc có chứa 2 ký tự).

Giá trị boolean kiểm tra là sau đó:

sizeof(detail::test<T>(0)) == 1 && !std::is_union<T>::value 

có nghĩa là: trở true chỉ khi không thể thiếu hằng 0 có thể được hiểu như là một con trỏ tới thành viên của loại T (trong trường hợp này đó là một kiểu lớp), nhưng nó không phải là union (cũng là một loại lớp có thể).

1

Kiểm tra là hàm bị quá tải hoặc là con trỏ tới thành viên trong T hoặc bất kỳ thứ gì. C++ yêu cầu sử dụng kết hợp tốt nhất. Vì vậy, nếu T là một loại lớp, có thể có một thành viên trong đó ... thì phiên bản đó được chọn và kích thước của nó là 1. Nếu T không phải là một loại lớp thì T :: * không có ý nghĩa để phiên bản của hàm được lọc bởi SFINAE và sẽ không có ở đó. Phiên bản bất cứ thứ gì được sử dụng và kích thước của kiểu trả về không phải là 1. Vì vậy, kiểm tra kích thước của sự trở lại gọi hàm đó dẫn đến quyết định xem loại có thể có các thành viên hay không ... nếu đó là một lớp hay không.

8

Một phần của những gì gây nhầm lẫn cho bạn, điều này không được giải thích bởi các câu trả lời khác cho đến nay, là các hàm test không bao giờ thực sự được gọi. Thực tế họ không có định nghĩa không quan trọng nếu bạn không gọi chúng. Khi bạn nhận ra, toàn bộ điều xảy ra vào thời gian biên dịch, mà không cần chạy bất kỳ mã nào.

Biểu thức sizeof(detail::test<T>(0)) sử dụng toán tử sizeof trên biểu thức cuộc gọi hàm. Toán hạng của sizeof là một ngữ cảnh không được đánh giá trước, có nghĩa là trình biên dịch không thực sự thực thi mã đó (tức là đánh giá nó để xác định kết quả). Không cần phải gọi hàm đó để biết số sizeof kết quả sẽnếu bạn gọi nó là gì. Để biết kích thước của kết quả, trình biên dịch chỉ cần xem các khai báo của các hàm test khác nhau (để biết các kiểu trả về của chúng) và sau đó thực hiện độ phân giải quá tải để xem cái nào sẽ gọi là và để tìm những gì sizeof kết quả sẽ là.

Phần còn lại của câu đố là chức năng unevaluated gọi detail::test<T>(0) sẽ xác định xem T thể được sử dụng để tạo thành một con trỏ-to-thành viên loại int T::*, mà chỉ có thể nếu T là một loại lớp (vì không lớp học có thể' t có các thành viên, và do đó không thể có con trỏ đến các thành viên của họ). Nếu T là một lớp thì có thể gọi quá tải test đầu tiên, nếu không quá tải thứ hai sẽ được gọi. Quá tải thứ hai sử dụng danh sách tham số printf kiểu ... có nghĩa là nó chấp nhận bất kỳ thứ gì, nhưng cũng được coi là phù hợp hơn bất kỳ hàm khả thi nào khác (nếu không chức năng sử dụng ... sẽ quá "tham lam" và được gọi là mọi lúc , ngay cả khi có mũ chức năng cụ thể hơn phù hợp với đối số chính xác). Trong mã này, hàm ... là một dự phòng cho "nếu không có gì khác phù hợp, hãy gọi hàm này", vì vậy nếu T không phải là loại lớp thì dự phòng được sử dụng. Nó không quan trọng nếu loại lớp thực sự có một biến thành viên loại int, nó là hợp lệ để tạo thành các loại int T::* anyway cho bất kỳ lớp nào (bạn chỉ có thể không làm cho rằng con trỏ đến thành viên tham khảo bất kỳ thành viên nếu loại không có thành viên int).

+1

cộng 1 cho ba dòng cuối cùng. –

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