Здравствуйте, niralex, Вы писали:
N>А как быть с шаблонными типами при условии чтобы функция MyType3::Id() оставалась constexpr
N>template<typename T> N>struct MyType3 { N> static constexpr TypeId Id(){ N> // return ? N> } N>};
Здравствуйте, niralex, Вы писали:
R>>Можно вынести идентификацию типов за пределы классов. Это позволит присвоить идентификаторы не только пользовательским типам, но и встроенным. После чего идентификаторы можно будет пробовать как-то комбинировать:
R>>
R>>P.S. Синтаксический сахар и диагностику можно добавить во вкусу.
N>Да, это было бы прекрасным решением, но дело в том что есть такие пользовательские типы как template<typename ...T> MyTuple {} и там "как-то комбинировать" не получается, слишком много комбинаций шаблонных типов. Я пришел к выводу, что для идентификатора нужен все-таки массив, но тогда не получается сделать идентификацию constexpr. N>А идея с вынесением за пределы классов хорошая, спасибо.
Начиная с C++17 есть fold expressions. Вот они как раз позволят и охватить множество комбинаций, и сохранить constexpr:
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, niralex, Вы писали:
N>>С fold expressions знаком. Вопрос как раз в "способе комбинирования". Слишком большое число комбинаций. Например для 15 типов это 15!=1307674368000 комбинаций. А еще типы подобные array<n, TMyType>, где в идентификаторе типа нужно учитывать n типа size_t.
R>Возможно, нужно просто проанализировать реальные потребности и на их основе ввести какие-то разумные ограничения в модель? Что-то мне подсказывает, что общее количество типов в программе, включая все воплощения шаблонных классов, будет существенно скромнее астрономических чисел.
Это будет библиотека в которой комбинировать типы будут пользователи и проанализировать их потребности сложно. Хотя, конечно основным пользователем буду я сам но, все-таки, не хочется себя ограничивать . Проект преследует помимо прочего, учебные цели, так как программированием профессионально не занимаюсь. Отсюда, возможно некий перфекционизм.
Здравствуйте, niralex, Вы писали:
N>Сценарий такой: функции LocalFunction, GetFromDll и GetRemoteFunction получают информацию о типах кодеров/декодеров для вызываемой сущности из параметров шаблона, из dll или от удаленного узла, а функция Bind проверяет соответствие этих типов. Вот именно для этого и нужно идентифицировать типы и передавать идентификаторы. Поскольку вызовы Bind происходят часто, хочется чтобы идентификация проходила на этапе компиляции (само сравнение, естественно происходит в рантайме).
Если я верно понял, у модулей всё равно есть какая-то подготовка. То есть мы не имеем какой-то API более или менее родной для каждой dll, а включаем в dll наш код для маршелинга. Так?
В любом случае нам нужно обеспечить две вещи.
1) Логическую совместимость вызывающей и вызываемой стороны. Тут должны быть какие-то правила, автоматически или полуавтоматически выводимые. Ну, например, можно ли наследника передавать вместо базы. И т. д.
2) Бинарную совместимость в случае, если (1) есть, но по разные стороны имеются разные языки/среды разработки и т. д.
Казалось бы, бы всегда можем использовать уникальные id всех сущностей, а в RT строить табличку что с чем совместимо с т. з. (1), и, соответственно, что надо использовать для (2)
Правда, если отказаться от идеи, что мы не делаем промежуточный модуль совместимости, а, наоборот, по API dll какой-то тулзой генерить переходную lib, то можно сделать генерилки для N платформ, переходников из каждой в каждую.
При загрузке/финальной сборке будут выбираться правильные версии этих интерфейсов и всё решится само собой во время загрузки приложения...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Нашел идею решения своего первоначального вопроса. Может кому-то понадобится...
N>Есть некий конечный набор пользовательских типов и задача их идентифицировать во время выполнения не используя RTTI. N>Тип идентификатора не важен. Варианты без constexpr известны.
В качестве идентификатора типа берем std::array.
Для шаблонных типов делаем constexpr-конкатенацию array
В результате идентификация типов происходит в compile-time! Мелочь, но приятная
Идентификаторы затем можно сериализовывать, передавать между модулями, сравнивать и т.д.
Буду благодарен, если кто укажет на ошибки, потенциальные проблемы, предложит улучшения, альтернативы.
P.s.
Небольшие косметические улучшения:
Написал функцию MakeTypeId(1), чтобы вместо return array<int8_t,1>{1} писать MakeTypeId(1);
Здравствуйте, niralex, Вы писали:
N>Есть некий конечный набор пользовательских типов и задача их идентифицировать во время выполнения не используя RTTI. N>Тип идентификатора TypeId не важен. Варианты без constexpr известны.
Здравствуйте, niralex, Вы писали:
N>Объекты инстанцируются в разных динамических модулях/процессах, используются разные компиляторы, поэтому эти адреса будут отличаться. Если бы все было в одном модуле то хватило бы стандартного RTTI
А ты понимаешь, что если тип, как-бы, один и тот же (т.е., происходит из одного и того же исходника), но при этом инстанциирован, скажем, из модулей, собранных разными компиляторами, то по большому счету это разные типы, и, например, применение к экземпляру типа, созданного одим компилятором методов, скомпилированных другим компилятором, может привести к ОЧЕНЬ неожиданным результатам.
Здравствуйте, niralex, Вы писали:
N>Есть некий конечный набор пользовательских типов и задача их идентифицировать во время выполнения не используя RTTI. N>Для не шаблонных типов это может выглядеть так:
N>using TypeId = const char *; N>struct MyType1{ N> static constexpr TypeId Id(){ N> return "1"; N> } N>}; N>struct MyType2{ N> static constexpr TypeId Id(){ N> return "2"; N> } N>};
N>А как быть с шаблонными типами при условии чтобы функция MyType3::Id() оставалась constexpr
N>template<typename T> N>struct MyType3 { N> static constexpr TypeId Id(){ N> // return ? N> } N>};
N>Тип идентификатора TypeId не важен. Варианты без constexpr известны.
Можно вынести идентификацию типов за пределы классов. Это позволит присвоить идентификаторы не только пользовательским типам, но и встроенным. После чего идентификаторы можно будет пробовать как-то комбинировать:
Есть некий конечный набор пользовательских типов и задача их идентифицировать во время выполнения не используя RTTI.
Для не шаблонных типов это может выглядеть так:
Здравствуйте, andrey.desman, Вы писали:
AD>А почему "во время выполнения" и constexpr?
А почему нет? Хотя бы ради оптимизации скорости.
Зачем каждый раз выполнять один и тот же код при определении типа,
если можно определить один раз при компиляции.
Ведь тип известен на момент компиляции и, теоретически мне кажется, это возможно.
Здравствуйте, niralex, Вы писали:
N>А как быть с шаблонными типами при условии чтобы функция MyType3::Id() оставалась constexpr
Чтобы constexpr вряд ли получится, потому что окончательной информацией об эквиавалентности типов не владеет никто владеет не компилятор, а линкер. А данные, вычисленные линкером, если я не ошибаюсь, в мире C++ константами не считаются.
А так, можно завести в каждом типе статическую переменную, и считать идентификатором типа ее адрес.
Здравствуйте, Pzz, Вы писали:
Pzz>Чтобы constexpr вряд ли получится, потому что окончательной информацией об эквиавалентности типов не владеет никто владеет не компилятор, а линкер. А данные, вычисленные линкером, если я не ошибаюсь, в мире C++ константами не считаются.
Pzz>А так, можно завести в каждом типе статическую переменную, и считать идентификатором типа ее адрес.
Объекты инстанцируются в разных динамических модулях/процессах, используются разные компиляторы, поэтому эти адреса будут отличаться. Если бы все было в одном модуле то хватило бы стандартного RTTI
Здравствуйте, so5team, Вы писали:
S>Здравствуйте, niralex, Вы писали:
N>>А как быть с шаблонными типами при условии чтобы функция MyType3::Id() оставалась constexpr
N>>template<typename T> N>>struct MyType3 { N>> static constexpr TypeId Id(){ N>> // return ? N>> } N>>};
S>Как вариант, можно сделать специализации Id() для конкретных сочетаний шаблонных типов и их параметров: https://wandbox.org/permlink/G4XrK5yp6m3cRUaZ
Да, это вариант, только специализаций слишком много писать придется...
Здравствуйте, Pzz, Вы писали:
Pzz>Здравствуйте, niralex, Вы писали:
N>>Объекты инстанцируются в разных динамических модулях/процессах, используются разные компиляторы, поэтому эти адреса будут отличаться. Если бы все было в одном модуле то хватило бы стандартного RTTI
Pzz>А ты понимаешь, что если тип, как-бы, один и тот же (т.е., происходит из одного и того же исходника), но при этом инстанциирован, скажем, из модулей, собранных разными компиляторами, то по большому счету это разные типы, и, например, применение к экземпляру типа, созданного одим компилятором методов, скомпилированных другим компилятором, может привести к ОЧЕНЬ неожиданным результатам.
А мне не нужно вызывать никаких методов, мне нужны только идентификаторы типов, например в виде char *, которые я буду передавать, сравнивать и т.д.
Здравствуйте, niralex, Вы писали:
N>А мне не нужно вызывать никаких методов, мне нужны только идентификаторы типов, например в виде char *, которые я буду передавать, сравнивать и т.д.
Что такое одинаковый тип? Например, если два типа называются одинаково, они одинаковые?
Здравствуйте, Pzz, Вы писали:
Pzz>Здравствуйте, niralex, Вы писали:
N>>А мне не нужно вызывать никаких методов, мне нужны только идентификаторы типов, например в виде char *, которые я буду передавать, сравнивать и т.д.
Pzz>Что такое одинаковый тип? Например, если два типа называются одинаково, они одинаковые?
Нет. Я поясню немного контекст...
Все множество типов о которых идет речь, являются оболочками для стандартных типов, включая контейнеры
и выполняют кодирование/декодирование в двоичный специфицированный формат.
Теперь представьте себе RPC, где параметры вызываемых функций кодируются и передаются в этом формате.
Набор вызываемых удаленных функций известен вызывающей стороне и перед связыванием локального вызова с одной из них необходимо проверить соответствие типов аргументов,
Вот в этом месте и нужно сравнить идентификаторы типов. В реальности все сложней, обращение к идентификаторам происходит часто,
поэтому я и хочу чтобы они не вычислялись каждый раз во время выполнения, а были определены при компиляции. Т.е. у каждого модуля может быть своя реализация типа с одинаковым названием,
но Id типа и формат в который они кодируют/декодируют одинаковы. В проекте вообще есть модули на nodejs, которые используют те же типы.
Здравствуйте, rg45, Вы писали:
R>Можно вынести идентификацию типов за пределы классов. Это позволит присвоить идентификаторы не только пользовательским типам, но и встроенным. После чего идентификаторы можно будет пробовать как-то комбинировать:
R>
R>P.S. Синтаксический сахар и диагностику можно добавить во вкусу.
Да, это было бы прекрасным решением, но дело в том что есть такие пользовательские типы как template<typename ...T> MyTuple {} и там "как-то комбинировать" не получается, слишком много комбинаций шаблонных типов. Я пришел к выводу, что для идентификатора нужен все-таки массив, но тогда не получается сделать идентификацию constexpr.
А идея с вынесением за пределы классов хорошая, спасибо.
насколько я понял, твой случай это использовать макрос NAMEOF_RAW()(https://github.com/Neargye/nameof#remarks), и к результату применить какоу-нить compile-time hash.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Здравствуйте, rg45, Вы писали:
R>Начиная с C++17 есть fold expressions. Вот они как раз позволят и охватить множество комбинаций, и сохранить constexpr:
R>
R>Ну, понятно, что способ комбинирования нужно додумывать так, чтобы он исключал коллизии и обеспечивал уникальные айдишники.
С fold expressions знаком. Вопрос как раз в "способе комбинирования". Слишком большое число комбинаций. Например для 15 типов это 15!=1307674368000 комбинаций. А еще типы подобные array<n, TMyType>, где в идентификаторе типа нужно учитывать n типа size_t.
Здравствуйте, niralex, Вы писали:
N>Объекты инстанцируются в разных динамических модулях/процессах, используются разные компиляторы, поэтому эти адреса будут отличаться. Если бы все было в одном модуле то хватило бы стандартного RTTI
IMHO в этом месте надо подробнее рассказать сценарий того, что хочется делать...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, niralex, Вы писали:
N>С fold expressions знаком. Вопрос как раз в "способе комбинирования". Слишком большое число комбинаций. Например для 15 типов это 15!=1307674368000 комбинаций. А еще типы подобные array<n, TMyType>, где в идентификаторе типа нужно учитывать n типа size_t.
Возможно, нужно просто проанализировать реальные потребности и на их основе ввести какие-то разумные ограничения в модель? Что-то мне подсказывает, что общее количество типов в программе, включая все воплощения шаблонных классов, будет существенно скромнее астрономических чисел.
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, niralex, Вы писали:
N>>Объекты инстанцируются в разных динамических модулях/процессах, используются разные компиляторы, поэтому эти адреса будут отличаться. Если бы все было в одном модуле то хватило бы стандартного RTTI
E>IMHO в этом месте надо подробнее рассказать сценарий того, что хочется делать...
Попробую описать сценарий... Реализуеся универсальный механизм связывания для делегатов:
Если делегат и вызываемая сщность находятся в одном модуле, связывание и передача параметров происходят стандартным образом, но если в разных, параметры проходят процедуру кодирования/декодирования (для согласования форматов представления данных, потокобезопасности и др.) Обработка выполняется кодерами/декодерами, которые реализованы как множество типов, включающих статические методы Encode/Decode.
Например:
// ---Модуль main---
/* Создание делегата с двумя параметрами, для кодирования которых используются void IntCodec::Encode(std::byte *buffer, int Value) и void StringCodec::Encode(std::byte *buffer, string Value) */
MyDelegate<IntCodec, StringCodec> d;
/* Делегат может быть связан с вызываемыми сущностями, которые предварительно должны быть зарегистрированы */
// Создание вызываемых сущностей
Callee f1 = LocalFunction<IntCodec, StringCodec>(func); // func - локальная функция с сигнатурой func(int, char)
Callee f2 = GetFromDll("libname", index); // index - номер функции, которая зарегистрирвоана в dll
Callee f3 = GetRemoteFunction("url", index); // index - номер функции, которая зарегистрирвоана на удаленном узле
d.Bind(f1); // связывание с локальной функцией
d(1, "direct call == std::funcion()"); // вызов
d.Bind(f2); // связывание с функцией из DLL
d(2, "Call with encoding/decoding"); // вызов
d.Bind(f3); // связывание с удаленной функцией
d(3, "Remote call with encoding/decoding"); // вызов
// ---Модуль DLL---
int index = RegisterFunction<IntCodec, StringCodec>(func); // func - внутренняя функция dll
// ---Remote module---
int index = RegisterFunction<IntCodec, StringCodec>(func); // func - внутренняя функция dll
Сценарий такой: функции LocalFunction, GetFromDll и GetRemoteFunction получают информацию о типах кодеров/декодеров для вызываемой сущности из параметров шаблона, из dll или от удаленного узла, а функция Bind проверяет соответствие этих типов. Вот именно для этого и нужно идентифицировать типы и передавать идентификаторы. Поскольку вызовы Bind происходят часто, хочется чтобы идентификация проходила на этапе компиляции (само сравнение, естественно происходит в рантайме).
Здравствуйте, niralex, Вы писали:
N>Это будет библиотека в которой комбинировать типы будут пользователи и проанализировать их потребности сложно. Хотя, конечно основным пользователем буду я сам но, все-таки, не хочется себя ограничивать . Проект преследует помимо прочего, учебные цели, так как программированием профессионально не занимаюсь. Отсюда, возможно некий перфекционизм.
Ах, как я тебя понимаю. Я и сам всю жизнь пытаюсь изобрести супер-универсальный всемогутор. А заканчивается всегда тем, что локальное специализированное решение оказывается более выигрышным и жизнеспособным, чем универсальное.
Здравствуйте, Erop, Вы писали:
E>Если я верно понял, у модулей всё равно есть какая-то подготовка. То есть мы не имеем какой-то API более или менее родной для каждой dll, а включаем в dll наш код для маршелинга. Так?
Да, в dll есть код, который позволяет получить метаинформацию о функциях: уникальный идентификатор функции и типы кодеров для их параметров.
Основная программа использует только эту информацию для связывания и вызова функций из dll, т.е. она не обращается к самой функции, а передает в dll идентификатор функции и параметры в закодированном виде.
E>В любом случае нам нужно обеспечить две вещи.
E>1) Логическую совместимость вызывающей и вызываемой стороны. Тут должны быть какие-то правила, автоматически или полуавтоматически выводимые. Ну, например, можно ли наследника передавать вместо базы. И т. д.
Я понимаю под логической совместимостью количество, порядок следования и тип параметров и тип возвращаемого значения.
E>2) Бинарную совместимость в случае, если (1) есть, но по разные стороны имеются разные языки/среды разработки и т. д.
Да. Для этого используются кодеры/декодеры для стандартных и пользовательских типов и специфицированный бинарный формат.
E>Казалось бы, бы всегда можем использовать уникальные id всех сущностей, а в RT строить табличку что с чем совместимо с т. з. (1), и, соответственно, что надо использовать для (2) E>Правда, если отказаться от идеи, что мы не делаем промежуточный модуль совместимости, а, наоборот, по API dll какой-то тулзой генерить переходную lib, то можно сделать генерилки для N платформ, переходников из каждой в каждую. E>При загрузке/финальной сборке будут выбираться правильные версии этих интерфейсов и всё решится само собой во время загрузки приложения...
Не совсем понял... Dll подгружаются/выгружаются динамически, связывание вызывающей сущности в основной программе с функцией в dll(или на удаленном сервере) должно быть в run-time. Как тут помогут "переходные lib"?