core: added enum-as-bitmask support for enum classes (thanks to @TerensTare for the suggestion)
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
* [Tag](#tag)
|
||||
* [Type list and value list](#type-list-and-value-list)
|
||||
* [Compressed pair](#compressed-pair)
|
||||
* [Enum as bitmask](#enum-as-bitmask)
|
||||
* [Utilities](#utilities)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
@@ -667,6 +668,64 @@ pair.first() = 42;
|
||||
There isn't much to describe then. It's recommended to rely on documentation and
|
||||
intuition. At the end of the day, it's just a pair and nothing more.
|
||||
|
||||
# Enum as bitmask
|
||||
|
||||
Sometimes it's useful to be able to use enums as bitmasks. However, enum classes
|
||||
aren't really suitable for the purpose out of the box. Main problem is that they
|
||||
don't convert implicitly to their underlying type.<br/>
|
||||
All that remains is to make a choice between using old-fashioned enums (with all
|
||||
their problems that I don't want to discuss here) or writing _ugly_ code.
|
||||
|
||||
Fortunately, there is also a third way: adding enough operators in the global
|
||||
scope to treat enum classes as bitmask transparently.<br/>
|
||||
The ultimate goal is to be able to write code like the following (or maybe
|
||||
something more meaningful, but this should give a grasp and remain simple at the
|
||||
same time):
|
||||
|
||||
```cpp
|
||||
enum class my_flag {
|
||||
unknown = 0x01,
|
||||
enabled = 0x02,
|
||||
disabled = 0x04
|
||||
};
|
||||
|
||||
const my_flag flags = my_flag::enabled;
|
||||
const bool is_enabled = !!(flags & my_flag::enabled);
|
||||
```
|
||||
|
||||
The problem with adding all operators to the global scope is that these will
|
||||
come into play even when not required, with the risk of introducing errors that
|
||||
are difficult to deal with.<br/>
|
||||
However, C++ offers enough tools to get around this problem. In particular, the
|
||||
library requires users to register all enum classes for which bitmask support
|
||||
should be enabled:
|
||||
|
||||
```cpp
|
||||
template<>
|
||||
struct entt::enum_as_bitmask<my_flag>
|
||||
: std::true_type
|
||||
{};
|
||||
```
|
||||
|
||||
This is handy when dealing with enum classes defined by third party libraries
|
||||
and over which the users have no control. However, it's also verbose and can be
|
||||
avoided by adding a specific value to the enum class itself:
|
||||
|
||||
```cpp
|
||||
enum class my_flag {
|
||||
unknown = 0x01,
|
||||
enabled = 0x02,
|
||||
disabled = 0x04,
|
||||
_entt_enum_as_bitmask
|
||||
};
|
||||
```
|
||||
|
||||
In this case, there is no need to specialize the `enum_as_bitmask` traits, since
|
||||
`EnTT` will automatically detect the flag and enable the bitmask support.<br/>
|
||||
Once the enum class has been registered (in one way or the other) all the most
|
||||
common operators will be available, such as `&`, `|` but also `&=` and `|=`.
|
||||
Refer to the official documentation for the full list of operators.
|
||||
|
||||
# Utilities
|
||||
|
||||
It's not possible to escape the temptation to add utilities of some kind to a
|
||||
|
||||
129
src/entt/core/enum.hpp
Normal file
129
src/entt/core/enum.hpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#ifndef ENTT_CORE_FLAG_HPP
|
||||
#define ENTT_CORE_FLAG_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Enable bitmask support for enum classes.
|
||||
* @tparam Type The enum type for which to enable bitmask support.
|
||||
*/
|
||||
template<typename Type, typename = void>
|
||||
struct enum_as_bitmask: std::false_type {};
|
||||
|
||||
|
||||
/*! @copydoc enum_as_bitmask */
|
||||
template<typename Type>
|
||||
struct enum_as_bitmask<Type, std::void_t<decltype(Type::_entt_enum_as_bitmask)>>: std::true_type {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam Type The enum class type for which to enable bitmask support.
|
||||
*/
|
||||
template<typename Type>
|
||||
inline constexpr bool enum_as_bitmask_v = enum_as_bitmask<Type>::value;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Operator available for enums for which bitmask support is enabled.
|
||||
* @tparam Type Enum class type.
|
||||
* @param lhs The first value to use.
|
||||
* @param rhs The second value to use.
|
||||
* @return The result of invoking the operator on the underlying types of the
|
||||
* two values provided.
|
||||
*/
|
||||
template<typename Type>
|
||||
[[nodiscard]] constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type>
|
||||
operator|(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
|
||||
return Type{static_cast<std::underlying_type_t<Type>>(lhs) | static_cast<std::underlying_type_t<Type>>(rhs)};
|
||||
}
|
||||
|
||||
|
||||
/*! @copydoc operator| */
|
||||
template<typename Type>
|
||||
[[nodiscard]] constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type>
|
||||
operator&(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
|
||||
return Type{static_cast<std::underlying_type_t<Type>>(lhs) & static_cast<std::underlying_type_t<Type>>(rhs)};
|
||||
}
|
||||
|
||||
|
||||
/*! @copydoc operator| */
|
||||
template<typename Type>
|
||||
[[nodiscard]] constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type>
|
||||
operator^(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
|
||||
return Type{static_cast<std::underlying_type_t<Type>>(lhs) ^ static_cast<std::underlying_type_t<Type>>(rhs)};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Operator available for enums for which bitmask support is enabled.
|
||||
* @tparam Type Enum class type.
|
||||
* @param value The value to use.
|
||||
* @return The result of invoking the operator on the underlying types of the
|
||||
* value provided.
|
||||
*/
|
||||
template<typename Type>
|
||||
[[nodiscard]] constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type>
|
||||
operator~(const Type value) ENTT_NOEXCEPT {
|
||||
return Type{~static_cast<std::underlying_type_t<Type>>(value)};
|
||||
}
|
||||
|
||||
|
||||
/*! @copydoc operator~ */
|
||||
template<typename Type>
|
||||
[[nodiscard]] constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, bool>
|
||||
operator!(const Type value) ENTT_NOEXCEPT {
|
||||
return !static_cast<std::underlying_type_t<Type>>(value);
|
||||
}
|
||||
|
||||
|
||||
/*! @copydoc operator| */
|
||||
template<typename Type>
|
||||
constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type &>
|
||||
operator|=(Type &lhs, const Type rhs) ENTT_NOEXCEPT {
|
||||
return (lhs = (lhs | rhs));
|
||||
}
|
||||
|
||||
|
||||
/*! @copydoc operator| */
|
||||
template<typename Type>
|
||||
constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type &>
|
||||
operator&=(Type &lhs, const Type rhs) ENTT_NOEXCEPT {
|
||||
return (lhs = (lhs & rhs));
|
||||
}
|
||||
|
||||
|
||||
/*! @copydoc operator| */
|
||||
template<typename Type>
|
||||
constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type &>
|
||||
operator^=(Type &lhs, const Type rhs) ENTT_NOEXCEPT {
|
||||
return (lhs = (lhs ^ rhs));
|
||||
}
|
||||
|
||||
|
||||
/*! @copydoc operator| */
|
||||
template<typename Type>
|
||||
[[nodiscard]] constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type>
|
||||
operator==(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
|
||||
return (static_cast<std::underlying_type_t<Type>>(lhs) == static_cast<std::underlying_type_t<Type>>(rhs));
|
||||
}
|
||||
|
||||
|
||||
/*! @copydoc operator| */
|
||||
template<typename Type>
|
||||
[[nodiscard]] constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type>
|
||||
operator!=(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "core/any.hpp"
|
||||
#include "core/attribute.h"
|
||||
#include "core/compressed_pair.hpp"
|
||||
#include "core/enum.hpp"
|
||||
#include "core/family.hpp"
|
||||
#include "core/hashed_string.hpp"
|
||||
#include "core/ident.hpp"
|
||||
|
||||
@@ -164,6 +164,7 @@ endif()
|
||||
SETUP_BASIC_TEST(algorithm entt/core/algorithm.cpp)
|
||||
SETUP_BASIC_TEST(any entt/core/any.cpp)
|
||||
SETUP_BASIC_TEST(compressed_pair entt/core/compressed_pair.cpp)
|
||||
SETUP_BASIC_TEST(enum entt/core/enum.cpp)
|
||||
SETUP_BASIC_TEST(family entt/core/family.cpp)
|
||||
SETUP_BASIC_TEST(hashed_string entt/core/hashed_string.cpp)
|
||||
SETUP_BASIC_TEST(ident entt/core/ident.cpp)
|
||||
|
||||
67
test/entt/core/enum.cpp
Normal file
67
test/entt/core/enum.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/enum.hpp>
|
||||
#include <entt/core/type_traits.hpp>
|
||||
|
||||
enum class detected {
|
||||
foo = 0x01,
|
||||
bar = 0x02,
|
||||
quux = 0x04,
|
||||
_entt_enum_as_bitmask
|
||||
};
|
||||
|
||||
enum class registered {
|
||||
foo = 0x01,
|
||||
bar = 0x02,
|
||||
quux = 0x04
|
||||
};
|
||||
|
||||
template<>
|
||||
struct entt::enum_as_bitmask<registered>
|
||||
: std::true_type
|
||||
{};
|
||||
|
||||
TEST(Enum, Functionalities) {
|
||||
auto test = [](auto identity) {
|
||||
using enum_type = typename decltype(identity)::type;
|
||||
|
||||
ASSERT_TRUE(!!((enum_type::foo | enum_type::bar) & enum_type::foo));
|
||||
ASSERT_TRUE(!!((enum_type::foo | enum_type::bar) & enum_type::bar));
|
||||
ASSERT_TRUE(!((enum_type::foo | enum_type::bar) & enum_type::quux));
|
||||
|
||||
ASSERT_TRUE(!!((enum_type::foo ^ enum_type::bar) & enum_type::foo));
|
||||
ASSERT_TRUE(!((enum_type::foo ^ enum_type::foo) & enum_type::foo));
|
||||
|
||||
ASSERT_TRUE(!(~enum_type::foo & enum_type::foo));
|
||||
ASSERT_TRUE(!!(~enum_type::foo & enum_type::bar));
|
||||
|
||||
ASSERT_TRUE(enum_type::foo == enum_type::foo);
|
||||
ASSERT_TRUE(enum_type::foo != enum_type::bar);
|
||||
|
||||
enum_type value = enum_type::foo;
|
||||
|
||||
ASSERT_TRUE(!!(value & enum_type::foo));
|
||||
ASSERT_TRUE(!(value & enum_type::bar));
|
||||
ASSERT_TRUE(!(value & enum_type::quux));
|
||||
|
||||
value |= (enum_type::bar | enum_type::quux);
|
||||
|
||||
ASSERT_TRUE(!!(value & enum_type::foo));
|
||||
ASSERT_TRUE(!!(value & enum_type::bar));
|
||||
ASSERT_TRUE(!!(value & enum_type::quux));
|
||||
|
||||
value &= (enum_type::bar | enum_type::quux);
|
||||
|
||||
ASSERT_TRUE(!(value & enum_type::foo));
|
||||
ASSERT_TRUE(!!(value & enum_type::bar));
|
||||
ASSERT_TRUE(!!(value & enum_type::quux));
|
||||
|
||||
value ^= enum_type::bar;
|
||||
|
||||
ASSERT_TRUE(!(value & enum_type::foo));
|
||||
ASSERT_TRUE(!(value & enum_type::bar));
|
||||
ASSERT_TRUE(!!(value & enum_type::quux));
|
||||
};
|
||||
|
||||
test(entt::type_identity<detected>{});
|
||||
test(entt::type_identity<registered>{});
|
||||
}
|
||||
Reference in New Issue
Block a user