strong_typedef через enum class
От: удусекшл  
Дата: 24.01.22 09:08
Оценка:
Здравствуйте!

А в чем проблема делать strong typedef через enum и просто переопределить все нужные операторы? Типа:
enum class NewType: int { dummy };
IMPLEMENT_ARITHMETIC_OPERATORS(NewType)
IMPLEMENT_RELATION_OPERATORS(NewType)
IMPLEMENT_UNDERLYING_TYPE_EQUAL_OPERATORS(NewType)

// или 

enum class NewFlagType: unsigned { dummy };
IMPLEMENT_BIT_OPERATORS(NewFlagType)
IMPLEMENT_UNDERLYING_TYPE_EQUAL_OPERATORS(NewFlagType)




При этом можно делать различия — если нужен арифметический тип — переопределяем всякие +-/*, если это флаговый typedef — то переопределяем только битовые операторы.

Вроде я как-то не видел такого, обычно делают класс/структуру с операторами преобразования в underlying_type.

Могут быть проблемы от того, что реально переменная такого типа принимает значения, которые не перечислены в enum?
Тогда, может, достаточно будет добавить в enum значения min_value/max_value?
Re: strong_typedef через enum class
От: night beast СССР  
Дата: 24.01.22 09:14
Оценка: +1
Здравствуйте, удусекшл, Вы писали:

У>А в чем проблема делать strong typedef через enum и просто переопределить все нужные операторы? Типа:


std::byte
Re[2]: strong_typedef через enum class
От: удусекшл  
Дата: 24.01.22 09:26
Оценка:
Здравствуйте, night beast, Вы писали:

У>>А в чем проблема делать strong typedef через enum и просто переопределить все нужные операторы? Типа:


NB>std::byte


Ясно, проблем нет. Почему тогда такое не часто используется, а делается что-то типа такого — https://www.foonathan.net/2016/10/strong-typedefs/
Re[3]: strong_typedef через enum class
От: night beast СССР  
Дата: 24.01.22 09:29
Оценка:
Здравствуйте, удусекшл, Вы писали:

NB>>std::byte


У>Ясно, проблем нет. Почему тогда такое не часто используется, а делается что-то типа такого — https://www.foonathan.net/2016/10/strong-typedefs/


типы бывают не только интегральные
Re: strong_typedef через enum class
От: reversecode google
Дата: 24.01.22 09:31
Оценка:
?
http://cpp11tipsandtraps.blogspot.com/2017/05/stdunderlyingtype.html
Re[4]: strong_typedef через enum class
От: удусекшл  
Дата: 24.01.22 09:32
Оценка:
Здравствуйте, night beast, Вы писали:

NB>>>std::byte


У>>Ясно, проблем нет. Почему тогда такое не часто используется, а делается что-то типа такого — https://www.foonathan.net/2016/10/strong-typedefs/


NB>типы бывают не только интегральные


Спасибо, кэп, я в курсе

Я сейчас задумался именно за интегральные типы — типа, как не перепутать в параметрах, где скорость передаётся, а где расстояние (условно говоря, и все — целочисленное)
Re: strong_typedef через enum class
От: sergii.p  
Дата: 24.01.22 11:26
Оценка: 2 (1)
Здравствуйте, удусекшл, Вы писали:

У>Вроде я как-то не видел такого, обычно делают класс/структуру с операторами преобразования в underlying_type.


делают класс/структуру в основном или в легаси (раньше не было enum class) или по незнанию. А так это уже походу довольно известный подход.
Единственное, если вам всё-таки нужно различать скорости, расстояния и прочее, лучше использовать специальные библиотеки (boost::units). Там уже всё продумано. Например, размерность скорости можно получить как
auto speed = distance / time; // здесь для скорости выведется новый уникальный тип
Re: strong_typedef через enum class
От: Alexander G Украина  
Дата: 24.01.22 11:34
Оценка:
Здравствуйте, удусекшл, Вы писали:


У>Вроде я как-то не видел такого, обычно делают класс/структуру с операторами преобразования в underlying_type.


Плюсы структуры — больше гибкость: можно добавить методы (непример chrono duration на энаме не сделаешь из-за этого), или можно потом добавить ещё данные.

Плюсы enum — более эффективная кодогенерация за счёт более эффективного ABI (актуально для Windows ABI, т.е. MSVC здесь https://godbolt.org/z/jc7KGbxW7)
Русский военный корабль идёт ко дну!
Отредактировано 24.01.2022 11:37 Alexander G . Предыдущая версия . Еще …
Отредактировано 24.01.2022 11:36 Alexander G . Предыдущая версия .
Re[2]: strong_typedef через enum class
От: удусекшл  
Дата: 24.01.22 11:59
Оценка:
Здравствуйте, Alexander G, Вы писали:

У>>Вроде я как-то не видел такого, обычно делают класс/структуру с операторами преобразования в underlying_type.


AG>Плюсы структуры — больше гибкость: можно добавить методы (непример chrono duration на энаме не сделаешь из-за этого), или можно потом добавить ещё данные.


AG>Плюсы enum — более эффективная кодогенерация за счёт более эффективного ABI (актуально для Windows ABI, т.е. MSVC здесь https://godbolt.org/z/jc7KGbxW7)


Меня эмбед АРМ интересует.

Ну и основной вопрос был в том — не поедет ли у компилятора крыша от того, что enum принимает значения, которые не объявлены в нем. Теоретически, он может на их основе делать предположения и как-то оптимизировать, что может выйти боком. Но судя по всему, этого можно не опасаться
Re[3]: strong_typedef через enum class
От: watchmaker  
Дата: 24.01.22 13:56
Оценка: 3 (2)
Здравствуйте, удусекшл, Вы писали:


У>Ну и основной вопрос был в том — не поедет ли у компилятора крыша от того, что enum принимает значения, которые не объявлены в нем. Теоретически, он может на их основе делать предположения и как-то оптимизировать, что может выйти боком. Но судя по всему, этого можно не опасаться


Ты можешь сделать перечисление с fixed underlying type и без оного. Для этих вариантов в стандарте прописано разное поведение:

В первом случае присваивание значения, не указанного среди значений перечисления, работает по правилам этого underlying type — если там нет проблем, то и у перечисления их не будет. Именно так устроен уже упомянутый std::byte, например.

Во втором случае такое присваивание ведёт к неопределённому поведению. И этого следует опасаться.




Если планируешь хранить в перечислении произвольные значения, то нужно всегда использовать перечисление с fixed underlying type.
В целом, раз этот тип у тебя и используется в качестве исходного в opaque typedef это получается автоматически — главное его просто указывать.
Re[4]: strong_typedef через enum class
От: удусекшл  
Дата: 24.01.22 14:04
Оценка:
Здравствуйте, watchmaker, Вы писали:


W>Если планируешь хранить в перечислении произвольные значения, то нужно всегда использовать перечисление с fixed underlying type.

W>В целом, раз этот тип у тебя и используется в качестве исходного в opaque typedef это получается автоматически — главное его просто указывать.

Ну, это я просто развернул, так-то у меня эти портяночки завернуты в макросы, используется одной строкой
Re[4]: strong_typedef через enum class
От: _NN_ www.nemerleweb.com
Дата: 26.01.22 13:52
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Во втором случае такое присваивание ведёт к неопределённому поведению. И этого следует опасаться.


Тут стоит уточнить, что оно будет только в случае выхода за границы неявного внутреннего типа перечисления:
Из стандарта

For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. Otherwise, the values of the enumeration are the values representable by a hypothetical integer type with minimal width M such that all enumerators can be represented. The width of the smallest bit-field large enough to hold all the values of the enumeration type is M. It is possible to define an enumeration that has values not defined by any of its enumerators. If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0.


Или https://en.cppreference.com/w/cpp/language/enum

Values of integer, floating-point, and enumeration types can be converted by static_cast or explicit cast, to any enumeration type. If the underlying type is not fixed and the source value is out of range, the behavior is undefined.


Я так понимаю, что если у нас определенны перечисления:
enum A { X = 0 };
enum B { Q = 0, W = 256 };
enum C { E = 0, R = 65000 };


То имеем следующее при 1 байт = 8 бит:
A a = X; // OK
a = static_cast<A>(120); // OK, влезаем в один байт
a = static_cast<A>(1 << 8); // Если int у нас больше 8-ми бит, то ОК иначе нет.

B b = Q; // OK
b = static_cast<B>(1 << 8); // OK
b = static_cast<B>(1 << 16); // UB

B c = E; // OK
c = static_cast<C>(1 << 8); // OK
c = static_cast<C>(1 << 15); // OK
c = static_cast<C>(1U << 32); // UB
http://rsdn.nemerleweb.com
http://nemerleweb.com
Отредактировано 27.01.2022 12:51 _NN_ . Предыдущая версия . Еще …
Отредактировано 27.01.2022 12:46 _NN_ . Предыдущая версия .
Re[5]: strong_typedef через enum class
От: watchmaker  
Дата: 27.01.22 12:42
Оценка:
Здравствуйте, _NN_, Вы писали:

NN>Из стандарта

Замечу, что стандарт С++ написан в своём особом стиле, где нет секции "все правила про enum". Вместо этого правила сгруппированы по другим критериям, из-за чего описание поведения enum оказывается разбросанными по всмему тексту.

В процитированном тобой фрагменте написано одно условие. Если оно нарушено, то будет UB. Хорошо. Но это не означает, что выполнение именно одного этого условия гарантирует отсутствия UB. Как раз из-за того, что другие ограничения перечислены в других секциях.
Цитата взята из секции [dcl.enum], но внезапно также нужно смотреть и на правила из [expr.static.cast] и других секций — они тоже накладывают ограничения.

_NN>Тут стоит уточнить, что оно будет только в случае выхода за границы неявного внутреннего типа перечисления:


В целом да, только правила не совсем такие, которые ты демонстрируешь в примерах. Правила упоротее.

_NN>То имеем следующее при 1 байт = 8 бит:

На самом деле от CHAR_BIT результат не зависит. Это первая особенность.

_NN>a = static_cast<A>(120); // OK, влезаем в один байт

Не-а, это UB. Потому что под enum A выделится меньше байта.

_NN>B c = E; // OK

_NN>c = static_cast<B>(1 << 8); // OK
_NN>c = static_cast<B>(1 << 16); // OK

В третьем блоке опечатка и вместо B везде C имеется в виду?
Тогда всё равно c = static_cast<С>(1 << 16) не OK и тоже вызовет проблемы.


Кстати, в gcc и clang уже давно завезли классный undefined-sanitizer: он как раз добавляет проверки в места, где компилятор оптимизирует код в соответствии с гарантиями стандарта.
Конечно, это не панацея, но санитайзер здорово позволяет ловить многие места, где происходит UB.

https://godbolt.org/z/xboqqM9rh :

runtime error: load of value 120, which is not a valid value for type 'A'
runtime error: load of value 65536, which is not a valid value for type 'C'
Re[6]: strong_typedef через enum class
От: _NN_ www.nemerleweb.com
Дата: 27.01.22 12:51
Оценка:
Здравствуйте, watchmaker, Вы писали:

Всё ок. В целом всё как и ожидается с поправками опечаток.

_NN>>То имеем следующее при 1 байт = 8 бит:

W>На самом деле от CHAR_BIT результат не зависит. Это первая особенность.
Это было для упрощения. Можно конечно и в CHAR_BIT считать.

_NN>>a = static_cast<A>(120); // OK, влезаем в один байт

W>Не-а, это UB. Потому что под enum A выделится меньше байта.
А так возможно ?
Разве не sizeof(char) является минимальным размером переменной ?
Битовые поля оставим в покое

_NN>>B c = E; // OK

_NN>>c = static_cast<B>(1 << 8); // OK
_NN>>c = static_cast<B>(1 << 16); // OK

W>В третьем блоке опечатка и вместо B везде C имеется в виду?

Да, конечно.
W>Тогда всё равно c = static_cast<С>(1 << 16) не OK и тоже вызовет проблемы.
Да, ещё одна опечатка , там должно быть 1 << 15, чтобы влезло в диапазон

W>Кстати, в gcc и clang уже давно завезли классный undefined-sanitizer: он как раз добавляет проверки в места, где компилятор оптимизирует код в соответствии с гарантиями стандарта.

W>Конечно, это не панацея, но санитайзер здорово позволяет ловить многие места, где происходит UB.
Без запуска анализаторов кода выходит в продакшн также не рекомендую
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[7]: strong_typedef через enum class
От: watchmaker  
Дата: 27.01.22 13:38
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>>>То имеем следующее при 1 байт = 8 бит:

W>>На самом деле от CHAR_BIT результат не зависит. Это первая особенность.
_NN>Это было для упрощения. Можно конечно и в CHAR_BIT считать.
Нет, дело не в упрощении. А в том, что с точки зрения языка все приведённые примеры либо верны, либо ошибочны вне зависимости от размера CHAR_BIT, будь он равен 8 или например 12, 16, или 32. Даже если на одной платформе под хранение перечисления понадобится несколько байт, а на другой хватит одного.

_NN>А так возможно ?

_NN>Битовые поля оставим в покое
А не получится отставить в стороне. Потому что в при декларации такого перечисления bitfield уже возник по стандарту.
И как видно по ссылке, компиляторы об этом тоже знают и выдают соответствующую диагностику.

_NN>Разве не sizeof(char) является минимальным размером переменной ?

padding — это отдельная тема.
Это место нельзя безопасно использовать, хотя оно и появляется, когда битовые поля выравниваются на границу байтов или когда обычные переменные в структурах выравниваются по своим правилам на границу aliginof (прошу прощения за тавтологию)
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.