Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > C/C++: Прочее > Метаклассы в C++


Автор: Flex Ferrum 17.07.18, 10:24
Если кто помнит (а кто не помнит - тем напомню) в прошлом году на конфах некто Герб Саттер взорвал C++-общественность докладами про метаклассы:
https://www.youtube.com/watch?v=6nsyX37nsRs

Если вкратце, программист описывает некий метакласс (например, Value или BitFields), в нём определяет определяет констрейнты, связанные с этим метаклассом, какие методы/поля надо ещё добавить, а потом компилятор соответствующим образом модифицирует AST для экземпляров этого метакласса:
metaclass_sample.png (, : 1340)

Подробности описаны здесь.

Всё бы ничего, но в стандарт это затащат не раньше 23-го года. А то и 26-го. До этого ещё static reflection должны принять и всё такое. В общем, долго ещё ждать. А вот пощупать уже сейчас хочется, поэтому решил я из своего автопрограммиста сделать автометапрограммиста. Чтобы мог похожую функциональность предоставить в рамках актуальных стандартов всем желающим, а не только тем, кто сидит на кастомных сборках clang'а. Пока предполагаю, что код, который нужно будет непосредственно писать разработчику, будет выглядеть как-то так:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    // Объявление метакласса
    METACLASS_DECL(interface)
    {
        enum Type
        {
            One = 1,
            Two,
            Three
        };
     
        static void generate(interface* instance)
        {
                    // Проверка свойств конкретного инстанса метакласса
            compiler.require(instance != nullptr, "Class instance is NULL!");
                    // Инжект в тело метакласса какого-то кода
            META_INJECT {int member;};
        }
    };
     
    // Инстанс метакласса
    METACLASS_INST(interface, IObject)
    {
    public:
        int AddRef();
        int Release();
        IObject* QueryInterface(uint32_t ifaceId);
    };

Автор: JoeUser 17.07.18, 10:44
Скрытый текст
Flex Ferrum, :lol:

Ой, Флекс, прямо де жа вю. Недавно закончил читать доку по Расту - в аккурат концепция трэйтов тамошних. Один в один!
Так, для справки напомню, ну и в меру моей "одноразовой" компетенции ... В Расте многие концепции ООП решаются совсем иначе классики С++/OOP Pascal/etc

1) Структуры - хранят только данные, наследования данных нет (но есть композиция и агрегация)
2) Реализации - хранят код "методов" для обработки данных смежной структуры
3) Трэйты - хранят "поведение", иными словами декларацию обязательно реализованных методов для отдельного поведения
4) Реализация трэйтов - понятное дело, реализация методов трэйта для структуры
6) Трэйты-объекты - предназначены для обеспечения динамической диспетчеризации (пока я механизм не осознал, прочел чисто концепцию).Типа Box<T:Display> обозначает ссылку на любой тип, реализующий трэйт Display

Получается что? :lol: Что в С++ к 23-26 году таки затащят то, что сейчас есть в Расте??? :lol:

D_KEY, OpenGL, плс, правьте - если я где-то ашыпся! :-?

Автор: Flex Ferrum 17.07.18, 10:57
JoeUser, ну, судя чисто по тому, что ты описал - это не совсем метаклассы (в понимании Саттера). Идея Саттера - "прокачать" шаблоны, концепты и статический рефлекшн (генеративный) так, чтобы из C++-кода можно было управлять генерацией AST. Это больше похоже на макросы из Nemerle.

Добавлено
То есть, скажем, пишешь ты очередную либу типа Dependency Ijection. Создаёшь метакласс Component, в нём описываешь - как генерируется метаинформация о связывании компонента, какие дополнительные методы в реализацию компонента вдвигаются и т. п. Или, положим, пишешь метакласс SerializableStruct, который добавляет в структуру методы сериализации/десериализации.

Автор: JoeUser 17.07.18, 11:53
Цитата Flex Ferrum @
Создаёшь метакласс Component, в нём описываешь - как генерируется метаинформация о связывании компонента, какие дополнительные методы в реализацию компонента вдвигаются и т. п.

Честно говоря - слишком размыто. Пока не отвечу, пока не понимаю чего надо.

Цитата Flex Ferrum @
Или, положим, пишешь метакласс SerializableStruct, который добавляет в структуру методы сериализации/десериализации.

