порядок инициализации inline static const
От: B0FEE664  
Дата: 12.04.24 15:45
Оценка: 34 (1)
В очередной раз пытаюсь написать перечисление с возможностью перебора элементов.
Возникает следующий вопрос:
в нижеприведённом коде вектор s_enum будет содержать значения в том же порядке, что они записаны в классе или же порядок не гарантируется?
class SeverityEnum
{
    public:
        using value_type = const char*;
    private:
        inline static std::vector<value_type>& RefValues()
        {
            static std::vector<value_type> s_enum;
            return s_enum;
        }
        inline static value_type EnumValue(const char* pStr)
        {
            value_type item{pStr};
            RefValues().push_back(item);
            return item;
        }
    public:
        inline static const value_type WARNING  = EnumValue("warning" );
        inline static const value_type MINOR    = EnumValue("minor"   );
        inline static const value_type MAJOR    = EnumValue("major"   );
        inline static const value_type CRITICAL = EnumValue("critical");
};
И каждый день — без права на ошибку...
Re: порядок инициализации inline static const
От: Кодт Россия  
Дата: 12.04.24 16:17
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>в нижеприведённом коде вектор s_enum будет содержать значения в том же порядке, что они записаны в классе или же порядок не гарантируется?


Интересный вопрос! В стандарте как-то это не очень освещено.
Вообще, выглядит логичным, что в каждой единице трансляции пачка определений (инлайновых) этих статических членов идёт в одном и том же порядке, поэтому и конструироваться они будут — неизвестно в какой единице трансляции, но точно перед main и точно подряд.

Но я бы постарался отмахаться от динамической инициализации вовсе.
Сделал бы какую-нибудь структуру — массив статических констант — с помощью кодогенерации.
Чтобы это всё превратилось вот в такое
enum IntLevel { nWarning, nMinor, nMajor, nCritical };

struct LevelDefinition { IntLevel level; const char* textual; };

const LevelDefinition definitions[] = { {nWarning, "warning"}, {nMinor, "minor"}, {nMajor, "major"}, {nCritical, "critical"}, };

const LevelDefinition& warning = definitions[nWarning], minor = definitions[nMinor], major = definitions[nMajor], critical = definitions[nCritical];

Десяток строк позора на макросах и ноль затрат на старте и финише программы.
Перекуём баги на фичи!
Re[2]: порядок инициализации inline static const
От: B0FEE664  
Дата: 12.04.24 16:47
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Вообще, выглядит логичным, что в каждой единице трансляции пачка определений (инлайновых) этих статических членов идёт в одном и том же порядке, поэтому и конструироваться они будут — неизвестно в какой единице трансляции, но точно перед main и точно подряд.


Хмм. "точно перед main и точно подряд"? Т.е. мьютек в EnumValue будет лишним? Или нет?

PS Понятно, что с кодогенерацией всё быстро и безопасно можно сделать, но это подходит только если кодогенерация уже используется в проекте, а добавлять кодогенерацию в процесс только ради перечислений чересчур затратно.
И каждый день — без права на ошибку...
Re[3]: порядок инициализации inline static const
От: Кодт Россия  
Дата: 13.04.24 00:56
Оценка:
Здравствуйте, B0FEE664, Вы писали:

К>>Вообще, выглядит логичным, что в каждой единице трансляции пачка определений (инлайновых) этих статических членов идёт в одном и том же порядке, поэтому и конструироваться они будут — неизвестно в какой единице трансляции, но точно перед main и точно подряд.


BFE>Хмм. "точно перед main и точно подряд"? Т.е. мьютек в EnumValue будет лишним? Или нет?


Точно перед майн и точно подряд.
Однако, если у тебя длл/сошка, то "перед майн" и "в однопоточном окружении" уже неточно. И это — одна из причин, по которой с динамической инициализацией лучше по возможности не связываться.

Кстати, насчёт подряд, — оказывается, у гцц есть хакерский атрибут [[init_priority (NNN)]]
https://stackoverflow.com/questions/211237/static-variables-initialisation-order/211314#211314
https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Attributes.html

BFE>PS Понятно, что с кодогенерацией всё быстро и безопасно можно сделать, но это подходит только если кодогенерация уже используется в проекте, а добавлять кодогенерацию в процесс только ради перечислений чересчур затратно.


