Khái niệm C ++ 20: Chuyên môn hóa mẫu nào được chọn khi đối số mẫu đủ điều kiện cho nhiều khái niệm?


23

Được :

#include <concepts>
#include <iostream>

template<class T>
struct wrapper;

template<std::signed_integral T>
struct wrapper<T>
{
    wrapper() = default;
    void print()
    {
        std::cout << "signed_integral" << std::endl;
    }
};

template<std::integral T>
struct wrapper<T>
{
    wrapper() = default;
    void print()
    {
        std::cout << "integral" << std::endl;
    }
};

int main()
{
    wrapper<int> w;
    w.print(); // Output : signed_integral
    return 0;
}

Từ mã ở trên, intđủ điều kiện cho cả hai std::integralstd::signed_integralkhái niệm.

Đáng ngạc nhiên là điều này biên dịch và in "Sign_integral" trên cả trình biên dịch GCC và MSVC. Tôi đã hy vọng nó không thành công với một lỗi dọc theo dòng "chuyên môn mẫu đã được xác định".

Được rồi, đó là hợp pháp, đủ công bằng, nhưng tại sao được std::signed_integralchọn thay vì std::integral? Có bất kỳ quy tắc nào được xác định trong tiêu chuẩn với chuyên môn mẫu nào được chọn khi nhiều khái niệm đủ điều kiện cho đối số mẫu không?


Tôi sẽ không nói rằng nó là hợp pháp chỉ bởi thực tế là (các) trình biên dịch chấp nhận nó, đặc biệt là trong giai đoạn đầu của việc áp dụng nó.
Slava

@ Trong trường hợp này, các khái niệm được thiết kế cẩn thận để chúng kết hợp với nhau một cách trực quan
Guillaume Racicot

@GuillaumeRacicot nó vẫn ổn, tôi chỉ nhận xét rằng kết luận "đó là hợp pháp vì trình biên dịch đã chấp nhận nó" cho phép nói sai. Tôi đã không nói điều này là không hợp pháp mặc dù.
Slava

Câu trả lời:


14

Điều này là do các khái niệm có thể chuyên biệt hơn các khái niệm khác, giống như cách tự sắp xếp mẫu. Điều này được gọi là thứ tự một phần của các ràng buộc

Trong trường hợp các khái niệm, chúng tồn tại lẫn nhau khi chúng bao gồm các ràng buộc tương đương. Ví dụ, đây là cách std::integralstd::signed_integralđược thực hiện:

template<typename T>
concept integral = std::is_integral_v<T>;

template<typename T> //   v--------------v---- Using the contraint defined above
concept signed_integral = std::integral<T> && std::is_signed_v<T>;

Bình thường hóa các ràng buộc trình biên dịch đun sôi biểu thức chống chỉ định này:

template<typename T>
concept integral = std::is_integral_v<T>;

template<typename T>
concept signed_integral = std::is_integral_v<T> && std::is_signed_v<T>;

Trong ví dụ này, signed_integralngụ ý integralhoàn toàn. Vì vậy, theo một nghĩa nào đó, một tích phân đã ký là "nhiều ràng buộc" hơn một tích phân.

Các tiêu chuẩn viết nó như thế này:

Từ [temp.func.order] / 2 (nhấn mạnh của tôi):

Thứ tự từng phần chọn mẫu nào trong hai mẫu hàm chuyên biệt hơn mẫu kia bằng cách chuyển đổi lần lượt từng mẫu (xem đoạn tiếp theo) và thực hiện khấu trừ đối số mẫu bằng cách sử dụng loại hàm. Quá trình khấu trừ xác định xem một trong các mẫu có chuyên biệt hơn các mẫu khác không. Nếu vậy, mẫu chuyên biệt hơn là mẫu được chọn bởi quy trình đặt hàng một phần. Nếu cả hai khoản khấu trừ thành công, thứ tự từng phần sẽ chọn mẫu bị ràng buộc nhiều hơn như được mô tả bởi các quy tắc trong [temp.constr.order] .

Điều đó có nghĩa là nếu có nhiều sự thay thế có thể cho một mẫu và cả hai đều được chọn từ thứ tự một phần, nó sẽ chọn mẫu bị ràng buộc nhất.

Từ [temp.constr.order] / 1 :

Một ràng buộc P tạo ra một ràng buộc Q khi và chỉ khi, đối với mọi mệnh đề phân biệt P i ở dạng bình thường không phân biệt của P , P i bao gồm mọi mệnh đề liên hợp Q j ở dạng bình thường kết hợp của Q , trong đó

  • một điều khoản ly tiếp P i subsumes một nối tiếp khoản Q j nếu và chỉ nếu có tồn tại một hạn chế nguyên tử P ia trong P i mà có tồn tại một hạn chế nguyên tử Q jb trong Q jP ia subsumes Q jb , và

  • một ràng buộc nguyên tử A bao gồm một ràng buộc nguyên tử B khác khi và chỉ khi AB giống hệt nhau bằng cách sử dụng các quy tắc được mô tả trong [temp.constr.atomic] .