В Расте пишется трэйт Serializе. Который определяет обязательные методы serialize()/deserialize(). Как только будет произведена имплементация данного трэйта для хотя бы для одной структуры - мы можем использовать "контейнеры", к примеру, в которых будут использованы объекты, поддерживающие поведение"Serialize". Дальше уже компайлер определяет - можем ли мы заполнять контейнер конкретным классом, либо у него не хватает реализации (методов) для конкретного поведения Serializе.

Пока - все ровное, не :lol:

Добавлено
PS: Ни кто не против, что я сюда частично обсуждение ЯП Rust приплел? :-?

Автор: Flex Ferrum 17.07.18, 12:02
Цитата JoeUser @
В Расте пишется трэйт Serializе. Который определяет обязательные методы serialize()/deserialize(). Как только будет произведена имплементация данного трэйта для хотя бы для одной структуры - мы можем использовать "контейнеры", к примеру, в которых будут использованы объекты, поддерживающие поведение"Serialize".

А трейт для структуры ты реализуешь сам (ручками) или можешь обобщённый код на основе reflection'а написать?

Автор: JoeUser 17.07.18, 12:09
Цитата Flex Ferrum @
А трейт для структуры ты реализуешь сам (ручками) или можешь обобщённый код на основе reflection'а написать?

Боюсь ошибиться (надо папиков Раста спрашивать). Но в Расте гораздо больше чем мне казалось - является шаблонами. Даже элементарное объявление "let mut str = "string" - является шаблоном. Чтобы ответить однозначно - приведи код на С++, если C++ не умеет - приведи псевдомашинный код чего хотелось.

Пока могу сказать одно ... Трэйты, объекты-трэйты, и трэйт-имплементации в Расте - параметризуются.

Автор: Flex Ferrum 17.07.18, 12:14
Пример... Да вот хотя бы в моём посте на картинке.

Автор: JoeUser 17.07.18, 12:19
Флекс, там слишком много буквоф!!! :(
Смысла прописывать операции, близкие по действию - не вижу.
Более того, это забивает основной вопрос! Типа, чонадо?!
Прошу "синтетический" пример, где есть только реализация "вопрошаемого" и ничего более.

Добавлено
И еще момент - на сколько я понял, искать аналоги "решений" для Раста, базируясь на идеологии С++ - это неправильно.
Правильно искать решения задачи на этих ЯП - пусть и чаще совсем непохожие друг на друга.

Автор: Flex Ferrum 17.07.18, 12:49
Цитата JoeUser @
Более того, это забивает основной вопрос! Типа, чонадо?!

Ну смотри. Ты описываешь структуру:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    FlagType {auto in, out, trunk, create, open};

Дальше описываешь метакласс, который делает примерно следующее:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    $class FlagType
    {
        constexpr
        {
               int curValue = 1;
               for (auto m : FlagType.members)
               {
                     m.SetValue(curValue);
                     curValue <<= 1;
               }
          }
    };

То есть код метакласса обходит все мемберы "своего" класса и каждому присваивает значение.

Автор: JoeUser 17.07.18, 13:19
Flex Ferrum, увы, в силу моих теперешних знаний, аналогов не вижу.
И тем не менее есть моменты:

1) Статическая инициализация сверх-большой структуры - приведет в увеличению кода значительно.
2) Динамическая инициализация сверх-большой структуры - приведет к небольшому проседанию "старта" при условии быстрых вычислений.
3) Можно пойти путями "bison/flex", сперва вычисляем - потом постим в код
4) C++ way в Rust - наверняка есть, призываю духов D_KEY и OpenGL

Автор: Flex Ferrum 17.07.18, 13:21
Цитата JoeUser @
1) Статическая инициализация сверх-большой структуры - приведет в увеличению кода значительно.

Скорее, времени компиляции. Ведь код метаклассов исполняется в compile time. Это contsexpr на стеродиах и анаболиках.

Добавлено
Я, собственно, и выбрал третий путь - утилита будет внутри себя исполнять этот код и продуцировать "исправленный" C++-исходник.

Автор: JoeUser 17.07.18, 13:28
Цитата Flex Ferrum @
Скорее, времени компиляции.

Э нееее ....!!!! :lol:
Я уже как-то генерировал тест с "а-ля" 4096-вложенными классами. Без специальных ключей - включалось все!
Попробуй, чисто ради эксперимента, заинициализировать свой массив (вектор) из 16192 элемента в статике числами Фибоначчи.
А потом попробуй динамически. Просто чуйка шепчет - не сильно, не ощутимо быстрее. А вот экзешник - посчитать надо.

