Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / C++ Новый топик    Ответить
 протащить константу из runtime'a в мета-мир  [new]
Cerebrum
Member

Откуда: Омикрон Персей 8
Сообщений: 7937
Всем привет!

Несколько дней уже бьюсь с одной проблемой идеи иссякли и начинаю ходить кругами. Скорее всего уже просто замылил все что можно и нельзя и не вижу какого-то адекватного решения, поэтому прошу помощи в одном важном для меня вопросе.

Предположим есть системная (не зависящая от меня) функция foo(). В результате своей работы, помимо всего прочего, она возвращает некую заранее определенную константу. Это может быть просто #define переменная или enum тип - не суть важно. Важно что это - константа известная на этапе компиляции!

Я хочу создать расширяемый (разработчиком) массив объектов, поведение которых должно определяться значением данной константы.
То есть, после отработки foo мы получаем данную константу, сохраняем ее в виде constexpr значения внутри динамически сгенерированной структуры и потом используем ее для выполнения какой-то специфической работы, в том числе и compile time.

Все пока просто, с одним условием - сгенерированный объект, хранящий эту переменную, не должен быть шаблонным.
Потому что я хочу иметь возможность собрать их в один общий контейнер, например, std::vector, создав таким образом кэш.

Как это должно выглядеть с точки зрения идеальной реализации

struct Value // должен оставаться независимым от типа (либо быть строго Value<T>, где T - общий тип для всех объектов)
{            // все что внутри (конструкторы, базовые классы, переменные, variable переменные) - пожалуйста

        inline static constexpr int value = {};

	template <int val>
	Value() : value(val) {} // это против законов физ... языка, но это просто пример, того что мне надо
                                // своего рода отложенная инициализация value значения в конструкторе объекта

        void do_something_useful() const {...}
}

формирование массива...
std::vector<Value> values;

int nValue = foo(...);
switch (nValue)                     // пересекаем границу runtime и compile-time миров
{
   case 1:
   {
        Value val<1>();             // так тоже делать нельзя, но... можно применить технику тегирования и вытащить значение из объекта-тега (ADL и всякое такое)...
        values.emplace_back(val);
   }
   break;

   case 2:
   {
        Value val<2>();           // выглядит коряво, но по другому пересечь границу миров не получится и это будет стабильный список
        values.emplace_back(val); // его не понадобится расширять, храниться он будет глубоко в недрах реализации. Снаружи все будет цивильно
   }
   break;

   // и т.д.
}

дальше, в некоторой функции bar, которая ближе к клиенту
void bar(const std::vector<Value>& values)
{
     const auto& v = values.front(); 
     static_assert(v::value == 1); // любая статическая проверка или compile-time работа с типами необходимая по логике

     v.do_something_useful();
}


Выйти из ситуации можно просто: создать bar как non-type template функцию и заставить пользователя самому руками передавать правильную цифру 1, 2, 3 и тд...

Хоть это и панацея для меня, как создателя библиотеки, но это - уродство потому что
1, с точки зрения архитектуры
я уже знаю какая должна быть цифра, т.к. получил ее когда-то раньше от foo и могу даже таскать ее с собой в качестве runtime значения внутри Value-объекта (но хочу таскать ее с собой в виде compite time значения, чтобы использовать в различных constexpr вычислениях и проверках, коих будет несколько больше чем в этом абстрактном примере)
2. с точки зрения надежности
пользователь может ввести все что угодно, чего быть не должно, а проверить это получится только в runtime

что я уже попробовал, но везде обломался по той или иной причине:
* создать для Value шаблонный базовый класс и передавать в него значение из коструктора

template <typename T, T val>
struct base
{
    inline static constexpr T value = val;
};

struct Value : base<int, ?> // ну вы поняли...
{
    template <typename T, T val>
    Value() : base 
};


* создать обычную переменную/объект членом Value и генерировать ее значение в конструкторе, но тогда как в каком виде ее объявить в самом Value, если у нее должен быть динамически определяемый тип хранимых данных

struct Value
{
    template <typename T, T val>
    Value() : store<T, val> {}

    std::integral_constant<int, ?> store; // ну вы поняли
};


* из предыдущего вытекает потребность в template variable (c++14) внутри объекта Value

