Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.116.13.113] |
|
Сообщ.
#1
,
|
|
|
Прошло без малого 4 года, как предложенная мною реализация мультиметодов на C++03 была выложена на всеобщее обозрение. Она начиналась ещё во времена C++0x, когда ни C++11, ни компиляторов для C++11 ещё и в помине не было, а были только штучные бэтки с поддержкой нольИксового драфта. Собственно, поэтому и был выбран C++03, ибо даже после 11-го года предыдущий Стандарт ещё какое-то время был бы актуален, да и легаси проекты никуда не денутся. Занимался я ею от случая к случаю, потихоньку добавлял реализации тех или иных хотелок, уже и C++11 вполне себе состоялся и вовсю маячил С++14... в конце-концов на оставшиеся хотелки я просто плюнул и решил, что для C++03 и так сойдёт, надо на чём-то остановиться, ибо вот-вот, и мультиметоды появятся в языке нативные, а мне в каждом случае от случая всё сложнее вспоминать, на чём же я полгода назад остановился, и что ещё нужно сделать. Выложил, что к тому времени вышло.
Нативных мультиметодов так и не дождались, так что в какой-то мере та тема всё ещё актуальна. Тем более, что если погуглить, то тема мультиметодов выглядит замусоленной по всем направлениям, хотя и достигнутым мною результатом по производительности и универсальности похвастаться смогут ой как немногие. Чего греха таить, я гордился своей реализацией. Ещё бы: Но и недостатков тоже хватало: Как бы немало, так что вопрос, а есть ли повод гордиться, то и дело меня занимал. Лично я считал, что да, финальная стоимость оверхеда с моей точки зрения перекрывает перечисленные неудобства, но всё не оставляла мысль, не обманываю ли я сам себя. Ведь как там говорится: любой уважающий себя программист должен написать Вот так, примерно 9 месяцев назад, и вышло, что я взял себя в руки и начал себя испытывать. Ну, мультиметоды испытывать, в смысле. |
Сообщ.
#2
,
|
|
|
Чтобы испытать особую гордость нужно сравнить производительность с другими. Честно говоря, было лень делать глубокое исследование, поэтому порыскав по инету, нашёл более-менее простое вот это, которое не завелось из-за каких-то её внутренних проблем с boost::fusion. Мол, не может кастануть поинтеры на функции к константным поинтерам. Покопавшись с день, плюнул, ибо разбираться в её бустовой начинке не было никакого желания, и решил написать свою. Только лучше. Автор там явно признаёт, что у него сильные проблемы с производительностью, так как его диспетчер фактически делает попытки dynamic_cast<> до тех пор, либо пока не кончатся сортированные по порядку наследования типы, либо пока не проканает. В результате среднее количество кастов равняется половине глубины списка типов на каждый динамически связываемый параметр. Я же решил пойти другим путём, естественно, избавившись от самого сильного недостатка: интрузивности visit-метода. Сразу вопрос, а как сие сделать? Ибо если нельзя спросить у объекта "ты кто?", тогда остаётся лишь спрашивать у кого-то ещё "это кто?". Спрашивать, правда, особо не у кого, разве что у RTTI. Поэтому выбрал путь Александреску: бинарный поиск по сортированному контейнеру с предзаготовленными функциями для каждого динамического типа.
Примерно за день придумал архитектуру, примерно через неделю она идеально работала. Сравнил производительность, подивился, ну и естественно немедленно возникло желание поделиться результатами. Вот только одно печалило: на дворе C++17, а код всё ещё 14-летней давности. Безобразие. В общем, взял себя в руки и решил-таки портировать на C++1x. Заодно избавившись и от других недостатков, конечно же, которые обусловлены ограничениями C++03. Процесс был довольно непростым. Так, я долго пытался на имеющемся движке диспетчера завести вариадики вместо списков типов, и в конце-концов понял, что овчинка выделки не стоит. Вариадики хороши для обобщённого кода, который ррраз! и что-то сделал, но для метакода, реализующего нетривиальные алгоритмы, для которых характерны итерации и ветвления, они подходят плохо. По ним практически невозможно ничего специализировать, только параметризировать политиками каждый элемент предикатами. Которые хотя уже и можно специализировать, но код от этого яснее всё равно не становится, и получается шило с мылом. <type_traits> тоже остаётся не у дел, т.к. работает с типами, а не шаблонами. Попытки выразить его функционал через using опять-таки ничего не упрощают, а гораздо чаще наоборот. В общем, после месяца экспериментов с новой архитектурой ничего удобнее специализаций и рекурсии не придумалось, так что всё вернулось во круги своя: списки типов никуда не делись, движок остался прежним, а на вариадиках наверчен лишь внешний интерфейс диспетчеров. Формально, всё не так печально, конечно, и вариадики с предикатами вполне себе нормально бы зашли, но для этого пришлось бы в корне менять метаалгоритм. Кому вдруг приспичит, та за ради бога: код копилефт при условии сохранения исходного копирайта, который за мной с благодарностями в адрес Flex Ferrum-а за идею Visitor-а. Так к середине зимы получилось избавить пользователей от лишних списков типов и typedef-ов, фактическую -арность сравнять с формальной, прикрутить форвард, что позволило не терять инфу о типах параметров по дороге от диспетчера до перекрытого мультиметода, и решить траблу с неявными кастами передаваемых в диспетчер параметров к заявленным типам аргументов мильтиметода, что на тот момент по-быстрому было реализовано через явные касты, что естественно расширяло диапазоны вольности компилятору, и это было нехорошо. Когда все некритичные унаследованные от C++03 недостатки были устранены, пришлось задуматься о нововведениях, иначе какой прок от порта, не реализующего новые фичи языка. Да, я о поддержке rvalue refs, и задуматься пришлось крепко. Причина задумчивости была в том, что rvalue refs на форварде неотличима от параметра-значения, и поэтому на классах в качестве параметров-значений движок постоянно норовил заюзать перемещающие конструкторы вместо копирующих. Попытки решить проблему быстро не увенчались успехом, так что я решил передохнуть. В лучших традициях той, C++03-ей, реализации, передох занял полгода. На минисейшоне в Москве во время второй моей командировки я-таки очередной раз взял себя в руки. В итоге проблему решил решать в лоб. И решил. Некрасиво, но снаружи не видно, да ещё и работает. Ещё долго, где-то с день, не решался, но всё-таки руки меня взяли, и я снял ограничение на тип динамически связываемых параметров. И снова вместо <type_traits> лобовое, тупое, но работающее решение. Зато удобно пользовать. Так что теперь они могут быть и ссылками, и cv-квалифицированными, и даже rvalue ссылками. Не могут только значениями, но это и понятно. Ну ещё нет cv-квалифицированных rvalue ссылок, потому что... оно кому надо? Не, я понимаю, что язык позволяет, но смысл? С моей точки зрения не больше, чем от cv-квалифицированных аргументов-значений. Опять-таки, копипаст ваш лучший друг, перфекционистам руки развязаны. |
Сообщ.
#3
,
|
|
|
Хватит слов, пора за дело. Начнём с конца: сначала, как использовать. Вот пример из той темы:
Предположим у нас есть две (несвязанных) иерархии полиморфных классов B1 и B2, и нам требуется мультиметод с такой сигнатурой: int (virtual const B2*, std::string&, virtual B1&); int (virtual const D21*, std::string&, virtual B1&); int (virtual const D22*, std::string&, virtual D12&); int (virtual const D22*, std::string&, virtual B1&); int (virtual const D23*, std::string&, virtual B1&); int (virtual const D23*, std::string&, virtual D11&); int (virtual const D23*, std::string&, virtual D13&); int (virtual const D22*, std::string&, virtual D14&); |
Сообщ.
#4
,
|
|
|
Давай реализацию.
|
Сообщ.
#5
,
|
|
|
Списки типов (tlist.h). Практически не изменились. Добавился лишь хелпер MakeTList<> для нерекурсивного создания списков через вариадики. Метаалгоритм GenHierarchy в RTTI-реализации не используется, однако оставил ради Visitor-реализации.
/*****************************************************************\ ** Списки типов. Несмотря на наличие variadic templates в C++1x, ** ** которые решают множество проблем в обобщённом коде, ** ** для метакода списки всё равно удобнее. ** \*****************************************************************/ #ifndef TLIST_H_E689D195_5567_4BB4_8DFA_F4B111F69D92 #define TLIST_H_E689D195_5567_4BB4_8DFA_F4B111F69D92 #include <type_traits> namespace Types_Lists { /* Терминальный тип */ struct NullType {}; /* Список типов. L - голова, любой тип, кроме терминального и списка. R - хвост, либо терминальный тип, либо список */ template <typename L, typename R> struct TList { typedef L Head; typedef R Tail; }; /* Создание списка через вариадики. */ template <typename L, typename ...R> struct MakeTypeList { typedef TList<L, typename MakeTypeList<R...>::type> type; }; template <typename T> struct MakeTypeList<T> { typedef TList<T, NullType> type; }; template <typename L, typename ...R> using MakeTList = typename MakeTypeList<L, R...>::type; /* Генератор веерной иерархии на основе списка типов T. Все типы из списка T применяются к шаблону Node. Первый его параметр - параметризированный тип Head из списка, второй - тип возвращаемого значения мультиметода. Все получившиеся Node<T::Head> не состоят в родстве относительно друг друга, что обеспечивает их равноправность при перекрытии. Получившая веерная иерархия будет т.о. содержать в каждом луче веера декларацию интерфейса абстрактного акцептора для отдельного типа из списка. От базы (самого производного класса GenHierarchy<>) можно однозначно добраться до любого узла путём обычного восходящего приведения типов. */ template <template <typename, typename> class Node, typename Ret, typename T> struct GenHierarchy; template <template <typename, typename> class Node, typename Ret, typename L, typename R> struct GenHierarchy<Node, Ret, TList<L, R> >: public Node<L, Ret>, public GenHierarchy<Node, Ret, R> { }; template <template <typename, typename> class Node, typename Ret> struct GenHierarchy<Node, Ret, NullType> { }; /* Метафункция получения самого базового класса из списка типов */ template <typename TL> struct MostBase; template <typename L, typename R> struct MostBase<TList<L, R> > { typedef typename MostBase<R>::type nextType; typedef typename std::conditional<std::is_base_of_v<L, nextType>, L, nextType>::type type; }; template <typename T> struct MostBase<TList<T, NullType> > { typedef T type; }; /* Метафункция получения длины списка */ template <typename TL> struct Length; template <typename L, typename R> struct Length<TList<L, R> > { enum { value = Length<R>::value + 1 }; }; template <> struct Length<NullType> { enum { value = 0 }; }; /* Метафункция получения типа из списка по его номеру */ template <typename TL, int I> struct GetType; template <typename L, typename R, int I> struct GetType<TList<L, R>, I> { typedef typename GetType<R, I-1>::type type; }; template <typename L, typename R> struct GetType<TList<L, R>, 0> { typedef L type; }; } // Types_Lists #endif // TLIST_H_E689D195_5567_4BB4_8DFA_F4B111F69D92 Кортежи (tuple.h). Тут изменений больше, т.к. поля теперь хранятся в rvalue refs, и нет необходимости копипастить конструкторы, ибо вариадики же. Тут уже можно было использовать стандартные кортежи из std, всё ж таки не boost, но это опять-таки значило изменить архитектуру движка диспетчера. Не стал. Хотите – на здоровье. /****************************************************************\ ** Кортежи. ** ** Стандартные из std по ряду причин без адаптеров не подходят. ** \****************************************************************/ #ifndef TUPLE_H_E689D195_5567_4BB4_8DFA_F4B111F69D92 #define TUPLE_H_E689D195_5567_4BB4_8DFA_F4B111F69D92 #include "tlist.h" namespace Tuples { using Types_Lists::TList; using Types_Lists::NullType; /* Узел кортежа, одно поле. Первый параметр требуется для уникальной его идентификации, если в пределах кортежа T неуникален. */ template <typename Hash, typename T> struct Field { T&& data; Field(T&& t) : data(std::forward<T>(t)) {} }; /* Генератор кортежа на основе списка типов T. В качестве Hash используется текущий срез списка типов. */ template <typename T> struct Tuple; template <typename L, typename R> struct Tuple<TList<L, R> >: public Field<TList<L, R>, L>, public Tuple<R> { typedef TList<L, R> TypeList; typedef L DataType; typedef Tuple<R> base_type; /* Конструкторы нужны, чтобы позволить полям иметь константные и ссылочные типы. */ Tuple(L&& l): Field<TList<L, R>, L>(std::forward<L>(l)) {} template <typename ...Args> Tuple(L&& l, Args&&... args): Field<TList<L, R>, L>(std::forward<L>(l)), base_type(std::forward<Args>(args)...) {} }; template <> struct Tuple<NullType> { }; /* Внутренности реализации. */ namespace details { /* Получить срез кортежа Tpl по номеру поля I от начала */ template <int I, typename Tpl> struct GetTupleSlice { typedef typename GetTupleSlice<I-1, typename Tpl::base_type>::type type; }; template <typename Tpl> struct GetTupleSlice<0, Tpl> { typedef Tpl type; }; } // details /* Получить поле I кортежа Tpl */ template <int I, typename Tpl> typename details::GetTupleSlice<I, Tpl>::type::DataType&& GetField(Tpl& tuple) { typedef typename details::GetTupleSlice<I, Tpl>::type TupleSlice; return std::forward<typename TupleSlice::DataType>( static_cast<Field<typename TupleSlice::TypeList, typename TupleSlice::DataType>&>(tuple).data); } } // Tuples #endif // TUPLE_H_E689D195_5567_4BB4_8DFA_F4B111F69D92 Мультиметоды (mmethod.h). Вот тут изменений ожидаемо больше. #ifndef MULTIMETHODS_H_E689D195_5567_4BB4_8DFA_F4B111F69D92 #define MULTIMETHODS_H_E689D195_5567_4BB4_8DFA_F4B111F69D92 #include <algorithm> #include <stdexcept> #include <typeinfo> #include <utility> # #include "tlist.h" #include "tuple.h" namespace MultiMethods { /* Фактически списки типов и кортежи разрабатывались для мультиметодов, и их использование без них вряд ли имеет смысл. Если нужны, лучше заюзать стандартные из буста или C++1x. Так как мультиметоды очень плотно связаны как с теми, так и с другими, нет смысла скрывать эту связь. */ using namespace Types_Lists; using namespace Tuples; /* Внутренности реализации (приватный интерфейс). */ namespace details { /* Строит список типов параметров "базового" мультиметода на основе акцепторов абстрактного диспетчера и типов параметров, с которыми вызван его operator(). Заменяет списки типов, описывающих иерархии динамически связываемых аргументов, на самые базовые классы в этих иерархиях. Применяя эту же операция для статически связваемых аргументов, фактически ничего не меняет, т.к. там всего один тип в списке.*/ template <typename> struct MakeParamTypes; template <typename Tl, typename Tr> struct MakeParamTypes<TList<Tl, Tr>> { typedef TList<typename Tl::template ParamType<typename MostBase<typename Tl::TypeList>::type>::type, typename MakeParamTypes<Tr>::type> type; }; template <> struct MakeParamTypes<NullType> { typedef NullType type; }; /* Класс-обёртка над std::type_info, позволяющая работать с ними в семантике значений. Хранит ссылку (точнее, указатель, чтобы не заморачиваться спец.методами и позволить компилятору самому их сгенерировать) на оригинальный std::type_info. Это в рамках Стандарта, т.к. объекты std::type_info имеют время жизни до конца программы. */ class TypeInfo { const std::type_info *info; int ind; // исходный индекс в списке типов, ибо после сортировки // экземпляры в массиве перемешаются public: TypeInfo(): /* DefaultConstructable */ info(&typeid(void)), ind(0) {} explicit TypeInfo(const std::type_info& ti, int idx=0): info(&ti), ind(idx) {} bool operator==(const TypeInfo& ti) const { return *info == *ti.info; } bool operator!=(const TypeInfo& ti) const { return *info != *ti.info; } bool operator==(const std::type_info& ti) const { return *info == ti; } bool operator!=(const std::type_info& ti) const { return *info != ti; } bool operator< (const TypeInfo& ti) const { return info->before(*ti.info); } bool operator< (const std::type_info& ti) const { return info->before( ti); } int index() const { return ind; } }; /********************************************************************************\ *************** Акцепторы. Всего их две штуки. ************** \********************************************************************************/ /* Динамический акцептор. Обрабатывает динамически связываемые параметры. Первый параметр - список типов, должен включать базовый и все производные от него классы, которые предполагается передавать аргументом в мультиметод, второй - тип возвращаемого значения мультиметода. */ template <typename TL, typename Ret> class LinkDynamic { public: typedef TL TypeList; /* Генератор конкретных акцепторов, реализующих чистые методы абстрактных акцепторов. Всего их два разных: один для последнего акцептора в списке и один для остальных. Этот генератор теоретически может иметь разный вид для разных абстрактных акцепторов, но в итоге этого не понадобилось, так что они выглядят одинаково. */ template <typename T, template <typename, typename, typename, typename, typename, typename, typename> class GA, typename ATL, typename CTL, typename TPL, typename SRC, typename SHT> struct MakeAcceptor { typedef GA<T, TypeList, ATL, CTL, TPL, SRC, SHT> type; }; /* Используется details::MakeParamTypes<> для получения типа параметра мультиметода. Для динамического акцептора это указатель на тип Head из TL. */ template <typename T> struct ParamType { typedef T *type; }; /* Получить RTTI параметра. Для динамического акцептора объекты хранятся по указателю, соответственно параметр нужно разыменовать, иначе вернётся RTTI указателя. */ template <typename T> static const std::type_info& deref(T* &&t) { return typeid(*t); } /* Для динамически связываемых параметров тип может и не найтись из-за ошибки программиста, так что проверка на точное соответствие важна. */ static bool check(const TypeInfo& l, const std::type_info& r) { return l == r; } }; /* Статическый акцептор. Обрабатывает статически связываемые параметры. Первый параметр - непосредственно тип аргумента мультиметода, второй - тип возвращаемого значения мультиметода. */ template <typename Type, typename Ret> class LinkStatic { public: typedef TList<Type, NullType> TypeList; /* Такой же генератор конкретных акцепторов. Предполагалось, что они с динамическим акцептором будут разными, однако не пригодилось. Оставлено как есть на всякий случай для будущих модификаций. */ template <typename T, template <typename, typename, typename, typename, typename, typename, typename> class GA, typename ATL, typename CTL, typename TPL, typename SRC, typename SHT> struct MakeAcceptor { typedef GA<T, TypeList, ATL, CTL, TPL, SRC, SHT> type; }; /* Используется details::MakeParamTypes<> для получения типа параметра мультиметода. Для статического акцептора это rvalue ссылка на тип Type. */ template <typename T> struct ParamType { typedef T&& type; }; /* Получить RTTI параметра. Для статического акцептора объекты хранятся по rvalue ссылке, соответственно разыменовывать ничего не надо, а rvalue ref скрывает любые cv-квалификаторы, поэтому не надо опасаться, что вместо RTTI параметра будет возвращено RTTI на его cv- или ref, главное только не форвардить. */ template <typename T> static const std::type_info& deref(T &&t) { return typeid(t); } /* В списке типов только один тип, а его несоответствие, ежели вдруг, будет выявлено ещё при компиляции. */ static bool check(const TypeInfo&, const std::type_info&) { return true; } }; /********************************************************************************\ ************** Создание внутренней VMT, основанной на RTTI. ************* ************** Часть I ************* \********************************************************************************/ /* Построение массива на экземплярах RTTI-класса. Рекурсию можно было бы переписать на вариадиках, однако сейчас это уже невозможно, т.к. исходные вариадики заменены на список типов. Кроме того, исходно вариадики тут могут быть задействованы только с C++17, т.к. ранее fold-expression в языке не было. первый параметр - исключительно для завершения рекурсии; его можно было бы не включать в список аргументов, ограничившись только шаблонным параметром, но тогда рекурсию можно было бы остановить только частичной специализацией, однако увы, для функций частичных специализаций не предусмотрено, а полная специализация была бы возможна, если исключить последний параметр, но для этого makeSheet() нужно было бы сделать методом Sheet, где явные специализации запрещены Стандартом; второй параметр - индекс в списке типов и он же начальный индекс в массие; последний может быть изменён после сортировки массива, так что связь с исходным индексом в списке типа приходится сохранять в самом экземпляре RTTI-класса; третий параметр - массив экземпляров RTTI-класса. */ template <typename Tl, typename T, size_t N> void makeSheet(Tl, int idx, T (&row)[N]) { row[idx] = TypeInfo(typeid(typename Tl::Head), idx); makeSheet(typename Tl::Tail(), idx+1, row); } // Стоп рекурсии. Можно вписать assert для контроля N... если вы параноик. template<typename T, size_t N> void makeSheet(NullType, int, T (&)[N]) {} /* Класс-обёртка над массивами экземпляров RTTI-класса. Хранит по массиву на каждый параметр мультиметода: массив для следующего параметра лежит в базовом классе массива для текущего. */ template <typename Tl> class Sheet; template <typename L, typename R> class Sheet<TList<L, R>>: public Sheet<R> { TypeInfo row[Length<typename L::TypeList>::value]; public: typedef Sheet<R> base_type; Sheet() // базовые классы уже построили и отсортировали свои массивы { makeSheet(typename L::TypeList(), 0, row); // строим массив std::sort(std::begin(row), std::end(row)); // и сортируем } // Страшная строчка. Константный метод возвращает константную ссылку на массив экземпляров // RTTI-классов известной размерности. // Можно было бы ограничиться публикацией row[], однако хочется его константности, а делать // row[] константным нельзя, т.к. он инициализируется в теле конструктора, а не инициализатором. const TypeInfo (&get()const)[Length<typename L::TypeList>::value] { return row; } }; template <> class Sheet<NullType>{}; /********************************************************************************\ ************* Стратегии работы с параметрами диспетчера. ************ ******* Увы, <type_traits> не поможет: там типы, а тут шаблоны ******* \********************************************************************************/ /* Преобразование типа аргумента диспетчера в тип его параметра. */ // Статически связываемые параметры. Типы совпадают. template <typename T> struct MakeArg { typedef T type; }; // Динамически связываемые параметры. // Список типов пребразуется в cv-неквалифицированный тип указателя на самый базовый класс. template <typename L, typename R> struct MakeArg<TList<L, R>>; // намерено не реализован template <typename L, typename R> struct MakeArg<TList<L, R>*> // * { typedef typename MostBase<TList<L, R>>::type *type; }; template <typename L, typename R> struct MakeArg<const TList<L, R>*> // const * { typedef typename MostBase<TList<L, R>>::type *type; }; template <typename L, typename R> struct MakeArg<const volatile TList<L, R>*> // const volatile * { typedef typename MostBase<TList<L, R>>::type *type; }; template <typename L, typename R> struct MakeArg<volatile TList<L, R>*> // volatile * { typedef typename MostBase<TList<L, R>>::type *type; }; template <typename L, typename R> struct MakeArg<TList<L, R>&> // & { typedef typename MostBase<TList<L, R>>::type *type; }; template <typename L, typename R> struct MakeArg<const TList<L, R>&> // const & { typedef typename MostBase<TList<L, R>>::type *type; }; template <typename L, typename R> struct MakeArg<const volatile TList<L, R>&> // const volatile & { typedef typename MostBase<TList<L, R>>::type *type; }; template <typename L, typename R> struct MakeArg<volatile TList<L, R>&> // volatile & { typedef typename MostBase<TList<L, R>>::type *type; }; template <typename L, typename R> struct MakeArg<TList<L, R>&&> // && { typedef typename MostBase<TList<L, R>>::type *type; }; // Технически тут могут быть cv-квалифицированные варианты для &&, но кому они нужны /* Выбор политики для обработки диспетчером его параметров */ // Статически связываемые параметры. Политика статического связывания. template <typename T, typename RT> struct MakeParam { typedef details::LinkStatic<T, RT> type; }; // Динамически связываемые параметры. Политика динамического связывания. // Все варианты как и у MakeArg<>. template <typename L, typename R, typename RT> struct MakeParam<TList<L, R>, RT>; template <typename L, typename R, typename RT> struct MakeParam<TList<L, R>*, RT> { typedef details::LinkDynamic<TList<L, R>, RT> type; }; template <typename L, typename R, typename RT> struct MakeParam<const TList<L, R>*, RT> { typedef details::LinkDynamic<TList<L, R>, RT> type; }; template <typename L, typename R, typename RT> struct MakeParam<const volatile TList<L, R>*, RT> { typedef details::LinkDynamic<TList<L, R>, RT> type; }; template <typename L, typename R, typename RT> struct MakeParam<volatile TList<L, R>*, RT> { typedef details::LinkDynamic<TList<L, R>, RT> type; }; template <typename L, typename R, typename RT> struct MakeParam<TList<L, R>&, RT> { typedef details::LinkDynamic<TList<L, R>, RT> type; }; template <typename L, typename R, typename RT> struct MakeParam<const TList<L, R>&, RT> { typedef details::LinkDynamic<TList<L, R>, RT> type; }; template <typename L, typename R, typename RT> struct MakeParam<const volatile TList<L, R>&, RT> { typedef details::LinkDynamic<TList<L, R>, RT> type; }; template <typename L, typename R, typename RT> struct MakeParam<volatile TList<L, R>&, RT> { typedef details::LinkDynamic<TList<L, R>, RT> type; }; template <typename L, typename R, typename RT> struct MakeParam<TList<L, R>&&, RT> { typedef details::LinkDynamic<TList<L, R>, RT> type; }; /* Построить список типов политик для всех параметров диспетчера. RT - return type, перенесён в начало списка параметров для бесконфликности с вариадиком. */ template <typename RT, typename T, typename ...List> struct MakeParams { typedef TList<typename MakeParam<T, RT>::type, typename MakeParams<RT, List...>::type> type; }; template <typename RT, typename T> struct MakeParams<RT, T> { typedef TList<typename MakeParam<T, RT>::type, NullType> type; }; /* Предобработка параметра, переданного диспетчеру. Нужна частичная специализация, т.к. работа основана на вариадике диспетчера, куда замешаны списки типов вместо фактических типов. А чтобы не заморачиваться MostBase<>, внутри нужен и простой шаблонный метод для обработки собственно параметра. */ // Статически связываемый параметр. Просто форвард. template <typename A> struct mkref { template <typename T> static T&& doIt(T&& t) { return std::forward<T>(t); } }; // Динамически связываемый параметр. Нужно убрать cv-квалификаторы и преобразовать в указатель. // Все варианты как и у MakeArg<>. template <typename L, typename R> struct mkref<TList<L, R>>; template <typename L, typename R> struct mkref<TList<L, R>*> { template <typename T> static T* doIt(T* t) { return t; } }; template <typename L, typename R> struct mkref<const TList<L, R>*> { template <typename T> static T* doIt(const T* t) { return const_cast<T*>(t); } }; template <typename L, typename R> struct mkref<const volatile TList<L, R>*> { template <typename T> static T* doIt(const volatile T* t) { return const_cast<T*>(t); } }; template <typename L, typename R> struct mkref<volatile TList<L, R>*> { template <typename T> static T* doIt(volatile T* t) { return const_cast<T*>(t); } }; template <typename L, typename R> struct mkref<TList<L, R>&> { template <typename T> static T* doIt(T& t) { return &t; } }; template <typename L, typename R> struct mkref<const TList<L, R>&> { template <typename T> static T* doIt(const T& t) { return &const_cast<T&>(t); } }; template <typename L, typename R> struct mkref<const volatile TList<L, R>&> { template <typename T> static T* doIt(const volatile T& t) { return &const_cast<T&>(t); } }; template <typename L, typename R> struct mkref<volatile TList<L, R>&> { template <typename T> static T* doIt(volatile T& t) { return &const_cast<T&>(t); } }; template <typename L, typename R> struct mkref<TList<L, R>&&> { template <typename T> static T* doIt(T&& t) { return &t; } }; /* Постобработка параметра, переданного диспетчеру. Нужна частичная специализация, т.к. работа основана на вариадике диспетчера, куда замешаны списки типов вместо фактических типов + там же лежат исходные cv- и ptr/rev квалификаторы. А чтобы не заморачиваться MostBase<>, внутри нужен и простой шаблонный метод для обработки собственно параметра. */ // Статически связываемый параметр. Всё могло быть проще, мог быть просто форвард. Но реальность // такова, что для просто форварда нет разницы между rvalue и rvalue ref, в результате объекты, // принимаемые мультиметодом по значению, переносятся вместо того, чтобы копироваться. template <typename X> struct dcref { template <typename T> static T doIt(T&& t) { return t; } }; template <typename X> struct dcref<X&> { template <typename T> static T& doIt(T& t) { return t; } }; template <typename X> struct dcref<const X&> { template <typename T> static const T& doIt(const T& t) { return t; } }; template <typename X> struct dcref<const volatile X&> { template <typename T> static const volatile T& doIt(const volatile T& t) { return t; } }; template <typename X> struct dcref<volatile X&> { template <typename T> static volatile T& doIt(volatile T& t) { return t; } }; // вот ради этого весь сыр-бор template <typename X> struct dcref<X&&> { template <typename T> static T&& doIt(T&& t){return std::forward<T>(t);} }; // Динамически связываемый параметр. Нужно вернуть исходные его cv- и ptr/ref квалификаторы. // Все варианты как и у MakeArg<>. template <typename L, typename R> struct dcref<TList<L, R>>; template <typename L, typename R> struct dcref<TList<L, R>*> { template <typename T> static T* doIt(T* t) { return t; } }; template <typename L, typename R> struct dcref<const TList<L, R>*> { template <typename T> static const T* doIt(T* t) { return const_cast<const T*>(t); } }; template <typename L, typename R> struct dcref<const volatile TList<L, R>*> { template <typename T> static const volatile T* doIt(T* t) { return const_cast<const volatile T*>(t); } }; template <typename L, typename R> struct dcref<volatile TList<L, R>*> { template <typename T> static volatile T* doIt(T* t) { return const_cast<volatile T*>(t); } }; template <typename L, typename R> struct dcref<TList<L, R>&> { template <typename T> static T& doIt(T* t) { return *t; } }; template <typename L, typename R> struct dcref<const TList<L, R>&> { template <typename T> static const T& doIt(T* t) { return const_cast<const T&>(*t); } }; template <typename L, typename R> struct dcref<const volatile TList<L, R>&> { template <typename T> static const volatile T& doIt(T* t) { return const_cast<const volatile T&>(*t); } }; template <typename L, typename R> struct dcref<volatile TList<L, R>&> { template <typename T> static volatile T& doIt(T* t) { return const_cast<volatile T&>(*t); } }; template <typename L, typename R> struct dcref<TList<L, R>&&> { template <typename T> static T&& doIt(T* t) { return std::move(*t); } }; } // details /********************************************************************************\ ***************** Абстрактный диспетчер **************** \********************************************************************************/ /* Конкретный диспетчер */ template <typename, typename, typename ...> class CallConcrete; /* Первый параметр - пользовательский класс с реализациями перекрытых мультиметодов, второй - тип возвращаемого мультиметодом значения, третий... - типы параметров. */ template <typename DI, typename ret_type, typename ...Args> class Dispatcher { // преобразовать типы параметров мультиметода в аргументы typedef typename details::MakeParams<ret_type, Args...>::type TTl; typedef details::Sheet<TTl> sheet_type; // тип RTTI-"матрицы" // массивы экземпляров RTTI-класса, хранящие RTTI всех классов каждого параметра из ...Args const sheet_type sheet; /* Акцепторы. IterXXX обрабатывает все параметры, кроме последнего, LastXXX обрабатывает последний параметр. YYYYStart нужен для CRTP: методы всех YYYYAcceptorCaller приводятся к методам YYYYStart, что делает их имеющих единый квалифицирующий класс. */ template <typename T, typename ATL, typename CTL, typename PTL, typename TPL, typename SRC, typename SHT> struct IterStart; template <typename T, typename ATL, typename CTL, typename PTL, typename TPL, typename SRC, typename SHT, typename MD> struct IterAcceptorCaller; template <typename T, typename ATL, typename CTL, typename PTL, typename TPL, typename SRC, typename SHT> struct LastStart; template <typename T, typename ATL, typename CTL, typename PTL, typename TPL, typename SRC, typename SHT, typename MD> struct LastAcceptorCaller; /* Генератор очередного акцептора на основе очередного среза списка типов акцепторов. */ template <typename T, typename ATL, typename PTL, typename TPL, typename SRC, typename SHT> struct AcceptorMaker; // эта специализация является очередной итерацией цикла и восстанавливает очередной тип template <typename T, typename AL, typename AR, typename PTL, typename TPL, typename SRC, typename SHT> struct AcceptorMaker <T, TList<AL, AR>, PTL, TPL, SRC, SHT> { typedef typename AL::template MakeAcceptor<T, IterStart, TList<AL, AR>, PTL, TPL, SRC, SHT>::type type; }; // эта специализация является последней итерацией цикла template <typename T, typename AL, typename PTL, typename TPL, typename SRC, typename SHT> struct AcceptorMaker <T, TList<AL, NullType>, PTL, TPL, SRC, SHT> { typedef typename AL::template MakeAcceptor<T, LastStart, TList<AL, NullType>, PTL, TPL, SRC, SHT>::type type; }; /********************************************************************************\ ************** Создание внутренней VMT, основанной на RTTI. ************* ************** Часть II. ************* \********************************************************************************/ /* Реализация акцепторов. Общий принцип заключается в генерации линейной иерархии акцепторов на основе списка типов иерархии параметра (для статически связываемых параметров список состоит из одного типа), заполнении массива методов всех таких акцепторов для каждого типа в списке, восстановлении динамического типа параметра по его RTTI и вызову того акцептора, который сопоставлен этому классу, для чего используются связь по индексам массива методов и исходного (несортированного) массива RTTI-классов. Параметры: - тип возвращаемого значения; - срез списка типов иерархии своего параметра; - срез списка типов параметров диспетчера; - список восстановленных динамических типов предыдущих параметров (в обратном порядке); - срез списка типов параметров мультиметода; - тип исходного кортежа с параметрами doIt(); - срез тип RTTI-"матрицы"; - самый производный акцептор (YYYYStart). */ /* Реализация акцептора последнего параметра мультиметода. Особенности: - срез списка типов параметров мультиметода не используется, ибо тут он уже пуст. - вызывает конкретный диспетчер вместо следующего акцептора. */ // это все апцепторы в иерархии, кроме самого базового template <typename L, typename R, typename CTL, typename PTL, typename TPL, typename SRC, typename SHT, typename MD> struct LastAcceptorCaller<ret_type, TList<L, R>, CTL, PTL, TPL, SRC, SHT, MD>: public LastAcceptorCaller<ret_type, R, CTL, PTL, TPL, SRC, SHT, MD> { typedef LastAcceptorCaller<ret_type, R, CTL, PTL, TPL, SRC, SHT, MD> base_type; typedef typename CTL::Head::template ParamType<L>::type arg_type; // конструктор строит вариадик из указателей на методы Accept() template <typename ...A> LastAcceptorCaller(SRC& ptl, TPL& src, const SHT& sht, A&&... args): base_type(ptl, src, sht, std::forward<A>(args)..., static_cast<ret_type (MD::*)(param_type)>(&LastAcceptorCaller::Accept)) {} // восстановление типа последнего параметра и вызов нужной специализации конкретного диспетчера typedef typename MostBase<typename CTL::Head::TypeList>::type most_base; typedef typename CTL::Head::template ParamType<most_base>::type param_type; ret_type Accept(param_type obj) { arg_type arg = static_cast<arg_type>(obj); // восстановленый тип // вызвать конкретный диспетчер; т.к. восстановленные типы в PTL лежат в обратном порядке, // последний восстановленный тип должен быть обработан специальным образом, поэтому // передаётся отдельно от RTL и кортежа. return CallConcrete<DI, ret_type, Args...>::template apply<SRC, PTL, arg_type>:: call(base_type::m_Params, std::forward<arg_type>(arg)); } }; // это базовый в иерархии акцептор; весь список типов иерархии параметра уже обработан производными // классами, так что этот с ними уже не работает, он только обслуживает внутреннюю VMT template <typename CTL, typename PTL, typename TPL, typename SRC, typename SHT, typename MD> struct LastAcceptorCaller<ret_type, NullType, CTL, PTL, TPL, SRC, SHT, MD> { typedef typename MostBase<typename CTL::Head::TypeList>::type most_base; typedef typename CTL::Head::template ParamType<most_base>::type param_type; /* Построение массива методов для восстановления типа динамически связываемого параметра. Фактически, это внутренняя VMT. Для статически связываемого параметра массив состоит из одного элемента. В C++17 и позже этот метод может быть реализован проще посредством fold-expression. Ниже приводится его возможная реализация. template <std::size_t ...Ints, typename ...Args> void getArgs(std::index_sequence<Ints...>, Args ...args) { ((accepts[Ints] = args), ...); } В более ранних C++1x приходится делать рекурсией, останавливаемой перегрузкой. */ template <int I, typename ...A> void getArgs(ret_type (MD::*at)(param_type), A&&... args) { accepts[I] = at; getArgs<I+1>(std::forward<A>(args)...); } template <int I> void getArgs(ret_type (MD::*at)(param_type)) { accepts[I] = at; } // конструктор строит внутреннюю VMT; закоменченный параметр тоже имеет отношение к fold-expression template <typename ...A> LastAcceptorCaller(SRC& ptl, TPL&, const SHT&, A&&... args): m_Params(ptl) { getArgs<0>(/*std::index_sequence_for<Args...>(), */std::forward<A>(args)...); } SRC &m_Params; // исходный кортеж MD *self; // LastStart // внутренняя VMT - массив методов Accept(); // в отличие от RTTI-"матрицы" sheet, у каждого параметра мультиметода она своя и потому // должна строиться в run-time, чем конструкторы LastAcceptorCaller и занимаются ret_type (MD::*accepts[Length<typename CTL::Head::TypeList>::value])(param_type); }; // самый производный в иерархии акцептор; нужен для реализации CRTP, т.к. только тут все методы // из базовых классов-акцепьтров можно свести к единому производному квалифицирующему классу template <typename L, typename R, typename CTL, typename PTL, typename TPL, typename SRC, typename SHT> struct LastStart<ret_type, TList<L, R>, CTL, PTL, TPL, SRC, SHT>: public LastAcceptorCaller<ret_type, TList<L, R>, CTL, PTL, TPL, SRC, SHT, LastStart<ret_type, TList<L, R>, CTL, PTL, TPL, SRC, SHT>> { typedef LastAcceptorCaller<ret_type, TList<L, R>, CTL, PTL, TPL, SRC, SHT, LastStart<ret_type, TList<L, R>, CTL, PTL, TPL, SRC, SHT>> base_type; // поле self лежит в самом базовом акцепторе LastStart(SRC& ptl, TPL& src, const SHT& sht): base_type(ptl, src, sht) { this->self = this; } }; /* Реализация акцептора всех параметров мультиметода, кроме последнего. Особенности: - используется, если мультиметод имеет более 1 параметра. */ // это все апцепторы в иерархии, кроме самого базового template <typename L, typename R, typename CTL, typename PTL, typename TPL, typename SRC, typename SHT, typename MD> struct IterAcceptorCaller<ret_type, TList<L, R>, CTL, PTL, TPL, SRC, SHT, MD> : public IterAcceptorCaller<ret_type, R, CTL, PTL, TPL, SRC, SHT, MD> { typedef IterAcceptorCaller<ret_type, R, CTL, PTL, TPL, SRC, SHT, MD> base_type; typedef typename CTL::Head::template ParamType<L>::type arg_type; // конструктор строит вариадик из указателей на методы Accept() template <typename ...A> IterAcceptorCaller(SRC& ptl, TPL& src, const SHT& sht, A&&... args): base_type(ptl, src, sht, std::forward<A>(args)..., static_cast<ret_type (MD::*)(param_type)>(&IterAcceptorCaller::Accept)) {} /* восстановление типа очередного параметра, генерация следующего акцептора, передача ему следующего среза списка акцепторов, базовых типов параметров мультиметода, RTTI-матрицы итп и накопленной восстановленной информации о динамических типах и его вызов. */ typedef typename MostBase<typename CTL::Head::TypeList>::type most_base; typedef typename CTL::Head::template ParamType<most_base>::type param_type; ret_type Accept(param_type arg) { typedef typename SHT::base_type SheetType; typedef typename AcceptorMaker<ret_type, typename CTL::Tail, TList<arg_type, PTL>, typename TPL::base_type, SRC, SheetType>::type acceptor; // следующий акцептор acceptor a(base_type::m_Params, static_cast<typename TPL::base_type&>(base_type::m_Args), static_cast<const SheetType&>(base_type::m_Sht)); // поиск в RTTI-массиве элемента, соответствующего динамическому типу пришедшего в диспетчер // аргумента, и получение его индекса в исходном списке типов, описывающем его иерархию auto idx = std::lower_bound(std::begin(base_type::m_Sht.get()), std::end(base_type::m_Sht.get()), details::TypeInfo(CTL::Tail::Head::deref(GetField<0>(base_type::m_Args)))); // т.к. поиск не обязан давать точное совпадение и может только указать место в сортированном // контейнере, где ему было бы самое место, нужно самостоятельно убедиться в том, что элемент // успешно найден; в противном случае это означает ошибку в описании списка типов в иерархии if ( idx == std::end(base_type::m_Sht.get()) || !CTL::Tail::Head::check(*idx, CTL::Tail::Head::deref(GetField<0>(base_type::m_Args)))) throw std::invalid_argument("IterAcceptorCaller<> error: unlisted parameter"); // индексируем внутреннюю VMT, вызывая Accept() того акцептора, чей индекс вернул поиск // в RTTI-массиве, и передавая аргумент мультиметода, т.с. восстанавливая его тип; // в результате итерируемся к обработке следующего параметра return (a.self ->* a.accepts[idx->index()])(GetField<0>(base_type::m_Args)); } }; // это базовый в иерархии акцептор; весь список типов иерархии параметра уже обработан производными // классами, так что этот с ними уже не работает, он только обслуживает внутреннюю VMT template <typename CTL, typename PTL, typename TPL, typename SRC, typename SHT, typename MD> struct IterAcceptorCaller<ret_type, NullType, CTL, PTL, TPL, SRC, SHT, MD> { typedef typename MostBase<typename CTL::Head::TypeList>::type most_base; typedef typename CTL::Head::template ParamType<most_base>::type param_type; // Построение массива методов для восстановления типа динамически связываемого параметра. // См. также коммент к подобному методу в LastAcceptorCaller. template <int I, typename ...A> void getArgs(ret_type (MD::*at)(param_type), A&&... args) { accepts[I] = at; getArgs<I+1>(std::forward<A>(args)...); } template <int I> void getArgs(ret_type (MD::*at)(param_type)) { accepts[I] = at; } // конструктор, строит внутреннюю VMT template <typename ...A> IterAcceptorCaller(SRC& ptl, TPL& src, const SHT& sht, A&&... args): m_Params(ptl), m_Args(src), m_Sht(sht) { getArgs<0>(std::forward<A>(args)...); } SRC &m_Params; // исходный кортеж TPL &m_Args; // срез кортежа по текущий аргумент const SHT &m_Sht; // срез RTTI-"матрицы" MD *self; // IterStart // внутренняя VMT - массив методов Accept(); // см. также коммент к подобному массиву в LastAcceptorCaller ret_type (MD::*accepts[Length<typename CTL::Head::TypeList>::value])(param_type); }; // самый производный в иерархии акцептор; нужен для реализации CRTP, см. коммент к LastStart template <typename L, typename R, typename CTL, typename PTL, typename TPL, typename SRC, typename SHT> struct IterStart<ret_type, TList<L, R>, CTL, PTL, TPL, SRC, SHT> : public IterAcceptorCaller<ret_type, TList<L, R>, CTL, PTL, TPL, SRC, SHT, IterStart<ret_type, TList<L, R>, CTL, PTL, TPL, SRC, SHT>> { typedef IterAcceptorCaller<ret_type, TList<L, R>, CTL, PTL, TPL, SRC, SHT, IterStart<ret_type, TList<L, R>, CTL, PTL, TPL, SRC, SHT>> base_type; // поле self лежит в самом базовом акцепторе IterStart(SRC& ptl, TPL& src, const SHT& sht): base_type(ptl, src, sht) { this->self = this; } }; /* Обобщённая точка входа. Создаёт акцептор первого параметра и вызывает его. См. комменты к IterAcceptorCaller::Accept(). */ template <typename Tpl> ret_type doIt(Tpl& tpl) { typedef typename Tpl::base_type TupleType; // срез кортежа typedef typename sheet_type::base_type SheetType; // срез RTTI-"матрицы" typedef typename AcceptorMaker<ret_type, TTl, NullType, TupleType, Tpl, SheetType>::type acceptor; acceptor a(tpl, static_cast<TupleType&>(tpl), static_cast<const SheetType&>(sheet)); auto idx = std::lower_bound(std::begin(sheet.get()), std::end(sheet.get()), details::TypeInfo(TTl::Head::deref(GetField<0>(tpl)))); if ( idx == std::end(sheet.get()) || !TTl::Head::check(*idx, TTl::Head::deref(GetField<0>(tpl)))) throw std::invalid_argument("doIt() error: unlisted parameter"); return (a.self ->* a.accepts[idx->index()])(GetField<0>(tpl)); } /* Промежуточный метод перед doIt(). Выполняет вторую фазу обработки параметров: создание и заполнение кортежа. После этого этапа вариадиков больше нет, а все параметры прочеканы и предобработны. */ ret_type decay(typename details::MakeArg<Args>::type&&... args) { typedef Tuple<typename details::MakeParamTypes<TTl>::type> TupleType; TupleType tuple(std::forward<typename details::MakeArg<Args>::type>(args)...); return doIt(tuple); } public: /* Точка вызова диспетчера. Аргументами могут прийти любые типы, т.к. они не обязаны строго совпадать с формальными параметрами мультиметода. Их соответствие проеряется по ходу дела, и ежели что, либо будет выполнен неявный каст, либо компилятор сообщит о проблеме. В отличие от реализации для C++03, тут возможно сохранить полную Стандатную семантику соответствия типов фактических параметров типам заявленных формальных. */ template <typename ...A> ret_type operator ()(A&&... args) { // Предобработка параметров, в ходе которой динамически связываемые сводятся к указателям на // cv-неквалифицированные значения, и выполняется проверка соответствия фатических параметров // формальным. Важно, чтобы static_cast<> выполнял каст к rvalue ref. Во-первых, это не даёт // ему выполнять явный каст типов там, где неявный бы провалился (например, каст explicit // конструктором), во-вторых, он сводит динамически связываемые параметры к их базовым классам, // что, вообще говоря, является его главным назначением. return decay(static_cast<typename details::MakeArg<Args>::type&&>( details::mkref<Args>::doIt(std::forward<A>(args))) ...); } }; /* Это тривиальный случай - у мультиметода параметры отсутствуют. */ template <typename DI, typename ret_type> struct Dispatcher<DI, ret_type> { ret_type operator ()() { return CallConcrete<DI, ret_type, NullType>::template apply<NullType, NullType, NullType>::call(); } }; /*******************************************************************************\ ***************** Конкретный диспетчер **************** \*******************************************************************************/ /* Первый параметр - пользовательский класс с реализациями перекрытых мультиметодов, второй - тип возвращаемого мультиметодом значения, третий - исходный вариадик формальных параметров, по которому нужно будет выполнить постобработку фактических. */ template <typename UI, typename Ret, typename ...Args> struct CallConcrete { /* Преобразование кортежа обратно в вариадик с учётом восстановленных динамических типов. Обрабатывает все элементы кортежа, кроме последнего, который пришёл отдельно и уже обработан. SRC - исходный кортеж; PL - список восстановленных типов (в обратном порядке); Head- последний восстановленный тип. И тут тоже пригодились бы fold-expression из C++17. */ template <typename SRC, typename PL, typename Head> struct apply { // обрабатывает очередной элемент кортежа // pl - исходный кортеж; // data - последний параметр мультиметода; в касте не нуждается, т.к. передаётся вне контежа, // и его тип уже целевой; // args - уже обработанные параметры (при первом вызове пуст) template <typename ...A> static Ret call(SRC& pl, Head&& data, A&& ...args) { // взять из кортежа очередной элемент (в обратном порядке), преобразовать к восстановленному // типу из PL и передать следующему преобразователю вместе с последним параметром и исходным // кортежем; так потихоньку наполняется вариадик return apply<SRC, typename PL::Tail, Head>::call(pl, std::forward<Head>(data), static_cast<typename PL::Head>(GetField<Length<PL>::value-1>(pl)), std::forward<A>(args)...); } }; /* Сюда попадаем, когда элементов кортежа больше нет (или и не было). Обрабатывает все элементы кортежа, кроме последнего. SRC - исходный кортеж; PL - список восстановленных типов (в обратном порядке); Head- последний восстановленный тип. И тут тоже пригодились бы fold-expression из C++17. */ template <typename SRC, typename Head> struct apply<SRC, NullType, Head> { // вызывается предыдущим apply<>::call(); все параметры восстановлены, осталось только // поставить на место последний параметр... // вообще говоря, эти два метода можно было бы объединить в один, если не то факт, что длины // вариадиков ...Args и ...A разные; поэтому сначала их sizeof... выравниваются template <typename ...A> static Ret call(SRC& pl, Head&& data, A&& ...args) { return call(std::forward<A>(args)..., std::forward<Head>(data)); } // ...выполнить постобработку каждого из них в соответствии с его политикой из ...Args // и вызвать пользовательский перекрытый мультиметод template <typename ...A> static Ret call(A&& ...args) { return UI::apply(details::dcref<Args>::doIt(std::forward<A>(args))...); } // отдельная специализация для беспараметрических мультиметодов static Ret call() { return UI::apply(); } }; }; } // MultiMethods #endif // MULTIMETHODS_H_E689D195_5567_4BB4_8DFA_F4B111F69D92 Коменты к реализации coming soon. |
Сообщ.
#6
,
|
|
|
Заждались, небось. Итак, краткое описание технологии.
За основу архитектуры нового диспетчера взята версия от C++03. Из мелких изменений: В целом, кому понятно, как работала версия C++03, легко поймёт, как теперь это же работает тут. За подробностями отсылаю туда, и там же лучше задавать вопросы, если что всё ещё непонятно. А тут займёмся немелкими изменениями. Начнём с создания диспетчера. Он имеет конструктор, в чью задачу входит первая половина важной работы по созданию внутренней VMT: он строит прямоугольную матрицу RTTI-классов. Каждый параметр мультиметода имеет массив таких классов, по одному на элемент своей иерархии. Скажем, если параметром является иерархия из пяти классов, то и массив будет создан из пяти элементов. Это особенно актуально для динамически связываемых параметров, но и статически связываемые тоже имеют такой список (из одного элемента). За хранение этого массива ответственнен Sheet<>. Каждый такой массив (экземпляр Sheet) описывает один параметр мультиметода, все такие массивы как раз и составляют матрицу. Они хранятся в линейной иерархии Sheet: самый первый параметр в самом производном узле Sheet-ов, самый правый – в самом базовом. Такой принцип хранения очень удобен для рекурсивного обхода параметров диспетчером, просто на каждой итерации очередному акцептору передаётся базовый класс текущего Sheet. Сами Sheet-ы хранят массивы RTTI-классов длиной в длину списка типов иерархии соответствующего параметра. Элементами этого массива являются адаптеры над std::type_info. Адаптеры нужны, чтобы придать последним семантику значений, ибо стандартные std::type_info ею не обладают. До кучи этот адаптер определяет operator<() через std::type_info::before(), ну и другие полезные операции. Важной деталью адаптера является также индекс, который сначала совпадает с позицией класса в списке типов иерархии, но в дальнейшем массив сортируется для последующего бинарного поиска, а значит итоговые позиции классов в списке типов не обязаны будет совпадать с исходными индексами в массиве, поэтому для сохранения этой связи нужен индекс. Перейдём к вызову диспетчера. Перегруженный в диспетчере operator() теперь принимает вариадик rvalue ссылок. Его назначение – как и ранее, принять все параметры, положить в кортеж и запустить итерации восстановления динамических типов. Только теперь это делается в два приёма. Сам оператор () выполняет предобработку параметров и передаёт их методу decay(), и вот он как раз делает всё перечисленное. А предобработка – это новая функциональность, которая в версии C++03 была не нужна. Она заключается в том, чтобы mkref<> занимается большей частью вторым пунктом, а static_cast<> первым. Но и вторым тоже, ибо а вдруг базовый класс приватный. Метод decay() же получает на входе вариадик уже готовых параметров, и ему остаётся лишь на основе вариадика типов мультиметода сварганить тип кортежа, создать его на стеке, инициализировать вариадиком своих параметров и передать в первую итерацию восстановления типов. Первая итерация выполняется в doIt(). Он создаёт акцептор для первого аргумента мультиметода, выполняет восстановление динамического типа первого параметра и вызывает по восстановленному типу нужный его Accept(). Остальные итерации выполняются уже внутри этих Accept(), и они полностью аналогичны первой итерации, только параметры и аргументы у каждого свои. Только акцептор для самого последнего аргумента отличается от предыдущих, т.к. у него другая задача: ему ничего не нужно восстанавливать, всё восстановлено до него; вместо этого ему нужно вызвать конкретный диспетчер, чтобы тот собрал в одну кучу восстановленное и осуществил выбор между перекрытыми мультиметодами. Но о нём потом, сейчас интересно, как осуществляется восстановление динамических типов, и это тема для отдельного абзаца. Каждый такой акцептор имеет имя, начинающиеся на Iter, и представляет собой в целом такую же иерархию, как и конкретный акцептор в версии для C++03. Каждый рабочий узел в нём называется IterAcceptorCaller, которых в иерархии несколько, для каждого типа из списка типов узел свой, базовым для которого является IterAcceptorCaller для следующего типа из этого же списка. Узел для самого последнего типа больше не нуждается в базовых классах, т.к. абстрактных акцепторов в этой реализации нет. (На самом деле базовый класс у него есть, но у него отдельная задача, описываемая парой предложений ниже.) Каждый IterAcceptorCaller в иерархии т.о. обслуживает свой тип в списке типов и при этом имеет свой Accept(). Конструктор каждого узла принимает вариадик из указателей на эти методы от более производных узлов и добавляет к нему свой. В результате самый базовый IterAcceptorCaller, которому в списке типов обслуживаемой им иерархии сопоставляется NullType, получает полный комплект таких указателей. Вот их, родимых, он и помещает в свой массив (длиной в длину списка типов иерархии обслуживаемого этим акцептором параметра), чем выполняет вторую половину работы по созданию внутренней VMT. В отличие от первой половины, эту половину нельзя выполнить при создании диспетчера в его конструкторе, т.к. набор генерируемых акцепторов создаётся только при вызове диспетчера, а не заранее, причём создаются все акцепторы на стеке, что делает диспетчер абсолютно потокобезопасным. Чтобы вторую половину тоже можно было сделать в конструкторе диспетчера, его архитектуру придётся существенно изменить, учесть, что одинаковые акцепторы могут входить в VMT несколько раз (т.к. у мультиметода могут быть одинаковые типы параметров) и, т.к. хранить все данные придётся в полях диспетчера, придётся озаботиться потоковой безопасностью, и при этом кортеж всё равно будет на стеке, т.к. его контент определяется только в точке вызова диспетчера. В общем, овчинка выделки не стоит, т.к. создание массива из указателей на методы задача весьма дешёвая, и её вполне можно позволить себе в ран-тайм. Делает он это не совсем чисто, т.к. я внезапно обнаружил, что распоследние версии VS и Intel Compiler ещё не понимают fold-expression из C++17. Там (точнее в LastAcceptorCaller) приводится вид метода getArgs(), который строит такой массив очень изящно, да и вообще, fold-expression были бы полезны ещё кое-где по диспетчеру, на минимум в трёх местах, но увы, я решил, что было бы некрасиво вычёркивать из списка пользователей мультиметодов владельцев VisualStudio из-за нерасторопности MS и Intel. Так же замечу, что помимо специализированного самого базового IterAcceptorCaller, в иерархии акцептора есть ещё и специализированный самый производный, называемый просто IterStart. Он очень прост, и если бы не один-единственный фактик, он был бы не нужен. Но нужен, ибо фактик: все указатели на методы Accept() имеют разную квалификацию, так как принадлежат разным классам. Да, они все в пределах одной иерархии и поэтому могут быть вызваны, будучи правильно деквалифицированы подходящим образом static_cast<>нутым this. Вот в этом как раз и состоит предназначение IterStart: именно к его типу this всех указателей на Accept() и кастуются. Ну и пара слов касательно собственно восстановления динамического типа. У параметра берётся его typeid() и ищется в текущем Sheet. Ищется посредством std::lower_bound(), который, увы, не обязан давать точное значение, ему вполне достаточно указать на точку в массиве, где оно должно было бы быть, если точного значения не нашлось. По идее, такого быть не должно, однако пользователь вполне может ошибиться либо с определением списка типов иерархии, либо указал класс вместо списка типов иерархии, либо просто с параметром, тупо передав не тот экземпляр. В общем, после std::lower_bound() нужно убедиться, что искомое вообще-то нашлось, и ежели вдруг нет, ловите std::invalid_argument(). Можно было бы, конечно, взять ближайший... вот только RTTI никак не регламентирует метод упорядочивания, используемый std::type_info::before(), и вряд ли будет хорошо, если при подобных ошибках пользователя диспетчер реализовал бы implementation defined behaviour. Да и вообще, у RTTI нельзя спросить "кто это?", а можно лишь "а вот это вот не вот этот ли случаем?" и получить лишь булевый ответ. ...И не надо мне тут советовать ещё и свою RTTI мутить вместо стандартной, и своей VMT за глаза достаточно. В общем, убедившись, что всё нашлось, остаётся лишь вытащить из указанного std::lower_bound() экземпляра RTTI класса его исходный индекс в списке типов иерархии и сындексировать указатель на Accept(), разыменовав его this-ом от IterStart. Профит, вызвали Accept() узла, ответственного за репрезентацию динамического типа параметра, и можем переходить к следующему аргументу мультиметода. Замечу, что для статически связываемых параметров всё это выполняется точно так же в полной мере. Просто для них список типов состоит из одного узла, поэтому выбор производится ...ну, из одного элемента. Я не стал отдельно оптимизировать этот случай, т.к. профита это даст в малоосязаемом количестве, компилятор и сам неплохо справляется с тривиальными случаями. Для них не надо восстанавливать динамический тип, т.к. позднее связывание для них не требуется. Вместо этого тип просто берётся из списка параметров мультиметода и остаётся неизменным. Если тип фактически переданного в диспетчер параметра неприводим к типу аргумента, компиляция провалится ещё в operator() абстрактного диспетчера, а если приводим, причём неявно, то уже в decay() он будет приведён к целевому типу. Последний по списку акцептор состоит из узлов LastAcceptorCalled. Строятся они по тому же принципу, что и для IterAcceptorCalled, включая самый производный LastStart, потому что восстановление типа, выполняемое предыдущим акцептором, его тоже касается, но его задача иная: получив управление в одном из своих Accept(), ему остаётся лишь вызвать конкретный диспетчер. Он представлен классом CallConcrete, и его задача заключается в получении списка восстановленных динамических типов, преобразовании значений из кортежа обратно в вариадик с кастом к восстановленным типам и вызове подходящего apply() из пользовательского контейнера с перекрытиями мультиметода. Каст выполняется static_cast<> даже для динамически связываемых параметров, несмотря на то, что каст нисходящий. Тем не менее, это безопасный каст, т.к. подсистема RTTI гарантирует, что объект на самом деле имеет целевой для static_cast<> тип, в противном случае ещё раньше вылетел бы std::invalid_argument(). Для статически связываемых параметров в этой точке получается identical conversion. Методы CallConcrete<>::apply<>::call() последовательно выбирают и обрабатывают поля кортежа, и когда те заканчиваются, срабатывает специализация CallConcrete<>::apply<> по пустому списку восстановленных типов, где call() в итоге вызывает перегруженные apply() в контейнере перекрытых мультиметодов, предварительно применив к каждому параметру постобработку. Постобработка требуется для обратного преобразования cv-неквалифицированных указателей на динамически связываемые параметры, которые именно в таком виде хранятся в кортеже и были предобработаны в mkref<> в operator(), обратно в их исходные представления на основе исходных типов из вариадика параметров мультиметода, для чего в CallConcrete этот вариадик тоже передаётся, а занимается этим dcref<>. В итоге получается вызов статического метода apply() нашего контейнера перекрытий, в который приходят все исходные, полученные абстрактным диспетчером, преобразованные к их реальным динамическим типам, в результате компилятор вынужден разрешить перегрузку этих apply(), и вуаля, цель достигнута. |
Сообщ.
#7
,
|
|
|
В заключение перепостчу примеры-тесты из темы о мультиметодах C++03, слегка передизайненные под новую версию.
Прикреплённый файлtests.zip (8,2 Кбайт, скачиваний: 85) Тут всё без изменений, тестятся разные варианты типов параметров в разных местах, разные способы передачи возвращаемых значений и разные -арности мультиметодов. Не обессудьте, тесты создавались для внутреннего использования, и по-прежнему я тут поленился привести их вывод к некоему понятному виду. И если с динамически связываемыми параметрами всё ещё более-менее понятно, но корректность обработки статически связываемых параметров показана в cout-е довольно неинтуитивно. Но как примеры вполне подойдут. В примерах выше все динамически связываемые параметры без изменений передаются по cv-неквалифицированным указателям. Как это было и в версии для C++03. Я решил этого не менять, но создать отдельные тесты для разных всех остальных способов передачи, включая rvalue refs для статически связываемых. Вот они. Прикреплённый файлnewTests.zip (3,4 Кбайт, скачиваний: 84) И наконец, с чего всё начиналось. Тест для измерения оверхеда диспетчера в наносекундах. Прикреплённый файлtestTimeRTTI.zip (1,33 Кбайт, скачиваний: 79) Сам по себе он особо ничего не значит, а значит лишь в сравнении с чем-либо ещё. Ну, вы можете взять его и переделать под C++03, будет с чем сравнивать. Но я бы посоветовал подождать... ещё одной темы. |