Автор: Flex Ferrum 17.07.18, 14:55
Цитата JoeUser @
Я уже как-то генерировал тест с "а-ля" 4096-вложенными классами. Без специальных ключей - включалось все!
Попробуй, чисто ради эксперимента, заинициализировать свой массив (вектор) из 16192 элемента в статике числами Фибоначчи.

Тут "немножко" другой подход. Проблема с метапрограммированием на шаблонах в том, что "язык" шаблонов ближе к функциональному, а его тьюринг-полнота основана на рекурсии. Поэтому задачи обработки глубоко вложенных конструкций и прочего таки встаёт. Рано или поздно. Когда ты понимаешь, что двадцати гигабайт памяти тебе уже не хватает для компиляции TensorFlow. :D C constexpr-вычислениями чуть другое. Компиляторную бомбу всё равно ещё можно сделать, но так или иначе, компилятор уже не только компилятор, но и интерпретатор, который тупо интерпретирует им самим же построенное AST. Поэтому всё становится проще, как с точки зрения программирования, так и с точки зрения компиляции. И эта часть языка (и стандарта) развивается очень активно - с constexpr-функций снимается всё больше ограничений (вспомни, какими они были в 11-ом стандарте), в двадцатом уже обещают строгий constexpr (который constexpr!), выполняющийся либо строго в компайл-тайм, либо не выполняющийся вообще.

Автор: Qraizer 17.07.18, 15:28
Цитата JoeUser @
Попробуй, чисто ради эксперимента, заинициализировать свой массив (вектор) из 16192 элемента в статике числами Фибоначчи.
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    #include <iostream>
     
    int a[16192];
     
    constexpr int fun()
    {
      a[0] = 0;
      a[1] = 1;
      for (int i = 2; i < 16192; ++i) a[i] = a[i-2] + a[i-1];
     
      return 0;
    }
     
    int x = fun();
     
    int main()
    {
      for (auto& i : a) std::cout << i << ' ';
    }
Вообще без напряга. Там переполнения после 47-го элемента, естественно, не суть.

Автор: JoeUser 17.07.18, 15:36
Цитата Flex Ferrum @
Тут "немножко" другой подход. Проблема с метапрограммированием на шаблонах в том, что "язык" шаблонов ближе к функциональному, а его тьюринг-полнота основана на рекурсии.

Флекс, давай отделять мух от котлет! Я понимаю, движется все. Се ля ви. Но предлагаю очень не забывать о принципе Паретто. Конечно, любые телодвижения в развитии важны! Но соотнеси их к вышеозначенному принципу! ...

Вопрос "что, зачем и какими силами" воспроизводится?!
Хорошо, если упрощает, а несли нет???!
А если это вааще не упало щяс???

Автор: Flex Ferrum 17.07.18, 15:48
Цитата JoeUser @
Вопрос "что, зачем и какими силами" воспроизводится?!
Хорошо, если упрощает, а несли нет???!
А если это вааще не упало щяс???

