Ну да, оператор сравнения для любых произвольных типов. Зачем тут шаблон если он в макрос тип передает? Сделать как и остальные без шаблона.
Вообще, страшная штука. Лучше не надо.
Здравствуйте, andrey.desman, Вы писали:
AD>Ну да, оператор сравнения для любых произвольных типов. Зачем тут шаблон если он в макрос тип передает? Сделать как и остальные без шаблона.
Да, это явно тупой проджоб
AD>Вообще, страшная штука. Лучше не надо.
Что именно не так с кодом? Ну, если считать, что шаблонный тип EnumType — это проджоб?
ЗЫ Политексный и сюда пробрался минусики ставить. Ты недавно в политике ничего не писал?
Здравствуйте, Marty, Вы писали:
AD>>Вообще, страшная штука. Лучше не надо. M>Что именно не так с кодом? Ну, если считать, что шаблонный тип EnumType — это проджоб?
Было похожее в твоей теме. Не нравится, что макросы и глобальное пространство.
Предпочитаю явный враппер
перечисление — это не набор констант, это не набор флагов, перечисление — это набор идентификаторов. Если это понять, то программировать станет намного легче:
namespace EEEconstants
{
inline constexpr int zero = 0;
inline constexpr int one = 1;
inline constexpr int two = 2;
};
struct CEEEconstant
{
inline constexpr static int zero = 0;
inline constexpr static int one = 1;
inline constexpr static int two = 2;
inline constexpr static std::array all = {zero, one, two};
};
static_assert(EEEconstants::zero == CEEEconstant::zero);
static_assert(EEEconstants::one < CEEEconstant::two);
struct MoveFileFlags_
{
inline constexpr static unsigned int copyAllowed = 1;
inline constexpr static unsigned int replaceExisting = 2;
inline constexpr static unsigned int overwrite = 2; //!< Same as replaceExistinginline constexpr static unsigned int writeThrough = 4;
inline constexpr static unsigned int validMask = copyAllowed | replaceExisting | overwrite | writeThrough;
};
Здравствуйте, vdimas, Вы писали:
BFE>>перечисление — это не набор констант, это не набор флагов, перечисление — это набор идентификаторов. V>Составляющих отдельную семантическую группу — перечислимый тип.
Рассмотрим структуру struct ABC { int a, b, c; }. В этой структуре перечислены поля a, b, c. Составляет ли поля struct ABC отдельную семантическую группу? Думаю — да. Означает ли это, что struct ABC — это перечислимый тип?
V>У тебя получилось наоборот — проще совершать ошибки, т.к. можно запросто перепутать константы из разных групп. )) V>Отказываться от типизации в языках, эту типизацию предоставляющую — такое себе...
Я так понял, что это и есть цель обсуждаемого кода:
Ну так у него это задано явно и намеренно. А в твоем подходе элементы разных перечислений сходу являются величинами совместимых типов, над которыми можно выполнять любые арифметические операции. Теряется типовая надежность.
BFE>PS А вы в курсе про потенциальное теоретическое UB при выходе за пределы диапазона значений enum class?
А эту проблему можно отправить отдыхать, явным заданием подходящего underlying type. А если к тому же в качестве допустимых операций рассматриваются только сравнение и битовые операции, то переполнению и вовсе неоткуда взяться.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, B0FEE664, Вы писали:
BFE>>>перечисление — это не набор констант, это не набор флагов, перечисление — это набор идентификаторов. V>>Составляющих отдельную семантическую группу — перечислимый тип. BFE>Рассмотрим структуру struct ABC { int a, b, c; }. В этой структуре перечислены поля a, b, c. Составляет ли поля struct ABC отдельную семантическую группу? Думаю — да. Означает ли это, что struct ABC — это перечислимый тип?
Типы образуются не только перечислением.
V>>У тебя получилось наоборот — проще совершать ошибки, т.к. можно запросто перепутать константы из разных групп. )) V>>Отказываться от типизации в языках, эту типизацию предоставляющую — такое себе... BFE>Я так понял, что это и есть цель обсуждаемого кода: BFE>
Не так.
Там битовые и арифметические унарные и бинарные операторы, которые сохраняют тип енумов.
А доп.операторы сравнения определены для енума и целых, имеющих тип underlying type.
BFE>PS А вы в курсе про потенциальное теоретическое UB при выходе за пределы диапазона значений enum class?
Однако, в случае битовых флагов этой практике столько же лет, сколько языку Си — ведь перечисляются отдельные флаги, а не все их возможные комбинаторные варианты.
Плюс еще есть трюк по использованию енумов как различимых (т.е. несовместимых) по типам целочисленных значений. Такой енум может не содержать при объявлении значений вовсе.
Ну и, в показанном коде макры независимы, т.е. для одних энумов можно определить только битовые операторы, для других только арифметические и т.д.
BFE>>
R>Ну так у него это задано явно и намеренно. А в твоем подходе элементы разных перечислений сходу являются величинами совместимых типов, над которыми можно выполнять любые арифметические операции. Теряется типовая надежность.
Нет при таком подходе никакой типовой надёжности! Вот такое скомпилируется:
UMBA_ENUM_CLASS_IMPLEMENT_UNDERLYING_TYPE_ARITHMETIC_OPERATORS(CEEE)
CEEE x = CEEE::two + 1;
при том, что в CEEE:
enum class CEEE
{
zero,
one,
two
};
нет значения three. Значит в x лежит невалидное значение. (Помимо формального UB см. ниже) это означает, что в коде нигде нельзя положиться на пришедшее значение CEEE. CEEE перестало отличаться от int. Считать, что x имеет тип CEEE — это обманывать себя и читателя.
BFE>>PS А вы в курсе про потенциальное теоретическое UB при выходе за пределы диапазона значений enum class? R>А эту проблему можно отправить отдыхать, явным заданием подходящего underlying type. А если к тому же в качестве допустимых операций рассматриваются только сравнение и битовые операции, то переполнению и вовсе неоткуда взяться.
Ээээ.... Вообще-то речь не про переполнение.
Если бы CEEE был объявлен как enum class CEEE : int... то тогда — да, UB нет, а вот без задания базового типа компилятор может полагаться на то, что есть только три значения. Да, я понимаю, что сами писатели стандарта пишут одно, а подразумевают другое, но формально это неопределённое поведение.
Здравствуйте, vdimas, Вы писали:
V>Там битовые и арифметические унарные и бинарные операторы, которые сохраняют тип енумов.
Нет, не сохраняют. В перечислении есть только те имена и значения, которые в нём заданы, а в результате этого кода перечисления становятся неотличимы от целых типов.
V>А доп.операторы сравнения определены для енума и целых, имеющих тип underlying type.
Не представляю зачем это может понадобиться.
BFE>>PS А вы в курсе про потенциальное теоретическое UB при выходе за пределы диапазона значений enum class? V>Однако, в случае битовых флагов этой практике столько же лет, сколько языку Си — ведь перечисляются отдельные флаги, а не все их возможные комбинаторные варианты.
Да, я давно призываю отказаться от этой порочной практики.
V>Плюс еще есть трюк по использованию енумов как различимых (т.е. несовместимых) по типам целочисленных значений. Такой енум может не содержать при объявлении значений вовсе.
Ну, да. Как std::byte. Кто-то им пользуется?
V>Ну и, в показанном коде макры независимы, т.е. для одних энумов можно определить только битовые операторы, для других только арифметические и т.д.
Определить-то можно, только это усложняет код неимоверно, если это делать как в вышеприведённом коде: при каждом изменении перечисления придётся проверять глазами все использования переменных этого типа.
Но главное даже не это. Главное, что такие операции не имеют смысла. Это всё равно, что считать, что если к яблоку прибавить 1, то получится апельсин, а если два — то груша.
Если вам нужно использовать отдельный арифметический тип, ну так используйте его:
struct CEEEconstant
{
inline constexpr static int zero = 0;
inline constexpr static int one = 1;
inline constexpr static int two = 2;
int value = zero;
};
Определите для него требуемые операции и будет понятно. А так, как в коде — это очень странно и нелогично.
enum class CEEE
{
zero,
one,
two
};
int main()
{
const CEEE eee = CEEE::two + 1; // error: no match for 'operator+' (operand types are 'CEEE' and 'int')
}
В устройство макроса я не вникал, если что. Я исходил из предположения, что он реализован через enum class.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, B0FEE664, Вы писали:
V>>Там битовые и арифметические унарные и бинарные операторы, которые сохраняют тип енумов. BFE>Нет, не сохраняют. В перечислении есть только те имена и значения, которые в нём заданы, а в результате этого кода перечисления становятся неотличимы от целых типов.
Верно, в этом и цель, если ты про набор операций.
А если про типизацию, то разница большая, конечно.
Бери условную сигнатуру:
ResultEnum someOp(Flags f, Ops o, Mode m);
В случае простых целых ты не защищён от того, чтобы подать флаги в опции, а режимы во флаги.
V>>А доп.операторы сравнения определены для енума и целых, имеющих тип underlying type. BFE>Не представляю зачем это может понадобиться.
Скорее всего, чтобы не вчитываться в такие сообщения компилятора:
<source>:15:31: error: no match for 'operator<' (operand types are 'SomeEnum' and 'int')
15 | bool r = SomeEnum::Value2 < 0;
| ~~~~~~~~~~~~~~~~ ^ ~
| | |
| SomeEnum int
BFE>>>PS А вы в курсе про потенциальное теоретическое UB при выходе за пределы диапазона значений enum class? V>>Однако, в случае битовых флагов этой практике столько же лет, сколько языку Си — ведь перечисляются отдельные флаги, а не все их возможные комбинаторные варианты. BFE>Да, я давно призываю отказаться от этой порочной практики.
Рядом тебе уже сказали, что в битовых операциях над флагами одного енума не может быть переполнения даже теоретически.
V>>Плюс еще есть трюк по использованию енумов как различимых (т.е. несовместимых) по типам целочисленных значений. Такой енум может не содержать при объявлении значений вовсе. BFE>Ну, да. Как std::byte. Кто-то им пользуется?
Таким трюком я иногда пользуюсь.
Это примерно как в первом сниппете, чтобы не оперировать "бестиповыми" интами.
V>>Ну и, в показанном коде макры независимы, т.е. для одних энумов можно определить только битовые операторы, для других только арифметические и т.д. BFE>Определить-то можно, только это усложняет код неимоверно, если это делать как в вышеприведённом коде: при каждом изменении перечисления придётся проверять глазами все использования переменных этого типа.
С какой целью проверять?
Что ты будешь при этом искать?
BFE>Но главное даже не это. Главное, что такие операции не имеют смысла. Это всё равно, что считать, что если к яблоку прибавить 1, то получится апельсин, а если два — то груша.
Почему нет? ))
На последовательном расположении значений енумов порой удобно выполнять диспетчеризацию в линейных массивах/таблицах.
BFE>Если вам нужно использовать отдельный арифметический тип, ну так используйте его: BFE>
BFE>struct CEEEconstant
BFE>{
BFE> inline constexpr static int zero = 0;
BFE> inline constexpr static int one = 1;
BFE> inline constexpr static int two = 2;
BFE> int value = zero;
BFE>};
BFE>
BFE>Определите для него требуемые операции и будет понятно. А так, как в коде — это очень странно и нелогично.
Тоже вариант, и тоже иногда использовался.
Но не всё было гладко.
Не исследовал, как оно сейчас, но в прошлом обёртка-структура над числами давали худший бинарный код, чем непосредственное использование типов-чисел.
Зато вариант с енумами давал идентичный код.
Плюс, надо не забыть прописать выравнивание структуры, чтобы работали правила упаковки полей С/С++, когда эти структуры используются как поля объектов.
Здравствуйте, B0FEE664, Вы писали:
BFE>нет значения three. Значит в x лежит невалидное значение.
Скорее всего, это хелпер, чтобы каждый раз не писать ручками так:
SomeEnum value = static_cast<SomeEnum>(static_cast<int>(SomeEnum::Value2) + 1);
(если в реальном коде такое применяется.)
Тогда, с точки зрения UB, разницы нет — расписываются ли приведения типов каждый раз, или единожды для данного enum
BFE>это означает, что в коде нигде нельзя положиться на пришедшее значение CEEE.
Это и сейчас так.
Компилятор ведь никак не гарантирует попадание значений enum в перечисленные оные при объявлении.
Для этого отродясь в некоторых местах ставились проверки, сколько себя помню. ))
BFE>CEEE перестало отличаться от int. Считать, что x имеет тип CEEE — это обманывать себя и читателя.
С этим, как раз, не спорят.
Если бы в языке был такой алиас, который порождал бы новый тип — эта задача решалась бы в одну строчку.
Есть языки, где можно написать условно так:
type volume = Float64;
type velocity = Float64;
И не складывать случайно в исходнике амперы с кельвинами.
R>>А если к тому же в качестве допустимых операций рассматриваются только сравнение и битовые операции, то переполнению и вовсе неоткуда взяться. BFE>Ээээ.... Вообще-то речь не про переполнение. BFE>Если бы CEEE был объявлен как enum class CEEE : int... то тогда — да, UB нет, а вот без задания базового типа компилятор может полагаться на то, что есть только три значения. Да, я понимаю, что сами писатели стандарта пишут одно, а подразумевают другое, но формально это неопределённое поведение.
Эти рассуждения понятны, но похоже, что ты не прочитал аргумент внимательно — в случае битовых операций ширина результата не меняется.