Постепенно изграждане на вектор от указатели на променливи по време на компилиране

Опитвам се да разреша проблем, при който имам няколко декларации на разпръснати променливи (в една единица за компилиране) и искам да изградя вектор от техните адреси по време на компилация (ако не е възможно като пространствено ефективен вектор, други структури от данни също ще работи, като например свързан списък).

static type var1;
static type var2;
static type var3;

// ...
for (type *i : varlist)
   do something with each varX

Като разширен пример и обосновка, приемете, че имам скриптов език и имам начин да модифицирам скриптови променливи от C++, като получа манипулатор, да речем scriptvar *. Тези манипулатори могат да бъдат намерени чрез извикване на функция get_var с име:

scriptvar *var1 = get_var ("namespace::var1");
scriptvar *var2 = get_var ("namespace::var2");

Това е бавно, така че искам да кеширам стойностите scriptvar * в глобалните променливи. За съжаление, трябва да извикам get_var само след инициализиране на скриптовия език, което може да се случи късно в програмата, така че не мога просто да използвам get_var в израз на инициализатор, но трябва да забавя извикването му.

Би било хубаво, ако мога да напиша клас scriptvarwrapper или да използвам някакво друго средство, което би ми позволило да декларирам тези глобални манипулиращи променливи във всяка точка, но също така да изградя пространствено ефективен вектор на тези променливи по време на компилиране, до който мога да получа достъп по-късно:

struct scriptvarwrapper
{
  scriptvar *handle;
  const char *name;
  / ...
};

static scriptvarwrapper var1 ("namespace::var1");
static scriptvarwrapper var2 ("namespace::var2");
static scriptvarwrapper var3 ("namespace::var3");

void init_vars ()
{
  for (scriptvarwrapper *i : somecontainer)
     i->handle = get_var (i->name);
}

Би било идеално, ако този контейнер в крайна сметка бъде просто масив/векторна структура от данни в паметта, която се състои от указателите към тези променливи. Очевидно целта е да се изгради това по време на компилация, така че решенията, поставящи нещо в std::vector в конструктора, няма да решат проблема.

Актуализация:

За да изясня проблема - това, което искам, е решение, което автоматично компилира масив или списък от всички тези декларирани променливи по време на компилиране, без да се налага да ги изброявам поотделно и без конструктор да изгражда списък динамично по време на изпълнение, вероятно чрез някои готини и изящен метод за метапрограмиране, като функция constexpr, която по някакъв начин свързва всички тези променливи заедно в списък или, за предпочитане, нещо, което води само до масив от указатели към всички обекти на scriptvarwrapper в паметта, завършващи със специална стойност или с известен размер.

По-конкретно, ръчното поставяне на обектите scriptvarwrapper в статичен масив няма да свърши работа, нито поставянето им в std::vector в техния конструктор ще бъде достатъчно.

Обосновката за това е поддържаемостта - ако добавя променлива някъде в моята програма, не искам отново да я изброявам отделно, защото това е лесно да се забрави - и ефективността - не искам да изграждам динамична структура от данни по време на изпълнение за това, което всъщност е константа, известна по време на компилация.

Предпочитанието за някакъв вид масив се дължи на ефективността - решение, което води до структура от 100 байта за всеки такъв обект по време на компилация, разбира се, не е много полезно, по същия начин би било разпределяне на много голям масив само за бъдещо разширяване.

Актуализация 2:

За да поясним допълнително, точният синтаксис и оформление и тип на хранилището не са толкова важни. Това, което е важно, е, че мога да имам няколко независими и разпръснати във файла(овете) декларации на променливи и техните адреси се поставят автоматично и без ръчно изброяване в някакъв вид контейнер само за четене, който по някакъв начин може да бъде повторен на време за изпълнение, за да намерите всички тези декларации.

Целта е да намеря ефективно всички тези променливи без възможността да забравя да изброя една отделно, нито да трябва да изграждам структури от данни динамично по време на изпълнение, тъй като цялата информация е известна по време на компилиране, а C++ е толкова готин език с компилиране метапрограмиране на времето. Уви, не знам как и дали изобщо е възможно.

Актуализация 3:

Съжалявам за всички тези актуализации, научавам колко трудно е да изразя този проблем. Ето пример как биха могли да изглеждат нещата:

static scriptvarwrapper var1 ("name");
ADD_LIST (var1); // magic macro

static scriptvarwrapper var2 ("name");
ADD_LIST (var2);

Ключът тук е, че въпреки че трябва да изброя всяка променлива и дори да използвам грозен макрос може би, е трудно да пренебрегна или забравя да изброя променлива, защото ADD_LIST е директно на мястото на декларацията - не забравяйте, че декларациите може да са разпръснати в дълъг файл или дори в някои включени файлове, така че търся решение, което прави трудно да забравя да включа декларация в моя списък.

