我可以安全地将数字转换为枚举并带有后备值吗?

问题描述 投票:0回答:2

我想写这样的东西:

template <typename E, E fallback>
E safe_cast_to_enum(std::underlying_type_t<E> e);

对于枚举类(或只是枚举?)E,将

e
转换为
E
的对应值(如果存在这样的值),否则转换为
fallback
值。

我可以一般地这样做吗?

备注:

  • 我怀疑至少在 C++20 之前答案是否定的,但无论如何 - 请(也)使用可以实现此目的的最早的 C++ 版本来回答。
  • 如果您愿意,您可以假设
    fallback
    是枚举的“有效”值,即它具有关联的命名标识符。
  • 如果您可以提供基于更强假设的解决方案(基础类型大小、枚举标识符覆盖的值集的性质、已知的最后一个值等),但它仍然是一个通用模板,而不是不同的每个枚举实施 - 这是一个有用的答案,尽管不完美。

另请参阅:

c++ reflection enums casting static-reflection
2个回答
1
投票

从 C++23 开始,不存在获取枚举有效值列表的标准机制。

但是,可以使用一些极其糟糕的编译器特定代码来提取此信息。

magic_enum库为主流编译器实现了此代码。


0
投票

让我们创建一个查找枚举名称的函数:

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 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<auto value>
requires std::is_enum_v<std::decay_t<decltype(value)>>
constexpr auto enum_strings = []<std::size_t ...i>{
    using value_type = std::decay_t<decltype(value)>;
    return std::array{
           enum_name<
           value_type{
           static_cast<
           std::underlying_type_t<
           value_type>(i)
}>()...};
}( std::make_index_sequence
 < std::min(1<<14
 , std::size_t
 { std::enum_limits<value>::max()
 - std::enum_limits<value>::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[idx] )
        ? decltype(value){num}
        : value;
};

我还没有对此代码进行测试;所以可能存在很多错误和错误。当仅包含头文件的库作为答案时会发生这种情况。但它可以提供关于如何反映有关枚举的一些基本元数据的想法。这也相当丑陋,需要大量重构以提高可读性。

© www.soinside.com 2019 - 2024. All rights reserved.