Більшість реалізацій генеричних даних (а точніше: параметричний поліморфізм) використовують стирання типу. Це значно спрощує проблему компіляції загального коду, але працює лише для типів у вікні: оскільки кожен аргумент є фактично непрозорим покажчиком, для виконання операцій над аргументами нам потрібен VTable або подібний механізм диспетчеризації. На Java:
<T extends Addable> T add(T a, T b) { … }
можна складати, перевіряти тип і називати так само, як і
Addable add(Addable a, Addable b) { … }
за винятком того, що дженерики надають перевіряючому типу набагато більше інформації на сайті виклику. Ця додаткова інформація може оброблятися змінними типів , особливо коли робляться загальні типи. Під час перевірки типу кожен загальний тип можна замінити змінною, назвемо це $T1
:
$T1 add($T1 a, $T1 b)
Потім змінна типу оновлюється більшою кількістю фактів, коли вони стають відомими, поки вона не може бути замінена конкретним типом. Алгоритм перевірки типу повинен бути написаний таким чином, що вміщує ці змінні типу, навіть якщо вони ще не вирішені до повного типу. У самій Java це зазвичай можна зробити легко, оскільки тип аргументів часто відомий до того, як потрібно знати тип виклику функції. Помітним винятком є вираз лямбда як аргумент функції, який вимагає використання змінних такого типу.
Набагато пізніше оптимізатор може генерувати спеціалізований код для певного набору аргументів, це фактично буде певним вкладом.
VTable для загальних аргументів можна уникнути, якщо загальна функція не виконує жодних операцій над типом, а лише передає їх іншій функції. Наприклад, функція Haskell call :: (a -> b) -> a -> b; call f x = f x
не повинна встановлювати x
аргументи. Однак для цього потрібна умовна умова виклику, яка може передавати значення, не знаючи їх розміру, що по суті обмежує його покажчиками.
C ++ сильно відрізняється від більшості мов у цьому відношенні. Класифікований клас або функція (я тут обговорюватиму лише шаблонні функції) не може називатися сама по собі. Натомість шаблони слід розуміти як метафункцію часу компіляції, яка повертає фактичну функцію. На хвилину ігноруючи умовиводи аргументу шаблону, загальний підхід зводиться до цих кроків:
Застосуйте шаблон до наданих аргументів шаблону. Наприклад, виклик template<class T> T add(T a, T b) { … }
як add<int>(1, 2)
би дав нам фактичну функцію int __add__T_int(int a, int b)
(або будь-який підхід до керування іменами).
Якщо код для цієї функції вже створений у поточному блоці компіляції, продовжуйте. В іншому випадку генеруйте код так, ніби функція int __add__T_int(int a, int b) { … }
була записана у вихідному коді. Це передбачає заміну всіх випадків аргументу шаблону його значеннями. Ймовірно, це перетворення AST → AST. Потім виконайте перевірку типу на створеному AST.
Складіть виклик так, як ніби був вихідний код __add__T_int(1, 2)
.
Зауважте, що шаблони C ++ мають складну взаємодію з механізмом вирішення перевантаження, який я не хочу тут описувати. Також зауважте, що для цього генерування коду неможливе використання шаблонного методу, який також є віртуальним - підхід на основі стирання не зазнає цього істотного обмеження.
Що це означає для вашого компілятора та / або мови? Ви повинні добре подумати про те, який тип дженериків ви хочете запропонувати. Стирання типу за відсутності висновку про тип - це найпростіший можливий підхід, якщо ви підтримуєте типи, що містяться в коробці. Спеціалізація шаблонів здається досить простою, але, як правило, передбачає керування іменами та (для декількох одиниць компіляції) суттєве дублювання у висновку, оскільки шаблони інстанціюються на сайті виклику, а не на сайті визначення.
Підхід, який ви показали, по суті є підходом до C ++. Однак ви зберігаєте спеціалізовані / інстанційні шаблони як "версії" головного шаблону. Це вводить в оману: вони концептуально не однакові, і різні екземпляри функції можуть мати диво різні типи. Це в подальшому ускладнить справи, якщо ви також дозволите перевантаження функцій. Натомість вам знадобиться поняття про набір перевантажень, який містить усі можливі функції та шаблони, які мають ім’я. За винятком вирішення перевантаження, ви можете вважати, що різні шаблони інстанції повністю відокремлені один від одного.