На главную
ПРАВИЛА 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.
Одни с годами умнеют, другие становятся старше.
Сегодня коментов тоже не будет. Отмечаем серебряную свадьбу. Сообщение будет удалено через 1 дн.
Одни с годами умнеют, другие становятся старше.
Заждались, небось. Итак, краткое описание технологии.
За основу архитектуры нового диспетчера взята версия от 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(), и вуаля, цель достигнута.

M
Тема на оформлении, to be continued
Сообщение отредактировано: Qraizer -
Одни с годами умнеют, другие становятся старше.
1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
0 пользователей:


Рейтинг@Mail.ru
[ Script Execution time: 0,2729 ]   [ 20 queries used ]   [ Generated: 15.08.18, 03:40 GMT ]