Làm thế nào để `void_t` hoạt động


148

Tôi đã xem cuộc nói chuyện của Walter Brown tại Cppcon14 về lập trình mẫu hiện đại ( Phần I , Phần II ) nơi anh ấy trình bày void_tkỹ thuật SFINAE của mình .

Ví dụ:
Đưa ra một mẫu biến đơn giản để ước tính voidnếu tất cả các đối số mẫu được hình thành tốt:

template< class ... > using void_t = void;

và đặc điểm sau đây kiểm tra sự tồn tại của biến thành viên được gọi là thành viên :

template< class , class = void >
struct has_member : std::false_type
{ };

// specialized as has_member< T , void > or discarded (sfinae)
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : std::true_type
{ };

Tôi đã cố gắng để hiểu tại sao và làm thế nào điều này hoạt động. Vì vậy, một ví dụ nhỏ:

class A {
public:
    int member;
};

class B {
};

static_assert( has_member< A >::value , "A" );
static_assert( has_member< B >::value , "B" );

1. has_member< A >

  • has_member< A , void_t< decltype( A::member ) > >
    • A::member tồn tại
    • decltype( A::member ) được hình thành tốt
    • void_t<> là hợp lệ và đánh giá void
  • has_member< A , void > và do đó nó chọn mẫu chuyên dụng
  • has_member< T , void > và đánh giá true_type

2. has_member< B >

  • has_member< B , void_t< decltype( B::member ) > >
    • B::member không tồn tại
    • decltype( B::member ) không thành công và thất bại âm thầm (sfinae)
    • has_member< B , expression-sfinae > vì vậy mẫu này bị loại bỏ
  • trình biên dịch tìm thấy has_member< B , class = void >với void là đối số mặc định
  • has_member< B > đánh giá false_type

http://ideone.com/HCTlBb

Câu hỏi:
1. Sự hiểu biết của tôi về điều này có đúng không?
2. Walter Brown tuyên bố rằng đối số mặc định phải là loại chính xác giống như đối số được sử dụng void_tđể nó hoạt động. Tại sao vậy? (Tôi không thấy lý do tại sao các loại này cần phải khớp, không phải bất kỳ loại mặc định nào thực hiện công việc?)


6
Quảng cáo 2) Hãy tưởng tượng khẳng định tĩnh được viết là : has_member<A,int>::value. Sau đó, chuyên môn hóa một phần mà đánh giá là has_member<A,void>không thể phù hợp. Do đó, nó cần phải has_member<A,void>::value, hoặc, với đường cú pháp, một đối số mặc định của loại void.
10:30

1
@dyp Cảm ơn, tôi sẽ chỉnh sửa nó. Mh, tôi không thấy cần phải has_member< T , class = void >mặc định void. Giả sử đặc điểm này sẽ chỉ được sử dụng với 1 đối số mẫu bất cứ lúc nào, thì đối số mặc định có thể là loại nào?
vô nghĩa

Câu hỏi thú vị.
AStopher

2
Lưu ý rằng trong đề xuất này, open-std.org/jtc1/sc22/wg21/docs/ con / 2015 / n4436.pdf , Walter đã đổi template <class, class = void>thành template <class, class = void_t<>>. Vì vậy, bây giờ chúng tôi có thể tự do làm bất cứ điều gì chúng tôi muốn với void_tviệc triển khai mẫu bí danh :)
JohnKoch

Câu trả lời:


132

1. Mẫu lớp chính

Khi bạn viết has_member<A>::value, trình biên dịch sẽ tra cứu tên has_membervà tìm thấy mẫu lớp chính , nghĩa là khai báo này:

template< class , class = void >
struct has_member;

(Trong OP, đó được viết như một định nghĩa.)

Danh sách đối số mẫu <A>được so sánh với danh sách tham số mẫu của mẫu chính này. Vì mẫu chính có hai tham số, nhưng bạn chỉ cung cấp một tham số, tham số còn lại được mặc định cho đối số mẫu mặc định : void. Như thể bạn đã viết has_member<A, void>::value.

2. Mẫu lớp học chuyên biệt

Bây giờ , danh sách tham số mẫu được so sánh với bất kỳ chuyên môn nào của mẫu has_member. Chỉ khi không có chuyên môn phù hợp, định nghĩa của mẫu chính được sử dụng làm dự phòng. Vì vậy, chuyên môn hóa một phần được tính đến:

template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };

Trình biên dịch cố gắng khớp các đối số khuôn mẫu A, voidvới các mẫu được xác định trong chuyên môn hóa từng phần: Tvoid_t<..>từng cái một. Đầu tiên , khấu trừ đối số mẫu được thực hiện. Chuyên môn hóa một phần ở trên vẫn là một mẫu với các tham số mẫu cần được "điền" bởi các đối số.

Mẫu đầu tiên T , cho phép trình biên dịch suy ra tham số mẫu T. Đây là một suy luận tầm thường, nhưng hãy xem xét một mô hình như T const&, nơi chúng ta vẫn có thể suy luận T. Đối với mẫu Tvà đối số mẫu A, chúng tôi suy luận TA.

Trong mẫu thứ hai void_t< decltype( T::member ) > , tham số mẫu Txuất hiện trong ngữ cảnh không thể suy ra từ bất kỳ đối số mẫu nào.

