Просмотр исходного кода

Merge topic 'enum_set-enhancements'

35dafcb5a1 cmext/enum_set: add various enhancements to increase usability
a3ae58c9c5 C++ features: add cm::is_scoped_enum from C++23

Acked-by: Kitware Robot <[email protected]>
Acked-by: buildbot <[email protected]>
Merge-request: !10248
Brad King 8 месяцев назад
Родитель
Сommit
0870e6c06b
4 измененных файлов с 474 добавлено и 54 удалено
  1. 5 0
      Help/dev/source.rst
  2. 225 18
      Tests/CMakeLib/testCMExtEnumSet.cxx
  3. 30 1
      Utilities/std/cm/type_traits
  4. 214 35
      Utilities/std/cmext/enum_set

+ 5 - 0
Help/dev/source.rst

@@ -221,6 +221,11 @@ Available features are:
   * ``<cm/vector>``:
     ``cm::erase``, ``cm::erase_if``, ``cm::ssize``
 
+* From ``C++23``:
+
+  * ``<cm/type_traits>``:
+    ``cm::is_scoped_enum``
+
 Additionally, some useful non-standard extensions to the C++ standard library
 are available in headers under the directory ``cmext/`` in namespace ``cm``.
 These are:

+ 225 - 18
Tests/CMakeLib/testCMExtEnumSet.cxx

@@ -3,7 +3,9 @@
 #include <initializer_list>
 #include <iostream>
 #include <iterator>
+#include <limits>
 #include <set>
+#include <type_traits>
 #include <utility>
 
 #include <cmext/enum_set>