Тут вот какое дело. Если для решения довольно простых задач (скажем, пресловутого конвертора строк в enum'ы) надо городить огород со сторонними утилитами - тут, эммм..., что-то не так в консерватории. В языке, в смысле. Метаклассы закрывают эту "дыру" и позволяют более гибко управлять процессом компиляции (в смысле, вмешиваться в работу компилятора) легальным способом получая нужные бенифиты. И это - здорово!

Добавлено
В любом случае: не попробуешь - не узнаешь. Вот я и хочу попробовать. Впрочем, уже сейчас (на этапе предварительного анализа подхода и реализации) вижу интересные вопросы, на которые в пропозале нет ответа...

Автор: JoeUser 18.07.18, 13:24
Цитата Qraizer @
Вообще без напряга. Там переполнения после 47-го элемента, естественно, не суть.

Да, а про constexpr я и забыл :blush:

Автор: Flex Ferrum 10.08.18, 09:32
Вид метаклассов, близкий к финальному.
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    METACLASS_DECL(Interface)
    {
        static void GenerateDecl()
        {
            compiler.require($Interface.variables().empty(), "Interface may not contain data members");
     
            for (auto& f : $Interface.functions())
            {
                compiler.require(!f.is_copy() && !f.is_move(), "Interface can't contain copy or move constructor");
                if (!f.has_access())
                    f.make_public();
     
                compiler.require(f.is_public(), "Inteface function must be public");
                f.make_pure_virtual();
            }
        }
    };
     
    METACLASS_INST(Interface, TestIface)
    {
    public:
        void TestMethod1();
        std::string TestMethod2(int param) const;
    };

Осталось только интерпретатор всего этого хозяйства напедалить.

Автор: JoeUser 10.08.18, 13:40
Цитата Flex Ferrum @
Вид метаклассов, близкий к финальному.

Это откуда инфа?
Флекс, давай представим что метаклассы реализованы и самодостаточны. Приведи, пожалуйста, пример - где они "решают". Где реально без них содом и гомора? Чисто чтобы осознать глубину глубин.

Автор: Flex Ferrum 10.08.18, 14:29
Цитата JoeUser @
Это откуда инфа?

Это от меня. В рамках моей реализации. :) Ну а примеры... Вот, скажем, задачка с visitor'ом:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    // Базовый класс для visitable-классов
    template<typename V, typename R = void>
    struct VisitableBase
    {
        virtual R Visit(V* v) = 0;
    };
     
    template<typename V, typename R = void>
    $class Visitable
    {
        constexpr
        {
             // Добавляем метод Visit в визитируемый класс
             -> R Visit(V* v) override {return v->Visit(*this);}
     
             // Добавляем наследование
             $Visitable.add_parent(VisitableBase<V, R>);
        }
    };
     
    class SampleVisitor;
    Visitable<SampleVisitor> Foo
    {
    public:
    // Тут ничего лишнего не пишем
    };
     
    Visitable<SampleVisitor> Bar
    {
    public:
    // Тут ничего лишнего не пишем
    };
     
    // Объявляем класс-визитор
    class SampleVisitor
    {
    public:
        void Visit(const Foo&) {std::cout << "SampleVisitor::Visit(Foo)\n";}
        void Visit(const Bar&) {std::cout << "SampleVisitor::Visit(Bar)\n";}
    };


В данном случае метакласс Visitable приведёт объявления классов Foo и Bar к виду, пригодному к обходу заданным визитором.

Автор: JoeUser 10.08.18, 14:47
Flex Ferrum, ты меня наталкиваешь на мысль, а-ля moc Qt. Сам посуди. С++ явно пока несовершенен. Берем язык сверхвысогого уровня (хотя можно и тот же C++), строим информационную модель, реализовываем ее в рамках языка программирования более низкого уровня. Профит!!!

Кстати! Некое подобие сказанного уже в стадии реализации - Haxe. Понимаю, все не так и не то. Но я говорю про концепцию. ИМХО право на жысть имеет.

Добавлено
ЗЫ: Чего действительно в haxe не хватает - так это ... увы мета :-?

Автор: Flex Ferrum 10.08.18, 14:54
Цитата JoeUser @
ты меня наталкиваешь на мысль, а-ля moc Qt. Сам посуди. С++ явно пока несовершенен. Берем язык сверхвысогого уровня (хотя можно и тот же C++), строим информационную модель, реализовываем ее в рамках языка программирования более низкого уровня. Профит!!!

