Sự khác biệt giữa các khái niệm và ràng buộc mẫu là gì?


96

Tôi muốn biết sự khác biệt về ngữ nghĩa giữa đề xuất khái niệm đầy đủ C ++ và các ràng buộc mẫu (ví dụ: các ràng buộc như đã xuất hiện trong Dlang hoặc đề xuất khái niệm mới cho C ++ 1y ).

Những khái niệm chính thức có khả năng làm gì hơn những ràng buộc khuôn mẫu không thể làm?


2
Đề xuất ràng buộc đi vào điều này.
chris

Tôi đang nhớ lại 4.8 Thiết kế các khái niệm , nhưng thực sự không có nhiều liệt kê về cách các khái niệm ràng buộc thêm. Nếu có bất cứ điều gì, các khái niệm của Google hiện có thể tiết lộ một số khác biệt dễ nhận thấy sau khi hiểu rõ hơn về các ràng buộc từ đề xuất.
chris

Các khái niệm IMHO cải thiện khả năng đọc và cung cấp khả năng lập trình rõ ràng hơn như yêu cầu của Alexander Stepanov từ lâu trong 'Các yếu tố của lập trình'. Đề xuất lite chỉ là một động thái hướng tới điều này để giảm bớt gánh nặng của các ràng buộc kiểu enable_if kỳ lạ được yêu cầu vào lúc này. Càng sớm càng tốt cho lập trình chung.
dirvine

Câu trả lời:


135

Những thông tin dưới đây đã lỗi thời. Nó cần được cập nhật theo bản nháp Con Concept Lite mới nhất.

Phần 3 của đề xuất ràng buộc đề cập đến vấn đề này một cách hợp lý.

Đề xuất khái niệm đã được đặt ra trong một thời gian ngắn với hy vọng rằng các ràng buộc (tức là khái niệm-lite) có thể được bổ sung và thực hiện trong một quy mô thời gian ngắn hơn, hiện đang hướng tới mục tiêu ít nhất là trong C ++ 14. Đề xuất ràng buộc được thiết kế để hoạt động như một quá trình chuyển đổi suôn sẻ sang định nghĩa khái niệm sau này. Ràng buộc là một phần của đề xuất khái niệm và là một khối xây dựng cần thiết trong định nghĩa của nó.

Trong Thiết kế Thư viện Khái niệm cho C ++ , Sutton và Stroustrup xem xét mối quan hệ sau:

Khái niệm = Ràng buộc + Tiên đề

Để nhanh chóng tóm tắt ý nghĩa của chúng:

  1. Ràng buộc - Một vị từ trên các thuộc tính có giá trị được đánh giá tĩnh của một kiểu. Yêu cầu hoàn toàn về cú pháp. Không phải là một miền trừu tượng.
  2. Tiên đề - Yêu cầu về ngữ nghĩa của các kiểu được cho là đúng. Không được kiểm tra tĩnh.
  3. Khái niệm - Yêu cầu chung, trừu tượng của thuật toán đối với đối số của chúng. Định nghĩa trong điều kiện ràng buộc và tiên đề.

Vì vậy, nếu bạn thêm các tiên đề (thuộc tính ngữ nghĩa) vào các ràng buộc (thuộc tính cú pháp), bạn sẽ có được các khái niệm.


Khái niệm-Lite

Đề xuất khái niệm-lite chỉ mang đến cho chúng ta phần đầu tiên, những ràng buộc, nhưng đây là một bước quan trọng và cần thiết để hướng tới các khái niệm chính thức.

Ràng buộc

Các ràng buộc đều là về cú pháp . Chúng cung cấp cho chúng ta một cách xác định tĩnh thuộc tính của một kiểu tại thời điểm biên dịch, để chúng ta có thể hạn chế các kiểu được sử dụng làm đối số mẫu dựa trên các thuộc tính cú pháp của chúng. Trong đề xuất hiện tại cho các ràng buộc, chúng được thể hiện bằng một tập con của phép tính mệnh đề bằng cách sử dụng các liên kết logic như &&||.

