На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
Модераторы: JoeUser, Qraizer, Hsilgos
  
> Мультиметоды на RTTI, продолжение темы
    Прошло без малого 4 года, как предложенная мною реализация мультиметодов на C++03 была выложена на всеобщее обозрение. Она начиналась ещё во времена C++0x, когда ни C++11, ни компиляторов для C++11 ещё и в помине не было, а были только штучные бэтки с поддержкой нольИксового драфта. Собственно, поэтому и был выбран C++03, ибо даже после 11-го года предыдущий Стандарт ещё какое-то время был бы актуален, да и легаси проекты никуда не денутся. Занимался я ею от случая к случаю, потихоньку добавлял реализации тех или иных хотелок, уже и C++11 вполне себе состоялся и вовсю маячил С++14... в конце-концов на оставшиеся хотелки я просто плюнул и решил, что для C++03 и так сойдёт, надо на чём-то остановиться, ибо вот-вот, и мультиметоды появятся в языке нативные, а мне в каждом случае от случая всё сложнее вспоминать, на чём же я полгода назад остановился, и что ещё нужно сделать. Выложил, что к тому времени вышло.
    Нативных мультиметодов так и не дождались, так что в какой-то мере та тема всё ещё актуальна. Тем более, что если погуглить, то тема мультиметодов выглядит замусоленной по всем направлениям, хотя и достигнутым мною результатом по производительности и универсальности похвастаться смогут ой как немногие. Чего греха таить, я гордился своей реализацией. Ещё бы:
    • поддержка статически и динамически связываемых параметров;
    • поддержка любых типов для статически связываемых параметров;
    • формально неограниченная -арность;
    • хоть и многобуквенное, но логичное и интуитивное определение для диспетчера;
    • благодаря использованию правил перегрузки из Стандарта, диагностика о неоднозначном перекрытии мультиметода выдаётся ещё в compile-time;
    • инкапсуляция базового мультиметода и его перекрытий в рамках выделенной области видимости; для каждого мультиметода;
    • отсутствие любого RTTI, включая dynamic_cast<>, только static_cast<>; отсутствие reinterpret_cast<>; наличествует, правда, const_cast<>, снимающий константность, однако Стандарт это не нарушает, т.к. объекты со снятым const только хранятся по ссылкам, и нигде не пишутся, только читаются для передачи параметрами в перекрытый мультиметод;
    • мизерный оверхед – два полиморфных вызова на каждый динамически связываемый параметр + остальное легко оптимизируется средненьким оптимизатором, включая отсутствие лишних копирований аргументов-значений.
    Но и недостатков тоже хватало:
    • в связи с отсутствием вариадиков -арность фактически ограничена заимплеменченной копипастой; правда, она элементарно увеличивалась копипастой же, но всё-таки;
    • в той же связи от списков типов никуда не деться; а typedef-ов ещё больше, чем списков типов;
    • в связи с засильем typedef и списков типов определение диспетчера хоть и логичное и интуитивное, но сильно уж многобуквенное;
    • динамически связываемые параметры могут передаваться только по не cv-квалифицированным указателям;
    • за низкий оверхед приходится платить патерном Visitor, для чего в классы следует добавить visit-метод; его вид, впрочем, очень формален, так что его реализация легко укладывается в макрос, и тем не менее см.ниже;
    • большая сложность использования в несинтетических проектах из-за взаимной связи visit-методов в классах пользователя, которые нельзя определить до объявления параметров мультиметода, и диспетчером, который нельзя определить до определения классов; в одной единице трансляции это ещё несложно обходится, но в реальных проектах с документирующими интерфейсы классов заголовками и раздельными их реализациями соблюсти все нюансы порядка связанных с диспетчером объявлений/определений становится весьма непросто;
    • из-за отсутствия форварда в C++03 на входе в диспетчер теряется информация о типах параметров, ибо все они принимаются по const&, поэтому в точке их принятия аргументами в перекрытом мультиметоде остаётся лишь надеяться, что программер нигде не ошибся в указаниях типов и передал то, что обещал; аукнуться может редко, в большинстве случаев компилер сообщит, ежели чё, но вот, например, по неконстантой ссылке передать rvalue вполне было можно;
    • при высокой -арности динамически связываемых параметров компилятор мог не найти для себя достаточно ресурсов или как минимум впасть в глубокую задумчивость; впрочем, тут влияет не столько -арность, сколько её комбинация с глубинами иерархий динамически связываемых параметров, однако попасть на такое несинтетически ещё над умудриться, ибо boost:spirit напрягает компилер заметно сильнее (не требуя, правда, баснословных ресурсов).
    Как бы немало, так что вопрос, а есть ли повод гордиться, то и дело меня занимал. Лично я считал, что да, финальная стоимость оверхеда с моей точки зрения перекрывает перечисленные неудобства, но всё не оставляла мысль, не обманываю ли я сам себя. Ведь как там говорится: любой уважающий себя программист должен написать мультиметоды... а, не, там про тетрис. Ну тогда всё правильно, если б там мультиметоды, тогда в натуре гордиться особо было бы нечем. Но кроме гордости есть ещё особая гордость и исключительная гордость, а это уже понятия другого уровня.
    Вот так, примерно 9 месяцев назад, и вышло, что я взял себя в руки и начал себя испытывать. Ну, мультиметоды испытывать, в смысле.
    Одни с годами умнеют, другие становятся старше.
      Чтобы испытать особую гордость нужно сравнить производительность с другими. Честно говоря, было лень делать глубокое исследование, поэтому порыскав по инету, нашёл более-менее простое вот это, которое не завелось из-за каких-то её внутренних проблем с 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-квалифицированных аргументов-значений. Опять-таки, копипаст ваш лучший друг, перфекционистам руки развязаны.
      Одни с годами умнеют, другие становятся старше.
        Хватит слов, пора за дело. Начнём с конца: сначала, как использовать. Вот пример из той темы:
        Предположим у нас есть две (несвязанных) иерархии полиморфных классов B1 и B2, и нам требуется мультиметод с такой сигнатурой:
        ExpandedWrap disabled
          int (virtual const B2*, std::string&, virtual B1&);
        Здесь по-прежнему virtual условно и показывает, что для этих параметров требуется позднее связывание. Заметьте, однако, что в демонстрационных целях я изменил способ передачи параметров. Ну, просто чтоб показать, что теперь позволяются не только cv-неквалифицированые поинтеры. Также будем считать, что перекрыть этот мультиметод нам понадобилось теми же сигнатурами, что и в том примере:
        ExpandedWrap disabled
          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&);
        Итак, что для реализации этого нужно сделать? Та сущую ерунду.
        • ExpandedWrap disabled
            #include "mmethod.h"
             
            namespace MM = MultiMethods;
             
            /* Наши тестовые классы */
            class B1;
            class D11;
            class D12;
            class D13;
            class D14;
             
            class B2;
            class D21;
            class D22;
            class D23;
             
            typedef MM::MakeTList<D13, D12, D11, B1, D14> Param1List;
            typedef MM::MakeTList<B2,  D21, D22, D23>     Param2List;
          Что такое? :o Опять списки типов?? М-м-м... да. С этим, увы, ничего не поделать даже в C++17, т.к. по-прежнему пройтись по иерархии классов никаким метакодом невозможно. Тут возможно помогут лишь метаклассы из недавнего пропозала. Так что тему определения списков типов, описывающих иерархию динамически связываемых параметров, можно считать закрытой: они нужны, и ещё будут нужны по меньшей мере 8 лет. Но и хорошая новость: этими списками всё и ограничивается. Кроме того, обратите внимание, что список типов строится вариадиком, что всё-таки удобнее, чем было.
        • Далее определяем наши классы. Тут принципиально ничего не изменилось. Та же иерархия пустых классов, что в примере прошлой темы:
          ExpandedWrap disabled
            /* Определяем классы */
            /* Первая иерархия */
            class B1
            {
            public:
              virtual ~B1(){}
            };
             
            class D11 : public B1 {};
            class D12 : public B1 {};
            class D13 : public D11{};
            class D14 : public D12{};
             
            /* Вторая иерархия */
            class B2
            {
            public:
              virtual ~B2(){}
            };
             
            class D21 : public B2 {};
            class D22 : public D21{};
            class D23 : public B2 {};
          Ну, нет макроса, внедряющего visit-метод за ненадобностью, и поэтому классы полиморфируются явно посредством виртуального деструктора. Понятно, что классы по-любому нужно как-то определить, так что эти определения непосредственно к мультиметодам не относятся.
        • Определение мультиметода и нескольких его перекрытий:
          ExpandedWrap disabled
            struct dispatchHere
            {
              static int apply(const B2*,  std::string& str,  B1&)
              {
                std::cout << "Triple B2 -nonconst ref-B1  Dispatch: " << &str << " - ";
                str += '0';
                return 31;
              }
              static int apply(const D21*, std::string& str,  B1&)
              {
                std::cout << "Triple D21-nonconst ref-B1  Dispatch: " << &str << " - ";
                str += '1';
                return 32;
              }
              static int apply(const D22*, std::string& str, D12&)
              {
                std::cout << "Triple D22-nonconst ref-D12 Dispatch: " << &str << " - ";
                str += '2';
                return 33;
              }
              static int apply(const D22*, std::string& str,  B1&)
              {
                std::cout << "Triple D22-nonconst ref-B1  Dispatch: " << &str << " - ";
                str += '3';
                return 34;
              }
              static int apply(const D23*, std::string& str,  B1&)
              {
                std::cout << "Triple D23-nonconst ref-B1  Dispatch: " << &str << " - ";
                str += '4';
                return 35;
              }
              static int apply(const D23*, std::string& str, D11&)
              {
                std::cout << "Triple D23-nonconst ref-D11 Dispatch: " << &str << " - ";
                str += '5';
                return 36;
              }
              static int apply(const D23*, std::string& str, D13&)
              {
                std::cout << "Triple D23-nonconst ref-D13 Dispatch: " << &str << " - ";
                str += '6';
                return 37;
              }
              static int apply(const D22*, std::string& str, D14&)
              {
                std::cout << "Triple D22-nonconst ref-D14 Dispatch: " << &str << " - ";
                str += '7';
                return 38;
              }
            };
          И снова никаких изменений, кроме подправленных типов параметров вслед за изменённым прототипом мультиметода. И снова подобные действия нужно было бы выполнять в любом случае, так что и этот код относится к мультиметодам только лишь следованием требованию инкапсулировать мультиметод и все его перекрытия в одной структуре и реализовывать их статическими публичными (точнее, доступными) методами apply().
        • И напоследок создаём диспетчер:
          ExpandedWrap disabled
            MM::Dispatcher<dispatchHere, int, const Param2List*, std::string&, Param1List&> callThis;
          Всё. Как я и обещал, никаких списков, вариадики рулят. Обратите внимание, как определяются динамически связываемые параметры: списком типов иерархии. Зато всё остальное просто и понятно: первый параметр – наша инкапсулирующая структура, второй – возвращаемое значение, остальные – параметры в их исходных типах. Также обратите внимание, что без typedef из первого пункта можно было бы и обойтись:
          ExpandedWrap disabled
            MM::Dispatcher<dispatchHere, int, const MM::MakeTList<B2,  D21, D22, D23>::type*, std::string&, MM::MakeTList<D13, D12, D11, B1, D14>::type&> callThis;
          просто с ними выглядит немного понятнее. Зато без них не будут нужны и опережающие объявления классов, так что первый пункт просто исчезает. В общем, решайте сами, как вам удобнее.
        • Использовать так же, как в том примере. Для удобства перепостчу сюда:
          ExpandedWrap disabled
              B1  o1;
              D11 o11;
              D12 o12;
              D13 o13;
              D14 o14;
             
              B2  o2;
              D21 o21;
              D22 o22;
              D23 o23;
             
              std::string s = " - it's the string";
             
              std::cout << "Before the passing into multimethod: " << s << std::endl;
             
              std::cout << callThis(&o2 , s, o12) << " returned" << std::endl;
              std::cout << callThis(&o21, s, o14) << " returned" << std::endl;
              std::cout << callThis(&o22, s, o12) << " returned" << std::endl;
              std::cout << callThis(&o22, s, o13) << " returned" << std::endl;
              std::cout << callThis(&o22, s, o14) << " returned" << std::endl;
              std::cout << callThis(&o23, s, o11) << " returned" << std::endl;
              std::cout << callThis(&o23, s, o12) << " returned" << std::endl;
              std::cout << callThis(&o23, s, o13) << " returned" << std::endl;
             
              std::cout << "Outside a multimethod:       " << s << std::endl;
        Ну как, сложно? Не правда ли, элементарно? А как насчёт посмотреть реализацию?
        Одни с годами умнеют, другие становятся старше.
          Давай реализацию. :)
          "Математики думают, что Бог в уравнениях, нейрологи уверены, что Бог в мозге, а программисты уверены, что Бог — один из них."
          Морган Фриман
          Реализация шаблонов Jinja2 для C++ Jinja2 C++
            Списки типов (tlist.h). Практически не изменились. Добавился лишь хелпер MakeTList<> для нерекурсивного создания списков через вариадики. Метаалгоритм GenHierarchy в RTTI-реализации не используется, однако оставил ради Visitor-реализации.
            ExpandedWrap disabled
              /*****************************************************************\
              ** Списки типов. Несмотря на наличие 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, но это опять-таки значило изменить архитектуру движка диспетчера. Не стал. Хотите – на здоровье.
            ExpandedWrap disabled
              /****************************************************************\
              **                          Кортежи.                            **
              ** Стандартные из 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). Вот тут изменений ожидаемо больше.
            ExpandedWrap disabled
              #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.
            Одни с годами умнеют, другие становятся старше.
              Заждались, небось. Итак, краткое описание технологии.
              За основу архитектуры нового диспетчера взята версия от C++03. Из мелких изменений:
              • удалён шаблонный интерфейс абстрактного акцептора за ненадобностью;
              • убран базовый класс из конкретного акцептора динамически связываемых параметров, т.к. абстрактный акцептор больше не нужен, т.к. акцепторы больше не делятся на абстрактные и конкретные; точнее, остались только конкретные, но называть их так уже не вполне правильно, т.к. в C++03 конкретные реализовывали интерфейсы, определённые абстрактными, а тут чистых методов нет вообще, так что теперь это просто акцепторы;
              • сами акцепторы LinkStatic<> и LinkDynamic<> переместились в приватный интерфейс абстрактного диспетчера, что обусловлено тем, что пользователю больше не нужно самостоятельно определять тип связывания.
              В целом, кому понятно, как работала версия 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 была не нужна. Она заключается в том, чтобы
              1. выполнить неявные касты статически связываемых параметров к типам их аргументов в соответствии с правилами Стандарта языка; так, можно под int передать какой-нибудь char или под std::string строковый литерал, однако нельзя, чтобы компилятор, например, смог заюзать каст-конструктор, если тот explicit;
              2. выполнить каст динамически связываемых параметров к их самым базовым типам, при этом независимо от фактического способа передачи свести их внутри диспетчера к cv-неквалифицированным указателям; хотя это реализовано и топорно, это оказалось проще, чем параметризировать методы работы с ними на уровне политик.
              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(), и вуаля, цель достигнута.
              Одни с годами умнеют, другие становятся старше.
                В заключение перепостчу примеры-тесты из темы о мультиметодах C++03, слегка передизайненные под новую версию.

                Прикреплённый файлПрикреплённый файлtests.zip (8,2 Кбайт, скачиваний: 13)

                Тут всё без изменений, тестятся разные варианты типов параметров в разных местах, разные способы передачи возвращаемых значений и разные -арности мультиметодов. Не обессудьте, тесты создавались для внутреннего использования, и по-прежнему я тут поленился привести их вывод к некоему понятному виду. И если с динамически связываемыми параметрами всё ещё более-менее понятно, но корректность обработки статически связываемых параметров показана в cout-е довольно неинтуитивно. Но как примеры вполне подойдут.
                В примерах выше все динамически связываемые параметры без изменений передаются по cv-неквалифицированным указателям. Как это было и в версии для C++03. Я решил этого не менять, но создать отдельные тесты для разных всех остальных способов передачи, включая rvalue refs для статически связываемых. Вот они.

                Прикреплённый файлПрикреплённый файлnewTests.zip (3,4 Кбайт, скачиваний: 12)

                И наконец, с чего всё начиналось. Тест для измерения оверхеда диспетчера в наносекундах.

                Прикреплённый файлПрикреплённый файлtestTimeRTTI.zip (1,33 Кбайт, скачиваний: 11)

                Сам по себе он особо ничего не значит, а значит лишь в сравнении с чем-либо ещё. Ну, вы можете взять его и переделать под C++03, будет с чем сравнивать. Но я бы посоветовал подождать... ещё одной темы. ;)
                Одни с годами умнеют, другие становятся старше.
                1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
                0 пользователей:


                Рейтинг@Mail.ru
                [ Script Execution time: 0,2374 ]   [ 18 queries used ]   [ Generated: 15.11.18, 19:03 GMT ]