@@ -16,25 +18,70 @@ void testDeclaration()
 {
   std::cout << "testDeclaration()" << std::endl;
 
-  enum class Test : std::uint8_t
   {
-    A,
-    B,
-    C,
-    D
-  };
-  cm::enum_set<Test> testSet1;
-  cm::enum_set<Test> testSet2{ Test::A, Test::C };
-  cm::enum_set<Test> testSet3 = testSet2;
+    enum class Test : std::uint8_t
+    {
+      A,
+      B,
+      C,
+      D
+    };
+    cm::enum_set<Test> testSet1;
+    cm::enum_set<Test> testSet2 = Test::A;
+    cm::enum_set<Test> testSet3 = Test::A | Test::C;
+    cm::enum_set<Test> testSet4 = Test::A + Test::C;
+    cm::enum_set<Test> testSet5{ Test::A, Test::C };
+    cm::enum_set<Test> testSet6 = testSet3;
 
-  if (!testSet1.empty()) {
-    ++failed;
+    if (!testSet1.empty()) {
+      ++failed;
+    }
+    if (testSet2.size() != 1) {
+      ++failed;
+    }
+    if (testSet3.size() != 2 || testSet4.size() != 2 || testSet5.size() != 2 ||
+        testSet6.size() != 2) {
+      ++failed;
+    }
+    if (testSet3 != testSet4 || testSet4 != testSet5 || testSet5 != testSet6) {
+      ++failed;
+    }
   }
-  if (testSet2.size() != 2) {
-    ++failed;
+  {
+    enum class Test : std::uint8_t
+    {
+      A,
+      B,
+      C,
+      D
+    };
+    cm::enum_set<Test> testSet1;
+    cm::enum_set<Test, 4> testSet2;
+
+    if (testSet1.size() != 0 ||
+        testSet1.max_size() !=
+          std::numeric_limits<
+            typename std::underlying_type<Test>::type>::digits) {
+      ++failed;
+    }
+    if (testSet2.size() != 0 || testSet2.max_size() != 4) {
+      ++failed;
+    }
   }
-  if (testSet3.size() != 2) {
-    ++failed;
+  {
+    enum class Test : std::uint8_t
+    {
+      A,
+      B,
+      C,
+      D,
+      cm_count = D
+    };
+    cm::enum_set<Test> testSet1;
+
+    if (testSet1.size() != 0 || testSet1.max_size() != 4) {
+      ++failed;
+    }
   }
 }
 
@@ -47,7 +94,8 @@ void testIteration()
     A,
     B,
     C,
-    D
+    D,
+    cm_count = D
   };
   cm::enum_set<Test> testSet{ Test::A, Test::C, Test::B };
 
@@ -86,7 +134,8 @@ void testEdition()
     B,
     C,
     D,
-    E
+    E,
+    cm_count = E
   };
 
   {
@@ -193,12 +242,169 @@ void testEdition()
   }
   {
     cm::enum_set<Test> testSet1;
-    cm::enum_set<Test> testSet2{ Test::A, Test::C, Test::B };
+    auto testSet2 = Test::A + Test::C + Test::B;
 
+    testSet1.set({ Test::A, Test::C, Test::B });
+    if (testSet1.size() != 3 || testSet1 != testSet2) {
+      ++failed;
+    }
+    testSet1.reset();
+    testSet1.set(Test::A | Test::C | Test::B);
+    if (testSet1.size() != 3 || testSet1 != testSet2) {
+      ++failed;
+    }
+    testSet1.reset();
+    testSet1.set(Test::A + Test::C + Test::B);
+    if (testSet1.size() != 3 || testSet1 != testSet2) {
+      ++failed;
+    }
+    testSet1.reset();
     testSet1 = { Test::A, Test::C, Test::B };
     if (testSet1.size() != 3 || testSet1 != testSet2) {
       ++failed;
     }
+    testSet1.reset();
+    testSet1 = Test::A | Test::C | Test::B;
+    if (testSet1.size() != 3 || testSet1 != testSet2) {
+      ++failed;
+    }
+    testSet1.clear();
+    testSet1 = Test::A + Test::C + Test::B;
+    if (testSet1.size() != 3 || testSet1 != testSet2) {
+      ++failed;
+    }
+    testSet1.clear();
+    testSet1 |= Test::A;
+    testSet1 |= Test::C | Test::B;
+    if (testSet1.size() != 3 || testSet1 != testSet2) {
+      ++failed;
+    }
+  }
+  {
+    cm::enum_set<Test> testSet1;
+    cm::enum_set<Test> testSet2{ Test::A, Test::C, Test::B };
+
+    testSet1.set();
+    if (testSet1.size() != 5 || testSet1.size() != testSet1.max_size()) {
+      ++failed;
+    }
+    testSet1.flip(Test::D | Test::E);
+    if (testSet1.size() != 3 || testSet1 != testSet2) {
+      ++failed;
+    }
+    testSet1.flip(Test::D);
+    testSet2 += Test::D;
+    if (testSet1.size() != 4 || testSet1 != testSet2) {
+      ++failed;
+    }
+    testSet1 ^= { Test::A, Test::B, Test::E, Test::D };
+    testSet2 = Test::C + Test::E;
+    if (testSet1.size() != 2 || testSet1 != testSet2) {
+      ++failed;
+    }
+    testSet1 ^= Test::A | Test::B | Test::E;
+    testSet2 = { Test::A, Test::B, Test::C };
+    if (testSet1.size() != 3 || testSet1 != testSet2) {
+      ++failed;
+    }
+  }
+}
+
+void testChecks()
+{
+  std::cout << "testChecks()" << std::endl;
+
+  {
+    enum class Test : std::uint8_t
+    {
+      A,
+      B,
+      C,
+      D,
+      cm_count = D
+    };
+
+    cm::enum_set<Test> testSet;
+
+    if (!testSet.empty()) {
+      ++failed;
+    }
+
+    testSet = Test::A;
+    if (testSet.empty()) {
+      ++failed;
+    }
+    if (!testSet) {
+      ++failed;
+    }
+    if (!testSet.contains(Test::A)) {
+      ++failed;
+    }
+    if (testSet.find(Test::A) == testSet.end()) {
+      ++failed;
+    }
+    if (testSet.find(Test::C) != testSet.end()) {
+      ++failed;
+    }
+  }
+  {
+    enum class Test : std::uint8_t
+    {
+      A,
+      B,
+      C,
+      D,
+      cm_count = D
+    };
+
+    cm::enum_set<Test> testSet;
+
+    if (!testSet.none()) {
+      ++failed;
+    }
+    if (testSet.any() || testSet.all()) {
+      ++failed;
+    }
+
+    testSet = Test::A;
+    if (!testSet.any() || testSet.none() || testSet.all()) {
+      ++failed;
+    }
+
+    testSet.set();
+    if (!testSet.all() || !testSet.any() || testSet.none()) {
+      ++failed;
+    }
+  }
+  {
+    enum class Test : std::uint8_t
+    {
+      A,
+      B,
+      C,
+      D,
+      cm_count = D
+    };
+
+    cm::enum_set<Test> testSet1;
+    cm::enum_set<Test> testSet2{ Test::A, Test::C };
+
+    if (!testSet1.none_of(testSet2) || testSet1.any_of(testSet2) ||
+        testSet1.all_of(testSet2)) {
+      ++failed;
+    }
+
+    testSet1 = Test::A | Test::D;
+    if (testSet1.none_of(testSet2) || !testSet1.any_of(testSet2) ||
+        testSet1.all_of(testSet2)) {
+      ++failed;
+    }
+
+    testSet1 |= Test::C;
+    if (testSet1.none_of(testSet2) || !testSet1.any_of(testSet2) ||
+        !testSet1.all_of(testSet2)) {
+      ++failed;
+    }
   }
 }
 }