Hãy xem xét một hạn chế trong hoạt động:

template <typename Cont>
  requires Sortable<Cont>()
void sort(Cont& container);

Ở đây chúng ta được xác định mẫu hàm được gọi sort. Bổ sung mới là mệnh đề yêu cầu . Mệnh đề yêu cầu đưa ra một số ràng buộc đối với các đối số mẫu cho hàm này. Đặc biệt, ràng buộc này nói rằng kiểu Contphải là một Sortablekiểu. Một điều gọn gàng là nó có thể được viết dưới dạng ngắn gọn hơn như:

template <Sortable Cont>
void sort(Cont& container);

Bây giờ nếu bạn cố gắng chuyển bất cứ thứ gì không được coi là Sortablecho hàm này, bạn sẽ gặp một lỗi rất hay ngay lập tức cho bạn biết rằng kiểu được suy ra Tkhông phải là Sortablekiểu. Nếu bạn đã làm điều này trong C ++ 11, bạn sẽ có một số lỗi khủng khiếp ném từ bên trong các sortchức năng mà làm cho không có ý nghĩa cho bất kỳ ai.

Các vị từ ràng buộc rất giống với các đặc điểm kiểu. Họ lấy một số kiểu đối số mẫu và cung cấp cho bạn một số thông tin về nó. Ràng buộc cố gắng trả lời các loại câu hỏi sau về loại:

  1. Loại này có toán tử như vậy bị quá tải không?
  2. Các kiểu này có thể được sử dụng làm toán hạng cho toán tử này không?
  3. Loại này có đặc điểm như vậy không?
  4. Biểu thức hằng này có bằng không? (đối với các đối số mẫu không phải kiểu)
  5. Loại này có một hàm gọi là yada-yada trả về loại đó không?
  6. Loại này có đáp ứng tất cả các yêu cầu cú pháp để được sử dụng như vậy không?

Tuy nhiên, các ràng buộc không có nghĩa là để thay thế các đặc điểm kiểu. Thay vào đó, họ sẽ làm việc tay đôi. Một số tính trạng kiểu hiện nay có thể được định nghĩa bằng khái niệm và một số khái niệm dưới dạng tính trạng kiểu.

Ví dụ

Vì vậy, điều quan trọng về các ràng buộc là họ không quan tâm đến ngữ nghĩa một iota. Một số ví dụ điển hình về các ràng buộc là:

  • Equality_comparable<T>: Kiểm tra xem kiểu có ==với cả hai toán hạng cùng kiểu hay không.

  • Equality_comparable<T,U>: Kiểm tra xem có một ==với toán hạng trái và phải của các loại đã cho

  • Arithmetic<T>: Kiểm tra xem kiểu có phải là kiểu số học hay không.

  • Floating_point<T>: Kiểm tra xem kiểu có phải là kiểu dấu phẩy động hay không.

  • Input_iterator<T>: Kiểm tra xem kiểu có hỗ trợ các hoạt động cú pháp mà trình lặp đầu vào phải hỗ trợ hay không.

  • Same<T,U>: Kiểm tra xem loại đã cho có giống nhau không.

Bạn có thể thử tất cả những điều này với một bản dựng khái niệm đặc biệt của GCC .


Vượt ra ngoài khái niệm-Lite

Bây giờ chúng ta đi sâu vào mọi thứ ngoài đề xuất khái niệm-Lite. Điều này thậm chí còn tương lai hơn chính tương lai. Mọi thứ từ đây trở đi có thể sẽ thay đổi khá nhiều.

Tiên đề

Tiên đề là tất cả về ngữ nghĩa . Chúng chỉ định các mối quan hệ, bất biến, đảm bảo phức tạp và những thứ khác. Hãy xem một ví dụ.