Ну, эммм... Именно это я и нацелился написать. Утилиту, которая внутри себя интерпретирует часть C++-текста и генерирует на его основе новый текст. То есть, скажем, результатом обработки кода из этого коммента: Метаклассы в C++ (сообщение #3775986) будет:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class TestIface
    {
    public:
        virtual void TestMethod1() = 0;
        virtual std::string TestMethod2(int param) const = 0;
    };

Но это - самые простейшие преобразования. Можно добавить наследование от некоего IObject, скажем. Или сделать так, чтобы методы были преобразованы так:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class TestIface
    {
    public:
        virtual result_t TestMethod1() nothrow = 0;
        virtual result_t TestMethod2(int param, std::string& returnVal) const nothrow = 0;
    };

Причём, замена одного другим может быть сделана в одном лишь месте - в определении метакласса.

Добавлено
Или, скажем, определяешь ты метакласс Component:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    template<typename ImplIfaces, typename RequireIfaces, typename HiddenIfaces = List<>>
    $class Component
    {
    // Реализацию сходу не напишу
    };

Потом пишешь:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    $Component<List<I1, I2>, List<I3, I4>> SomeComponent
    {
    //..
    };

А он тебе генерирует:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class SomeComponent : public I1, public I2, public ComponentBase
    {
    public:
        Component(I3*, I4*) {}
     
        enum {ComponentId = 0xffffff;}
        const std::string& GetComponentName() {return "SomeComponent";}
        
        // Реализация
    };

Автор: JoeUser 10.08.18, 15:15
Цитата Flex Ferrum @
Ну, эммм... Именно это я и нацелился написать. Утилиту, которая внутри себя интерпретирует часть C++-текста и генерирует на его основе новый текст

Хм :scratch: Идея оч-оч интересная. Но не покидает ощущение де жа вю!!!

Чисто про хронологии ....

1993 год. Прихожу я на фирму новичком. Сам из себя такой матеро-запаскалевший, ориентированный чисто объектно. За плечами asm-либа работы с окнами в режиме CGA/EGA/VGA, даже "снег" снимал на CGA на основе обратного хода луча ... А мне нач отдела заявляет - все херня, учи Clarion. Я туда, я сюда, да как так, да я ... проиграл я ему на простейшей задаче, вчистую!!! Сел и выучил посля Clarion.

Чем интересен язык? Язык шаблонов шаблонов ... шаблонов. Каркас очередного модуля собирается единожды, определяются entry сниппетов, а вот там уже дальше ад и израиль. Если честно, по IDE клариона скучаю.

2018 год. Познакомился с языком шаблонов Blade. Тоже свой цымус. Понятко что, для чего, и как. Реализация приятная и красивая.

Лично мое имховое резюме: я против усложнения Стандарта языка C++. Но я строго "за" за создание мета-систем генерации кода, реализации метасистем на основе паттернов проектирования. Вот как-то так :-?

Автор: Flex Ferrum 10.08.18, 16:06
Я для себя тоже такой язык нашёл - Jinja2. Даже его порт с питона на С++ сделал. Но... Мы должны пойти глубже. :)

Автор: JoeUser 10.08.18, 17:44
Цитата Flex Ferrum @
Мы должны пойти глубже.

ИМХО, нужно начинать с ТУ, делать ТЗ, и только потом прогать и докать сделанное.

Автор: Flex Ferrum 10.08.18, 18:28
Цитата JoeUser @
Цитата Flex Ferrum @
Мы должны пойти глубже.

ИМХО, нужно начинать с ТУ, делать ТЗ, и только потом прогать и докать сделанное.

Так в первом посте ссылка на пропозал соответствующий. :)

Автор: Flex Ferrum 17.08.18, 15:52
Не хотел я в первый релиз Jinja2 затаскивать макросы. Но без них будет определённо тоскливо...
user posted image

А такого меташаблонного кода в реализации автопрограммиста будет в достатке...

Автор: korvin 20.08.18, 22:24
Цитата Flex Ferrum @
в прошлом году на конфах некто Герб Саттер взорвал C++-общественность докладами про метаклассы

Это те метаклассы, которые, как минимум в CLOS с 1994-го? Да и в Qt они вроде давно есть. В чём взрыв-то?

Автор: Flex Ferrum 20.08.18, 22:27
Цитата korvin @
Это те метаклассы, которые, как минимум в CLOS с 1994-го? Да и в Qt они вроде давно есть. В чём взрыв-то?

Про CLOS не в курсе, а в Qt я про такое не слышал.

Автор: Flex Ferrum 27.08.18, 12:52
В общем, оно начинает потихоньку дышать:
Для:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    METACLASS_DECL(Interface)
    {
        static void GenerateDecl()
        {
            compiler.message("Hello world from metaprogrammer!");
            compiler.require($Interface.variables().empty(), "Interface may not contain data members");
     
            for (auto& f : $Interface.functions())
            {
                compiler.require(f.is_implicit() || (!f.is_copy_ctor() && !f.is_move_ctor()), "Interface can't contain copy or move constructor");
                if (!f.has_access())
                    f.make_public();
     
                compiler.require(f.is_public(), "Inteface function must be public");
                f.make_pure_virtual();
            }
        }
     
     
        static int GenerateImpl()
        {
            int a = 0;
            return a + 2;
        }
    };
     
    METACLASS_INST(Interface, TestIface)
    {
    public:
        void TestMethod1();
        std::string TestMethod2(int param) const;
    };
     
    METACLASS_INST(Interface, BadTestIface)
    {
    public:
        void TestMethod1();
        std::string TestMethod2(int param) const;
     
    private:
        int m_val;
    };

На консоли можно увидеть:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    ### Trying to call 'meta::CompilerImpl::message/void message(const char *msg)'
    ###### Hello world from metaprogrammer!
    ### Trying to call 'meta::CompilerImpl::require/void require(bool, const char *message)'
    ...
    ### Trying to call 'meta::CompilerImpl::message/void message(const char *msg)'
    ###### Hello world from metaprogrammer!
    ### Trying to call 'meta::CompilerImpl::require/void require(bool, const char *message)'
    ###### Interface may not contain data members