Следователно в идеалния случай конструкторът или простото действие на деклариране на scriptvarwrapper ще се увери, че е в списъка, така че да не може да бъде пренебрегнато. Решение, което поставя всичко в std::vector в конструктора, ще работи, освен че ще се почувства грозно поради времето за изпълнение.

Като стар човек на C, обмислях да използвам GCC разширения, за да ги поставя в техния собствен ELF раздел, подобно на самите конструктори, които работят върху ELF системи - те се събират от указател в техен собствен раздел и всички такива секции са свързани във връзка време, със специален обектен файл в края, който доставя крайната стойност на сентинела.


person Remember Monica    schedule 18.04.2018    source източник
comment
можете ли да кеширате променливи с помощта на get_var веднага след инициализиране на скриптовия език?   -  person Andrew Kashpur    schedule 18.04.2018
comment
може би std::map може да свърши работа   -  person    schedule 18.04.2018
comment
@AndrewKashpur, ако имате предвид дали манипулаторът, върнат от get_var, остава валиден за неопределено време, отговорът е да за този пример. Проблемът е, че get_var не може да бъде извикан при инициализация на програмата, в противен случай конструкторът може просто да го извика.   -  person Remember Monica    schedule 18.04.2018
comment
@user2176127 std::map не може да се използва по време на компилиране, нали?   -  person Remember Monica    schedule 18.04.2018
comment
Вашият проблем може да бъде решен чрез „отражение“, но глупавият C++ в момента не предлага тази функционалност.   -  person Andreas H.    schedule 19.04.2018


Отговори (1)


Не съм напълно сигурен дали разбирам въпроса ви, но защо просто не използвате класически стари масиви:

static int var1;
static int var2;
static int var3;

static int* vars[] = { &var1, &var2, &var3, nullptr };

for(size_t i = 0; vars[i]; ++i)
    std::cout << *vars[i] << std::endl;

Това трябва да работи за всички типове данни и е гарантирано, че ще се случи по време на компилиране.

Следната версия също е изградена по време на компилация (поне в Visual C++ 2017):

static const auto vararr = std::array<int*, 3>{ &var1, &var2, &var3 };

Същото може да се направи за вашия scriptvarwrapper:

struct scriptvarwrapper
{
    scriptvar *handle;
    const char *name;
};

static scriptvarwrapper vars[] = {
    {nullptr, "var1"},
    {nullptr, "var2"},
    {nullptr, nullptr}
};

void init_vars()
{
    for (size_t i = 0; vars[i].name; ++i)
        vars[i].handle = get_var(vars[i].name);
}

В C++ скриптовата променлива 'var1' може да бъде достъпна от vars[0].handle, а 'var2' е във vars[1].handle.

Може би бихте предпочели следното решение:

struct scriptvarwrapper
{
    scriptvar **handle;
    const char *name;
};

static scriptvar *var1 = nullptr;
static scriptvar *var2 = nullptr;

static scriptvarwrapper vars[] = {
    { &var1, "var1"},
    { &var2, "var2"},
    {nullptr, nullptr}
};

void init_vars()
{
    for (size_t i = 0; vars[i].name; ++i)
        *vars[i].handle = get_var(vars[i].name);
}

var1 и var2 като scriptvars, инициализирани към nullptr и добавени към масив от времето за компилиране 'vars' с помощта на 'scriptvarwrapper'. В 'init_vars' scriptvars се инициализират и след това могат да се използват чрез достъп до 'var1' и 'var2' (без дори да знаят за масива от време на компилиране, използван за инициализирането им)

Решение без време за компилиране, но лесно за използване:

class ScriptVar
{
public:
    ScriptVar(const char *name_)
        : name(name_)
    {
        vars.insert(this);
    }

    scriptvar* operator->()
    {
        return handle;
    }

    static void initVars()
    {
        for (auto var : vars)
            var->handle = get_var(var->name);
    }

private:
    static std::set<ScriptVar*> vars;
    const char *name;
    scriptvar *handle;
};

const ScriptVar var1("namespace::var1");
const ScriptVar var2("namespace::var2");

Всяка дефинирана ScriptVar се регистрира в ScriptVar::vars и след извикване на ScriptVar::initVars() всички дефинирани ScriptVars могат да бъдат достъпни с оператора ->.

person Andreas H.    schedule 18.04.2018
comment
Това са хубави алтернативи, но нито една от тях не изчислява списъка по време на компилиране - първите няколко изискват от мен да свърша работата ръчно, последната е по време на изпълнение. Ще се опитам да подобря въпроса ми с надеждата, че ще стане по-ясен. - person Remember Monica; 18.04.2018
comment
Мисля, че това, от което наистина се нуждаете, е някакъв вид препроцесор, който предлага някаква функционалност за отразяване. Опасявам се, че това не може да се направи със стандартен c++ поне до C++20 (вижте meetingcpp.com/blog/items/) - person Andreas H.; 19.04.2018