避免重复复制粘贴 static_cast<size_t>(enum_type) 以将枚举 class 转换为其基础类型
Avoiding repetitive copy-paste of static_cast<size_t>(enum_type) for casting an enum class to its underlying type
我的代码中有一个枚举类型,如下所示:
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 问题:
Davis Herring 在 C++23 添加的评论中添加:std::to_underlying 当我们有 C++23 编译器支持时,这应该是答案。但问题是针对 C++20 的。
您的第一个选择是使用 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:投射到枚举而不是来自枚举
根据@Nathan Pierson and @apple apple in the comments, the casting can be to the enum, with 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
不是任何枚举值,这也应该可以正常工作,因为我们有一个固定的枚举(具有规定的基础类型,请注意枚举 class 始终是固定的,即使没有明确说明)。另见:What happens if you static_cast invalid value to enum class?
如果您经常将作用域枚举转换为整数...您实际上不需要作用域枚举。你似乎想要的是一堆 static constexpr
变量在他们自己的命名空间中:
struct GameConsts
{
static constexpr size_t NumGhosts = 4;
...
};
您似乎不需要作用域枚举。随便写
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);
}
我的代码中有一个枚举类型,如下所示:
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 问题:
Davis Herring 在 C++23 添加的评论中添加:std::to_underlying 当我们有 C++23 编译器支持时,这应该是答案。但问题是针对 C++20 的。
您的第一个选择是使用 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:投射到枚举而不是来自枚举
根据@Nathan Pierson and @apple apple in the comments, the casting can be to the enum, with 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
不是任何枚举值,这也应该可以正常工作,因为我们有一个固定的枚举(具有规定的基础类型,请注意枚举 class 始终是固定的,即使没有明确说明)。另见:What happens if you static_cast invalid value to enum class?
如果您经常将作用域枚举转换为整数...您实际上不需要作用域枚举。你似乎想要的是一堆 static constexpr
变量在他们自己的命名空间中:
struct GameConsts
{
static constexpr size_t NumGhosts = 4;
...
};
您似乎不需要作用域枚举。随便写
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);
}