struct Value
{
    template <typename T, T val>
    struct local_storage
    {
         // constexpr и всякое остальное для хранения compile-time значения
    };

    template <typename T, T val>
    static local_storage<T, val>  store;

    template <typename T, T val>
    constexpr Value() : store<T, val> {} // ну вы поняли
};


компилятору это тоже не нравится, ему надо чтобы я не затягивал с инициализацией static значения, либо он говорит, что не могу проинициализоровать это в конструкторе

я точно не хочу использовать type erasure, std::any, std::variant и любое другое, что требует динамического выделения памяти, потому что как раз от него и пытаюсь уйти. И пока все замечательно получается, и осталось совсем чуть чуть, фактически эта проблема - последнее, что мешать закончить работу над данной библиотекой и начать ей пользоваться, но застрял, казалось бы на пустом месте. Вот

Если кто подскажет что-то дельное буду премного благодарен
--------------------------------------------------------------
o(O_O)o
18 мар 20, 10:57    [22101165]     Ответить | Цитировать Сообщить модератору
 Re: протащить константу из runtime'a в мета-мир  [new]
CEMb
Member

Откуда: Столько
Сообщений: 1994
Cerebrum
Все пока просто, с одним условием - сгенерированный объект, хранящий эту переменную, не должен быть шаблонным.
Потому что я хочу иметь возможность собрать их в один общий контейнер, например, std::vector, создав таким образом кэш.
1. Отнаследуй шаблон от интерфейса?
1. Сделай свой контейнер для контейнеров? Вот это было бы классно, потому что я когда маялся темой про динамическую типизацию на момент компиляции, много читал интернеты, и понял, что никто, и даже я не осилил нормально статический type erasure(просто потому что он противоречит нормальной логике).
1. А что там с boost::any? (сам не видел)
19 мар 20, 06:06    [22101872]     Ответить | Цитировать Сообщить модератору
 Re: протащить константу из runtime'a в мета-мир  [new]
Cerebrum
Member

Откуда: Омикрон Персей 8
Сообщений: 7937
CEMb
Отнаследуй шаблон от интерфейса?

про наследование уже писал ниже. можешь привести свой пример?
Если речь про virtual базовый класс, то тут без динамики никак, а значит, проще взять type erasure.
Да и даже если взять, что это даст? Это все runtime темы. Можно будет дернуть функцию производного через базовый без знания конкретики производного, а мне не нужно функцию, мне надо к compile-time константе доступ получить и проверить ее в compile-time, чуть позже чем она фактически появилась - вот что самое сложное
CEMb
Сделай свой контейнер для контейнеров?

Думал об этом, но пока по временным трудозатратам это неоправдано, да и с самой архитектурой есть неоднозначности. Какой она должна быть? Шаблоны не подходят, иначе я бы взял любой из std. Что еще остается? Что именно должен хранить этот контейнер? По факту это должны быть разнородные объекты типа template <int> object, то есть по факту размер их будет фиксированным, а вот тип - нет. Можно, конечно, заморочится, но пока решил зайти с другого фланга, может удастся вынести switch куда-то ближе к клиентскому коду, но при это скрыв реализацию внутри, и не заставлять пользователя вызывать bar в стиле double d = obj.bar<double, 1, 2, 3...>(.....)
CEMb
boost::any?

а что с ним? подозреваю тоже самое что и с std::any, а про него уже написал. boost'ом не пользуюсь и всегда обходил этот тестовый полигон std за километр, т.е. тащить его в проект ради одной фичи... ну, такое себе
CEMb
не осилил нормально статический type erasure

подозреваю, что статический type erause возможен, только в статически выделенной памяти Картинка с другого сайта., то есть для объектов с заранее известным размером и выравниванием. И сводится, как правило, к имитации таблицы виртуальных ф-ций, а ля trampoline (см. Davide Di Gennaro - Advanced Metaprogramming in Classic C++),
+ Антон Полухин про fast pimpl

Ссылка на позицию в клипе: https://youtu.be/mkPTreWiglk?t=0s

+ Filipp Gelman “unique_pseudofunction - N overloads for the price of 1
19 мар 20, 09:18    [22101908]     Ответить | Цитировать Сообщить модератору
 Re: протащить константу из runtime'a в мета-мир  [new]