@@ -208,6 +414,7 @@ int testCMExtEnumSet(int /*unused*/, char* /*unused*/[])
   testDeclaration();
   testIteration();
   testEdition();
+  testChecks();
 
   return failed;
 }

+ 30 - 1
Utilities/std/cm/type_traits

@@ -23,7 +23,7 @@ using enable_if_t = typename std::enable_if<B, T>::type;
 
 #endif
 
-#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703)
+#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
 
 // Helper classes
 using std::bool_constant;
@@ -57,4 +57,33 @@ using void_t = typename make_void<ArgTypes...>::type;
 
 #endif
 
+#if (__cplusplus >= 202302L ||                                                \
+     (defined(_MSVC_LANG) && _MSVC_LANG >= 202302L)) &&                       \
+  __cpp_lib_is_scoped_enum == 202011L
+
+using std::is_scoped_enum;
+
+#else
+
+namespace internals {
+template <typename T, bool = std::is_enum<T>::value>
+struct is_scoped_enum_helper : std::false_type
+{
+};
+
+template <typename T>
+struct is_scoped_enum_helper<T, true>
+  : public cm::bool_constant<
+      !std::is_convertible<T, typename std::underlying_type<T>::type>::value>
+{
+};
+}
+
+template <typename T>
+struct is_scoped_enum : public internals::is_scoped_enum_helper<T>
+{
+};
+
+#endif
+
 } // namespace cm

+ 214 - 35
Utilities/std/cmext/enum_set

@@ -15,19 +15,53 @@
 #include <cm/type_traits>
 
 //
-// Class enum_set offers the capability to manage a set of enum values
-// Only 'enum class' with unsigned base type are supported.
+// Class enum_set offers the capability to manage a set of enum values.
+// Only the 'enum class' type with unsigned base type is supported. Moreover,
+// all definitions must be specified without a value.
 //
 // The methods offered by 'enum_set' are close as possible to the 'std::set'
-// container plus some useful methods from 'std::bitset' like 'flip'.
+// container as well as the methods from 'std::bitset'.
 //
 // Internally, this class use 'std::bitset' container to manage the