Có hai lý do cho việc này:

  • Biểu thức bên trong decltypeđược loại trừ rõ ràng khỏi suy luận đối số mẫu. Tôi đoán điều này là bởi vì nó có thể phức tạp tùy ý.

  • Ngay cả khi chúng tôi sử dụng một mẫu mà không decltypethích void_t< T >, thì việc khấu trừ Txảy ra trên mẫu bí danh đã giải quyết. Đó là, chúng tôi giải quyết mẫu bí danh và sau đó cố gắng suy ra loại Ttừ mẫu kết quả. Tuy nhiên, mẫu kết quả là void, không phụ thuộc vào Tvà do đó không cho phép chúng ta tìm một loại cụ thể cho T. Điều này tương tự như bài toán toán cố gắng đảo ngược một hàm hằng (theo nghĩa toán học của các thuật ngữ đó).

Khấu trừ đối số mẫu đã kết thúc (*) , bây giờ các đối số khuôn mẫu được suy ra được thay thế. Điều này tạo ra một chuyên môn trông như thế này:

template<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };

Các loại void_t< decltype( A::member ) >bây giờ có thể được đánh giá. Nó được hình thành tốt sau khi thay thế, do đó, không có Thất bại thay thế xảy ra. Chúng tôi nhận được:

template<>
struct has_member<A, void> : true_type
{ };

3. Lựa chọn

Bây giờ , chúng ta có thể so sánh danh sách tham số mẫu của chuyên ngành này với các đối số mẫu được cung cấp cho bản gốc has_member<A>::value. Cả hai loại đều khớp chính xác, vì vậy chuyên môn hóa một phần này được chọn.


Mặt khác, khi chúng tôi xác định mẫu là:

template< class , class = int > // <-- int here instead of void
struct has_member : false_type
{ };

template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };

Chúng tôi kết thúc với cùng một chuyên ngành:

template<>
struct has_member<A, void> : true_type
{ };

nhưng danh sách đối số mẫu của chúng tôi has_member<A>::valuebây giờ là <A, int>. Các đối số không khớp với các tham số của chuyên môn hóa và mẫu chính được chọn làm dự phòng.


(*) Tiêu chuẩn, IMHO gây nhầm lẫn, bao gồm quá trình thay thế và khớp các đối số khuôn mẫu được chỉ định rõ ràng trong quy trình khấu trừ đối số mẫu . Ví dụ: (sau N4296) [temp. Class.spec.match] / 2:

Chuyên môn hóa một phần khớp với danh sách đối số mẫu thực tế đã cho nếu các đối số mẫu của chuyên môn hóa một phần có thể được suy ra từ danh sách đối số mẫu thực tế.

Nhưng điều này không chỉ có nghĩa là tất cả các tham số mẫu của chuyên môn hóa một phần phải được suy luận; điều đó cũng có nghĩa là sự thay thế phải thành công và (có vẻ như?) các đối số khuôn mẫu phải khớp với các tham số khuôn mẫu (được thay thế) của chuyên môn hóa một phần. Lưu ý rằng tôi không hoàn toàn nhận thức được nơi Tiêu chuẩn chỉ định so sánh giữa danh sách đối số được thay thế và danh sách đối số được cung cấp.


3
Cảm ơn bạn! Tôi đã đọc đi đọc lại nhiều lần và tôi đoán suy nghĩ của tôi về cách suy luận đối số khuôn mẫu hoạt động chính xác và những gì trình biên dịch chọn cho mẫu cuối cùng là không chính xác.
vô nghĩa

1
@ JulianSchaub-litb Cảm ơn! Đó là một chút buồn, mặc dù. Có thực sự không có quy tắc nào để khớp đối số mẫu với chuyên môn không? Ngay cả đối với các chuyên ngành rõ ràng?
dyp

2
Đối số mẫu mặc định của W / r / t, open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2008
TC

1
@dyp Một vài tuần sau đó và đọc rất nhiều về điều này và với một gợi ý từ đoạn trích này tôi nghĩ rằng tôi bắt đầu hiểu cách thức hoạt động của nó. Lời giải thích của bạn làm cho từ đọc để đọc có ý nghĩa hơn với tôi, cảm ơn!
vô nghĩa

1
Tôi muốn thêm, rằng thuật ngữ mẫu chính là khóa (các mẫu gặp lần đầu tiên trong mã)
vô nghĩa

18
// specialized as has_member< T , void > or discarded (sfinae)
template<class T>
struct has_member<T , void_t<decltype(T::member)>> : true_type
{ };

Đó là chuyên môn hóa trên chỉ tồn tại khi nó được hình thành tốt, vì vậy khi decltype( T::member )có giá trị và không mơ hồ. sự chuyên môn hóa là như vậy cho has_member<T , void>trạng thái trong bình luận.

Khi bạn viết has_member<A>, đó là has_member<A, void>do đối số mẫu mặc định.

Và chúng tôi có chuyên môn hóa cho has_member<A, void>(vì vậy kế thừa từ true_type) nhưng chúng tôi không có chuyên môn hóa cho has_member<B, void>(vì vậy chúng tôi sử dụng định nghĩa mặc định: kế thừa từ false_type)

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.