Безопасно ли повторно интерпретировать переменную класса enum для ссылки на базовый тип?

я видел reinterpret_cast Я использовал для приращения классов enum, и я хотел бы знать, является ли это использование приемлемым в стандарте C ++.

enum class Foo : int8_t
{
Bar1,
Bar2,
Bar3,
Bar4,

First = Bar1,
Last = Bar4
};

for (Foo foo = Foo::First; foo <= Foo::Last; ++reinterpret_cast<int8_t &>(foo))
{
...
}

Я знаю, что приведение к ссылке на базовый класс безопасно в случае тривиальных классов. Но поскольку enum-классы не являются событиями, неявно преобразуемыми в их базовые типы, я не уверен, будет ли гарантированно работать код, описанный выше, и работать во всех компиляторах. Есть какие-нибудь подсказки?

13

Решение

Возможно, вы захотите перегрузить оператор ++ для вашего перечисления, если вы действительно хотите повторить его значения:

Foo& operator++( Foo& f )
{
using UT = std::underlying_type< Foo >::type;
f = static_cast< Foo >( static_cast< UT >( f ) + 1 );
return f;
}

и использовать

for (Foo foo = Foo::First; foo != Foo::Last; ++foo)
{
...
}

Чтобы ответить на вопрос о том, reinterpret_cast разрешено, все начинается с 5.2.10 / 1:

5.2.10 Переинтерпретация приведения [expr.reinterpret.cast]

1 Результат выражения reinterpret_cast<T>(v) является результатом преобразования выражения v печатать T, Если T тип ссылки lvalue или ссылка rvalue на тип функции, результат — значение lvalue; если T является rvalue ссылкой на тип объекта, результат — xvalue; в противном случае результатом является значение prvalue и стандартные преобразования преобразования lvalue-to-rvalue (4.1), array-to-pointer (4.2) и function-to-pointer (4.3) выполняются в выражении v, Преобразования, которые могут быть выполнены явно с использованием reinterpret_cast перечислены ниже. Никакое другое преобразование не может быть выполнено явно, используя reinterpret_cast,

(акцент мой)

Реинтерпретация с использованием ссылок основана на указателях в соответствии с 5.2.10 / 11:

11 Выражение типа glvalue T1 может быть приведен к типу «ссылка на T2Если выражение типа «указатель на T1»Можно явно преобразовать в тип« указатель на T2» используя reinterpret_cast, Результат ссылается на тот же объект, что и источник glvalue, но с указанным типом. [ Замечания: То есть, для lvalues, эталонный состав reinterpret_cast<T&>(x) имеет тот же эффект, что и преобразование *reinterpret_cast<T*>(&x) со встроенным & а также * операторы (и аналогично для reinterpret_cast<T&&>(x)). — конечная нота ] Временное создание не производится, копирование не производится, а конструкторы (12.1) или функции преобразования (12.3) не вызываются.

Что превращает вопрос из этого:

reinterpret_cast<int8_t&>(foo)

Насколько это законно:

*reinterpret_cast<int8_t*>(&foo)

Следующая остановка — 5.2.10 / 7:

7 Указатель на объект может быть явно преобразован в указатель на объект другого типа. Когда prvalue v типа «указатель на T1»Преобразуется в тип« указатель на резюме T2», Результат static_cast<cvT2*>(static_cast<cvvoid*>(v)) если оба T1 а также T2 стандартные типы (3.9) и требования к выравниванию T2 не более строгие, чем те, T1или если какой-либо тип void, Преобразование значения типа «указатель на T1Указатель на тип T2» (где T1 а также T2 являются типами объектов и где требования выравнивания T2 не более строгие, чем те, T1) и возврат к исходному типу возвращает исходное значение указателя. Результат любого другого такого преобразования указателя не определен.

Учитывая 3.9 / 9 оба int8_t и ваш тип перечисления являются стандартными типами разметки, в который вопрос теперь преобразован:

*static_cast<int8_t*>(static_cast<void*>(&foo))

Это где вам не повезло. static_cast определено в 5.2.9, и нет ничего, что делает вышеупомянутое законным — фактически 5.2.9 / 5 является явным намеком на то, что это незаконно. Другие пункты не помогают:

  • 5.2.9 / 13 требует T* -> void* -> T* где T должно быть идентичным резюме)
  • 5.2.9 / 9 и 5.2.9 / 10 не об указателях, а о значениях
  • 5.2.9 / 11 о классах и иерархиях классов
  • 5.2.9 / 12 о указателях членов класса

Мой вывод из этого заключается в том, что ваш код

reinterpret_cast<int8_t&>(foo)

не является законным, его поведение не определяется стандартом.

Также обратите внимание, что вышеупомянутые 5.2.9 / 9 и 5.2.9 / 10 несут ответственность за то, чтобы сделать код законным, который я дал в первоначальном ответе и который вы все еще можете найти в верхней части.

13

Другие решения

Приращение получает доступ к значению foo через lvalue другого типа, что является неопределенным поведением, за исключением случаев, перечисленных в 3.10 [basic.lval]. Типы перечисления и их базовые типы не включены в этот список, поэтому код имеет неопределенное поведение.

С некоторыми компиляторами, которые поддерживают нестандартные расширения, вы можете сделать это через type-punning:

union intenum
{
int8_t i;
Foo    e;
};

intenum ie;
for (ie.e = Foo::First; ie.e <= Foo::Last; ++ie.i)
// ...

но это тоже не переносимо, потому что доступ intenum::i после сохранения значения в intenum::e не допускается стандартом.

Но почему бы просто не использовать целое число и конвертировать по мере необходимости?

for (int8_t i = static_cast<int8_t>(Foo::First);
i <= static_cast<int8_t>(Foo::Last);
++i)
{
Foo e = static_cast<Foo>(i);
// ...
}

Это портативный и безопасный.

(Это все еще не очень хорошая идея, IMHO, потому что может быть несколько перечислителей с одинаковым значением или значений типа перечисления, которые не имеют соответствующей метки перечислителя.)

1

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

Если базовый тип класса enum изменяется, ++reinterpret_cast<int8_t &>(foo) молча ломается

Более безопасная версия:

foo = static_cast<Foo>(static_cast<std::underlying_type<Foo>::type>(foo) + 1);

Или же,

++reinterpret_cast<std::underlying_type<Foo>::type&>(foo);
0
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector