【问题标题】:Avoiding repetitive copy-paste of static_cast<size_t>(enum_type) for casting an enum class to its underlying type避免重复复制粘贴 static_cast<size_t>(enum_type) 以将枚举类转换为其基础类型
【发布时间】:2022-01-15 23:45:18
【问题描述】:

我的代码中有一个枚举类型,如下所示:

enum class GameConsts: size_t { NUM_GHOSTS = 4 };

我发现自己重复了所需的 static_cast 以获得 enum 值:

Ghost ghosts[static_cast<size_t>(GameConsts::NUM_GHOSTS)];
// and...
for(size_t i = 0; i < static_cast<size_t>(GameConsts::NUM_GHOSTS); ++i) { ... }

避免这种重复的static_cast 的最佳方法是什么?

在此ISO proposal discussion 中提出了允许实现强制转换运算符的选项,但似乎已放弃。

相关SO问题:Overloading cast operator for enum class


Davis Herring 在 C++23 添加的评论中添加:std::to_underlying 当我们支持 C++23 编译器时,这应该是答案。但问题是针对 C++20 的。

【问题讨论】:

  • 如果您将枚举转换为它们的底层类型,您是否考虑过可能您这样做太频繁了?那也许你真的想要 enum class 的类型安全?
  • @NicolBolas 这个问题来自我的学生,他们在 SO 中发布它有点害羞,所以我为他们做了,并在我给出的答案下方发布。我很乐意讨论答案,看看是否还有其他更好的选择。
  • 你能准确地展示for循环中的内容吗?通过范围迭代很可能可以轻松避免整个问题。
  • std::to_underlying 在二月份被添加到 C++23 中。
  • 好吧,如果某些枚举总是必须转换为size_t,那么它可能一开始就不应该是枚举。如果每次使用时总是需要强制转换枚举,那么拥有枚举有什么意义?

标签: c++ enums casting c++20 enum-class


【解决方案1】:

您的第一个选择是使用constexpr 而不是枚举:

constexpr size_t NUM_GHOSTS = 4;

你可以把它放在适当的上下文中,比如 GameConsts struct:

struct GameConsts {
    static constexpr size_t NUM_GHOSTS = 4;
};

那么就不需要强制转换了:

Ghost ghosts[GameConsts::NUM_GHOSTS];

如果您确实需要一个枚举,因为您有一个要一起管理的相关值的列表,那么您可以使用无范围的枚举(即从enum class 中删除class 并使用普通的普通-旧的enum) 但将其放在结构中以保留上下文。我将在这里使用另一个枚举的示例,管理几个相关的值。

enum class GameKeys: char { UP = 'W', RIGHT = 'D', DOWN = 'X', LEFT = 'A' };

static_cast 的重复使用可能会发生在上述枚举中,在这样的 switch-case 中:

char key_pressed;
// ...
switch(key_pressed) {
    case static_cast<char>(GameKeys::UP): // ...
        break;
    case static_cast<char>(GameKeys::RIGHT): // ...
        break;
    case static_cast<char>(GameKeys::DOWN): // ...
        break;
    case static_cast<char>(GameKeys::LEFT): // ...
        break;
}

为避免重复使用static_cast,您可以使用:


选项 1:在结构中使用简单的无范围枚举

struct GameKeys {
    enum: char { UP = 'W', RIGHT = 'D', DOWN = 'X', LEFT = 'A' };
};

而且由于旧式枚举可以隐式转换为其基础类型,您可以摆脱转换:

switch(key_pressed) {
    case GameKeys::UP: // ...
        break;
    case GameKeys::RIGHT: // ...
        break;
    case GameKeys::DOWN: // ...
        break;
    case GameKeys::LEFT: // ...
        break;
}

选项 2:添加自己的转换函数

如果您确实喜欢或必须使用enum class,您可能有一个简单的转换函数,复制粘贴只是调用该函数,比完整的static_cast 语法更简单:

enum class GameKeys: char { UP = 'W', RIGHT = 'D', DOWN = 'X', LEFT = 'A' };

// a simple "val" function - specific for our GameKeys
constexpr char val(GameKeys key) { return static_cast<char>(key); }

还有:

switch(key_pressed) {
    case val(GameKeys::UP): // ...
        break;
    case val(GameKeys::RIGHT): // ...
        break;
    case val(GameKeys::DOWN): // ...
        break;
    case val(GameKeys::LEFT): // ...
        break;
}

如果您选择最后一个选项,您可能希望将其推广到任何类型的enum,使用this code

// creating a "concept" for enums
template<typename E>
concept EnumType = std::is_enum_v<E>;

// creating a generic "val" function for getting the underlying_type value of an enum
template<EnumType T>
constexpr auto val(T value) {
    return static_cast<std::underlying_type_t<T>>(value);
}

选项 3:投射到枚举而不是从枚举

根据 cmets 中的 @Nathan Pierson@apple apple 的建议,可以使用 this code 对枚举进行强制转换:

char key_pressed = 'E';
// cast to the enum
GameKeys key = static_cast<GameKeys>(key_pressed);
switch(key) {
    case GameKeys::UP: // ...
        break;
    case GameKeys::RIGHT: // ...
        break;
    case GameKeys::DOWN: // ...
        break;
    case GameKeys::LEFT: // ...
        break;
    default: // ignore any other keys
        break;
}

即使key_pressed 不是任何枚举值,这也应该可以正常工作,因为我们有一个固定的枚举(具有声明的基础类型,请注意枚举类始终是固定的,即使没有明确声明)。另见:What happens if you static_cast invalid value to enum class?

【讨论】:

  • 为什么首先在switch 语句中需要static_cast&lt;char&gt;?您已经可以在枚举上switch
  • imo 你应该在这里将char 转换为GameKeys,而不是反过来。
  • @NathanPierson key_pressed 字符可能是任何其他击键(不是来自 4 个有效的枚举值),在这种情况下应被忽略。因此,将按下的字符转换为枚举可能失败,我相信代码不会变得更简单或更好。
  • @AmirKirsh 在这种情况下不会失败stackoverflow.com/questions/18195312/…;
  • @appleapple 这是一个很好的参考,我会用一个额外的选项将它应用到我的答案中。谢谢。
【解决方案2】:

如果您不断地将范围枚举转换为整数...您实际上并不想要范围枚举。你似乎想要的是一堆 static constexpr 在它们自己的命名空间中的变量:

struct GameConsts
{
  static constexpr size_t NumGhosts = 4;
  ...
};

【讨论】:

    【解决方案3】:

    看来您不需要范围枚举。随便写

    namespace GameConsts {
        constexpr std::size_t NUM_GHOSTS = 4;
        // Add 'inline' if you can use C++17 or later modes and
        // don't want the declaration to result in multiple objects.
    }
    

    struct GameConsts {
        static constexpr std::size_t NUM_GHOSTS = 4;
    };
    

    std::to_underlying的替换

    C++20

    在 C++20 中你可以写

    template<class E>
    constexpr std::underlying_type_t<E> to_underlying(E e) noexcept
    {
        return static_cast<std::underlying_type_t<E>>(e);
    }
    

    此实现受到严格限制,因为自 C++20 起,std::underlying_type 必须对 SFINAE 友好。

    C++11

    如果您希望将实现反向移植到 C++11(第一个具有范围枚举的 C++ 标准修订版),也许您应该编写此代码以避免 std::underlying_type 中的未定义行为。

    template<class T, bool = std::is_enum<T>::value>
    struct aux_underlying_type {};
    template<class E>
    struct aux_underlying_type<E, true> {
        static_assert(bool(sizeof(E)), "The template parameter must be a complete enumeration type.");
        using type = typename std::underlying_type<E>::type;
    };
    
    template<class E>
    using aux_underlying_type_t = typename aux_underlying_type<E>::type;
    
    template<class E>
    constexpr aux_underlying_type_t<E> to_underlying(E e) noexcept
    {
        return static_cast<aux_underlying_type_t<E>>(e);
    }
    

    【讨论】:

      猜你喜欢
      • 2013-01-13
      • 2012-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-07
      相关资源
      最近更新 更多