Один раз написать или припахать какое-нибудь готовое решение на макросах.
Рефлексию энумов же давно колхозят все, кому не лень. (И на буст-препроцессоре это в пять строчек можно сделать).

https://yandex.ru/search/?text=c%2B%2B+enum+reflection
Перекуём баги на фичи!
Re: порядок инициализации inline static const
От: K13 http://akvis.com
Дата: 13.04.24 09:37
Оценка: +2
BFE>В очередной раз пытаюсь написать перечисление с возможностью перебора элементов.

А чем не устраивает magic_enum ?

https://github.com/Neargye/magic_enum
Re[2]: порядок инициализации inline static const
От: _NN_ www.nemerleweb.com
Дата: 13.04.24 10:00
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Интересный вопрос! В стандарте как-то это не очень освещено.

К>Вообще, выглядит логичным, что в каждой единице трансляции пачка определений (инлайновых) этих статических членов идёт в одном и том же порядке, поэтому и конструироваться они будут — неизвестно в какой единице трансляции, но точно перед main и точно подряд.

Разве стандарт не гарантирует в одном файле инициализацию по порядку следования ?
Иначе бы простейший код просто бы не работал:
int i = 1;
int j = i;

int main() {}
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[3]: порядок инициализации inline static const
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 13.04.24 12:14
Оценка: 1 (1)
Здравствуйте, B0FEE664, Вы писали:

BFE>Хмм. "точно перед main и точно подряд"?


Точно перед main, а вот порядок сохранять вроде никто не обещает


BFE>Т.е. мьютек в EnumValue будет лишним? Или нет?


Инициализация статических переменных потоекобезопасна, начиная с 11го стандарта, до этого таких гарантий не было


BFE>PS Понятно, что с кодогенерацией всё быстро и безопасно можно сделать, но это подходит только если кодогенерация уже используется в проекте, а добавлять кодогенерацию в процесс только ради перечислений чересчур затратно.


Можешь взять моё — https://github.com/al-martyn1/umba-enum-gen

Я как раз добавил только для перечислений, и теперь не нарадуюсь. Мне эта кодогенерация сильно жизнь упростила, теперь во всех проектах enum'ы только через генерацию и делаю. Бонусом получилось то, что очень удобно все эти перечисления пробрасывать во встраиваемый скриптовый движок

Можно делать флаговые энамы, можно — простые энамы.
Можно задавать в отдельном файле — https://github.com/al-martyn1/marty_draw_context/blob/main/_generators/draw_text_flags.txt
Можно — в командной строке — https://github.com/al-martyn1/marty_draw_context/blob/ad8b6fe76bbe5072dfcb8c29742bb47531d0f391/_generators/_generate_all.bat#L110

Легким движением руки брюки превращаются в элегантные шорты — https://github.com/al-martyn1/marty_draw_context/blob/ad8b6fe76bbe5072dfcb8c29742bb47531d0f391/draw_context_enums.h#L537

Вернее, то, во что превращаются брюки, можно настроить в конфиге — https://github.com/al-martyn1/umba-enum-gen/blob/main/_distr_conf/conf/umba-enum-gen/cpp/default.txt

Можно настроить генерацию под любой язык, только конфиги надо напилить, по умолчанию аплаятся такие флаги:
umba-enum-gen --lang=cpp --template=default


Для разной целевой генерации можно использовать разные стили именования:
--namespace-name-style=STYLE
--enum-name-style=STYLE
--enum-values-style=STYLE

    DefaultStyle - use name as is.
    
    CppStyle - lowercase name parts separated by underscore.
    
    CamelStyle - first name part is in lowercase, next ones - PascalStyle.
    
    PascalStyle - all name parts starts with uppercas letter.
    
    CppCamelMixedStyle - name parts separated by underscore, first is in
    lowercase, next ones - PascalStyleCppPascalMixedStyle - all PascalStyle name
    parts separated by underscore.
    
    DefineStyle - uppercase name parts separated by underscore.