Mặc dù Equality_comparable<T,U>ràng buộc sẽ cho bạn biết rằng có một operator== loại có các loại TU, nó không cho bạn biết hoạt động đó có nghĩa là gì . Đối với điều đó, chúng ta sẽ có tiên đề Equivalence_relation. Tiên đề này nói rằng khi các đối tượng của hai loại này được so sánh với operator==việc cho true, các đối tượng này là tương đương. Điều này có vẻ thừa, nhưng chắc chắn là không. Bạn có thể dễ dàng xác định một operator==mà thay vào đó hoạt động như một operator<. Bạn sẽ thật ác khi làm điều đó, nhưng bạn có thể.

Một ví dụ khác là một Greatertiên đề. Thật tốt khi nói hai đối tượng kiểu Tcó thể được so sánh với ><toán tử, nhưng chúng có nghĩa là gì? Các Greatertiên đề nói rằng khi và chỉ khi xlớn rồi y, sau đó ylà ít hơn x. Đặc điểm kỹ thuật được đề xuất một tiên đề như vậy trông giống như:

template<typename T>
axiom Greater(T x, T y) {
  (x>y) == (y<x);
}

Vì vậy, tiên đề trả lời các loại câu hỏi sau:

  1. Hai toán tử này có mối quan hệ này với nhau không?
  2. Toán tử này cho loại như vậy có nghĩa là điều này không?
  3. Thao tác trên kiểu đó có phức tạp không?
  4. Kết quả này của toán tử có ngụ ý rằng điều này là đúng không?

Có nghĩa là, họ hoàn toàn quan tâm đến ngữ nghĩa của các kiểu và hoạt động trên các kiểu đó. Những thứ này không thể được kiểm tra tĩnh. Nếu điều này cần được kiểm tra, một kiểu nào đó phải tuyên bố rằng nó tuân theo những ngữ nghĩa này.

Ví dụ

Dưới đây là một số ví dụ phổ biến về tiên đề:

  • Equivalence_relation: Nếu hai đối tượng so sánh ==, chúng là tương đương.

  • Greater: Bất cứ khi nào x > y, sau đó y < x.

  • Less_equal: Bất cứ khi nào x <= y, sau đó !(y < x).

  • Copy_equality: Đối với xycủa loại T: nếu x == y, một đối tượng mới cùng loại được tạo ra bằng cách xây dựng bản sao T{x} == yvà vẫn x == y(nghĩa là nó không phá hủy).

Các khái niệm

Bây giờ các khái niệm rất dễ định nghĩa; chúng chỉ đơn giản là sự kết hợp của các ràng buộc và tiên đề . Chúng cung cấp một yêu cầu trừu tượng về cú pháp và ngữ nghĩa của một kiểu.

Ví dụ, hãy xem xét Orderedkhái niệm sau :

concept Ordered<Regular T> {
  requires constraint Less<T>;
  requires axiom Strict_total_order<less<T>, T>;
  requires axiom Greater<T>;
  requires axiom Less_equal<T>;
  requires axiom Greater_equal<T>;
}

Lưu ý đầu tiên cho kiểu mẫu Tđược Ordered, nó cũng phải đáp ứng các yêu cầu của Regularkhái niệm. Các Regularkhái niệm này là một yêu cầu rất cơ bản mà các loại là well-behaved - nó có thể được xây dựng, phá hủy, sao chép và so sánh.

Ngoài các yêu cầu đó, các Orderedyêu cầu Tđáp ứng một ràng buộc và bốn tiên đề:

  • Ràng buộc: Một Orderedkiểu phải có operator<. Điều này được kiểm tra tĩnh nên nó phải tồn tại.
  • Tiên đề: Đối xyloại T:
    • x < y đưa ra một thứ tự tổng số nghiêm ngặt.
    • Khi nào xlớn hơn y, ynhỏ hơn x, và ngược lại.
    • Khi xnhỏ hơn hoặc bằng y, ykhông nhỏ hơn x, và ngược lại.
    • Khi xlớn hơn hoặc bằng y, ykhông lớn hơn x, và ngược lại.

