Hầu hết các triển khai của thuốc generic (hay đúng hơn là: đa hình tham số) đều sử dụng kiểu xóa. Điều này đơn giản hóa rất nhiều vấn đề biên dịch mã chung, nhưng chỉ hoạt động đối với các kiểu được đóng hộp: vì mỗi đối số thực sự là một con trỏ mờ, chúng ta cần một cơ chế điều phối VTable hoặc tương tự để thực hiện các thao tác trên các đối số. Trong Java:
<T extends Addable> T add(T a, T b) { … }
có thể được biên dịch, kiểm tra kiểu và được gọi giống như
Addable add(Addable a, Addable b) { … }
ngoại trừ việc generic cung cấp trình kiểm tra loại với nhiều thông tin hơn tại trang web cuộc gọi. Thông tin bổ sung này có thể được xử lý với các biến loại , đặc biệt là khi các loại chung được suy ra. Trong quá trình kiểm tra loại, mỗi loại chung có thể được thay thế bằng một biến, hãy gọi nó là $T1
:
$T1 add($T1 a, $T1 b)
Biến loại sau đó được cập nhật với nhiều sự kiện hơn khi chúng được biết đến, cho đến khi nó có thể được thay thế bằng một loại cụ thể. Thuật toán kiểm tra loại phải được viết theo cách chứa các biến loại này ngay cả khi chúng chưa được phân giải thành loại hoàn chỉnh. Trong chính Java, điều này thường có thể được thực hiện dễ dàng vì loại đối số thường được biết trước loại cuộc gọi hàm cần được biết. Một ngoại lệ đáng chú ý là biểu thức lambda là đối số hàm, yêu cầu sử dụng các biến loại như vậy.
Rất lâu sau, một trình tối ưu hóa có thể tạo mã chuyên biệt cho một tập hợp các đối số nhất định, điều này sau đó sẽ thực sự là một kiểu nội tuyến.
Có thể tránh được VTable cho các đối số được nhập chung nếu hàm chung không thực hiện bất kỳ hoạt động nào trên loại mà chỉ chuyển chúng sang hàm khác. Ví dụ, hàm Haskell call :: (a -> b) -> a -> b; call f x = f x
sẽ không phải đóng hộp x
đối số. Tuy nhiên, điều này không đòi hỏi một quy ước gọi có thể đi qua các giá trị mà không biết kích thước của chúng, về cơ bản hạn chế nó cho con trỏ.
C ++ rất khác với hầu hết các ngôn ngữ về mặt này. Một lớp hoặc hàm templated (tôi sẽ chỉ thảo luận về các hàm templated ở đây) không thể gọi được. Thay vào đó, các mẫu nên được hiểu là một hàm meta thời gian biên dịch trả về một hàm thực tế. Bỏ qua suy luận đối số mẫu trong giây lát, cách tiếp cận chung sau đó thực hiện theo các bước sau:
Áp dụng mẫu cho các đối số mẫu được cung cấp. Ví dụ, gọi template<class T> T add(T a, T b) { … }
như add<int>(1, 2)
sẽ cung cấp cho chúng ta chức năng thực tế int __add__T_int(int a, int b)
(hoặc bất kỳ cách tiếp cận xáo trộn tên nào được sử dụng).
Nếu mã cho chức năng đó đã được tạo trong đơn vị biên dịch hiện tại, hãy tiếp tục. Mặt khác, tạo mã như thể một hàm int __add__T_int(int a, int b) { … }
đã được ghi trong mã nguồn. Điều này liên quan đến việc thay thế tất cả các lần xuất hiện của đối số mẫu bằng các giá trị của nó. Đây có lẽ là một phép biến đổi AST → AST. Sau đó, thực hiện kiểm tra loại trên AST được tạo.
Biên dịch cuộc gọi như thể mã nguồn đã được __add__T_int(1, 2)
.
Lưu ý rằng các mẫu C ++ có tương tác phức tạp với cơ chế giải quyết quá tải, điều mà tôi không muốn mô tả ở đây. Cũng lưu ý rằng việc tạo mã này làm cho không thể có một phương thức templated cũng là ảo - một cách tiếp cận dựa trên kiểu xóa không bị hạn chế đáng kể này.
Điều này có ý nghĩa gì đối với trình biên dịch và / hoặc ngôn ngữ của bạn? Bạn phải suy nghĩ cẩn thận về loại thuốc generic mà bạn muốn cung cấp. Loại xóa trong trường hợp không có suy luận kiểu là cách tiếp cận đơn giản nhất có thể nếu bạn hỗ trợ các kiểu đóng hộp. Chuyên môn hóa mẫu có vẻ khá đơn giản, nhưng thường liên quan đến việc xáo trộn tên và (đối với nhiều đơn vị biên dịch) sao chép đáng kể trong đầu ra, vì các mẫu được khởi tạo tại trang web cuộc gọi, không phải trang web định nghĩa.
Cách tiếp cận mà bạn đã chỉ ra về cơ bản là cách tiếp cận khuôn mẫu giống như C ++. Tuy nhiên, bạn lưu trữ các mẫu chuyên biệt / được khởi tạo dưới dạng các phiên bản của Cameron. Điều này là sai lệch: chúng không giống nhau về mặt khái niệm và các tức thời khác nhau của một chức năng có thể có các loại cực kỳ khác nhau. Điều này sẽ làm phức tạp mọi thứ trong thời gian dài nếu bạn cũng cho phép quá tải chức năng. Thay vào đó, bạn sẽ cần một khái niệm về một tập hợp quá tải có chứa tất cả các hàm và mẫu có thể có chung tên. Ngoại trừ việc giải quyết quá tải, bạn có thể coi các mẫu khởi tạo khác nhau hoàn toàn tách biệt với nhau.