Bläddra i källkod

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

Michele Caini 4 år sedan
förälder
incheckning
4b629045c3
5 ändrade filer med 257 tillägg och 0 borttagningar
  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)
     * [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 - 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/attribute.h"
 #include "core/compressed_pair.hpp"
+#include "core/enum.hpp"
 #include "core/family.hpp"
 #include "core/hashed_string.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(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 - 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>{});
+}