Добавлено
Теперь борюсь с range-for.

Автор: JoeUser 27.08.18, 13:17
Flex Ferrum, глянь плс бегло эту реализацию макросов. Просто дай оценку. Интересно :)

Автор: Flex Ferrum 27.08.18, 13:21
Ну куда-то в эту сторону, да. Только для нативного C++.

Автор: JoeUser 27.08.18, 13:24
Цитата Flex Ferrum @
Только для нативного C++.

Да, Haxe - считай метаязык. Потом из него можно C++ код получить, как вариант.

Автор: korvin 27.08.18, 16:26
Цитата Flex Ferrum @
в Qt я про такое не слышал.

Как-то так, но в подробности я не вдавался.

Автор: Flex Ferrum 27.08.18, 16:30
Цитата korvin @
Цитата Flex Ferrum @
в Qt я про такое не слышал.

Как-то так, но в подробности я не вдавался.

Не, это совсем другое. В Qt moc экспозит метаинформацию в райнтайм. Метаклассы же, о которых здесь идёт речь, работают с метаинформацией в compile-time. И могут модифицировать AST.

Автор: Flex Ferrum 28.08.18, 23:06
Итак, первые шаги автопрограммиста по полю метаклассов. Сегодня таки получился end-to-end-тест. Итак.
Объявление метакласса:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    METACLASS_DECL(Interface)
    {
        static void GenerateDecl()
        {
            compiler.message("Hello world from metaprogrammer!");
            compiler.require($Interface.variables().empty(), "Interface may not contain data members");
     
            for (auto& f : $Interface.functions())
            {
                compiler.require(f.is_public(), "Inteface function must be public");
                f.make_pure_virtual();
            }
        }
    };

Его инстанс:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    METACLASS_INST(Interface, TestIface)
    {
    public:
        void TestMethod1();
        std::string TestMethod2(int param) const;
    };

Что генерирует автопрограммист в соответствии с определением того и другого:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class TestIface {
    public:
      virtual void TestMethod1() = 0;
      virtual std::string TestMethod2(int param) const = 0;
     
    protected:
    private:
    };

Ну и как это потом используется в коде:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class TestIfaceImpl : public TestIface
    {
        // TestIface interface
    public:
        void TestMethod1() override;
        std::string TestMethod2(int param) const override;
    };
     
    void TestIfaceImpl::TestMethod1()
    {
    }
     
    std::string TestIfaceImpl::TestMethod2(int) const
    {
        return "Hello World!";
    }

Всё настолько прозрачно, насколько это вообще возможно.

Автор: Flex Ferrum 29.10.18, 11:41
Продолжаем экзерсизы перед выступлением на CoreHard++. Потихоньку привожу в чувство такой вот код:
user posted image

Автор: Flex Ferrum 30.10.18, 10:32
В общем, в итоге оно заработало. Итак, что мы имеем.

1. Два метакласса, описывающие, что именно нужно добавить в класс, чтобы он был сериализуемым с помощью буста:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    $_metaclass(BoostSerializable)
    {
        static void GenerateDecl()
        {
            $_inject(public) [&, name="serialize"](const auto& ar, unsigned int ver) -> void
            {
                $_constexpr for (auto& v : $BoostSerializable.variables())
                    $_inject(_) ar & $_v(v.name());
            };
        }
    };
     
    $_metaclass(BoostSerializableSplitted)
    {
        static void GenerateDecl()
        {
            $_inject(public) [&, name="load"](const auto& ar, unsigned int ver) -> void
            {
                $_constexpr for (auto& v : $BoostSerializableSplitted.variables())
                    $_inject(_) ar << $_v(v.name());
            };
     
            $_inject(public) [&, name="save"](const auto& ar, unsigned int ver) -> void
            {
                $_constexpr for (auto& v : $BoostSerializableSplitted.variables())
                    $_inject(_) ar >> $_v(v.name());
            };
     
            $_inject(public) [&, name="serialize"](auto& ar, const unsigned int ver) -> void
            {
                boost::serialization::split_member(ar, $_str(*this), ver);
            };
        }
    };


