Browse Source

core: added enum-as-bitmask support for enum classes (thanks to @TerensTare for the suggestion)

Michele Caini 4 years ago
parent
commit
4b629045c3
5 changed files with 257 additions and 0 deletions
  1. 59 0
      docs/md/core.md
  2. 129 0
      src/entt/core/enum.hpp
  3. 1 0
      src/entt/entt.hpp
  4. 1 0
      test/CMakeLists.txt
  5. 67 0
      test/entt/core/enum.cpp

+ 59 - 0
docs/md/core.md

@@ -28,6 +28,7 @@
     * [Tag](#tag)
     * [Tag](#tag)
     * [Type list and value list](#type-list-and-value-list)
     * [Type list and value list](#type-list-and-value-list)
 * [Compressed pair](#compressed-pair)
 * [Compressed pair](#compressed-pair)
+* [Enum as bitmask](#enum-as-bitmask)
 * [Utilities](#utilities)
 * [Utilities](#utilities)
 <!--
 <!--
 @endcond TURN_OFF_DOXYGEN
 @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
 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.
 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
 # Utilities
 
 
 It's not possible to escape the temptation to add utilities of some kind to a
 It's not possible to escape the temptation to add utilities of some kind to a

+ 129 - 0
src/entt/core/enum.hpp

@@ -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

+ 1 - 0
src/entt/entt.hpp

@@ -3,6 +3,7 @@
 #include "core/any.hpp"
 #include "core/any.hpp"
 #include "core/attribute.h"
 #include "core/attribute.h"
 #include "core/compressed_pair.hpp"
 #include "core/compressed_pair.hpp"
+#include "core/enum.hpp"
 #include "core/family.hpp"
 #include "core/family.hpp"
 #include "core/hashed_string.hpp"
 #include "core/hashed_string.hpp"
 #include "core/ident.hpp"
 #include "core/ident.hpp"

+ 1 - 0
test/CMakeLists.txt

@@ -164,6 +164,7 @@ endif()
 SETUP_BASIC_TEST(algorithm entt/core/algorithm.cpp)
 SETUP_BASIC_TEST(algorithm entt/core/algorithm.cpp)
 SETUP_BASIC_TEST(any entt/core/any.cpp)
 SETUP_BASIC_TEST(any entt/core/any.cpp)
 SETUP_BASIC_TEST(compressed_pair entt/core/compressed_pair.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(family entt/core/family.cpp)
 SETUP_BASIC_TEST(hashed_string entt/core/hashed_string.cpp)
 SETUP_BASIC_TEST(hashed_string entt/core/hashed_string.cpp)
 SETUP_BASIC_TEST(ident entt/core/ident.cpp)
 SETUP_BASIC_TEST(ident entt/core/ident.cpp)

+ 67 - 0
test/entt/core/enum.cpp

@@ -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>{});
+}