--enum-serialize-style=STYLE

    All - generate all style names for deserialization. If this serialize value
    name style taken, PascalStyle names used to serialize value.
    
    DefaultStyle - use name as is.
    
    HyphenStyle - lowercase name parts separated by hyphen.
    
    HyphenCamelMixedStyle - name parts separated by hyphen, first is in
    lowercase, next ones - PascalStyle.
    
    HyphenPascalMixedStyle - name parts separated by hyphen, all part in
    PascalStyle.
    
    CppStyle - lowercase name parts separated by underscore.
    
    CamelStyle - first name part is in lowercase, next ones - PascalStyle.
    
    PascalStyle - all name parts starts with uppercas letter.
    
    CppCamelMixedStyle - name parts separated by underscore, first is in
    lowercase, next ones - PascalStyleCppPascalMixedStyle - all PascalStyle name
    parts separated by underscore.
    
    DefineStyle - uppercase name parts separated by underscore.
Маньяк Робокряк колесит по городу
Re[3]: порядок инициализации inline static const
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 13.04.24 12:21
Оценка: -1
Здравствуйте, _NN_, Вы писали:

_NN>Разве стандарт не гарантирует в одном файле инициализацию по порядку следования ?


Не гарантирует


_NN>Иначе бы простейший код просто бы не работал:

_NN>
_NN>int i = 1;
_NN>int j = i;

_NN>int main() {}
_NN>


Вот такой код тоже будет работать:
#include <iostream>

extern int i;

int j = i;
int i = 333;

int main()
{
    std::cout << "j: " << j << "\n";
}


https://ideone.com/MFJXnw
Маньяк Робокряк колесит по городу
Re[4]: порядок инициализации inline static const
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 13.04.24 16:11
Оценка:
Здравствуйте, Marty, Вы писали:

andrey.desman, ты с чем не согласен? Не прокомментируешь?

Я вполне могу ошибаться, и хотелось бы знать, в чем я не прав. Мы ж тут не в "политике", чтобы молчаливыми минусами разбрасываться
Маньяк Робокряк колесит по городу
Re[4]: порядок инициализации inline static const
От: _NN_ www.nemerleweb.com
Дата: 13.04.24 17:24
Оценка:
Здравствуйте, Marty, Вы писали:

M>Здравствуйте, _NN_, Вы писали:


_NN>>Разве стандарт не гарантирует в одном файле инициализацию по порядку следования ?


M>Не гарантирует


M>Вот такой код тоже будет работать:

M>
M>#include <iostream>

M>extern int i;

M>int j = i;
M>int i = 333;

M>int main()
M>{
M>    std::cout << "j: " << j << "\n";
M>}
M>


M>https://ideone.com/MFJXnw


Здесь у нас другой код.
В оригинале нет extern.
Поэтому порядок должен быть.

Насколько я вижу GCC (другие тоже ?) инициализирует литералы сразу, а то что можно вычислить по мере объявления
#include <iostream>

extern int a;
extern int b;

int d = a + 100; // 100 , "а" не вычислен
int a = b + 111; // 222
int b = 111; // литерал 111
int c = a + 100; // 322 , "а" вычислен

int main()
{
    std::cout << a << " " << b << " " << c << " " << d;
}

https://gcc.godbolt.org/z/sxobncYr4
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[5]: порядок инициализации inline static const
От: andrey.desman  
Дата: 13.04.24 17:39
Оценка: 6 (1) +1
Здравствуйте, Marty, Вы писали:

M>andrey.desman, ты с чем не согласен? Не прокомментируешь?

M>Я вполне могу ошибаться, и хотелось бы знать, в чем я не прав. Мы ж тут не в "политике", чтобы молчаливыми минусами разбрасываться

Гарантирует инит по порядку. У тебя для i константная инициализация, она выполяется до динамической.
А для j уже динамическая инициализация, она выполняется после.
Но порядок интересен для динамической инициализации, он в рамках юнита определен.
Re[5]: порядок инициализации inline static const
От: andrey.desman  
Дата: 13.04.24 17:50
Оценка:
Здравствуйте, _NN_, Вы писали:


_NN>Насколько я вижу GCC (другие тоже ?) инициализирует литералы сразу, а то что можно вычислить по мере объявления


Да, константная инициализация выполняется до динамической.
Все, что еще не динамически инициализировано, инициализировано нулями. Ниже у тебя не a не вычислен, а a проинициализирован статически, но еще не проинициализирован динамически.