Дальше объявляем классы, как обычные структуры. Ну... Почти обычные:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    $_struct(TestStruct, BoostSerializable)
    {
        int a;
        std::string b;
    };
     
    $_struct(TestStruct1, BoostSerializableSplitted)
    {
        int a;
        std::string b;
    };


Метапрограммист делает "вжух" и на выходе получается:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class TestStruct {
    public:
      template <typename T0> void serialize(const T0 &ar, unsigned int ver) {
        ar &a;
        ar &b;
        ;
      }
      int a;
      std::string b;
     
    protected:
    private:
    };
     
    class TestStruct1 {
    public:
      template <typename T0> void load(const T0 &ar, unsigned int ver) {
        ar << a;
        ar << b;
        ;
      }
      template <typename T0> void save(const T0 &ar, unsigned int ver) {
        ar >> a;
        ar >> b;
        ;
      }
      template <typename T0> void serialize(T0 &ar, const unsigned int ver) {
        boost::serialization::split_member(ar, *this, ver);
      }
      int a;
      std::string b;
     
    protected:
    private:
    };


Добавлено
Такое вот средство копипасты. Причём, замечу, там ошибка в объявлении типа параметра ar. Изменяем пару мест в объявлении метаклассов:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    $_metaclass(BoostSerializable)
    {
        static void GenerateDecl()
        {
            $_inject(public) [&, name="serialize"](auto& ar, unsigned int ver) -> void
            {
                $_constexpr for (auto& v : $BoostSerializable.variables())
                    $_inject(_) ar & $_v(v.name());
            };
        }
    };
     
    $_metaclass(BoostSerializableSplitted)
    {
        static void GenerateDecl()
        {
            $_inject(public) [&, name="load"](auto& ar, unsigned int ver) -> void
            {
                $_constexpr for (auto& v : $BoostSerializableSplitted.variables())
                    $_inject(_) ar << $_v(v.name());
            };
     
            $_inject(public) [&, name="save"](auto& ar, unsigned int ver) -> void
            {
                $_constexpr for (auto& v : $BoostSerializableSplitted.variables())
                    $_inject(_) ar >> $_v(v.name());
            };
     
            $_inject(public) [&, name="serialize"](auto& ar, const unsigned int ver) -> void
            {
                boost::serialization::split_member(ar, $_str(*this), ver);
            };
        }
    };


Вжух!
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class TestStruct {
    public:
      template <typename T0> void serialize(T0 &ar, unsigned int ver) {
        ar &a;
        ar &b;
        ;
      }
      int a;
      std::string b;
     
    protected:
    private:
    };
     
    class TestStruct1 {
    public:
      template <typename T0> void load(T0 &ar, unsigned int ver) {
        ar << a;
        ar << b;
        ;
      }
      template <typename T0> void save(T0 &ar, unsigned int ver) {
        ar >> a;
        ar >> b;
        ;
      }
      template <typename T0> void serialize(T0 &ar, const unsigned int ver) {
        boost::serialization::split_member(ar, *this, ver);
      }
      int a;
      std::string b;
     
    protected:
    private:
    };

Автор: Flex Ferrum 08.04.19, 09:27
С++ Russia на подходе, тулза развивается. Приросла таким вот функционалом:

1. Делаем метакласс, который генерирует visitor для заданного набора типов:
user posted image

2. Делаем инстанс этого метакласса. Всё просто:
user posted image

3. И получаем на выходе хорошенький такой классец-визитор с нужными методами:
user posted image

4. Теперь мы лёгким движением руки добавляем к инстансу ещё метакласс Interface:
user posted image

5. И инстанс превращается... Превращается инстанс... В элегантный интерфейсный класс визитора:
user posted image

Вуаля!

Автор: Qraizer 08.04.19, 12:03
А по иерархиям классов можно ходить? Чтоб получить src...dst в отношении предок...потомок.

Добавлено
Хотя... а вдруг один ко многим...

Автор: Flex Ferrum 08.04.19, 12:06
Цитата Qraizer @
А по иерархиям классов можно ходить? Чтоб получить src...dst в отношении предок...потомок.

Можно (в идеале). Текущая реализация этого пока не поддерживает.

Добавлено
О, да, кстати. Твои мультиметоды в этом стиле реализуются более чем тривиально. :)

Автор: Qraizer 08.04.19, 13:04
Ну вот я и попытался. Пока можно лишь частично, генерируя visit-методы. Также к сожалению не получилось разделить объявление и определение visit-метода.

Автор: Flex Ferrum 08.04.19, 13:08
Цитата Qraizer @
Пока можно лишь частично, генерируя visit-методы.