Anatoly Moskovsky
Member

Откуда: Odessa
Сообщений: 6494
Cerebrum
я точно не хочу использовать type erasure, std::any, std::variant и любое другое, что требует динамического выделения памяти

В std::any должен быть small object optimization.
std::variant вообще не использует кучу.

На вашем месте я бы использовал std::variant и std::visit для матчинга. Тогда компилятор все по максимуму вытянет в компайл-тайм.
19 мар 20, 15:55    [22102291]     Ответить | Цитировать Сообщить модератору
 Re: протащить константу из runtime'a в мета-мир  [new]
Cerebrum
Member

Откуда: Омикрон Персей 8
Сообщений: 7937
Anatoly Moskovsky
std::variant и std::visit

тоже думал про это

но пока родилась идея, вечером проверять буду
19 мар 20, 17:00    [22102397]     Ответить | Цитировать Сообщить модератору
 Re: протащить константу из runtime'a в мета-мир  [new]
CEMb
Member

Откуда: Столько
Сообщений: 1994
Cerebrum
про наследование уже писал ниже. можешь привести свой пример?
+ да, тут динамика

да тут всё просто, вот у меня была реализация "различных" параметров, примерно такая:

интерфейс:
class IParam
{
public:
	//	CONTROLS
	virtual void ParamDlgAdd(IParamDlg *pParDlg) = 0;
	virtual void ParamDlgDel(IParamDlg *pParDlg) = 0;
	virtual void ParamDlgDelAll() = 0;

	virtual void Backup() = 0;
	virtual void Restore() = 0;
	virtual bool IsChanged() = 0;
	virtual void Default() = 0;
	virtual void SetStrValue(const tstring& str) = 0;
	virtual tstring GetStrValue() = 0;
	virtual void SetRange() = 0;
	//	WND
	virtual LRESULT OnInitDialog(HWND hWnd) = 0;
};

И сам шаблон, примерно:
template<class T> class CParam : public IParamReg, public IParam
{
public:
	//...
protected:
	T		m_Data;			//	Param data
	T		m_Backup;		//	Value backup storage for Param data
	//…
};

и потом ещё кучка реализаций работы с разными типами через строку. Т.о. строка тут играет роль type erase

Ну и в контейнер складываются указатели на IParam
Cerebrum
проще взять type erasure
ну вот в тех реализациях, которые я видел в интернете, как-то не проще

Вообще, нормальный type erase (для stl-я) невозможен в принципе. При складировании вы избавляетесь от типа. Но когда вам надо что-то сделать с этим объектом... вы уже не знаете, кто он, и вот тогда начинаются хаки с извлечением типа...
Cerebrum
тащить его в проект ради одной фичи... ну, такое себе
да вроде boost модульный, тащатся только конкретно указанные шаблоны, я довольно часто пользуюсь.
Cerebrum
подозреваю, что статический type erause возможен, только в статически выделенной памяти Картинка с другого сайта., то есть для объектов с заранее известным размером и выравниванием. И сводится, как правило, к имитации таблицы виртуальных ф-ций, а ля trampoline (см. Davide Di Gennaro - Advanced Metaprogramming in Classic C++),
Ну у меня получилось статически так: 21889200, параметры разных типов лежат в одной коробке, но да, надо знать типы заранее. Хотя, как правило, мы всегда заранее знаем, какие типы будем использовать. Если появляются новые типы, их просто добавляем в список. Но у этого решения было опять же две реализации получения объекта назад. Один - явно указываем тип, который хотим получить(и имя). Второй - просто указываем имя и на выход получаем (или не получаем) чёрную коробку, которая кастуется в один из типов списка, или не кастуется(реализация мечты про добавление в сигнатуру функции типа возвращаемого значения). Собственно, использовалось то, что удобнее в данный момент.
20 мар 20, 06:00    [22102650]     Ответить | Цитировать Сообщить модератору
 Re: протащить константу из runtime'a в мета-мир  [new]
Cerebrum
Member

Откуда: Омикрон Персей 8
Сообщений: 7937
CEMb,

понял, спасибо за пример
20 мар 20, 08:32    [22102675]     Ответить | Цитировать Сообщить модератору
Все форумы / C++ Ответить