Что на самом деле делает статическое приведение перечислителя T к std::underlying_type<T>?

Я изучаю современный C++ и сейчас читаю о шаблонах и автоматическом выводе типов.

В качестве задачи я попытался написать функцию из книги для решения проблемы, и в итоге она очень незначительно отличалась от того, что предлагалось в книге.

Упражнение: Напишите функцию-шаблон, которая возвращает целое значение перечисления.

Моя функция:

template<typename T>
constexpr auto EnumToIntegral(T enumerator) noexcept
{
    return std::underlying_type_t<T>(enumerator);
}

Функция книги:

template<typename T>
constexpr auto EnumToIntegral(T enumerator) noexcept
{
    return static_cast<std::underlying_type_t<T>>(enumerator);
}

Единственная разница здесь — это static_cast, смысл которого я не могу понять. Обе функции компилируются и выполняются как положено.

Глядя на другие фрагменты сообщений, посвященных получению целочисленных перечисляемых значений, я также вижу использование static_cast, но мне не удалось найти для этого никаких оснований.

Я пробовал и с static_cast, и без него, но результат один и тот же. Значения перечисления имеют тип int, а функция, использующая этот результат, — std::get.

Некоторые люди предпочитают static_cast вместо «омни-приведения» большого молотка (приведение в стиле C — даже если вы используете приведение в стиле «функция», это все равно приведение в стиле C).

Eljay 07.04.2024 21:16

ОТ: стандартное решение — std::to_underlying, если у вас C++23

Artyer 07.04.2024 21:17

Первая (ваша версия) — это приведение в стиле C. Старый, который не рекомендуется использовать в C++. По сути, вы выполняете (int)MyEnum, который преобразует константу перечислителя в ее значение int. Второй, используя static_cast, выполняет «правильное» приведение константы перечислителя к int.

TheNomad 07.04.2024 21:18

Спасибо, это имеет смысл! Должен ли я ответить на свой вопрос, чтобы закрыть это?

Horace Ness 07.04.2024 21:19

@HoraceNess Хотя это и не реальная ситуация с вашим вопросом, поскольку примитивы «просты» (и вам вполне может сойти с рук приведение в стиле C), если вы хотите прочитать о реальных различиях на практике, связанных с приведением типов объектов, вот материал для ночного чтения: Когда следует использовать static_cast, Dynamic_cast, const_cast и reinterpret_cast? и Обычный каст, static_cast и динамический_cast . Это может расширить ваши взгляды на каждую ситуацию и использовать их, когда они вам понадобятся, в зависимости от сценария.

TheNomad 07.04.2024 21:23

Главное, что он делает, это меняет тип выражения на std::underlying_type<T> ;-). если вы спросите: «Что он делает такого, чего не делает приведение в стиле C», то ответ будет «ничего». Приведение в стиле C здесь является статическим.

Peter - Reinstate Monica 07.04.2024 21:23
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
96
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Основными приведениями в C++ являются static_cast, const_cast, reinterpret_cast и dynamic_cast.

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

В языке также существует явное преобразование типов (в нотации приведения), более известное как «приведение в стиле C» для формы синтаксиса, полученной из C. Однако, за одним небольшим исключением, это всего лишь комбинация ранее упомянутых типов.

Такое выражение приведения выполнит первую возможную последовательность приведения из следующего списка:

  • const_cast
  • static_cast
  • static_cast, а затем const_cast
  • reinterpret_cast
  • reinterpret_cast, а затем const_cast

Кроме того, это выражение приведения позволяет static_cast в этом выборе игнорировать доступность базовых классов, т. е. оно может преобразовать указатель производного класса в указатель частного базового класса, что не позволяет ни одна другая конструкция в языке.

Итак, приведения в стиле C опасны, потому что они могут привести почти все ко всему остальному с трудом, чтобы быть уверенным в семантике и избежать ошибок.

Проблема теперь в том, что существуют две разные формы синтаксиса для явного преобразования типов, которые разделяют семантику приведения в стиле C:

  • (T)e (обозначение приведения, также известное как «приведение в стиле C»)
  • T(e) (функциональное обозначение)

где T — целевой тип и e исходное выражение. Первая форма широко известна, поскольку именно ее использует синтаксис C. Однако второй вариант, специфичный для C++, по-прежнему имеет ту же семантику явного преобразования типов, что и приведение типов в стиле C, но сбивает с толку, поскольку он похож на

  • T(e1, e2)
  • T()
  • T{e}

где e1 и e2 снова являются выражениями. Хотя формально выражения преобразования типов все еще являются явными, они не выполняют последовательность приведения, как упоминалось выше, а вместо этого получают результат посредством прямой инициализации объявления воображаемой переменной той же формы. Только если одно выражение используется в круглых, а не фигурных скобках, это явное преобразование типа с упомянутыми выше полномочиями.

Итак, если кто-то хочет последовательно избегать приведения в стиле C, ему также следует избегать выражений формы T(e).

Но это то, что вы используете. static_cast более очевиден и указывает, какой фундаментальный тип приведения вам нужен, а не то, что компилятор должен попытаться использовать список, упомянутый выше, и потенциально сделать неправильный выбор.

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

На самом деле форма T{e} (инициализация прямого списка), как правило, даже слабее, чем static_cast, но все же сильнее, чем неявное преобразование. Но здесь этого будет недостаточно, поскольку оно не позволяет выполнять преобразование для типов перечислений с ограниченной областью действия.

(За исключением инициализации с помощью фигурных скобок, все это не имеет ничего общего с современными функциями C++, кстати. Эти правила присутствовали со времен первого стандарта C++ C++98.)

Приведения в стиле C — это не столько абстракция, сколько путаница более конкретных приведений.

Spencer 07.04.2024 21:57

@Spencer Да, это был неправильный выбор слов.

user17732522 07.04.2024 21:58
T(e) — это не актерский состав в стиле C, если вы это предлагаете. Приведения функциональных стилей не существуют в C. Редактировать: я прочитал это немного более внимательно и не думаю, что вы это предлагаете, но это не на 100% ясно.
Ted Lyngmo 07.04.2024 21:59

@TedLyngmo Нет, это так. Кажется, это малоизвестно (по понятным причинам, как я уже упоминал в своем ответе): eel.is/c++draft/expr.type.conv#2.sentence-1 Например, using T = int&; float x = 0; int& y = T(x); скомпилирует и вызовет UB при использовании y, потому что это reinterpret_cast. Обычно это не так уж и плохо, поскольку функциональная нотация не позволяет использовать спецификаторы составного типа непосредственно в синтаксисе.

user17732522 07.04.2024 22:02

@TedLyngmo Я не говорю, что он существует на C, кстати. Я использую «приведение в стиле C» для обозначения семантики «явного преобразования типов», как это называется в стандарте. Я думаю, что «приведение в стиле C» можно также интерпретировать как конкретную синтаксическую форму, используемую в C, а не как лежащую в ее основе семантику в C++.

user17732522 07.04.2024 22:07

Да, понял. Это эквивалентно приведению в стиле C, поэтому, возможно, его можно было бы назвать эквивалентом приведения в стиле C или чем-то подобным, чтобы не создавать впечатления, что приведение действительно работает в C.

Ted Lyngmo 07.04.2024 22:09

Другие вопросы по теме