_NN>
_NN>int d = a + 100; // 100 , "а" не вычислен
_NN>int a = b + 111; // 222
_NN>
Re[4]: порядок инициализации inline static const
От: B0FEE664  
Дата: 15.04.24 10:26
Оценка:
Здравствуйте, Marty, Вы писали:

BFE>>Хмм. "точно перед main и точно подряд"?

M>Точно перед main,
Это ведь гарантии реализации, а не стандарта?

M> а вот порядок сохранять вроде никто не обещает

Для членов класса могли бы и добавить...

BFE>>Т.е. мьютек в EnumValue будет лишним? Или нет?

M>Инициализация статических переменных потоекобезопасна, начиная с 11го стандарта, до этого таких гарантий не было
Насколько я понимаю, потокобезопасность гарантирует только, что одна и та же переменная не будет инициализироваться одновременно из двух разных потоков, но не гарантирует, что две разные переменные не будут инициализироваться одновременно.
Т.е. инициализация переменных:
        inline static const value_type WARNING  = EnumValue("warning" );
        inline static const value_type MINOR    = EnumValue("minor"   );

Может производится параллельно, WARNING в одном потоке, а MINOR — другом, а тогда исполнение EnumValue будет происходить одновременно в двух разных потоках:
inline static value_type EnumValue(const char* pStr)
{
  value_type item{pStr};
  RefValues().push_back(item);
  return item;
}

а такого push_back вектора не переживёт...
Или я ошибаюсь и потокобезопасность инициализации статических переменных гарантирует, что инициализация ни одной статической переменной не может происходить одновременно с инициализацией другой статической переменной?
И каждый день — без права на ошибку...
Re[5]: порядок инициализации inline static const
От: andrey.desman  
Дата: 15.04.24 17:20
Оценка: 5 (1)
Здравствуйте, B0FEE664, Вы писали:

BFE>>>Хмм. "точно перед main и точно подряд"?

M>>Точно перед main,
BFE>Это ведь гарантии реализации, а не стандарта?

Не точно перед main вообще-то.

M>> а вот порядок сохранять вроде никто не обещает

BFE>Для членов класса могли бы и добавить...

Так и есть.

BFE>>>Т.е. мьютек в EnumValue будет лишним? Или нет?

M>>Инициализация статических переменных потоекобезопасна, начиная с 11го стандарта, до этого таких гарантий не было
BFE>Насколько я понимаю, потокобезопасность гарантирует только, что одна и та же переменная не будет инициализироваться одновременно из двух разных потоков, но не гарантирует, что две разные переменные не будут инициализироваться одновременно.

Вот тут все написано:
https://en.cppreference.com/w/cpp/language/initialization#Dynamic_initialization

2) Partially-ordered dynamic initialization, which applies to all inline variables that are not an implicitly or explicitly instantiated specialization. If a partially-ordered V is defined before ordered or partially-ordered W in every translation unit, the initialization of V is sequenced before the initialization of W (or happens-before, if the program starts a thread).


То есть happens-before в случае многопоточки. При условии, конечно, что они везде определены в одном и том же порядке, что для классов обычно верно. Ты же не пишешь классы дважды с разным порядком статик инлайнов?

Что касается отложенной инициализации, то там же ниже:

If the initialization of an inline variable is deferred, it happens before the first ODR-use of that specific variable.


То есть, они могут быть не все инициализированы (например, до MAJOR, а CRITICAL будет uninit), но только по порядку.

Но это все тлен, инициализация этих inline Enum никак не связана с вектором, поэтому во-первых, по факту мьютекс на вектор нужен, в том числе и на чтение, а во-вторых, мьютекс все равно не спасет от пустого вектора.

Лучше, как советовал Кодт, константная инициализация.
Re[6]: порядок инициализации inline static const
От: B0FEE664  
Дата: 16.04.24 10:21
Оценка:
Здравствуйте, andrey.desman, Вы писали:

AD>То есть happens-before в случае многопоточки. При условии, конечно, что они везде определены в одном и том же порядке, что для классов обычно верно. Ты же не пишешь классы дважды с разным порядком статик инлайнов?

Ага. Спасибо.

