Могу ли я безопасно преобразовать число в перечисление с резервным значением?

Я хотел бы написать что-то вроде:

      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;
};

Я не тестировал этот код; так что, вероятно, есть много ошибок и ошибок. Но это происходит, когда в качестве ответа создается библиотека только для заголовков. Но это может дать представление о том, как могут быть отражены некоторые основные метаданные о перечислениях. Это также довольно уродливо и требует большого рефакторинга для удобства чтения.

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