Комментарии 13
Всё это, конечно, делается и на голом C++:
unordered_mapс ключом из парыtype_indexи методами для регистрации. Локализация правок будет та же. Только компилятор не будет проверять покрытие всех пар, конкретные типы придётся привести к базовому и в каждом обработчике кастовать обратно вручную — компилятор эти касты также не проверяет. В ППП за этим следит компилятор.
На C++ можно тоже через таблицу реализовать, а с шаблонами не надо в обработчиках ничего кастовать. Проверить таблицу можно во время запуска приложения (перед main), например. А как в вашем ППП-компиляторе реализована проверка?
Про шаблоны соглашусь. Тогда разница в том, кто ведёт таблицу, разработчик или язык.
По проверке. Реализация проверок осуществляется во время компиляции. Пока таблица собирается целиком и проверяется на пропущенные пары при запуске, тоже перед main. Так получается из-за раздельной компиляции. В планах перенести сборку таблицы в свой компоновщик.
Реализация проверок осуществляется во время компиляции. Пока таблица собирается целиком и проверяется на пропущенные пары при запуске, тоже перед main.
Взаимоисключающие утверждения.
В планах перенести сборку таблицы в свой компоновщик.
Больше похоже на правду. Мне вот сложно представить полноценную проверку на этапе компиляции с раздельной компиляцией.
В статье мало технических деталей как это устроено внутри и для чего нужен аж целый компилятор для этого, если это может быть реализовано в C++ библиотеке.
Взаимоисключающие утверждения
Неточно выразился. На этапе компиляции проверяются связь специализаций с обобщением и уникальность специализаций.
Спасибо за обсуждение. Технических деталей правда мало, так как статья изначально планировалась обзорной. Если будет интерес к теме со стороны читателей, можно будет написать статью с разбором технических деталей и осветить все вопросы, которые всплывут в комментариях к этой статье. В том числе подробнее сравненить компилятор с C++ библиотекой. Если есть интерес прямо сейчас - можно почитать здесь.
То есть это "процедурно‑параметрическое программирование" - по сути мультиметоды с динамической диспетчеризацией, то есть свободные функции + какой-то аналог таблицы виртуальных функций?
И интересно как это устроено на низком уровне, то есть те самые таблицы. Ведь если в C++ у нас указатель на vbable хранится в начале объекта, то здесь несколько объектов...
Именно. Матчасть с примерами даже в вике
Лучше конечно в английской вике.
Изобретено в 1995г, под большинство языков программирования есть библиотеки.
Впрочем для геймдева, для которого приводятся примеры, применимо с оговорками невысокой производительности.
Не виртуальные таблицы, а локальные индексные. В объекте лежит не указатель на таблицу, а признак специализации(целое число, индекс). Таблицы принадлежат не типам, а обобщающим функциям. Для мультиметода таблица многомерная.
Как это реализовано в PPC описано в статье в разделе 3 (Отображение процедурно-параметрических конструкций в низкоуровневое представление).
Если вопрос в конце не риторический, то вот пример достаточно большой системы: MyCompany. Она написана на lsFusion, где методов, собственно, тоже нет. Свойства и действия глобальные, а реализация может выбираться по классам всех параметров.
Ваш пример с атакой выглядел бы примерно так:
CLASS ABSTRACT Unit;
CLASS Warrior : Unit;
CLASS Mage : Unit;
CLASS Worker : Unit;
attack ABSTRACT MULTI (Unit, Unit);
attack(Warrior attacker, Worker target) + {
// Warrior attacks Worker
}
attack(Mage attacker, Warrior target) + {
// Mage attacks Warrior
}MULTI обозначает, что все реализации не должны пересекаться по сигнатуре. Но можно сделать LIST, тогда можно добавлять много реализаций.
То есть реализация выбирается не только по атакующему, как у обычного метода, а по фактическим классам обоих параметров. Никакие instanceof и Visitor для этого не нужны. При этом реализации можно добавлять из отдельных модулей, не меняя базовый.
Минус ровно тот, который вы описали: нельзя открыть класс и увидеть всё, что он умеет. Но это решается поиском реализаций и использований в IDE.
А вот тезис "новый тип ничего не ломает" спорный. Если для него подходит базовая реализация, то да. Если нужна отдельная специализация, а разработчик её забыл, всё может прекрасно скомпилироваться и работать неправильно. В lsFusion для абстрактного действия можно потребовать полное покрытие классов. Без такого контроля проблема никуда не исчезает, просто проявляется в другом месте.
Языков, в которых есть мультиметоды, сейчас хватает. Начиная с CLOS. В основном доступ к параметрам реализуется с использованием хеширования, что медленнее чем в ЗЗС. Сравнение и обзор есть в статье: Сравнение производительности для различных способов реализации мультиметодов. Системы анализа и обработки данных. – 2025. – № 4 (100). – С. 69–84.(https://journals.nstu.ru/vestnik/catalogue/contents/view_article?id=41742).
На уровне PPC полиморфные функции могут контролироваться примерно также как при использовании абстрактных классов в C++. Обобщенная функция может быть абстрактной. Тогда все альтернативы должны быть обязательно реализованы. Если обобщенная функция имеет тело, то она, как и виртуальные методы, используется по умолчанию там, где нет ее замены.
Хотелось бы увидеть реакцию бренч предиктора в процессоре на это
Если на виртуальные методы как-то работает - то и тут будет. Насколько я понимаю (так себе) - предиктору вообще пофиг как образуется адрес перехода: что он у тебя захардкодан, что он у тебя из vtable берется, что он по формуле считается. Предсказание пишется в линейку кеша инструкций - типа вот в этой линейке на пятой инструкции полетим на такой-то адрес. Повезло и реально полетели - ок, не повезло - выкидываем спекулятивные результаты до перехода и начинаем с места где был переход.
Если у тебя цикл по зайчикам и белочкам в перемешку - будет вероятность предсказать переход 50%, как ты там не делай.

Свободные функции вместо методов, но с полиморфизмом. Что это даёт на самом деле?