На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
Модераторы: JoeUser, Qraizer, Hsilgos
  
> Мультиметоды без RTTI, окончание темы.
    В предыдущей теме я получил то, что хотел: реализовал динамическую диспетчеризацию в ран-тайм с поддержкой фич C++0x. На этом можно было бы остановиться, однако сравнивать реализации C++03 с C++1x как-то некрасиво. Всё-таки работа, несвязанная с диспетчеризацией, разная и оформлена по-разному, поэтому в условиях высокой производительности visit-ного C++03 эта разница может составить существенную часть от общих временны́х ресурсов. В общем, я покумекал и решил, что интрузивный вариант тоже следует перенести под C++1x, тем более что в ряде случаев потеря производительности из-за RTTI и сам факт использования RTTI в приложении может оказаться неприемлемым, а терять поддержку C++1x из-за использования версии C++03 тоже не всем захочется.
    Благо в этом не было ничего особо сложного. Фактически работа диспетчера состоит из пяти этапов: предварительная обработка параметров, в ходе которой те приводятся к удобной для обработки виду, подготовка стартовых структур типов (большей частью ещё при компиляции), итерирование по параметрам, в ходе которого восстанавливаются и сохраняются в списке типов динамические типы переданных параметров, постобработка параметров, заключающаяся г.о. в приведению принятых типов к их восстановленным реальным, и вызов перекрытого мультиметода, в ходе которого компилятор разрешает перегрузку и выполняет восходящие неявные касты, если надо. Из этих пяти этапов последний вообще делает компилятор, выполнение итераций перекладываются на акцепторы, и им как бы пофик на то, что и как происходит вокруг них, а остальное не зависит от способа работы с полиморфными классами. Поэтому однажды перепроектировав пред- и пост- хендлинг параметров, я никак не затронул работу акцепторов, а значит перенести старые акцепторы под новый C++1x не должно составить труда, учитывая, что вся пред- и пост- обработка мною сознательно были выполнены т.о., чтобы не затронуть рабочие структуры акцепторов.
    Ну, как бы так и вышло. Всего лишь пришлось добавить форвард и универсальные ссылки в Accept()-ы акцепторов. Но вот основная проблема с сильной зависимостью диспетчера и определений visit-методов, о которой говорилось в прошлой теме, осталась, да ещё и в усугублённом качестве. Объявления visit-методов, т.к. они виртуальные, иначе какой же это Visitor, без полиморфизма-то, с одной стороны не могут быть шаблонными, с другой зависят от соответствующих им абстрактных акцепторов, т.к. принимают их параметром, значит определения классов не могут располагаться перед ними; с другой – если диспетчер к точке определения классов уже будет создан, он не будет иметь информации о взаимном родстве между классами одной иерархии, поэтому на финальном этапе работы диспетчера компилятор не сумеет выполнять неявные касты производных классов к базовым, значит определение диспетчера не может быть расположено перед определениями классов. Прямое противоречие. В версии C++03 это было проблемой, но проблемой решаемой, хотя и неудобно. Там пользователь посредством typedef LinkDynamic<> сам создавал типы абстрактных акцепторов, и они были отделены от диспетчера, поэтому можно было расположить определения классов между этими определениями. Но в новом интерфейсе пользователь больше не работает с акцепторами сам, диспетчер самостоятельно их создаёт на основе вариадика с параметрами. Поэтому я столкнулся с неприятной дилеммой: либо вернуть заботу об абстрактных акцепторах пользователю, либо искать иной путь. К счастью, второй вариант нашёлся. Пусть и не такой удобный, как с RTTI версией.
    Понятно, что по-любому придётся разделить акцепторы и диспетчер. Я решил, что введение понятия прототипа для мультиметода вполне укладывается в логику и не выглядит таким уж костылём. Поэтому тут теперь из mmethod.h экспортируются два шаблона: помимо собственно Dispatcher ещё и Prototype. Теперь всё ранее передаваемое Dispatcher без изменений нужно передавать в Prototype, создаваемый typedef, а Dispatcher-у указывать именно его.
    Вот тот же пример, что в прошлой теме.
    ExpandedWrap disabled
      /* Наши тестовые классы */
      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;
       
      /* Прототип */
      class dispatchHere;
      typedef MM::Prototype<dispatchHere, int, const Param2List*, std::string&, Param1List&> Proto;
    Всё то же, но добавился прототип. Его сигнатура в точности такая же, как у диспетчера в той теме.
    ExpandedWrap disabled
      /* Определяем классы */
      /* Первая иерархия */
      class B1
      {
      public:
        MAKE_ACCEPTABLE(Proto, Param1List, B1);
      };
       
      class D11 : public B1
      {
      public:
        MAKE_ACCEPTABLE(Proto, Param1List, D11);
      };
       
      class D12 : public B1
      {
      public:
        MAKE_ACCEPTABLE(Proto, Param1List, D12);
      };
       
      class D13 : public D11
      {
      public:
        MAKE_ACCEPTABLE(Proto, Param1List, D13);
      };
       
      class D14 : public D12
      {
      public:
        MAKE_ACCEPTABLE(Proto, Param1List, D14);
      };
       
      /* Вторая иерархия */
      class B2
      {
      public:
        MAKE_ACCEPTABLE(Proto, Param2List, B2);
      };
       
      class D21 : public B2
      {
      public:
        MAKE_ACCEPTABLE(Proto, Param2List, D21);
      };
       
      class D22 : public D21
      {
      public:
        MAKE_ACCEPTABLE(Proto, Param2List, D22);
      };
       
      class D23 : public B2
      {
      public:
        MAKE_ACCEPTABLE(Proto, Param2List, D23);
      };
    Определения классов теперь включают знакомый макрос. Чуточку другой, правда. Об этом ниже.
    Контейнер с нашим базовым мультиметодом и его перекрытиями никак не изменился:
    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;
        }
      };
    Можно было бы не приводить и отослать в прошлую тему, но мне скопипастить не жалко.
    Наконец диспетчер:
    ExpandedWrap disabled
      /* Диспетчер */
      MM::Dispatcher<Proto> 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;
    Замечу лишь, что в версии для C++03 диспетчер было можно даже создать до определения классов и перекрытий, важно лишь, чтобы он впервые вызван был после этого. Тут, увы, это не проканает, и не проканает с RTTI-версией: ранее список типов из абстрактных акцепторов передавался в диспетчер шаблонным параметром, и инстанцирование зависимых от него сущностей, а значит и создание мульти-VMT, откладывалось до первого требования посредством вызова operator(), несмотря на полиморфный код в doIt() и акцепторах, т.к. его использование зависело лишь от operator(), а теперь этот список либо создаётся (в RTTI-версии), либо импортируется из прототипа (в интрузивной версии) непосредственно при инстанцировании диспетчера, поэтому все зависящие от него сущности инстанцируются тоже сразу, ибо в VMT для линкера нужно предоставить место уже сейчас.
    Из того, что осталось – вид макроса MAKE_ACCEPTABLE(). Понятно, что коли мы уже не создаём акцепторы сами, то определение сигнатуры visit-метода становится нетривиальной задачей. Решить её на самом деле несложно: когда создавали мы, мы знали, что использовать; когда теперь создаёт прототип, то он и знает эти сигнатуры, так что спрашивать нужно у него. Для этого в нём предусмотрен несложный метаалгоритм getArg<>. Ему даётся список типов, описывающий иерархию, и он его ищет у себя, в параметрах акцепторов, где же именно тот, который на основе конкретно этой иерархии строился. Если нашёл, возвращает результатом, и уже оттуда можно получить и сигнатуру акцептора, и тип возвращаемого значения. Так что теперь макросу передаётся не возвращаемый тип и акцептор, а список типов иерархии и собственно прототип. Ну и сам класс, конечно, это не изменилось. Зато если вы ещё раньше, в версии C++03, не сделали себе два других макроса, то тут они же есть готовые: это MAKE_ACCEPTABLE_DECL(), который только объявляет visit-метод в предположении, что его определение будет позже и где-то, и его можно использовать вместо MAKE_ACCEPTABLE(), и MAKE_ACCEPTABLE_IMPL(), который нужно ставить вне класса, и который собственно это определение и создаёт. Использование этих двух макросов вместо оригинального одного позволяет разделить visit-метод на объявление и определение и в итоге вынести последнее, например, из .h в .cpp.
    Остаётся лишь один вопрос... нет, два: что делать, если тип параметра входит в сигнатуру мультиметода более одного раза, и что делать, если одинаковые типы параметров используются разными мультиметодами, естественно с разными прототипами. Ответ на оба вопроса: ничего. Важно, чтобы список типов иерархии был верным, и в прототипе этот список фигурировал. Абстрактные акцепторы на основе одного и того же списка типов инстанцируются всё равно одинаковые, это вот конкретные могут отличаться, так что макросам пофигу, сколько раз этот список типов входит в прототип, и в какой именно прототип. И даже пофигу, каким именно образом передаётся фактический параметр: то ли про ссылке, то ли по указатели, то ли константный, то ли нет, итп. В общем, смотрите нижележащие примеры (которые будут, обещаю), там никаких заморочек, и всё работает.
    Одни с годами умнеют, другие становятся старше.
      Что-то халявлю, надо навёрстывать.
      Итак, реализация. Списки типов и кортежы те же самые. Ну, мне по-прежнему несложно скопипастить, чтоб по разным темам не гонять.
      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
      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
      А вот мультиметоды, конечно, другие.
      ExpandedWrap disabled
        #ifndef MULTIMETHODS_H_0A0E1659_FD72_4B5B_A091_EF59C8050584
        #define MULTIMETHODS_H_0A0E1659_FD72_4B5B_A091_EF59C8050584
         
        #include "tlist.h"
        #include "tuple.h"
         
        namespace MultiMethods
        {
         
        /* Фактически списки типов и кортежи разрабатывались для мультиметодов, и их использование
           без них вряд ли имеет смысл. Если нужны, лучше заюзать стандартные из буста или C++1x.
           Так как мультиметоды очень плотно связаны как с теми, так и с другими, нет смысла скрывать эту
           связь. */
        using namespace Types_Lists;
        using namespace Tuples;
         
        /* Внутренности реализации (приватный интерфейс). */
        namespace details
        {
         
        /* Узел веерной иерархии абстрактного акцептора. Документирует интерфейс Visitor-а */
        template <typename T, typename Ret> class Acceptor
        {
        public:
          virtual Ret Accept(T*&& obj) = 0;
        };
         
        /* Строит список типов параметров "базового" мультиметода на основе
           акцепторов абстрактного диспетчера и типов параметров, с которыми вызван его 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;
        };
         
        /********************************************************************************\
        ***************      Абстрактные акцепторы. Всего их две штуки.     **************
        \********************************************************************************/
         
        /* Динамический акцептор. Обрабатывает динамически связываемые параметры.
           Первый параметр - список типов, должен включать базовый и все производные от него классы,
                             которые предполагается передавать аргументом в мультиметод,
           второй          - тип возвращаемого значения мультиметода. */
        template <typename TL, typename Ret> class LinkDynamic: public GenHierarchy<details::Acceptor, Ret, TL>
        {
        public:
          typedef TL TypeList;
         
          // вызывается конкретным акцептором, восстановливает динамический тип t
          template <typename T, typename X>
          Ret apply(T* t, X&)
          {
            return t->Accept(*this);   // делегировать восстановление типа самому t
          }
          
          /* Генератор конкретных акцепторов, реализующих чистые методы абстрактных акцепторов.
             Всего их два разных. */
          template <typename T,
                    template <typename, typename, typename, typename, typename, typename> class GA,
                    typename ATL, typename CTL, typename TPL, typename SRC>
          struct MakeAcceptor
          {
            typedef GA<T, TL, ATL, CTL, TPL, SRC> type;
          };
         
          /* Используется details::MakeParamTypes<> для получения типа параметра мультиметода.
             Для динамического акцептора это указатель на тип Head из TL. */
          template <typename T> struct ParamType { typedef T* type; };
        };
         
        /* Статическый акцептор. Обрабатывает статически связываемые параметры.
           Первый параметр - непосредственно тип аргумента мультиметода,
           второй          - тип возвращаемого значения мультиметода. */
        template <typename Type, typename Ret> class LinkStatic
        {
        public:
          typedef TList<Type, NullType> TypeList;
         
          // вызывается конкретным акцептором; восстанавливать нечего, просто вызывает следующий акцептор
          template <typename T, typename X>
          Ret apply(T&& t, X& acceptor)
          {
            return acceptor.Accept(std::forward<T>(t));
          }
         
          /* Такой же генератор конкретных акцепторов. */
          template <typename T,
                    template <typename, typename, typename, typename, typename, typename> class GA,
                    typename ATL, typename CTL, typename TPL, typename SRC>
          struct MakeAcceptor
          {
            typedef GA<T, TypeList, ATL, CTL, TPL, SRC> type;
          };
         
          /* Используется details::MakeParamTypes<> для получения типа параметра мультиметода.
             Для статического акцептора это rvalue ссылка на тип Type. */
          template <typename T> struct ParamType { typedef T&& type; };
        };
         
        /********************************************************************************\
        *************        Стратегии работы с параметрами диспетчера.       ************
        *******       Увы, <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;
         
        /* Прототип диспетчера. Должен быть отделён от диспетчера, иначе получается циклическая связь между
           определениями абстрактных акцепторов и объявлениями visit-методов в классах пользователя.
           Первый параметр - пользовательский класс с реализациями перекрытых мультиметодов,
           второй          - тип возвращаемого мультиметодом значения,
           третий...       - типы параметров. */
        template <typename DI, typename RetType, typename ...Args>
        struct Prototype
        {
        protected:
          // преобразовать типы параметров мультиметода в аргументы
          // фактически это список абстрактных акцепторов
          typedef typename details::MakeParams<RetType, Args...>::type TTl;
         
        public:
          typedef RetType ret_type;
         
          /* Получить тип абстрактного акцептора по списку типов иерархии.
             Требуется в visit-методах пользовательских классов, чтобы от него выбрать свой интерфейс.
             В версии для C++03 абстрактные акцепторы создавались пользователем, тут их создаёт сам диспетчер,
             так что пользовать диспетчер стало проще, а реализовать visit-метод сложнее. */
          template <typename TL>            struct getArg;
          template <typename H, typename T> struct getArg<TList<H, T>>
          {
            // Найти список типов иерархии в аргументах мультиметода
            template <typename X, typename Y, typename Tl>            struct findType;
            template <typename X, typename Y, typename L, typename R> struct findType<X, Y, TList<L, R>>
            {
              typedef typename findType<X, typename R::Head::TypeList, R>::type type;
            };
            template <typename X, typename L, typename R>             struct findType<X, X, TList<L, R>>
            {
              typedef L type;
            };
            // Найти LinkDynamic<> запрошенного TList<> в TTl
            typedef typename findType<TList<H, T>, typename TTl::Head::TypeList, TTl>::type type;
          };
        };
        // Специализация для беспараметрических мультиметодов
        template <typename DI, typename RetType> struct Prototype<DI, RetType> {};
         
        /* Абстрактный диспетчер. Принимает свой прототип. */
        template <typename Proto> class Dispatcher;
         
        template <typename DI, typename RetType, typename ...Args>
        class Dispatcher<Prototype<DI, RetType, Args...>>: Prototype<DI, RetType, Args...>
        {
          using typename Prototype<DI, RetType, Args...>::TTl;
         
        /********************************************************************************\
        ***************       Конкретные акцепторы. Всего их две штуки      **************
        \********************************************************************************/
         
          /* Конкретные акцепторы. IterAcceptorCaller обрабатывает все параметры, кроме последнего,
             LastAcceptorCaller обрабатывает последний параметр. */
          template <typename T, typename ATL, typename CTL,
                                typename PTL, typename TPL, typename SRC> struct IterAcceptorCaller;
          template <typename T, typename ATL, typename CTL,
                                typename PTL, typename TPL, typename SRC> struct LastAcceptorCaller;
         
          /* Генератор очередного конкретного акцептора на основе очередного среза списка типов
             абстрактных акцепторов. */
          template <typename T, typename ATL, typename PTL, typename TPL,
                                typename SRC>               struct AcceptorMaker;
          // эта специализация является очередной итерацией цикла и генерирует очередной акцептор
          template <typename T, typename AL,  typename AR, typename PTL,
                                typename TPL, typename SRC> struct AcceptorMaker<T, TList<AL, AR>, PTL, TPL, SRC>
          {
            typedef typename AL::template MakeAcceptor<T, IterAcceptorCaller,
                                                TList<AL, AR>,       PTL, TPL, SRC>::type type;
          };
          // эта специализация является последней итерацией цикла
          template <typename T, typename AL,  typename PTL, typename TPL,
                                typename SRC>               struct AcceptorMaker<T, TList<AL, NullType>, PTL, TPL, SRC>
          {
            typedef typename AL::template MakeAcceptor<T, LastAcceptorCaller,
                                                TList<AL, NullType>, PTL, TPL, SRC>::type type;
          };
         
          /* Реализация конкретных акцепторов. Общий принцип заключается в генерации линейной иерархии
             на основе абстрактного акцептора. Очередной узел в иерархии является производным от предыдущего
             узла и реализует интерфейс очередного Visitor-а из веерной иерархии. В совокупности вся линейная
             иерархия конкретного акцептора реализует все интерфейсы этого Visitor-а. Последний узел
             производится непосредственно от абстрактного акцептора, который эти интерфейсы документировал.
             В целом, т.к. и конкретный, и абстрактный акцепторы строятся на основе одинаковых списков типов,
             реализованным оказывается каждый узел из веерной иерархии, и каждый - в своём узле линейной
             иерархии. В результате позднее связываение метода Accept() вызовет нужный узел, которому
             известен его тип в списке, тем самым динамический тип восстанавливается, и тип параметра может
             быть приведён к восстановленному без dynamic_cast<>. Восстановленные типы накапливаются в
             списке восстановленных типов. В заключение вызывается следующий конкретный акцептор.
             Параметры:
             - тип возвращаемого значения;
             - срез списка типов иерархии своего параметра;
             - срез списка типов параметров диспетчера (точнее, его прототипа);
             - список восстановленных динамических типов предыдущих параметров (в обратном порядке);
             - срез списка типов параметров мультиметода;
             - тип исходного кортежа с параметрами doIt(). */
         
          /* Реализация конкретного акцептора последнего параметра мультиметода. Особенности:
             - срез списка типов параметров мультиметода не используется, ибо тут он уже пуст.
             - вызывает конкретный диспетчер вместо следующего конкретного акцептора. */
         
          // это все апцепторы в иерархии, кроме самого базового
          template <typename L, typename R, typename CTL, typename PTL, typename TPL, typename SRC>
          struct LastAcceptorCaller<RetType, TList<L, R>, CTL, PTL, TPL, SRC>:
                                                public LastAcceptorCaller<RetType, R, CTL, PTL, TPL, SRC>
          {
            typedef LastAcceptorCaller<RetType, R,  CTL, PTL, TPL, SRC> base_type;
            typedef typename CTL::Head::template ParamType<L>::type     arg_type;
         
            LastAcceptorCaller(SRC& ptl, TPL& src): base_type(ptl, src) {}
         
            // Восстанавливает тип последнего параметра и вызывает нужную специализацию конкретного
            // диспетчера для преобразования кортежа обратно в вариадик; последний параметр нет смысла
            // включать в кортеж, т.к. это потребует также модификации списков типов, потому
            // его проще передать явно вместе с его восстановленным типом.
            RetType Accept(arg_type&& obj)
            {
              return CallConcrete<DI, RetType, Args...>::template apply<SRC, PTL, arg_type>
                                                ::call(base_type::m_Params, std::forward<arg_type>(obj));
            }
          };
          // это базовый в иерархии акцептор; весь список типов иерархии параметра уже обработан производными
          // классами, так что этот с ними уже не работает, он только хранит общую для них информацию и
          // наследует интерфейсы Visitor-а от абстрактного акцептора
          template <typename CTL, typename PTL, typename TPL, typename SRC>
          struct LastAcceptorCaller<RetType, NullType, CTL, PTL, TPL, SRC> : public CTL::Head
          {
            LastAcceptorCaller(SRC& ptl, TPL&): m_Params(ptl) {}
         
            SRC &m_Params;
          };
         
          /* Реализация конкретного акцептора всех параметров мультиметода, кроме последнего. Особенности:
             - используется, если мультиметод имеет более 1 параметра. */
          template <typename L, typename R, typename CTL, typename PTL, typename TPL, typename SRC>
          struct IterAcceptorCaller<RetType, TList<L, R>, CTL, PTL, TPL, SRC> :
                                                public IterAcceptorCaller<RetType, R, CTL, PTL, TPL, SRC>
          {
            typedef IterAcceptorCaller<RetType, R,  CTL, PTL, TPL, SRC> base_type;
            typedef typename CTL::Head::template ParamType<L>::type     arg_type;
         
            IterAcceptorCaller(SRC& ptl, TPL& src): base_type(ptl, src) {}
         
            /* восстановление типа очередного параметра, генерация следующего конкретного акцептора,
               передача ему следующего среза списка абстрактных акцепторов, базовых типов параметров
               мультиметода и накопленной восстановленной информации о динамических типах и его вызов. */
            // выглядит странным, но да, сам параметр метода не нужен, важен лишь его тип для того, чтобы
            // динамический тип был правильно восстановлен; (точнее, даже он нужен лишь самому последнему
            // акцептору, а тип нужен лишь для помещения в список восстановленных типов); сами параметры
            // всё равно лежат в кортеже, им нужен только лишь static_cast<>, который позже сделает
            // конкретный диспетчер
            RetType Accept(arg_type&&)
            {
              typedef decltype(GetField<0>(base_type::m_Args)) realType;
              typedef typename AcceptorMaker<RetType, typename CTL::Tail, TList<arg_type, PTL>,
                                                      typename TPL::base_type, SRC>::type acceptor;
         
              acceptor a(base_type::m_Params, static_cast<typename TPL::base_type&>(base_type::m_Args));
         
              return a.apply(std::forward<realType>(GetField<0>(base_type::m_Args)), a);
            }
          };
          // это базовый в иерархии акцептор; весь список типов иерархии параметра уже обработан производными
          // классами, так что этот с ними уже не работает, он только хранит общую для них информацию и
          // наследует интерфейсы Visitor-а от абстрактного акцептора
          template <typename CTL, typename PTL, typename TPL, typename SRC>
          struct IterAcceptorCaller<RetType, NullType, CTL, PTL, TPL, SRC> : public CTL::Head
          {
            IterAcceptorCaller(SRC& ptl, TPL& src): m_Params(ptl), m_Args(src) {}
         
            SRC &m_Params;
            TPL &m_Args;
          };
         
          /* Обобщённая точка входа. Создаёт акцептор первого параметра и вызывает его.
             См. комменты к IterAcceptorCaller::Accept(). */
          template <typename Tpl>
          static RetType doIt(Tpl& tpl)
          {
            typedef decltype(GetField<0>(tpl)) realType;
            typedef typename Tpl::base_type TupleType;
            typedef typename AcceptorMaker<RetType, TTl, NullType, TupleType, Tpl>::type acceptor;
         
            acceptor a(tpl, static_cast<TupleType&>(tpl));
         
            return a.apply(std::forward<realType>(GetField<0>(tpl)), a);
          }
         
          /* Промежуточный метод перед doIt().
             Выполняет вторую фазу обработки параметров: создание и заполнение кортежа. После этого этапа
             вариадиков больше нет, а все параметры прочеканы и предобработны. */
          RetType 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:
          using typename Prototype<DI, RetType, Args...>::ret_type;
         
          /* Точка вызова диспетчера. Аргументами могут прийти любые типы, т.к. они не обязаны строго
             совпадать с формальными параметрами мультиметода. Их соответствие проеряется по ходу дела,
             и ежели что, либо будет выполнен неявный каст, либо компилятор сообщит о проблеме.
             В отличие от реализации для 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 RetType>
        struct Dispatcher<Prototype<DI, RetType>>
        {
          RetType operator ()()
          {
            return CallConcrete<DI, RetType, 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
         
        /* Метод, который должен иметь любой полиморфный класс в иерархии, желающий участвовать в динамическом
           связывании. Реализован как макро для удобства.
           Первый параметр - прототип диспетчера; из него будет вытащен список параметров мультиметода;
           второй параметр - список типов иерархии, которой принадлежит полиморфный класс, он будет искаться
                             в списке параметров в прототипе;
           третий параметр - тип, на который указывает this, т.е. сам класс.
           Метод является visit-методом в паттерне Visitor. Он получает свой конкретный акцептор, должен
           запросить (static_cast<>-ом) у него конкретно свой интерфейс и вызвать его, сообщив т.с. свой
           динамический тип. Фактически static_cast<> отбирает из всего дерева интерфейсов свой, что и
           является восстановлением его динамического типа, а вызов Accept() сообщает его конкретному
           акцептору, бросая управление в реализующий этот интерфейс метод где-то в линейной иерархии его
           узлов.
           Проблема тут в том, чтобы создать для visit-метода правильную сигнатуру. Пользователький код её
           не знает, т.к. типы абстрактных акцепторов генерируется самим прототипом. Поэтому приходится
           задействовать метакод для получения недостающих данных. */
         
        // Тело visit-метода
        #define MAKE_ACCEPTABLE_BODY(Proto, PList, Concrete)                                            \
              {                                                                                         \
                return static_cast<MultiMethods::details::Acceptor<Concrete,                            \
                                                          typename Proto::ret_type>&>(a).Accept(this);  \
              }
         
        // Объявление visit-метода в классе
        #define MAKE_ACCEPTABLE_DECL(Proto, PList, Concrete)                                            \
              virtual typename Proto::ret_type Accept(typename Proto::template getArg<PList>::type &a);
         
        // Внутриклассное определение visit-метода
        #define MAKE_ACCEPTABLE(Proto, PList, Concrete)                                                 \
              virtual typename Proto::ret_type Accept(typename Proto::template getArg<PList>::type &a)  \
              MAKE_ACCEPTABLE_BODY(Proto, PList, Concrete)
         
        // Внеклассное определение visit-метода
        #define MAKE_ACCEPTABLE_IMPL(Proto, PList, Concrete)                                            \
              typename Proto::ret_type Concrete::Accept(typename Proto::template getArg<PList>::type &a)\
              MAKE_ACCEPTABLE_BODY(Proto, PList, Concrete)
         
        #endif // MULTIMETHODS_H_0A0E1659_FD72_4B5B_A091_EF59C8050584
      Эти немного короче, потому что RTTI использовало больше кода в акцепторах. Даже не знаю, стоит ли описывать внутренности. Всё C++1x-овое описано в прошлой теме, всё Visitor-ное – в старой C++03, изменения и от той, и от другой покрыты предыдущим постом. Пожалуй, воздержусь от дополнительных комментариев. Просто напомню общий принцип. Абстрактный акцептор на основе списка типов иерархии объявляет дерево интерфейсов, узлы которого представлены шаблонным абстрактным классом Acceptor, параметризируемым типом класса из этой иерархии; конкретный акцептор на основе того же списка типов реализует эти интерфейсы, наследуясь от абстрактного. Абстрактный приходит в visit-метод параметра, тот его кастует к Acceptor<> своего типа, зовёт его метод, и тот всплывает в его реализации где-то в конкретном акцепторе, тем самым восстанавливая динамический тип параметра. Подробности достаточно хорошо описаны в теме версии C++03.
      Ниже те же тесты, но интрузивные.

      Прикреплённый файлПрикреплённый файлtests.zip (8,74 Кбайт, скачиваний: 5)
      Прикреплённый файлПрикреплённый файлnewTests.zip (3,9 Кбайт, скачиваний: 5)
      Прикреплённый файлПрикреплённый файлtestTimeVisit.zip (1,54 Кбайт, скачиваний: 4)

      Замечу, что RTTI генерит меньше кода, но компилится дольше. Это заметно на высоко-арных мультиметодах.
      Я одно время размышлял, не стоит ли склеить обе версии в одну библиотеку. В конце-концов они разнятся только реализацией акцепторов, кои можно передать извне политикой. Передумал. Во-первых, они требуют чуть по-разному оформлять декларации диспетчеров и своих классов, во-вторых, у них всё ж разные свойства, примерно как у вектора с ассоциативным массивом. Так что пусть лучше будут две библиотеки. Другое дело, что надо бы было пространства имён по-разному обозвать... что-то не подумал сразу. Ну, я уверен, вы справитесь, а мне лениво перезаливать тесты, которые от таких перемен не будут компилиться.
      Одни с годами умнеют, другие становятся старше.
        А чего бы тебе всё это на гитхаб не выложить?
        "Математики думают, что Бог в уравнениях, нейрологи уверены, что Бог в мозге, а программисты уверены, что Бог — один из них."
        Морган Фриман
        Реализация шаблонов Jinja2 для C++ Jinja2 C++
          Ты шутишь, надеюсь? Во-первых, тут полно белых ниток, и лень доводить до мировых стандартов качества. Может у кого-то его навалом, или ему за это платят, кодят же boost люди, но у меня на это нет мотивации. Во-вторых, где я найду время оформить по правилам гитхаба? Ты видишь, сколько времени я угробил на эти темы, а ведь тут оформления почитай-то и нет.
          Одни с годами умнеют, другие становятся старше.
            Цитата Qraizer @
            Ты шутишь, надеюсь? Во-первых, тут полно белых ниток, и лень доводить до мировых стандартов качества. Может у кого-то его навалом, или ему за это платят, кодят де boost люди, но у меня на это нет мотивации. Во-вторых, где я найду время оформить по правилам гитхаба? Ты видишь, сколько времени я угробил на эти темы, а ведь тут оформления почитай-то и нет.

            Ты слишком высокого мнения о гитхабе. :)

            Добавлено
            И нет, я не шучу.
            "Математики думают, что Бог в уравнениях, нейрологи уверены, что Бог в мозге, а программисты уверены, что Бог — один из них."
            Морган Фриман
            Реализация шаблонов Jinja2 для C++ Jinja2 C++
              Теперь самое время вернуться к началу. Давайте для затравки я приведу выжимку из отчётов testTimeVisit и testTimeRTTI на Intel Core i3-2100, x86 mode. Тут чем медленнее процессор, тем лучше видно, на i7, к примеру, интрузивный отчёт пестрит нулями.
              ExpandedWrap disabled
                >testTimeRTTI.exe
                 
                Triple B2 -B1 -B2  Dispatch: 10925
                1 returned: 662
                Triple B2 -B1 -B2  Dispatch: 1987
                1 returned: 331
                Triple B2 -B1 -B2  Dispatch: 1655
                1 returned: 331
                Triple B2 -B1 -B2  Dispatch: 993
                1 returned: 331
                 
                Triple B2 -B1 -B2  Dispatch: 1324
                1 returned: 662
                Triple B2 -B1 -B2  Dispatch: 1655
                1 returned: 662
                Triple B2 -B1 -B2  Dispatch: 993
                1 returned: 331
                Triple B2 -B1 -B2  Dispatch: 1324
                1 returned: 331
                 
                ...
                 
                Triple B2 -B1 -B2  Dispatch: 993
                1 returned: 662
                Triple D23-B1 -D21 Dispatch: 993
                5 returned: 331
                Triple D23-D13-D22 Dispatch: 1324
                7 returned: 662
                Triple D23-D11-D23 Dispatch: 993
                6 returned: 331
                 
                Triple B2 -B1 -B2  Dispatch: 1325
                1 returned: 331
                Triple D23-B1 -D21 Dispatch: 993
                5 returned: 331
                Triple D23-D14-D22 Dispatch: 1325
                8 returned: 662
                Triple B2 -B1 -B2  Dispatch: 1324
                1 returned: 662

              ExpandedWrap disabled
                >testTimeVisit.exe
                 
                Triple B2 -B1 -B2  Dispatch: 3642
                1 returned: 331
                Triple B2 -B1 -B2  Dispatch: 1324
                1 returned: 331
                Triple B2 -B1 -B2  Dispatch: 662
                1 returned: 331
                Triple B2 -B1 -B2  Dispatch: 331
                1 returned: 331
                 
                Triple B2 -B1 -B2  Dispatch: 331
                1 returned: 331
                Triple B2 -B1 -B2  Dispatch: 331
                1 returned: 331
                Triple B2 -B1 -B2  Dispatch: 331
                1 returned: 332
                Triple B2 -B1 -B2  Dispatch: 331
                1 returned: 332
                 
                ...
                 
                Triple B2 -B1 -B2  Dispatch: 331
                1 returned: 331
                Triple D23-B1 -D21 Dispatch: 662
                5 returned: 331
                Triple D23-D13-D22 Dispatch: 331
                7 returned: 331
                Triple D23-D11-D23 Dispatch: 663
                6 returned: 331
                 
                Triple B2 -B1 -B2  Dispatch: 331
                1 returned: 331
                Triple D23-B1 -D21 Dispatch: 662
                5 returned: 331
                Triple D23-D14-D22 Dispatch: 331
                8 returned: 331
                Triple B2 -B1 -B2  Dispatch: 993
                1 returned: 331
              Это Microsoft C/C++ v19.15.26726, максимальная оптимизация -O2 -GL. Даже тут видно, что время измеряется с дискретностью 331 наносекунда, и лучше не измерить, потому абсолютные результаты скорее приблизительные, но относительные вполне хорошо показательны. И это демонстрирует, что RTTI медленнее от 3-х до 6-и раз. В среднем в 4. Очень неплохой результат для такой небыстрой штуки, как бинарный поиск по сортированному массиву с ключом на std::type_info::before(). Вот примерно это после всех оптимизаций я и увидел (изначально разница была куда существеннее, раз в 15, что примерно и ожидалось) и потому решил, что гордиться Visitor-ом хоть и можно, но не особо, а особо гордиться нечем.
              Но. Это было первое впечатление. Потом-то я сообразил, что производительность одной отдельно взятой подсистемы RTTI на одной отдельно взятой паре иерархий классов ещё ничего не значит. Вооружившись отладчиком, полез в дебри ассемблера и улицезрел, что std::type_info::before() у Студии просто сравнивает this, и в случае неравенства делает memcmp() на чём-то типа std::type_info::name(), только замангленного. Ну да, имена классов у меня короткие, отличаются буквально на первом-третьем символе, иерархии всего в четыре-пять элементов, даже не факт, что сортировка и бинарный поиск оказались выгоднее банального линейного, который можно было несортированным построить на std::type_index. Так что пример, можно сказать, сильно перекошенный в сторону производительности для RTTI-версии. Но это моё личное мнение, тем более, что ваша реализация RTTI ещё неизвестно как устроена.
              Ну, с особой гордостью, как бы, дело прояснилось. Настала пора заценить повод для исключительной. А что такое исключительная в данном контексте? Правильно: насколько реализация приближается к идеалу. А что такое идеал? Правильно: это мультиметоды, поддержанные в языке нативно. Жаль только, нету её. Я вздохнул, и открыл вот этот документ...
              Одни с годами умнеют, другие становятся старше.
                Цитата Qraizer @
                Во-первых, тут полно белых ниток, и лень доводить до мировых стандартов качества. Может у кого-то его навалом, или ему за это платят, кодят де boost люди, но у меня на это нет мотивации. Во-вторых, где я найду время оформить по правилам гитхаба?
                Да брось, какие там правила? :) Выкладываешь код как есть, пишешь в Readme ссылку на эту тему, создаёшь issue с заголовком "перенести описание с исходников на гитхаб" и ждёшь пулл реквестов :D Наверняка даже переведут на английский, если действительно хорошая реализация и на английском подобного материала не найдёшь.
                Подпись была включена в связи с окончанием срока наказания
                  Цитата Qraizer @
                  Ты шутишь, надеюсь?

                  Это очень серьезно!!!
                  Пилить в гитхабе прожект с демками и скриптами сборки - это по-человечески!!!
                  Мои программные ништякиhttp://majestio.info
                    Тут как бы ещё какой момент. Во-первых, каких-либо особых правил оформления проектов там нет. Ну, за исключением желательности ReadMe.md и необходимости лицензии. А бенифитов - куча. Исходники удобно смотреть, комментировать, скачивать. Захочу затащить в свой проект - просто сделаю репу сабмодулем. Это просто. :) Захочу - форкну к себе и докручу что-нибудь. И всё это гораздо проще, чем возиться с zip-архивами.

                    Добавлено
                    Это потом уже (при желании) можно навтыкать всяких трависов, кодаси и прочих полезных сервисов. Но совсем не обязательно.
                    "Математики думают, что Бог в уравнениях, нейрологи уверены, что Бог в мозге, а программисты уверены, что Бог — один из них."
                    Морган Фриман
                    Реализация шаблонов Jinja2 для C++ Jinja2 C++
                      Крайзер, свуай мультиметоды в github. Они мне понадобились в проекте. :)
                      Сообщение отредактировано: Flex Ferrum -
                      "Математики думают, что Бог в уравнениях, нейрологи уверены, что Бог в мозге, а программисты уверены, что Бог — один из них."
                      Морган Фриман
                      Реализация шаблонов Jinja2 для C++ Jinja2 C++
                        Блин, я всё тему никак не закончу, а вы меня хотите озадачить ещё и тамошними проблемами с публикацией, оформлением и переводом этого опуса и коментов в коде. У меня там даже учётки нет.
                        Короче. Надо-таки закончить, бо новые полгода на горизонте.

                        Открыл я, значица, тот документ. Первое, что меня удивило, что там хоть и говорится о мультиметодах, но термин используется особый: "открытые мультиметоды". Как бы ну и ладно, какая разница, как называть, главное, чтоб работало, как задумано. Потом я нашёл причину уточнения: оказывается, "открытые" подчёркивает тот факт, что перекрываемые сигнатуры мультиметода и сам мультиметод являются свободными функциями, тогда как в классическом понимании мультиметодов они подразумеваются быть методами классов. Всего-то. Честно говоря, я вообще не понял фишки, ибо сразу отклонил идею реализовывать свои мультиметоды в контексте классов, связанных с типами его параметров. Просто потому, что мультиметод не может быть однозначно соотнесён с каким-то конкретным классом, если типы его динамических параметров принадлежат разным иерархиям, поэтому и делать его методом какого-либо из классов, являющихся каким-либо его параметром, неправильно или тогда уж делать его методом сразу всех таких классов, что ещё больше сбивает с толку, не говоря уже о сложности реализации сего. Собственно поэтому вы видите здесь отдельную структурку со статическими методами apply(), но не только поэтому. Ещё и потому, что, во-первых, как-то инкапсулировать конкретный мультиметод с его перекрытиями очень даже полезно, особенно, если таких мультиметодов в программе несколько, во-вторых, так воистину удобно передавать диспетчеру сразу весь комплект сигнатур, связанных единой зависимостью и борющихся за звание "most viable" в неком конкретном вызове, чтобы при этом ещё и ничего лишнего не цеплялось: вызывается чётко нужный диспетчер, и он работает с чётким фиксированным набором сигнатур.
                        Ок, отметил я, мне плюсик, ибо даже open multi-methods тут оказались неидеальны, и за счёт открытости теряют в инкапсуляции. Причём в сравнительном анализе там далее указывается, что ...э-э-э, классические, назовём их так, мультиметоды тут могут дать фору в производительности открытым, т.к. сразу готовы быть завязаны на свою VMT, а значит смещения в таблицах будут известны в компайл-тайм, тогда как открытым придётся это разруливать в ран-тайм, но достигается это сильной связью множественной диспетчеризации с определениями участвующих в ней классов, что здорово бьёт по удобству практического использования и на простоте модификации и расширяемости, ибо таковы свойства любой сильной связи между сущностями. И снова плюсик, на этот раз в сравнении с классическими мультиметодами, т.к. у меня даже Visitor не имеет сильных связей между классами как своей иерархии, так и других (хотя и имеет такую с самим диспетчером, пережить это куда проще, ибо связь всегда к нему одному вместо целого графа перекрёстных связей между кучей классов). А RTTI так вообще никаких связей не имеет (только с инструментами самого языка, которые и так есть и никуда не денутся и без всяких мультиметодов).
                        Идём далее. Затем идёт разбор, как именно предлагается построить диспетчеризацию, и как для этого должны быть модифицированы компилятор и компоновщик. Тут я к своему удивлению не нашёл никаких знаковых отличий от своей реализации. Т.е. вот вообще, если отвлечься от способа реализации, алгоритмы, можно сказать, совпадают. И я, и они, предлагают строить таблицы, проходя по которым, сгенерированный компилятором код будет выбирать те или иные очередные массивы, которые итерационно в итоге дадут ему чёткий указатель на конкретный открытый ...э-э-э, мульти-метод. Только у них это готовые таблицы с готовыми смещениями, и компилятор генерирует код, который выполняет косвенно-косвенно-косвенно-косвенную – формула 4k + 1 их собственная, я её не выдумывал – их поочерёдную индексацию, у меня же это код, который обходится без таблиц... но точно так же генерируется компилятором, разве что по не заранее прошитым в нём алгоритмам, когда (и если) открытые мультиметоды будут аппрувлены, а по описанному мной метаалгоритму, и его сложность всего 2k – вдвое эффективнее, если обращения к памяти приравнять к виртуальным вызовам (что естественно не так, однако особенности кеширования данных и кода (аккумулятивно) их в этом показателе уравнивают). Естественно, последний менее экономичен в плане объёма кода, зато их метод менее экономичен в плане объёма данных, и если данные соптимизировать очень сложно, они сами об этом и пишут в контексте двупараметрического visit-диспетчера, называемого «C++ Visitor» (кстати, Flex Ferrum, не его ли ты слямзил для той давней Холиварной темы?), тогда как код компиляторы оптимизировать умеют неплохо уже... э-э-э, вчера. Ну что, снова плюсик? Думаю, что да. Если эффективность тут и не на моей стороне, то по крайне мере она близка к идеалу, насколько это только возможно. Это я про Visitor, конечно, с RTTI дела не так радужно.
                        Наконец, общий объём ресурсов. Тут я должен признаться, что был несколько озадачен. Я как-то не особо вижу, что бы им удалось влезть в то бутылочное горло, которое так радужно нарисовали: 4k + 1 обращений в память + 1 виртуальный вызов. Возможно, это только предварительная оценка на случай диспетчеризации одного единственного мультиметода. Попробую коротко описать ход своих мыслей. Понятно, что коли есть языковая нативная поддержка, никаких visitor-ов не требуется, всё можно подготовить заранее. Вот только где? Первая мысль – заимплементить поле в VMT. В этом варианте я столкнулся с проблемой, что т.о., во-первых, теряется обратная бинарная совместимость с существующими библиотеками, которые – все! – придётся пересобирать, во-вторых, из-за того, что компилятор не может знать заранее, будет ли конкретный класс выступать целью множественной диспетчеризации и используются ли мультиметоды в программе вообще, выходит, что он на всякий случай должен будет всегда предполагать такое использование, а значит нарушается принцип нулевой стоимости. И это особенно обидно, т.к. мультиметоды ожидаемо будут использоваться нечасто. Выходит, что VMT не самое подходящее место. К счастью есть более подходящий кандидат: RTTI-запись для него. В VMT уже есть поле для ссылки на неё, а в самой записи предусмотреть ещё одно поле не будет проблемой. Любая реализация может сохранить бинарную совместимость со своими предыдущими версиями, т.к. новая запись может быть расположена тупо в конце имеющейся своей implementation defined реализации std::type_info, а в отличие от записей в VMT-таблице RTTI-записи не обязаны собираться в массив; и кроме того, принцип нулевой стоимости потенциально может остаться быть соблюдённым, т.к. ни разу не будучи востребованным, это поле легко удаляется линкером, ибо все COMDAT-записи у него перед глазами (это при условии, конечно, что RTTI-записи действительно лежат в секциях COMDAT, но с другой стороны, а где ещё хранить статичную информацию, которая может быть сгенерирована компилятором в разных единицах трансляции, но в итоговой программе должна быть в единственном экземпляре, ибо ровно для этого COMDAT и предназначены).
                        Ок, говорю, но что дальше? Хватит ли одного поля? Конечно, нет, хранить нужно много всякого, но это поле будет служить лишь ссылкой. Куда? Хороший вопрос. Начнём с того, что мультиметодов в программе может быть много, а класс может входить в сигнатуру любого из них да ещё и не по разу. Отсюда следует, что нужно отдельно хранить таблицу, описывающую сами мультиметоды и ещё как-то их идентифицировать, и отдельно вон те самые массивы косвенно-косвенно-косвенно...й индексации. Ну, с идентификацией мультметодов всё просто, для них просто имеются свои RTTI-записи... сложность лишь в том, что такая таблица тоже должна быть одна на всю программу, но собираться по частям из разных единиц трансляции. Но похожая проблема уже решена на примере VMT, так что можно поручить компоновщику формирование итогого массива описаний мультиметодов, а в коде, обращающимся к записям конкретных мультеметодов, навтыкать extern-ссылок, чтоб он заполнил их конкретными адресами. Ок, а дальше? А дальше формат данных в RTTI-записи для мультиметода чётко расскажет, RTTI каких (базовых) классов ожидаются в каждом параметре (статически связываемые параметры не исключение, просто для них всегда всего один вариант выбора, подготавливаемый ещё при компиляции), а вот уже та самая новая ссылка в RTTI-записи классов должна указывать на... хи-хи, массив. Да, массив. Загляните в документ ещё раз. Там предусмотрена многомерная таблица, на пересечении всех измерений которой находится указатель на нужное перекрытие. Количество измерений таблицы равно количеству динамически связываемых параметров. (Ничего не напоминает?) А индексация выполняется посредством явных ссылок друг на друга. (Конечно напоминает, так же работает мой алгоритм.) Но вот беда: мультметодов не один, их потенциально много. Значит что? Правильно: вместо явных ссылок на следующее измерение должны быть ссылки на массивы, а вот их элементы уже будут ссылаться на следующие измерения. Т.о. массив, на который ссылается RTTI-запись класса, должен быть ещё как-то индексирован чем-то, что определяется RTTI-записью мультиметода плюс номером его параметра. Ну, с первым всё понятно, их несложно просто пронумеровать в самой же сводной таблице мультиметодов, а вот с номером параметра ...и тут оказалось, что с ним ничего не надо делать, т.к. если даже некий класс (точнее, содержащая его иерархия) будет входить параметром в некий мультиметод более одного раза, то разные такие параметры конфликтовать не будут, т.к. всё равно представлены разными срезами таблицы диспетчеризации.

                        Думаете всё? Как бы не так.
                        Одни с годами умнеют, другие становятся старше.
                          Цитата Qraizer @
                          Блин, я всё тему никак не закончу, а вы меня хотите озадачить ещё и тамошними проблемами с публикацией, оформлением

                          Да к чёрту перевод текста и его оформления для GitHub. Ты исходники положи. :)
                          "Математики думают, что Бог в уравнениях, нейрологи уверены, что Бог в мозге, а программисты уверены, что Бог — один из них."
                          Морган Фриман
                          Реализация шаблонов Jinja2 для C++ Jinja2 C++
                            Цитата Qraizer @
                            Думаете всё? Как бы не так.
                            Когда я попытался представить себе, как всё это безобразие теперь использовать, я просто запутался в этих массивах массивов, ссылающихся на массивы, индексируемые номером из RTTI мультиметода, который ещё надо найти по RTTI-записям классов. У меня получилось не то что 4 обращения в память, там и десяток легко получался. Но тут – внезапно – я вспомнил, что RTTI-записи можно брать сразу с параметров, в результате чего тип дополнительного поля в RTTI класса можно просто заменить на порядковый номер в его иерархии и напрямую использовать для индексации массивов.
                            Тут покумекать оказалось проще. В результате получилось примерно следующее:
                            1. идём по заполненному компоновщиком адресу к RTTI мультиметода и вычитываем оттуда указатель на первое измерение многомерной диспетчерной таблицы;
                            2. берём первый параметр, лезем к его VMT, оттуда к RTTI, откуда берём его индекс в его иерархии;
                            3. индексируем первое измерение таблицы и получаем ссылку на следующее;
                            4. повторяем пункты 2 и 3 до исчерпания динамически связываемых параметров;
                            5. конечная ссылка будет являться искомым адресом (возможно) перекрытого базового мультиметода.
                            Внезапно я получил те же 4 обращения в память на каждый полиморфный параметр, и не менее внезапно оказалось, что сами мультиметоды ни в каких RTTI не нуждаются, достаточно, чтобы компоновщик по extern-ссылке сразу разместил адрес первого измерения его таблицы. Ну, скорее всего ещё одно обращение займёт собственно полиморфный вызов целевого метода, т.к. для предоставления релоков загрузчику ОС без штрафов для приложения из-за copy-on-write из read only кодовых секций в файл подкачки скорее всего эти вызовы, как обычно, будут осуществляться через переходники. Итого ровно 4k+1. Я с усилием почухал тыковку и вынужден был признать, что в документе всё верно. Но это при условии, что классы в иерархии действительно можно однозначно и непротиворечиво, несмотря ни на какие раздельные компиляции, перенумеровать, и похоже, что это так, ибо в конце концов нумерацию так же можно поручить компоновщику. Плюс этот вариант потребует больше статичных данных, генерируемых компилятором, которые к тому же создают бо́льшую нагрузку на кеш из-за меньшей корреляции обращений к памяти, но это ИМХО мелочи.
                            Т.о. я подтвердил своё первое впечатление, что мой метакод фактически реализует тот же алгоритм (только по-другому: вместо генерации многомерных таблиц он итерационно-рекурсивно генерирует код, на лету вычисляющий элементы этой таблицы), а значит действительно работает максимально близко к идеалу. Улучшить его ещё сильнее можно лишь заменой кода на данные, т.е. этой самой нативной реализацией мультиметодов, что средствами настоящего Стандарта языка, включительно C++20, т.е. без спец.поддержки со стороны инструментальных средств разработки, невозможно. Заодно это сможет существенно сэкономить ресурсы, т.к. данные займут меньше места в исполняемом модуле, чем код, но вот на предмет производительности я бы не был так категоричен, ибо код, генерируемый бы компилятором в нативной реализации, вряд ли был бы проще кода, генерируемого моим метакодом, конечно при условии включённой оптимизации, а т.к. у меня обращений к памяти нет совсем, то два полиморфных вызова супротив 4 обращений уж как-нибудь поборются. Настало время для исключительной гордости. Причём это относится и к версии C++03. (Тут должны быть шампанское, чепчики, персидский ковёр, танец живота, араб с опахалом, кальян и фейерверк, но нужного смайла я почему-то не нашёл.)
                            В заключение хочется сказать ещё об одном нюансе. Мои размышлизмы касательно возможной реализации мультиметодов в языке привели меня к другому методу, нежели описан в документе. И тот, что описан, мне не понравился. Зато там описываются ковариантные возвращаемые типы и рассмотрена динамическая линковка. Над первыми мне было лень думать, я успокоил себя тем, что ковариантность по факту скорее сахар, чем лютое удобство, ибо в реальном позднем связывании всё равно придётся опираться на базовые интерфейсы, а ежели уверен в возврате чего-то от него производного, static_cast<> выручит. А если не уверен, то dynamic_cast<>. А с импортом из DLLек и SOшек, увы, по-любому будут проблемы, ибо шаблоны, без extern если, крайне сложно себе представить вот так импортируемыми. Но что меня по-настоящему удивило, так что неупоминание там кастов this к заявленным в перекрытиях типам. (Ну или я плохо искал.) Ведь запросто может оказаться так, что восстановленный динамический тип не будет совпадать с типом аргумента most viable сигнатуры, и его придётся кастить. Поэтому я подозреваю, что при больших иерархиях и разумно среднем количестве перекрытий базовой сигнатуры подавляющее большинство выбираемых из таблицы целевых адресов будут уникальны и вести к прокси, который будет кастить настоящий this каждого динамически связываемого параметра к типу аргумента, указанному в сигнатуре, которая в итоге выбрана. Сейчас компиляторы тоже это делают, если некий виртуальный метод в очередном производном классе не перекрыт, но там достаточно это сделать один раз, т.к. this тоже один, а тут подобных переходников можно ожидать в весьма немалом количестве, ибо возможных комбинаций this, увы, геометрическое от -арности мультиметода и величин полиморфных иерархий количество. Это может вполне себе так некисло сравнять объёмы предположительного нативного кода и моего.

                            Ну, пожалуй, на этом остановлюсь. Фух, хоть на диссертацию подавай.
                            Одни с годами умнеют, другие становятся старше.
                              Flex Ferrum, сырцы и тут есть. Бери на здоровье. Мне до конца этой недели ещё много что успеть надо по тестовому окружению для MPC5200 под Rational Test Realtime, иначе по голове получу. И ещё было бы очень неплохо хотя бы начать для MFC5485, ибо шароварная лицензия кончается, а дальше $5000.
                              Одни с годами умнеют, другие становятся старше.
                              1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
                              0 пользователей:


                              Рейтинг@Mail.ru
                              [ Script Execution time: 0,2399 ]   [ 17 queries used ]   [ Generated: 25.09.18, 15:04 GMT ]