AD>Что касается отложенной инициализации, то там же ниже:

AD>

AD>If the initialization of an inline variable is deferred, it happens before the first ODR-use of that specific variable.

AD>То есть, они могут быть не все инициализированы (например, до MAJOR, а CRITICAL будет uninit), но только по порядку.
AD>Но это все тлен, инициализация этих inline Enum никак не связана с вектором, поэтому во-первых, по факту мьютекс на вектор нужен, в том числе и на чтение, а во-вторых, мьютекс все равно не спасет от пустого вектора.
Интересно.
Действительно, от пустого вектора не спастись... Хотя, если сделать value_type объектом с конструктором в который добавить side effect...:

However, as long as anything from a translation unit is ODR-used, all non-local variables whose initialization or destruction has side effects will be initialized even if they are not used in the program.

То всё должно получится.
Кстати, зачем мьютекс если есть гарантия на happens-before?
И каждый день — без права на ошибку...
Re[7]: порядок инициализации inline static const
От: andrey.desman  
Дата: 17.04.24 08:01
Оценка:
Здравствуйте, B0FEE664, Вы писали:

AD>>Что касается отложенной инициализации, то там же ниже:

AD>>

AD>>If the initialization of an inline variable is deferred, it happens before the first ODR-use of that specific variable.

AD>>То есть, они могут быть не все инициализированы (например, до MAJOR, а CRITICAL будет uninit), но только по порядку.
AD>>Но это все тлен, инициализация этих inline Enum никак не связана с вектором, поэтому во-первых, по факту мьютекс на вектор нужен, в том числе и на чтение, а во-вторых, мьютекс все равно не спасет от пустого вектора.
BFE>Интересно.
BFE>Действительно, от пустого вектора не спастись... Хотя, если сделать value_type объектом с конструктором в который добавить side effect...:

То ничего не поменяется. Статику что конструктор завть, что через функцию инициализироваться — все одно.
И вообще, это касается non-inline статиков. С inline все сложнее.

BFE>

BFE> However, as long as anything from a translation unit is ODR-used, all non-local variables whose initialization or destruction has side effects will be initialized even if they are not used in the program.

BFE>То всё должно получится.
BFE>Кстати, зачем мьютекс если есть гарантия на happens-before?

Это гарантия на инициализацию статик инлайнов относительно друг друга. Но они никак не связаны с вектором, в который ты их добавляешь. В одном месте ты можешь взять вектор через RefValues() и что-то с ним делать, а в другом будет происходить отложенная инициализация. Так в теории по стандарту. На практике, скорее всего, этот набор инлайнов попадет в первый попавшийся cpp и их инициализация произойдет со всеми остальными статиками из этого cpp до входа в main, но это не точно.
Re: порядок инициализации inline static const
От: fk0 Россия https://fk0.name
Дата: 04.05.24 10:26
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>В очередной раз пытаюсь написать перечисление с возможностью перебора элементов.

BFE>Возникает следующий вопрос:
BFE>в нижеприведённом коде вектор s_enum будет содержать значения в том же порядке, что они записаны в классе или же порядок не гарантируется?

Не гарантируется и не следует на это пытаться полагаться! Проблемой может стать вовсе не компилятор, а линкер.
Хотя обычно порядок более-менее сохраняется и это может вводить в заблуждение.
Re[3]: порядок инициализации inline static const
От: fk0 Россия https://fk0.name
Дата: 04.05.24 10:27
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Разве стандарт не гарантирует в одном файле инициализацию по порядку следования ?


Ни разу. Все подобные примеры лишь демонстрируют, что инициализация POD-типов превращается
в константы в .data секции и они "инициализируются" ещё при компиляции (при запуске сразу содержат
какие надо значения).
Re[2]: порядок инициализации inline static const
От: fk0 Россия https://fk0.name
Дата: 04.05.24 10:28
Оценка:
Здравствуйте, K13, Вы писали:

BFE>>В очередной раз пытаюсь написать перечисление с возможностью перебора элементов.


K13>А чем не устраивает magic_enum ?

K13>https://github.com/Neargye/magic_enum

Всем, кроме того, что на всякий чих тащить в проект миллион библиотек с гитхаба -- дурная идея.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.