Такая задача. Допустим, есть базовый интерфейс Base с чистыми виртуальными функциями, от него куча наследников.
Допустим, есть некая шаблонная функция (одна из многих специализаций). Например такая:
здесь bar — тоже шаблонная функция, возможно это та же функция foo() или какая-то обертка (смысл в том что с помощью этих шаблонных функций осуществляется рекурсивный обход сложной древовидной структуры, связи которой основаны на указателях Base*)
Проблема в следующем. Типом T может быть некоторый тип в иерархии (Base или какой-то наследник), а реальными элементами вектора могут быть указатели на любые наследники этого типа, разные в разных элементах вектора. Нужно вызвать foo<>() для настоящего типа (который в рантайме), а не для того который виден статически на этапе компиляции.
В качестве решения можно ввести в интерфейс Base и в каждый класс-наследник некую специальную виртуальную функцию, единственная задача которой — вызвать foo<>() для своего аргумента this. То есть из шаблона осуществляется некий вызов, класс определяется только в рантайме, вызывается функция-переходник нужного класса-наследника, из которой вызывается foo<>() — уже статически, с нужным типом. То есть вместо bar() будет что-то вроде v[i]->visit();
Но тут возникает несколько неприятных технических вещей. Система шаблонов сплетается с иерархией классов в единое целое, а хотелось бы оставить их независимыми. Если иерархия классов видима из системы шаблонов, то наоборот — нет, и крайне не хотелось бы сливать все в один заголовочный файл. Нужно обеспечить видимость foo<>() везде, где она вызывается (на самом деле это единственный файл с макросом, который уже вставлен в каждый класс-наследник Base). Прототип шаблонной функции foo<>() объявленный перед интерфейсом Base не помогает, компилятор не воспринимает его как прототип... наверное это и правильно, ведь на самом деле там несколько десятков реализаций.
Нет ли каких-то стандартных решений для исходной задачи?
Здравствуйте, Caracrist, Вы писали:
C>почему v[i]->visit(); не угодил?
Там код более универсальный, чем класс Base и любые его наследники.
Функция foo<>() обрабатывает не только иерархию Base, но и все простые типы, многие stl-типы, некоторые не входящие в иерархию Base составные типы, а также любые структурные типы, для которых определены внешние функции обработки.
Например, внутри объекта Derived1 могут быть поля int, float, std::string, сишные массивы из этих типов, несколько указателей на классы иерархии Base, несколько массивов таких указателей, несколько объектов и указателей на объекты специальных типов, не входящих в иерархию Base, и т.д. Для каждого поля вызывается своя версия шаблонной функции, причем это делается почти автоматически, через хитрую систему макросов.
Поэтому v[i]->visit() должна каким-то образом опять вызвать foo<>() уже для this == v[i], чтобы универсально обработать все внутренние поля v[i].
А просто вызвать я ее не могу, возникают циклические инклуды.
Здравствуйте, x-code, Вы писали:
XC>через хитрую систему макросов.
Вы там не перемудрили случаем?
XC>Поэтому v[i]->visit() должна каким-то образом опять вызвать foo<>() уже для this == v[i], чтобы универсально обработать все внутренние поля v[i].
А зачем вам вызывать foo<>() уже для this == v[i]? Вызывайте foo<>() внутри реализации visit этого класса для каждого нужного поля этого класса. Или вы хотите чтобы оно как-то само пробегалось по всем полям? Тогда в foo<>() надо делать соответствующий механизм без инклюдов конкретной реализации класса Base.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Здравствуйте, x-code, Вы писали:
XC>Нет ли каких-то стандартных решений для исходной задачи?
Стандартные есть, но не в чистом С++.
Во-первых, partial class из C# и прототипы из ObjC.
Во-вторых, мультиметоды как способ вынести виртуальные функции вовне.
Ближе всего к С++ классические ООП-шные мультиметоды на двойной диспетчеризации, паттерн Посетитель.
Но если это жмёт, то можно и вообще на тэгах сделать, сравнивая и ветвясь по идентификатору типа.
Тут вопрос в том, насколько ветвистая и насколько расширяемая иерархия типов и обработчиков.
И с чем связано желание изолировать реализацию:
— слишком долго компилируется
— макаронистый код, жалко отдавать в паблик
— нужно обеспечить дешёвое масштабирование
А то, если типов и обработчиков мало, то пусть все видят всех. Максимум, наколбасить интерфейсов для каждого обработчика и его ответной части.
А что, если не абстрагировать, не говорить "некие объекты и функция foo", а "вот такого рода объекты и вот с таким именем функция, делающая вот что".
Тогда телепатия будет мощнее.
Здравствуйте, Кодт, Вы писали:
К>А что, если не абстрагировать, не говорить "некие объекты и функция foo", а "вот такого рода объекты и вот с таким именем функция, делающая вот что".
Сделал, хотя и не совсем универсально. Была ошибка, забыл указать namespace в extern-описании шаблонной функции.
Для универсальности нужно было что-то вроде шаблонных виртуальных функций, которые бы создавались в нужном количестве для каждого значения шаблонного аргумента, или не создавались вовсе, если бы никто их не инстанцировал. Но такого, насколько я понимаю, в С++ нет.
Поэтому сейчас, для того чтобы перейти от статического типа к реальному динамическому, я жестко прибил вызов шаблонной функции-обработчика (параметром которой как раз и должен быть реальный динамический тип, а не статический) внутри виртуальной прокладки; а хотелось передать ее шаблонным параметром, чтобы не связывать эти две разные группы кода.