Угу. А ты прям на автопрограммисте играешься?

Добавлено
Цитата Qraizer @
Также к сожалению не получилось разделить объявление и определение visit-метода.

Будет позже.

Автор: Qraizer 08.04.19, 13:37
Не. Просто пробую хотя бы представить себе, как бы это выглядело. Нужно генерить объявления
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    virtual typename Proto::ret_type Accept(typename Proto::template getArg<PList>::type &a);
в нужных классах, определения
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    typename Proto::ret_type Concrete::Accept(typename Proto::template getArg<PList>::type &a){ return static_cast<MultiMethods_Visitor::details::Acceptor<Concrete, typename Proto::ret_type>&>(a).Accept(this); }
и списки
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    typedef MultiMethods_Visitor::MakeTList</*...*/> PList;
в нужных местах единиц трансляции. Пока не вижу явных возможностей для реализации.

Добавлено
Для RTTI нужно только последнее.

Добавлено
Впрочем, имея последнее, первое два, наверное, реально пропустить через итераторы.

Автор: Flex Ferrum 08.04.19, 13:56
А, понял. Смотри, как это может выглядеть:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    template<typename ... Types>
    inline void Multimethod(meta::ClassInfo dst, const meta::ClassInfo& src)
    {
        meta::ClassInfo dispIface;
     
        // Add extra 'Visit' methods to dst dependent on specific type from 'Types'
        for (auto& t : t_$(Types ...))
            $_inject_v(dispIface, public) [name="Dispatch", is_pure=true](const $_t(t)* obj) {}
     
        $_inject_v(dst, public) dispIface;
     
        meta::ClassInfo dispImpl;
        dispImpl.add_base(dispIface);
        auto tplParam = dispImpl.add_template_type_param("T");
     
        $_inject_v(dispImpl, private) { const $_t(tplParam)* m_leftObj;}
     
        for (auto& t : t_$(Types ...))
        {
            $_inject_v(dispIface, public) [name="Dispatch"](const $_t(t)* obj) {$_str(Call)($_str(m_leftObj), obj);};
            $_inject_v(dst) [name="Call"](auto* lhs, auto* rhs) {
                  $_str(lhs)->$_mem("Invoke")();
            };
            // ... и всё в том же духе
         }
     
        $_inject_v(dst, public) dispImpl;
    }

Автор: JoeUser 08.04.19, 19:28
Парни, я осознаю, что для малоподготовленного (ну как для мня) - вышеприведенное есть лютая дичь.
Приведите примеры, плс, получаемого профита ... ну или обозначьте границы, когда он действительно может появиться.

Автор: Flex Ferrum 08.04.19, 19:50
Цитата JoeUser @
Парни, я осознаю, что для малоподготовленного (ну как для мня) - вышеприведенное есть лютая дичь.

Если делать это "старым" способом (и заглянуть под капот) - дичь будет не менее лютая.
Цитата JoeUser @
Приведите примеры, плс, получаемого профита ...

Получаемый профит в том, что производимые манипуляции с типами - более явные и (главное) в императивном, а не функциональном стиле. Попробуй реализовать тот же визитор на "обычном" C++ - и поймёшь, о чём речь. :)

Автор: Qraizer 09.04.19, 15:23
Если я правильно распознал происходящее, что совсем не факт, то это не совсем то. Смотри.
Ты в вариадик Types интегрируешь интерфейс dispIface. Это архитектурная ошибка, т.к. создаёт зависимость классов пользователя от класса абстрактного диспетчера. Это плохо, т.к. диспетчер создаёт интерфейс визитора в процессе своего инстанцирования, поэтому эта зависимость ведёт к циклу. Вместо этого подразумевается, что визит-метод документируется базовым классом в иерархии, и производные его лишь перекрывают, диспетчер же просто зовёт метод базового класса, а не какого-то интерфейса.
Далее. Свои внутренние классы акцепторов диспетчер генерирует сам, метаклассы ему для этого не нужны. Поэтому цикл по вариадику по сути лишний. Его следовало бы делать по списку классов в иерархии. Вот его бы получить... К тому же это решение ограничено двупараметрическим мультиметодом, тогда как у меня ещё в C++03 были обобщённые n-параметрические. По списку параметров диспетчер тоже итерируется классически.

Автор: Flex Ferrum 09.04.19, 15:50
Ну, я пытался на ходу что-то путное выдумать.

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)