данная задача нерешаема без скатывания в RTTI...
Про сами лупхолы можно послушать здесь. И посмотреть мой репозиторий. В особенности комментарии здесь
можно еще посмотреть на эту библиотеку https://github.com/vampyrofangclub/horrifiction (40 строчек кода) - там в ридми показан пример как можно применять библиотеку для стирания типов и рантайм-полиморфизма
Спасибо, посмотрю. Но не понял зачем отдельная библиотека для рантайм-полиморфизма? RTTI ведь стандартный есть...
тот самый который никто не использует
Текущий RTTI в С++ ограничен сишным интерфейсом (требующий конкретные типы) - то есть это значит что ты не можешь например работать полиморфно с рантайм-объектами у которых методы возвращают auto-тип или параметры принимают auto-тип. Например допустим я хочу сохранять в контейнере объекты имеющие метод auto draw(auto lambdaCallback) а потом вызывать этот метод для каждого объекта в контейнере не зная его настоящий тип. Или например хочу сохранить лямбду в std::function но сейчас это невозможно в случае если у лямбды будет тип возврата auto или она принимает auto-параметр. А вот рантайм-полимформизм через эту технику лупхолов делает все это возможным
Это же все поломается если делать в разных единицах трансляции, разве нет?
Да, конечно. Но можно смотреть на это как на рефлексию которая точно так же будет в С++26 ограничена одной единицей трансляции
Но кстати конкретно вот такое применение лупхолов для рантайм-полиформизма можно было бы заменить фичей которая называется "давайте разрешим шаблонные виртуальные методы" и тогда это потенциально может работать между разными TU (но в свою очередь это усложняет ABI и требует от линкера дополнительного процесса по мерджингу виртуальных таблиц из разных единиц трансляции)
Тут можно подумать в сторону того что в модных языках называется boxing. То есть можно по простому сохранить пару object+function. Тогда функции реализующие trait можно писать отдельно.
Что касается техники лупхолов то для объектов с методом "auto draw(auto lambdaCb)" не будет происходить "мономорфизации в стиралке типов", "не требует наличие JIT" и не создает "комбинаторного взрыва в вызывающем коде" По сути техника рантайм-полиморфизма через лупхолы автоматизирует то что и так приходится делать вручную. Например если ты хочешь хранить в контейнере объекты двух типов имеющие шаблонный метод "auto draw(auto lambdaCb)" struct Rect { int width; int height; auto draw(auto labmdaCb){ ... } }; struct Circle { int radius; auto draw(auto labmdaCb){ ... } } то тебе придется объявить некую Shape-обертку чтобы завернуть в нее объект Rect или Circle перед добавлением в контейнер попутно сохранив числовой айдишник типа int TypeId; template<typename T> auto GetIdForType = [](){ return TypeId++; }() struct Shape { void* ptr; int typeId; Shape(auto obj){ ptr = obj; typeId = GetIdForType<decltype(obj)>; } } auto vec = std::vector<Shape>(); vec.push_back(Shape(new Rect(10, 10)); vec.push_back(Shape(new Circle(20)); и дальше тебе нужно будет вручную перебирать возможные типы на совпадение с typeId у объекта Shape for(auto shape: vec){ if(shape.typeId == GetTypeId<Rect*>){ auto obj = static_cast<Rect*>(shape.ptr); //теперь у нас статический тип и мы можем вызывать шаблонный метод draw() auto res = obj->draw([](...){...}); } if(shape.typeId == GetTypeId<Circle*>){ auto obj = static_cast<Circle*>(shape.ptr); //теперь у нас статический тип и мы можем вызывать шаблонный метод draw() auto res = obj->draw([](...){...}) } } Вот и все. Вот пример того как можно реализовать на С++ рантайм-полиморфизм для методов с auto-возвратом или с auto-параметрами без всяких лупхолов И техника рантайм-полиморфизма через лупхолы в данном примере будет иметь точно такой же оверхед - то есть все что будут делать лупхолы (в случае использования этой библиотеки) так это за юзера выполнят эти две строчки на рантайм-проверку typeId для типа Circle и Rect и юзеру всего лишь нужно будет передать лямбду получив параметром объект конкретного типа for(auto shape: vec){ shape.visit([](auto obj){ //теперь obj у нас статический тип и мы можем вызвать шаблонный метод draw() auto res = obj->draw([](){...}); }) } И поскольку мы выяснили что лупхолы не добавляют оверхеда и всего лишь автоматизируют то что разработчик и так вынужден писать вручную то дальше можно рассказать в чем прелесть лупхолов (помимо того что убирает болерплейт ручного матчинга). И прелесть механизма стирания типов через лупхолы заключается в возможности заматчить тип который разработчику неизвестен, например когда мы возвращаем разные лямбды из функции по рантайм-условию auto getFn(bool flag) { if (std::rand() % 2) { return Any(new auto([=](auto val) { return val + 1; })); } else { auto someVal = 10; return Any(new auto([=](auto val) { return someVal + val; })); } } здесь возвращаемый тип лямбды зависит от того будет ли в рантайме делиться на 2 некое рандомное число и ты можешь написать подобно обертки Shape (где сохраняешь void* ptr и int typeId) аналогичную обертку Any для лямбд но дальше у тебя тупик потому что ты не знаешь на какой тип нужно матчить typeId int main(){ auto any = getFn(); if(any.typeId == GetTypeId<??????>){ ... } } а вот механизм лупхолов как раз таки позволяет узнать этот тип и автоматически проверить совпадение typeId и юзеру всего лишь нужно будет передать лямбду где он получит конкретный объект лямбды аргументом int main(){ auto any = getFn(); any.visit([](auto fn) { std::cout << (*fn)(42) << "\n"; //43 std::cout << (*fn)(42.42) << "\n"; //43.42 }); auto any = getFn(); any2.visit([](auto fn) { std::cout << (*fn)(42) << "\n"; //52 std::cout << (*fn)(42.42) << "\n"; //52.42 }); }
Так я уже выше сказал - да, здесь ограничение на то что это будет работать внутри одной единицы трансляции (или точнее могут быть разные TU, но эти типы для которых нужно стереть тип должны быть объявлены в хедерах) собственно как и у будущей рефлексии. Если аргумент про JIT касался возможности работы разных единиц трансляции то тогда пардон, не так понял
а как иначе?
и как это люди обходятся без реализации ваших идей .... загадка .... 🤷🏻♂️
типы не просто должны быть объявлены в хедерах у каждой единицы трансляции должен совпадать порядок в котором всё множество типов регистрируется в залупхоле у стиралки (точнее даже не порядок, а мнение компилятора о том в каком порядке произвести залупхольное волшебство, чтобы id типов совпали, их порядок в ручных vtable и прочее) иначе как минимум пол дня весёлой отладки странных сегфолтов обеспечены (и у меня уже были пару раз)
страдаем круглые сутки и ждём не дождемся когда он их начнёт писать уже не сюда, а в рассылку WG21
Они увидят всю глубину радикализма
порядок регистрации совпадать не должен но есть нюансы с тем что матчинг может произойти раньше чем регистрация (как никак это все же костыльная техника) но этим можно управлять если понимать как происходит порядок вывода типов и инстанциирования у функций по коду программы. Ну и никто не предлагает всю архитектуру строить поверх вот этой техники но применять ее в ограниченном порядке, где-то локально в одном или парочке мест вполне возможно
ну локально в парочке мест можно и без этого пострадать
а что мешает забиндить лямбду так, чтобы она вызывалась по универсальному интерфейсу? Внутри себя она знает типы, поэтому там можно оперировать с шаблонами
Обсуждают сегодня