-// set of enum. The size of the bitset is deduced from the underlying type of
-// the enum.
+// set of enum.
+//
+// The size of the bitset is deduced from the underlying type of
+// the enum or can be set explicitly as template parameter:
+//
+// enum class Example : unsigned { A, B, C, D };
+// using ExampleSet = enum_set<Example, 4>;
+//
+// Another possibility is to add, at the end of the list, the definition
+// 'cm_count' with the value of the precedent definition:
+//
+//   enum class Example : unsigned { A, B, C, D, cm_count = D };
+//   using ExampleSet = enum_set<Example>;
+//
+// To facilitate the usage of the enum_set, operators '+' and '|' can be used
+// as alternate to the 'initializer_list':
+//
+//  auto set1 = Example::A | Example::B | Example::C;
+//  auto set2 = Example::A + Example::B;
+//  set2.set(Example::C | Example::D);
 //
 
 namespace cm {
 
+namespace internals {
+template <typename Enum, typename U = void>
+struct enum_size
+{
+  static constexpr auto value =
+    std::numeric_limits<typename std::underlying_type<Enum>::type>::digits;
+};
+template <typename Enum>
+struct enum_size<Enum, cm::void_t<decltype(Enum::cm_count)>>
+{
+  static constexpr auto value =
+    static_cast<typename std::underlying_type<Enum>::type>(Enum::cm_count) + 1;
+};
+}
+
 template <typename EnumSet>
 class enum_set_iterator
 {
@@ -110,9 +144,9 @@ private:
 };
 
 template <
-  typename Enum,
+  typename Enum, std::size_t Size = internals::enum_size<Enum>::value,
   typename cm::enable_if_t<
-    std::is_enum<Enum>::value &&
+    cm::is_scoped_enum<Enum>::value &&
       std::is_unsigned<typename std::underlying_type<Enum>::type>::value,
     int> = 0>
 class enum_set
@@ -133,9 +167,16 @@ public:
   using const_reverse_iterator = std::reverse_iterator<const_iterator>;
 
   constexpr enum_set() noexcept = default;
+  enum_set(key_type e) { this->insert(e); }
   enum_set(enum_set const& other) noexcept { this->insert(other); }
   enum_set(std::initializer_list<value_type> list) { this->insert(list); }
 
+  enum_set& operator=(key_type e)
+  {
+    this->Set.reset();
+    this->insert(e);
+    return *this;
+  }
   enum_set& operator=(enum_set const& other) noexcept
   {
     this->Set.reset();
@@ -186,41 +227,116 @@ public:
   size_type max_size() const noexcept { return this->Set.size(); }
 
   // Modifiers
-  void clear() noexcept { this->Set.reset(); }
 
-  enum_set& operator+=(key_type e)
+  // set all elements
+  enum_set& set()
+  {
+    this->Set.set();
+    return *this;
+  }
+  enum_set& set(key_type e)
   {
     this->insert(e);
     return *this;
   }
-  enum_set& operator+=(enum_set const& other) noexcept
+  enum_set& set(enum_set const& other) noexcept
   {
-    this->erase(other);
+    this->insert(other);
     return *this;
   }
-  enum_set& operator+=(std::initializer_list<value_type> list)
+  enum_set& set(std::initializer_list<value_type> list)
   {
     this->insert(list);
     return *this;
   }
+  // alternate syntax for bit set
+  enum_set& operator+=(key_type e) { return this->set(e); }
+  enum_set& operator+=(enum_set const& other) noexcept
+  {
+    return this->set(other);
+  }
+  enum_set& operator+=(std::initializer_list<value_type> list)
+  {
+    return this->set(list);
+  }
+  // alternate syntax for bit set
+  enum_set& operator|=(key_type e) { return this->set(e); }
+  enum_set& operator|=(enum_set const& other) noexcept
+  {
+    return this->set(other);
+  }
+  enum_set& operator|=(std::initializer_list<value_type> list)
+  {
+    return this->set(list);
+  }
 
-  enum_set& operator-=(key_type e)
+  // reset all elements
+  void clear() noexcept { this->Set.reset(); }
+  enum_set& reset()
+  {
+    this->Set.reset();
+    return *this;
+  }
+  enum_set& reset(key_type e)
   {
     this->erase(e);
     return *this;
   }
-  enum_set& operator-=(enum_set const& other) noexcept
+  enum_set& reset(enum_set const& other) noexcept
   {
     this->erase(other);
     return *this;
   }
-  enum_set& operator-=(std::initializer_list<value_type> list)
+  enum_set& reset(std::initializer_list<value_type> list)
   {
     this->erase(list);
     return *this;
   }
+  // alternate syntax for bit reset
+  enum_set& operator-=(key_type e) { return this->reset(e); }
+  enum_set& operator-=(enum_set const& other) noexcept
+  {
+    return this->reset(other);
+  }
+  enum_set& operator-=(std::initializer_list<value_type> list)
+  {
+    return this->reset(list);
+  }
+
+  // toggle the specified enum
+  enum_set& flip(key_type e)
+  {
+    this->Set.flip(static_cast<size_type>(e));
+    return *this;
+  }
+  // toggle all the enums stored in the other enum_set
+  enum_set& flip(enum_set const& other) noexcept
+  {
+    this->Set ^= other.Set;
+    return *this;
+  }
+  // toggle all the enums specified in the list
+  enum_set& flip(std::initializer_list<value_type> list)
+  {
+    for (auto e : list) {
+      this->Set.flip(static_cast<size_type>(e));
+    }
+    return *this;
+  }
+  // alternate syntax for bit toggle
+  enum_set& operator^=(key_type key) { return this->flip(key); }
+  // toggle all the enums stored in the other enum_set
+  enum_set& operator^=(enum_set const& other) noexcept
+  {
+    return this->flip(other);
+  }
+  // toggle all the enums specified in the list
+  enum_set& operator^=(std::initializer_list<value_type> list)
+  {
+    return this->flip(list);
+  }
 
-  std::pair<iterator, bool> insert(value_type value)
+  std::pair<iterator, bool> insert(key_type value)
   {
     auto exist = this->contains(value);
     if (!exist) {
@@ -280,18 +396,6 @@ public:
     other.Set = tmp;
   }
 
-  // toggle the specified enum
-  void flip(key_type key) { this->Set.flip(static_cast<size_type>(key)); }
-  // toggle all the enums stored in the other enum_set
-  void flip(enum_set const& other) noexcept { this->Set ^= other.Set; }
-  // toggle all the enums specified in the list
-  void flip(std::initializer_list<value_type> list)
-  {
-    for (auto e : list) {
-      this->Set.flip(static_cast<size_type>(e));
-    }
-  }
-
   // Lookup
   size_type count(key_type e) const { return this->contains(e) ? 1 : 0; }
 
@@ -310,11 +414,37 @@ public:
     return this->end();
   }
 
+  // Checks
   bool contains(key_type e) const
   {
     return this->Set.test(static_cast<size_type>(e));
   }
 
+  bool all() const { return this->Set.all(); }
+  bool any() const { return this->Set.any(); }
+  bool none() const { return this->Set.none(); }
+  // alternate syntax to none()
+  bool operator!() const { return this->Set.none(); }
+
+  bool all_of(enum_set const& set) const
+  {
+    auto result = set;
+    result.Set &= this->Set;
+    return result == set;
+  }
+  bool any_of(enum_set const& set) const
+  {
+    auto result = set;
+    result.Set &= this->Set;
+    return result.any();
+  }
+  bool none_of(enum_set const& set) const
+  {
+    auto result = set;
+    result.Set &= this->Set;
+    return result.none();
+  }
+
 private:
   template <typename E>
   friend inline bool operator==(enum_set<E> const& lhs,
@@ -328,44 +458,79 @@ private:
 
   bool test(size_type pos) const { return this->Set.test(pos); }
 
-  std::bitset<std::numeric_limits<size_type>::digits> Set;
+  std::bitset<Size> Set;
 };
 
 // non-member functions for enum_set
 template <typename Enum>
 inline enum_set<Enum> operator+(enum_set<Enum> const& lhs, Enum rhs)
 {
-  return enum_set<Enum>(lhs) += rhs;
+  return enum_set<Enum>{ lhs } += rhs;
 }
 template <typename Enum>
 inline enum_set<Enum> operator+(enum_set<Enum> const& lhs,
                                 enum_set<Enum> const& rhs) noexcept
 {
-  return enum_set<Enum>(lhs) += rhs;
+  return enum_set<Enum>{ lhs } += rhs;
 }
 template <typename Enum>
 inline enum_set<Enum> operator+(enum_set<Enum> const& lhs,
                                 std::initializer_list<Enum> const rhs)
 {
-  return enum_set<Enum>(lhs) += rhs;
+  return enum_set<Enum>{ lhs } += rhs;
+}
+
+template <typename Enum>
+inline cm::enum_set<Enum> operator|(cm::enum_set<Enum> const& lhs, Enum rhs)
+{
+  return enum_set<Enum>{ lhs } |= rhs;
+}
+template <typename Enum>
+inline cm::enum_set<Enum> operator|(Enum lhs, cm::enum_set<Enum> const& rhs)
+{
+  return enum_set<Enum>{ lhs } |= rhs;
+}
+template <typename Enum>
+inline cm::enum_set<Enum> operator|(cm::enum_set<Enum> const& lhs,
+                                    cm::enum_set<Enum> const& rhs)
+{
+  return enum_set<Enum>{ lhs } |= rhs;
 }
 
 template <typename Enum>
 inline enum_set<Enum> operator-(enum_set<Enum> const& lhs, Enum rhs)
 {
-  return enum_set<Enum>(lhs) -= rhs;
+  return enum_set<Enum>{ lhs } -= rhs;
 }
 template <typename Enum>
 inline enum_set<Enum> operator-(enum_set<Enum> const& lhs,
                                 enum_set<Enum> const& rhs) noexcept
 {
-  return enum_set<Enum>(lhs) -= rhs;
+  return enum_set<Enum>{ lhs } -= rhs;
 }
 template <typename Enum>
 inline enum_set<Enum> operator-(enum_set<Enum> const& lhs,
                                 std::initializer_list<Enum> const rhs)
 {
-  return enum_set<Enum>(lhs) -= rhs;
+  return enum_set<Enum>{ lhs } -= rhs;
+}
+
+template <typename Enum>
+inline enum_set<Enum> operator^(enum_set<Enum> const& lhs, Enum rhs)
+{
+  return enum_set<Enum>{ lhs } ^= rhs;
+}
+template <typename Enum>
+inline enum_set<Enum> operator^(enum_set<Enum> const& lhs,
+                                enum_set<Enum> const& rhs) noexcept
+{
+  return enum_set<Enum>{ lhs } ^= rhs;
+}
+template <typename Enum>
+inline enum_set<Enum> operator^(enum_set<Enum> const& lhs,
+                                std::initializer_list<Enum> const rhs)
+{
+  return enum_set<Enum>{ lhs } ^= rhs;
 }
 
 template <typename Enum>
@@ -398,3 +563,17 @@ inline void erase_if(enum_set<Enum>& set, Predicate pred)
   }
 }
 } // namespace cm
+
+template <typename Enum,
+          typename cm::enable_if_t<cm::is_scoped_enum<Enum>::value, int> = 0>
+inline cm::enum_set<Enum> operator+(Enum lhs, Enum rhs)
+{
+  return cm::enum_set<Enum>{ lhs, rhs };
+}
+// Alternate syntax
+template <typename Enum,
+          typename cm::enable_if_t<cm::is_scoped_enum<Enum>::value, int> = 0>
+inline cm::enum_set<Enum> operator|(Enum lhs, Enum rhs)
+{
+  return cm::enum_set<Enum>{ lhs, rhs };
+}