Kết hợp các ràng buộc và tiên đề như thế này cho bạn các khái niệm. Chúng xác định các yêu cầu về cú pháp và ngữ nghĩa cho các kiểu trừu tượng để sử dụng với các thuật toán. Các thuật toán hiện phải giả định rằng các kiểu được sử dụng sẽ hỗ trợ các hoạt động nhất định và thể hiện ngữ nghĩa nhất định. Với các khái niệm, chúng tôi sẽ có thể đảm bảo rằng các yêu cầu được đáp ứng.

Trong thiết kế khái niệm mới nhất , trình biên dịch sẽ chỉ kiểm tra xem các yêu cầu cú pháp của khái niệm có được đáp ứng bởi đối số mẫu hay không. Các tiên đề được bỏ chọn. Vì tiên đề biểu thị ngữ nghĩa không có giá trị tĩnh (hoặc thường không thể kiểm tra hoàn toàn), tác giả của một kiểu sẽ phải tuyên bố rõ ràng rằng kiểu của chúng đáp ứng tất cả các yêu cầu của một khái niệm. Điều này được gọi là bản đồ khái niệm trong các thiết kế trước đây nhưng sau đó đã bị loại bỏ.

Ví dụ

Dưới đây là một số ví dụ về các khái niệm:

  • Regular các kiểu có thể xây dựng, có thể hủy, có thể sao chép và có thể được so sánh.

  • Orderedhỗ trợ các loại operator<và có tổng thứ tự nghiêm ngặt và ngữ nghĩa sắp xếp khác.

  • Copyablecác kiểu là bản sao có thể xây dựng, có thể hủy, và nếu xbằng yxđược sao chép, bản sao cũng sẽ so sánh bằng y.

  • Iteratorloại loại phải gắn liền value_type, reference, difference_type, và iterator_categoryđó chính họ phải đáp ứng một số khái niệm. Họ cũng phải hỗ trợ operator++và có thể tham khảo.

Con đường dẫn đến khái niệm

Ràng buộc là bước đầu tiên hướng tới một tính năng khái niệm đầy đủ của C ++. Chúng là một bước rất quan trọng, vì chúng cung cấp các yêu cầu có thể thực thi tĩnh của các loại để chúng ta có thể viết các hàm và lớp mẫu sạch hơn nhiều. Bây giờ chúng ta có thể tránh một số khó khăn và xấu xí của std::enable_ifvà những người bạn lập trình siêu hình của nó.

Tuy nhiên, có một số điều mà đề xuất ràng buộc không thực hiện được:

  1. Nó không cung cấp một ngôn ngữ định nghĩa khái niệm.

  2. Ràng buộc không phải là bản đồ khái niệm. Người dùng không cần phải chú thích cụ thể các loại của họ khi đáp ứng các ràng buộc nhất định. Chúng được kiểm tra tĩnh đã sử dụng các tính năng ngôn ngữ thời gian biên dịch đơn giản.

  3. Việc triển khai các mẫu không bị ràng buộc bởi các ràng buộc trên các đối số mẫu của chúng. Nghĩa là, nếu mẫu hàm của bạn thực hiện bất kỳ điều gì với một đối tượng có kiểu ràng buộc mà nó không nên làm, trình biên dịch không có cách nào để chẩn đoán điều đó. Một đề xuất khái niệm đầy đủ tính năng sẽ có thể làm được điều này.

Đề xuất ràng buộc đã được thiết kế đặc biệt để đề xuất khái niệm đầy đủ có thể được giới thiệu trên đó. Với bất kỳ may mắn nào, quá trình chuyển đổi đó sẽ là một chuyến đi khá suôn sẻ. Nhóm khái niệm đang tìm cách giới thiệu các ràng buộc cho C ++ 14 (hoặc trong một báo cáo kỹ thuật ngay sau đó), trong khi các khái niệm đầy đủ có thể bắt đầu xuất hiện vào khoảng thời gian C ++ 17.