Điều này mô tả thuật toán tiêu thụ mà trình biên dịch sử dụng để sắp xếp các ràng buộc và do đó là các khái niệm.


2
Có vẻ như bạn đang đi giữa một đoạn ...
ShadowRanger

11

C ++ 20 có một cơ chế để quyết định khi một thực thể bị ràng buộc cụ thể nào "bị ràng buộc" hơn một thực thể khác. Đây không phải là một điều đơn giản.

Điều này bắt đầu với khái niệm phá vỡ một ràng buộc thành các thành phần nguyên tử của nó, một quá trình gọi là chuẩn hóa ràng buộc . Nó lớn và quá phức tạp để đi vào đây, nhưng ý tưởng cơ bản là mỗi biểu thức trong một ràng buộc được chia thành các phần khái niệm nguyên tử của nó, theo cách đệ quy, cho đến khi bạn đạt đến một biểu thức phụ thành phần không phải là một khái niệm.

Vì vậy, hãy xem xét cách integralvà các signed_integralkhái niệm được định nghĩa :

template<class T>
  concept integral = is_integral_v<T>;
template<class T>
  concept signed_­integral = integral<T> && is_signed_v<T>;

Sự phân rã của integralchỉ là is_integral_v. Sự phân hủy của signed_integralis_integral_v && is_signed_v.

Bây giờ, chúng ta đến với khái niệm về hạn chế hạn chế . Điều này khá phức tạp, nhưng ý tưởng cơ bản là một ràng buộc C1 được cho là "tạo ra" một ràng buộc C2 nếu sự phân tách của C1 chứa mọi biểu thức con trong C2. Chúng ta có thể thấy rằng integralkhông bao hàm signed_integral, nhưng signed_integral không bao hàm integral, vì nó chứa tất cả những gì integralkhông.

Tiếp theo, chúng ta đến để đặt hàng các thực thể bị ràng buộc:

Một khai báo D1 ít nhất là bị ràng buộc như một khai báo D2 nếu * D1 và D2 ​​đều là các khai báo bị ràng buộc và các ràng buộc liên quan của D1 bao gồm các khai báo của D2; hoặc * D2 không có ràng buộc liên quan.

Bởi vì các khoản signed_integralphụ integral, <signed_integral> wrapper"ít nhất là bị ràng buộc" như <integral> wrapper. Tuy nhiên, điều ngược lại là không đúng sự thật, do sự sụt giảm không thể đảo ngược.

Do đó, theo quy tắc cho các thực thể "ràng buộc hơn":

Một khai báo D1 bị ràng buộc nhiều hơn một khai báo D2 khác khi D1 ít nhất bị ràng buộc như D2 và D2 ​​ít nhất không bị ràng buộc như D1.

<integral> wrapperít nhất là không bị ràng buộc như <signed_integral> wrapper, cái sau được coi là hạn chế hơn cái trước.

Và do đó, khi cả hai có thể áp dụng, tuyên bố ràng buộc hơn sẽ thắng.


Xin lưu ý rằng các quy tắc hạn chế ràng buộc dừng khi gặp một biểu thức không phải là a concept. Vì vậy, nếu bạn đã làm điều này:

template<typename T>
constexpr bool my_is_integral_v = std::is_integral_v<T>;

template<typename T>
concept my_signed_integral = my_is_integral_v<T> && std::is_signed_v<T>;

Trong trường hợp này, my_signed_integral sẽ không bao gồm std::integral. Mặc dù my_is_integral_vđược định nghĩa giống hệt nhau std::is_integral_v, bởi vì đó không phải là một khái niệm, các quy tắc phụ của C ++ không thể nhìn xuyên qua nó để xác định rằng chúng giống nhau.

Vì vậy, các quy tắc tiêu thụ khuyến khích bạn xây dựng các khái niệm ngoài hoạt động trên các khái niệm nguyên tử.


3

Với Partial_ordering_of_constraint

Một ràng buộc P được cho là bao gồm ràng buộc Q nếu có thể chứng minh rằng P ngụ ý Q cho đến khi xác định các ràng buộc nguyên tử trong P và Q.

Mối quan hệ thuê bao xác định thứ tự một phần của các ràng buộc, được sử dụng để xác định:

  • ứng cử viên khả thi tốt nhất cho chức năng không phải mẫu trong độ phân giải quá tải
  • địa chỉ của hàm không phải mẫu trong tập quá tải
  • kết quả phù hợp nhất cho đối số mẫu
  • đặt hàng một phần các chuyên ngành mẫu lớp
  • thứ tự một phần của các mẫu chức năng

Và khái niệm std::signed_integralbao gồm std::integral<T>khái niệm:

template < class T >
concept signed_integral = std::integral<T> && std::is_signed_v<T>;

Vì vậy, mã của bạn là ok, như std::signed_integrallà "chuyên ngành" hơn.

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.