Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.118.2.15] |
|
Сообщ.
#1
,
|
|
|
Если кто помнит (а кто не помнит - тем напомню) в прошлом году на конфах некто Герб Саттер взорвал C++-общественность докладами про метаклассы:
https://www.youtube.com/watch?v=6nsyX37nsRs Если вкратце, программист описывает некий метакласс (например, Value или BitFields), в нём определяет определяет констрейнты, связанные с этим метаклассом, какие методы/поля надо ещё добавить, а потом компилятор соответствующим образом модифицирует AST для экземпляров этого метакласса: Прикреплённая картинка
Подробности описаны здесь. Всё бы ничего, но в стандарт это затащат не раньше 23-го года. А то и 26-го. До этого ещё static reflection должны принять и всё такое. В общем, долго ещё ждать. А вот пощупать уже сейчас хочется, поэтому решил я из своего автопрограммиста сделать автометапрограммиста. Чтобы мог похожую функциональность предоставить в рамках актуальных стандартов всем желающим, а не только тем, кто сидит на кастомных сборках clang'а. Пока предполагаю, что код, который нужно будет непосредственно писать разработчику, будет выглядеть как-то так: // Объявление метакласса 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); }; |
Сообщ.
#2
,
|
|
|
Скрытый текст Flex Ferrum, Ой, Флекс, прямо де жа вю. Недавно закончил читать доку по Расту - в аккурат концепция трэйтов тамошних. Один в один! Так, для справки напомню, ну и в меру моей "одноразовой" компетенции ... В Расте многие концепции ООП решаются совсем иначе классики С++/OOP Pascal/etc 1) Структуры - хранят только данные, наследования данных нет (но есть композиция и агрегация) 2) Реализации - хранят код "методов" для обработки данных смежной структуры 3) Трэйты - хранят "поведение", иными словами декларацию обязательно реализованных методов для отдельного поведения 4) Реализация трэйтов - понятное дело, реализация методов трэйта для структуры 6) Трэйты-объекты - предназначены для обеспечения динамической диспетчеризации (пока я механизм не осознал, прочел чисто концепцию).Типа Box<T:Display> обозначает ссылку на любой тип, реализующий трэйт Display Получается что? Что в С++ к 23-26 году таки затащят то, что сейчас есть в Расте??? D_KEY, OpenGL, плс, правьте - если я где-то ашыпся! |
Сообщ.
#3
,
|
|
|
JoeUser, ну, судя чисто по тому, что ты описал - это не совсем метаклассы (в понимании Саттера). Идея Саттера - "прокачать" шаблоны, концепты и статический рефлекшн (генеративный) так, чтобы из C++-кода можно было управлять генерацией AST. Это больше похоже на макросы из Nemerle.
Добавлено То есть, скажем, пишешь ты очередную либу типа Dependency Ijection. Создаёшь метакласс Component, в нём описываешь - как генерируется метаинформация о связывании компонента, какие дополнительные методы в реализацию компонента вдвигаются и т. п. Или, положим, пишешь метакласс SerializableStruct, который добавляет в структуру методы сериализации/десериализации. |
Сообщ.
#4
,
|
|
|
Цитата Flex Ferrum @ Создаёшь метакласс Component, в нём описываешь - как генерируется метаинформация о связывании компонента, какие дополнительные методы в реализацию компонента вдвигаются и т. п. Честно говоря - слишком размыто. Пока не отвечу, пока не понимаю чего надо. Цитата Flex Ferrum @ Или, положим, пишешь метакласс SerializableStruct, который добавляет в структуру методы сериализации/десериализации. В Расте пишется трэйт Serializе. Который определяет обязательные методы serialize()/deserialize(). Как только будет произведена имплементация данного трэйта для хотя бы для одной структуры - мы можем использовать "контейнеры", к примеру, в которых будут использованы объекты, поддерживающие поведение"Serialize". Дальше уже компайлер определяет - можем ли мы заполнять контейнер конкретным классом, либо у него не хватает реализации (методов) для конкретного поведения Serializе. Пока - все ровное, не Добавлено PS: Ни кто не против, что я сюда частично обсуждение ЯП Rust приплел? |
Сообщ.
#5
,
|
|
|
Цитата JoeUser @ В Расте пишется трэйт Serializе. Который определяет обязательные методы serialize()/deserialize(). Как только будет произведена имплементация данного трэйта для хотя бы для одной структуры - мы можем использовать "контейнеры", к примеру, в которых будут использованы объекты, поддерживающие поведение"Serialize". А трейт для структуры ты реализуешь сам (ручками) или можешь обобщённый код на основе reflection'а написать? |
Сообщ.
#6
,
|
|
|
Цитата Flex Ferrum @ А трейт для структуры ты реализуешь сам (ручками) или можешь обобщённый код на основе reflection'а написать? Боюсь ошибиться (надо папиков Раста спрашивать). Но в Расте гораздо больше чем мне казалось - является шаблонами. Даже элементарное объявление "let mut str = "string" - является шаблоном. Чтобы ответить однозначно - приведи код на С++, если C++ не умеет - приведи псевдомашинный код чего хотелось. Пока могу сказать одно ... Трэйты, объекты-трэйты, и трэйт-имплементации в Расте - параметризуются. |
Сообщ.
#7
,
|
|
|
Пример... Да вот хотя бы в моём посте на картинке.
|
Сообщ.
#8
,
|
|
|
Флекс, там слишком много буквоф!!!
Смысла прописывать операции, близкие по действию - не вижу. Более того, это забивает основной вопрос! Типа, чонадо?! Прошу "синтетический" пример, где есть только реализация "вопрошаемого" и ничего более. Добавлено И еще момент - на сколько я понял, искать аналоги "решений" для Раста, базируясь на идеологии С++ - это неправильно. Правильно искать решения задачи на этих ЯП - пусть и чаще совсем непохожие друг на друга. |
Сообщ.
#9
,
|
|
|
Цитата JoeUser @ Более того, это забивает основной вопрос! Типа, чонадо?! Ну смотри. Ты описываешь структуру: FlagType {auto in, out, trunk, create, open}; Дальше описываешь метакласс, который делает примерно следующее: $class FlagType { constexpr { int curValue = 1; for (auto m : FlagType.members) { m.SetValue(curValue); curValue <<= 1; } } }; То есть код метакласса обходит все мемберы "своего" класса и каждому присваивает значение. |
Сообщ.
#10
,
|
|
|
Flex Ferrum, увы, в силу моих теперешних знаний, аналогов не вижу.
И тем не менее есть моменты: 1) Статическая инициализация сверх-большой структуры - приведет в увеличению кода значительно. 2) Динамическая инициализация сверх-большой структуры - приведет к небольшому проседанию "старта" при условии быстрых вычислений. 3) Можно пойти путями "bison/flex", сперва вычисляем - потом постим в код 4) C++ way в Rust - наверняка есть, призываю духов D_KEY и OpenGL |
Сообщ.
#11
,
|
|
|
Цитата JoeUser @ 1) Статическая инициализация сверх-большой структуры - приведет в увеличению кода значительно. Скорее, времени компиляции. Ведь код метаклассов исполняется в compile time. Это contsexpr на стеродиах и анаболиках. Добавлено Я, собственно, и выбрал третий путь - утилита будет внутри себя исполнять этот код и продуцировать "исправленный" C++-исходник. |
Сообщ.
#12
,
|
|
|
Цитата Flex Ferrum @ Скорее, времени компиляции. Э нееее ....!!!! Я уже как-то генерировал тест с "а-ля" 4096-вложенными классами. Без специальных ключей - включалось все! Попробуй, чисто ради эксперимента, заинициализировать свой массив (вектор) из 16192 элемента в статике числами Фибоначчи. А потом попробуй динамически. Просто чуйка шепчет - не сильно, не ощутимо быстрее. А вот экзешник - посчитать надо. |
Сообщ.
#13
,
|
|
|
Цитата JoeUser @ Я уже как-то генерировал тест с "а-ля" 4096-вложенными классами. Без специальных ключей - включалось все! Попробуй, чисто ради эксперимента, заинициализировать свой массив (вектор) из 16192 элемента в статике числами Фибоначчи. Тут "немножко" другой подход. Проблема с метапрограммированием на шаблонах в том, что "язык" шаблонов ближе к функциональному, а его тьюринг-полнота основана на рекурсии. Поэтому задачи обработки глубоко вложенных конструкций и прочего таки встаёт. Рано или поздно. Когда ты понимаешь, что двадцати гигабайт памяти тебе уже не хватает для компиляции TensorFlow. C constexpr-вычислениями чуть другое. Компиляторную бомбу всё равно ещё можно сделать, но так или иначе, компилятор уже не только компилятор, но и интерпретатор, который тупо интерпретирует им самим же построенное AST. Поэтому всё становится проще, как с точки зрения программирования, так и с точки зрения компиляции. И эта часть языка (и стандарта) развивается очень активно - с constexpr-функций снимается всё больше ограничений (вспомни, какими они были в 11-ом стандарте), в двадцатом уже обещают строгий constexpr (который constexpr!), выполняющийся либо строго в компайл-тайм, либо не выполняющийся вообще. |
Сообщ.
#14
,
|
|
|
Цитата JoeUser @ Попробуй, чисто ради эксперимента, заинициализировать свой массив (вектор) из 16192 элемента в статике числами Фибоначчи. #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 << ' '; } |
Сообщ.
#15
,
|
|
|
Цитата Flex Ferrum @ Тут "немножко" другой подход. Проблема с метапрограммированием на шаблонах в том, что "язык" шаблонов ближе к функциональному, а его тьюринг-полнота основана на рекурсии. Флекс, давай отделять мух от котлет! Я понимаю, движется все. Се ля ви. Но предлагаю очень не забывать о принципе Паретто. Конечно, любые телодвижения в развитии важны! Но соотнеси их к вышеозначенному принципу! ... Вопрос "что, зачем и какими силами" воспроизводится?! Хорошо, если упрощает, а несли нет???! А если это вааще не упало щяс??? |
Сообщ.
#16
,
|
|
|
Цитата JoeUser @ Вопрос "что, зачем и какими силами" воспроизводится?! Хорошо, если упрощает, а несли нет???! А если это вааще не упало щяс??? Тут вот какое дело. Если для решения довольно простых задач (скажем, пресловутого конвертора строк в enum'ы) надо городить огород со сторонними утилитами - тут, эммм..., что-то не так в консерватории. В языке, в смысле. Метаклассы закрывают эту "дыру" и позволяют более гибко управлять процессом компиляции (в смысле, вмешиваться в работу компилятора) легальным способом получая нужные бенифиты. И это - здорово! Добавлено В любом случае: не попробуешь - не узнаешь. Вот я и хочу попробовать. Впрочем, уже сейчас (на этапе предварительного анализа подхода и реализации) вижу интересные вопросы, на которые в пропозале нет ответа... |
Сообщ.
#17
,
|
|
|
Цитата Qraizer @ Вообще без напряга. Там переполнения после 47-го элемента, естественно, не суть. Да, а про constexpr я и забыл |
Сообщ.
#18
,
|
|
|
Вид метаклассов, близкий к финальному.
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; }; Осталось только интерпретатор всего этого хозяйства напедалить. |
Сообщ.
#19
,
|
|
|
Цитата Flex Ferrum @ Вид метаклассов, близкий к финальному. Это откуда инфа? Флекс, давай представим что метаклассы реализованы и самодостаточны. Приведи, пожалуйста, пример - где они "решают". Где реально без них содом и гомора? Чисто чтобы осознать глубину глубин. |
Сообщ.
#20
,
|
|
|
Цитата JoeUser @ Это откуда инфа? Это от меня. В рамках моей реализации. Ну а примеры... Вот, скажем, задачка с visitor'ом: // Базовый класс для 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 к виду, пригодному к обходу заданным визитором. |
Сообщ.
#21
,
|
|
|
Flex Ferrum, ты меня наталкиваешь на мысль, а-ля moc Qt. Сам посуди. С++ явно пока несовершенен. Берем язык сверхвысогого уровня (хотя можно и тот же C++), строим информационную модель, реализовываем ее в рамках языка программирования более низкого уровня. Профит!!!
Кстати! Некое подобие сказанного уже в стадии реализации - Haxe. Понимаю, все не так и не то. Но я говорю про концепцию. ИМХО право на жысть имеет. Добавлено ЗЫ: Чего действительно в haxe не хватает - так это ... увы мета |
Сообщ.
#22
,
|
|
|
Цитата JoeUser @ ты меня наталкиваешь на мысль, а-ля moc Qt. Сам посуди. С++ явно пока несовершенен. Берем язык сверхвысогого уровня (хотя можно и тот же C++), строим информационную модель, реализовываем ее в рамках языка программирования более низкого уровня. Профит!!! Ну, эммм... Именно это я и нацелился написать. Утилиту, которая внутри себя интерпретирует часть C++-текста и генерирует на его основе новый текст. То есть, скажем, результатом обработки кода из этого коммента: Метаклассы в C++ (сообщение #3775986) будет: class TestIface { public: virtual void TestMethod1() = 0; virtual std::string TestMethod2(int param) const = 0; }; Но это - самые простейшие преобразования. Можно добавить наследование от некоего IObject, скажем. Или сделать так, чтобы методы были преобразованы так: class TestIface { public: virtual result_t TestMethod1() nothrow = 0; virtual result_t TestMethod2(int param, std::string& returnVal) const nothrow = 0; }; Причём, замена одного другим может быть сделана в одном лишь месте - в определении метакласса. Добавлено Или, скажем, определяешь ты метакласс Component: template<typename ImplIfaces, typename RequireIfaces, typename HiddenIfaces = List<>> $class Component { // Реализацию сходу не напишу }; Потом пишешь: $Component<List<I1, I2>, List<I3, I4>> SomeComponent { //.. }; А он тебе генерирует: class SomeComponent : public I1, public I2, public ComponentBase { public: Component(I3*, I4*) {} enum {ComponentId = 0xffffff;} const std::string& GetComponentName() {return "SomeComponent";} // Реализация }; |
Сообщ.
#23
,
|
|
|
Цитата Flex Ferrum @ Ну, эммм... Именно это я и нацелился написать. Утилиту, которая внутри себя интерпретирует часть C++-текста и генерирует на его основе новый текст Хм Идея оч-оч интересная. Но не покидает ощущение де жа вю!!! Чисто про хронологии .... 1993 год. Прихожу я на фирму новичком. Сам из себя такой матеро-запаскалевший, ориентированный чисто объектно. За плечами asm-либа работы с окнами в режиме CGA/EGA/VGA, даже "снег" снимал на CGA на основе обратного хода луча ... А мне нач отдела заявляет - все херня, учи Clarion. Я туда, я сюда, да как так, да я ... проиграл я ему на простейшей задаче, вчистую!!! Сел и выучил посля Clarion. Чем интересен язык? Язык шаблонов шаблонов ... шаблонов. Каркас очередного модуля собирается единожды, определяются entry сниппетов, а вот там уже дальше ад и израиль. Если честно, по IDE клариона скучаю. 2018 год. Познакомился с языком шаблонов Blade. Тоже свой цымус. Понятко что, для чего, и как. Реализация приятная и красивая. Лично мое имховое резюме: я против усложнения Стандарта языка C++. Но я строго "за" за создание мета-систем генерации кода, реализации метасистем на основе паттернов проектирования. Вот как-то так |
Сообщ.
#24
,
|
|
|
Я для себя тоже такой язык нашёл - Jinja2. Даже его порт с питона на С++ сделал. Но... Мы должны пойти глубже.
|
Сообщ.
#25
,
|
|
|
Цитата Flex Ferrum @ Мы должны пойти глубже. ИМХО, нужно начинать с ТУ, делать ТЗ, и только потом прогать и докать сделанное. |
Сообщ.
#26
,
|
|
|
Цитата JoeUser @ Цитата Flex Ferrum @ Мы должны пойти глубже. ИМХО, нужно начинать с ТУ, делать ТЗ, и только потом прогать и докать сделанное. Так в первом посте ссылка на пропозал соответствующий. |
Сообщ.
#27
,
|
|
|
Не хотел я в первый релиз Jinja2 затаскивать макросы. Но без них будет определённо тоскливо...
А такого меташаблонного кода в реализации автопрограммиста будет в достатке... |
Сообщ.
#28
,
|
|
|
Цитата Flex Ferrum @ в прошлом году на конфах некто Герб Саттер взорвал C++-общественность докладами про метаклассы Это те метаклассы, которые, как минимум в CLOS с 1994-го? Да и в Qt они вроде давно есть. В чём взрыв-то? |
Сообщ.
#29
,
|
|
|
Цитата korvin @ Это те метаклассы, которые, как минимум в CLOS с 1994-го? Да и в Qt они вроде давно есть. В чём взрыв-то? Про CLOS не в курсе, а в Qt я про такое не слышал. |
Сообщ.
#30
,
|
|
|
В общем, оно начинает потихоньку дышать:
Для: 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; }; На консоли можно увидеть: ### 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. |
Сообщ.
#31
,
|
|
|
Flex Ferrum, глянь плс бегло эту реализацию макросов. Просто дай оценку. Интересно
|
Сообщ.
#32
,
|
|
|
Ну куда-то в эту сторону, да. Только для нативного C++.
|
Сообщ.
#33
,
|
|
|
Цитата Flex Ferrum @ Только для нативного C++. Да, Haxe - считай метаязык. Потом из него можно C++ код получить, как вариант. |
Сообщ.
#34
,
|
|
|
Сообщ.
#35
,
|
|
|
Сообщ.
#36
,
|
|
|
Итак, первые шаги автопрограммиста по полю метаклассов. Сегодня таки получился end-to-end-тест. Итак.
Объявление метакласса: 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(); } } }; Его инстанс: METACLASS_INST(Interface, TestIface) { public: void TestMethod1(); std::string TestMethod2(int param) const; }; Что генерирует автопрограммист в соответствии с определением того и другого: class TestIface { public: virtual void TestMethod1() = 0; virtual std::string TestMethod2(int param) const = 0; protected: private: }; Ну и как это потом используется в коде: 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!"; } Всё настолько прозрачно, насколько это вообще возможно. |
Сообщ.
#37
,
|
|
|
Продолжаем экзерсизы перед выступлением на CoreHard++. Потихоньку привожу в чувство такой вот код:
|
Сообщ.
#38
,
|
|
|
В общем, в итоге оно заработало. Итак, что мы имеем.
1. Два метакласса, описывающие, что именно нужно добавить в класс, чтобы он был сериализуемым с помощью буста: $_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); }; } }; Дальше объявляем классы, как обычные структуры. Ну... Почти обычные: $_struct(TestStruct, BoostSerializable) { int a; std::string b; }; $_struct(TestStruct1, BoostSerializableSplitted) { int a; std::string b; }; Метапрограммист делает "вжух" и на выходе получается: 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. Изменяем пару мест в объявлении метаклассов: $_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); }; } }; Вжух! 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: }; |
Сообщ.
#39
,
|
|
|
С++ Russia на подходе, тулза развивается. Приросла таким вот функционалом:
1. Делаем метакласс, который генерирует visitor для заданного набора типов: 2. Делаем инстанс этого метакласса. Всё просто: 3. И получаем на выходе хорошенький такой классец-визитор с нужными методами: 4. Теперь мы лёгким движением руки добавляем к инстансу ещё метакласс Interface: 5. И инстанс превращается... Превращается инстанс... В элегантный интерфейсный класс визитора: Вуаля! |
Сообщ.
#40
,
|
|
|
А по иерархиям классов можно ходить? Чтоб получить src...dst в отношении предок...потомок.
Добавлено Хотя... а вдруг один ко многим... |
Сообщ.
#41
,
|
|
|
Цитата Qraizer @ А по иерархиям классов можно ходить? Чтоб получить src...dst в отношении предок...потомок. Можно (в идеале). Текущая реализация этого пока не поддерживает. Добавлено О, да, кстати. Твои мультиметоды в этом стиле реализуются более чем тривиально. |
Сообщ.
#42
,
|
|
|
Ну вот я и попытался. Пока можно лишь частично, генерируя visit-методы. Также к сожалению не получилось разделить объявление и определение visit-метода.
|
Сообщ.
#43
,
|
|
|
Цитата Qraizer @ Пока можно лишь частично, генерируя visit-методы. Угу. А ты прям на автопрограммисте играешься? Добавлено Цитата Qraizer @ Также к сожалению не получилось разделить объявление и определение visit-метода. Будет позже. |
Сообщ.
#44
,
|
|
|
Не. Просто пробую хотя бы представить себе, как бы это выглядело. Нужно генерить объявления
virtual typename Proto::ret_type Accept(typename Proto::template getArg<PList>::type &a); 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); } typedef MultiMethods_Visitor::MakeTList</*...*/> PList; Добавлено Для RTTI нужно только последнее. Добавлено Впрочем, имея последнее, первое два, наверное, реально пропустить через итераторы. |
Сообщ.
#45
,
|
|
|
А, понял. Смотри, как это может выглядеть:
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; } |
Сообщ.
#46
,
|
|
|
Парни, я осознаю, что для малоподготовленного (ну как для мня) - вышеприведенное есть лютая дичь.
Приведите примеры, плс, получаемого профита ... ну или обозначьте границы, когда он действительно может появиться. |
Сообщ.
#47
,
|
|
|
Цитата JoeUser @ Парни, я осознаю, что для малоподготовленного (ну как для мня) - вышеприведенное есть лютая дичь. Если делать это "старым" способом (и заглянуть под капот) - дичь будет не менее лютая. Цитата JoeUser @ Приведите примеры, плс, получаемого профита ... Получаемый профит в том, что производимые манипуляции с типами - более явные и (главное) в императивном, а не функциональном стиле. Попробуй реализовать тот же визитор на "обычном" C++ - и поймёшь, о чём речь. |
Сообщ.
#48
,
|
|
|
Если я правильно распознал происходящее, что совсем не факт, то это не совсем то. Смотри.
Ты в вариадик Types интегрируешь интерфейс dispIface. Это архитектурная ошибка, т.к. создаёт зависимость классов пользователя от класса абстрактного диспетчера. Это плохо, т.к. диспетчер создаёт интерфейс визитора в процессе своего инстанцирования, поэтому эта зависимость ведёт к циклу. Вместо этого подразумевается, что визит-метод документируется базовым классом в иерархии, и производные его лишь перекрывают, диспетчер же просто зовёт метод базового класса, а не какого-то интерфейса. Далее. Свои внутренние классы акцепторов диспетчер генерирует сам, метаклассы ему для этого не нужны. Поэтому цикл по вариадику по сути лишний. Его следовало бы делать по списку классов в иерархии. Вот его бы получить... К тому же это решение ограничено двупараметрическим мультиметодом, тогда как у меня ещё в C++03 были обобщённые n-параметрические. По списку параметров диспетчер тоже итерируется классически. |
Сообщ.
#49
,
|
|
|
Ну, я пытался на ходу что-то путное выдумать.
|