5
Cần lưu ý rằng các khái niệm-lite không kiểm tra các ràng buộc đối với việc thực hiện chính khuôn mẫu. Vì vậy, bạn có thể tuyên bố rằng bất kỳ DefaultConstructable nào cũng có thể được sử dụng, nhưng trình biên dịch sẽ không ngăn bạn vô tình sử dụng một hàm tạo bản sao. Một đề xuất khái niệm đầy đủ tính năng hơn sẽ.
Nicol Bolas

24
Đóbản nháp đầu tiên ?!
Nicol Bolas

2
@sftrabbit, câu trả lời rất hay. Nhưng tôi có một câu hỏi: làm thế nào một trình biên dịch sẽ kiểm tra xem một kiểu có đáp ứng các yêu cầu ngữ nghĩa của một khái niệm không?
Rayniery

1
Liệu (có rất nhiều sự không chắc chắn) các 'tiên đề' sẽ được kiểm tra trong thời gian chạy hay chúng sẽ chỉ đóng vai trò là loại thẻ hứa hẹn?
Red XIII,

4
@ScarletAmaranth: Bởi vì nó tổng hợp để tự động chứng minh một định lý trong thời gian giới hạn hữu hạn. Có hai trở ngại cho việc này: 1. Việc chứng minh bất kỳ định lý nào trong thời gian giới hạn hữu hạn là một điều cực kỳ khó về mặt lý thuyết và không thể với công nghệ hiện tại. 2. Bạn không thể chứng minh một tiên đề, trừ khi việc chứng minh dựa trên các tiên đề khác. (Trong trường hợp đó về mặt toán học, nó trở thành "không phải là tiên đề") Những tiên đề này nhằm mục đích nói với trình biên dịch "Tất nhiên bạn có thể đảo ngược phép so sánh nếu bạn cho là nó hữu ích ...", hoặc "Có, EmptySet luôn được sử dụng trong giao lộ cho cùng một kết quả. "
Laurent LA RIZZA


4

2 xu của tôi:

  1. Đề xuất khái niệm-lite không có nghĩa là để thực hiện "kiểm tra kiểu" của việc triển khai mẫu . Tức là, Con Concept-lite sẽ đảm bảo tính tương thích giao diện (trên danh nghĩa) tại trang web khởi tạo mẫu. Trích dẫn từ bài báo: "khái niệm lite là một phần mở rộng của C ++ cho phép sử dụng các vị từ để ràng buộc các đối số mẫu". Và đó là nó. Nó không nói rằng nội dung mẫu sẽ được kiểm tra (riêng biệt) so với các vị từ. Điều đó có thể có nghĩa là không có khái niệm đầu tiên-lớp của archtypes khi bạn đang nói về khái niệm-lite. các kiểu kiến ​​trúc, nếu tôi nhớ không nhầm, trong đề xuất nặng về khái niệm là các kiểu cung cấp không ít hơn không kém để đáp ứng việc triển khai khuôn mẫu.

  2. concept-lite sử dụng các hàm constexpr được tôn vinh với một chút thủ thuật cú pháp được hỗ trợ bởi trình biên dịch. Không có thay đổi trong các quy tắc tra cứu.

  3. Lập trình viên không bắt buộc phải viết bản đồ khái niệm.

  4. Cuối cùng, trích dẫn một lần nữa "Đề xuất ràng buộc không trực tiếp đề cập đến đặc điểm hoặc cách sử dụng ngữ nghĩa; nó chỉ nhắm vào việc kiểm tra cú pháp." Điều đó có nghĩa là các tiên đề không nằm trong phạm vi (cho đến nay).

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.