Могу ли я безопасно преобразовать число в перечисление с резервным значением?
Я хотел бы написать что-то вроде:
template <typename E, E fallback>
E safe_cast_to_enum(std::underlying_type_t<E> e);
который для класса перечисления (или просто перечисления?) E приводитe
до соответствующего значенияE
, если такое значение существует, и оценить в противном случае.
Могу ли я сделать это в целом?
Примечания:
- Я подозреваю, что ответ будет отрицательным, по крайней мере, до C++20, но независимо от этого - пожалуйста (также) ответьте, используя самую раннюю версию C++, с которой это возможно.
- Если хотите, можете предположить
fallback
является «действительным» значением перечисления, т. е. имеет связанный именованный идентификатор. - Если вы можете предложить решение, основанное на несколько более строгих предположениях (размер основного типа, характер набора значений, охватываемых идентификаторами перечисления, известное последнее значение и т. д.), но которое по-прежнему является общим шаблоном, а не другой реализацией для каждого перечисления - это полезный, хотя и несовершенный ответ.
Смотрите также:
1 ответ
Давайте создадим функцию, которая находит имена перечислений:
template<auto value>
requires std::is_enum_v<std::decay_t<decltype(value)>>
consteval std::string_view enum_name() {
constexpr static auto location=std::source_location::current();
constexpr static std::string_view full_name= location.function_name();
constexpr static auto closeb = full_name.rfind('>');
constexpr static auto openb = full_name.find("<", 0, closeb);
return full_name.substr(openb, closeb - openb);
};
Далее мы должны убедиться, что результат вышеуказанной функции не является выражением преобразования. Эта часть сильно зависит от платформы:
constexpr bool is_enum_name(std::string_view name){
if (auto brace = name.find_last_of("})"); brace==name.npos)
return true;
else
return name.find(':',brace)!=name.npos;
};
Выражение преобразования невозможно без фигурных скобок. Если после последней закрывающей скобки есть оператор области действия (::
), то оно должно быть частьюenum class
квалификатор имени; в противном случае произошло преобразование. Далее нам нужно поместить имена и валидаторы в массив constexpr. Сейчас мы можем делать разные вещи; Я предпочитаю хранить необработанные значения:
template<auto value>
requires std::is_enum_v<std::decay_t<decltype(value)>>
using enum_limits =
std::numeric_limits<
std::underlying_type_t<
std::decay_t< decltype(value)
>>>;
template<typename enm>
requires std::is_enum_v<enm>
constexpr auto enum_strings = []<std::size_t ...i>{
return std::array{
enum_name< enm{ static_cast<
std::underlying_type_t<enm>>(i)
}>()...};
}( std::make_index_sequence
< std::min(1<<14
, std::size_t
{ std::enum_limits<enm{}>::max()
- std::enum_limits<enm{}>::min()
})>);
Теперь вы можете использовать приведенный выше LUT по своему вкусу. Можно создать массив валидаторов изbool
тоже, но я пока оставляю это. Также возможно использоватьstd::optional
или хранить пустоstring_view
для недопустимых значений.
template<auto value>
requires std::is_enum_v<std::decay_t<decltype(value)>>
constexpr decltype(value)
to_enum( std::underlying_type_t
< std::decay_t
< decltype(value)>> num){
if (std::bit_cast<std::size_t>(num)
auto idx = static_cast<std::size_t>(num)
- enum_limits<value>::min();
return is_enum_name
( enum_string
< std::decay_t
< decltype(value)
>>[idx] )
? decltype(value){num}
: value;
};
Я не тестировал этот код; так что, вероятно, есть много ошибок и ошибок. Но это происходит, когда в качестве ответа создается библиотека только для заголовков. Но это может дать представление о том, как могут быть отражены некоторые основные метаданные о перечислениях. Это также довольно уродливо и требует большого рефакторинга для удобства чтения.