From b13eef5a909ba038e71cf5e788930bceb4ca3e0d Mon Sep 17 00:00:00 2001
From: Marc Chevrier <marc.chevrier@gmail.com>
Date: Wed, 27 Apr 2022 19:24:57 +0200
Subject: cm::enum_set: container that contains a set of unique enum values.

The enum must be an `enum class` with an unsigned integer as base type.
---
 Help/dev/source.rst                 |   5 +
 Tests/CMakeLib/CMakeLists.txt       |   1 +
 Tests/CMakeLib/testCMExtEnumSet.cxx | 204 +++++++++++++++++++
 Utilities/std/cmext/enum_set        | 393 ++++++++++++++++++++++++++++++++++++
 4 files changed, 603 insertions(+)
 create mode 100644 Tests/CMakeLib/testCMExtEnumSet.cxx
 create mode 100644 Utilities/std/cmext/enum_set

diff --git a/Help/dev/source.rst b/Help/dev/source.rst
index 9be4451..0ee104f 100644
--- a/Help/dev/source.rst
+++ b/Help/dev/source.rst
@@ -117,6 +117,11 @@ These are:
   * ``cm::contains``:
     Checks if element or key is contained in container.
 
+* ``<cmext/enum_set>``
+
+  * ``cm::enum_set``:
+    Container to manage set of elements from an ``enum class`` definition.
+
 * ``<cmext/iterator>``:
 
   * ``cm::is_terator``:
diff --git a/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt
index 87925bd..1d45162 100644
--- a/Tests/CMakeLib/CMakeLists.txt
+++ b/Tests/CMakeLib/CMakeLists.txt
@@ -29,6 +29,7 @@ set(CMakeLib_TESTS
   testUVStreambuf.cxx
   testCMExtMemory.cxx
   testCMExtAlgorithm.cxx
+  testCMExtEnumSet.cxx
   )
 if (CMake_TEST_FILESYSTEM_PATH OR NOT CMake_HAVE_CXX_FILESYSTEM)
   list(APPEND CMakeLib_TESTS testCMFilesystemPath.cxx)
diff --git a/Tests/CMakeLib/testCMExtEnumSet.cxx b/Tests/CMakeLib/testCMExtEnumSet.cxx
new file mode 100644
index 0000000..64c437b
--- /dev/null
+++ b/Tests/CMakeLib/testCMExtEnumSet.cxx
@@ -0,0 +1,204 @@
+
+#include <cstdint>
+#include <initializer_list>
+#include <iostream>
+#include <iterator>
+#include <set>
+#include <utility>
+
+#include <cmext/enum_set>
+
+namespace {
+
+int failed = 0;
+
+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;
+
+  if (!testSet1.empty()) {
+    ++failed;
+  }
+  if (testSet2.size() != 2) {
+    ++failed;
+  }
+  if (testSet3.size() != 2) {
+    ++failed;
+  }
+}
+
+void testIteration()
+{
+  std::cout << "testIteration()" << std::endl;
+
+  enum class Test : std::uint8_t
+  {
+    A,
+    B,
+    C,
+    D
+  };
+  cm::enum_set<Test> testSet{ Test::A, Test::C, Test::B };
+
+  if (testSet.size() != 3) {
+    ++failed;
+  }
+
+  std::set<std::uint8_t> reference{ static_cast<std::uint8_t>(Test::A),
+                                    static_cast<std::uint8_t>(Test::B),
+                                    static_cast<std::uint8_t>(Test::C) };
+  std::set<std::uint8_t> s;
+
+  for (auto e : testSet) {
+    s.insert(static_cast<std::uint8_t>(e));
+  }
+  if (s != reference) {
+    ++failed;
+  }
+
+  s.clear();
+  for (auto rit = testSet.rbegin(); rit != testSet.rend(); rit++) {
+    s.insert(static_cast<std::uint8_t>(*rit));
+  }
+  if (s != reference) {
+    ++failed;
+  }
+}
+
+void testEdition()
+{
+  std::cout << "testEdition()" << std::endl;
+
+  enum class Test : std::uint8_t
+  {
+    A,
+    B,
+    C,
+    D,
+    E
+  };
+
+  {
+    cm::enum_set<Test> testSet{ Test::A, Test::C, Test::B };
+
+    auto pos = testSet.insert(Test::E);
+    if (!pos.second || testSet.size() != 4 || *(pos.first) != Test::E ||
+        testSet.find(Test::E) == testSet.end()) {
+      ++failed;
+    }
+    testSet.insert(Test::E);
+    if (testSet.size() != 4 || testSet.find(Test::E) == testSet.end()) {
+      ++failed;
+    }
+
+    testSet.erase(Test::A);
+    if (testSet.size() != 3 || testSet.find(Test::A) != testSet.end()) {
+      ++failed;
+    }
+    testSet.erase(Test::A);
+    if (testSet.size() != 3 || testSet.find(Test::A) != testSet.end()) {
+      ++failed;
+    }
+  }
+  {
+    cm::enum_set<Test> testSet{ Test::A, Test::C, Test::B };
+
+    testSet += { Test::D, Test::E };
+
+    std::set<std::uint8_t> reference{ static_cast<std::uint8_t>(Test::A),
+                                      static_cast<std::uint8_t>(Test::B),
+                                      static_cast<std::uint8_t>(Test::C),
+                                      static_cast<std::uint8_t>(Test::D),
+                                      static_cast<std::uint8_t>(Test::E) };
+    std::set<std::uint8_t> s;
+    for (auto e : testSet) {
+      s.insert(static_cast<std::uint8_t>(e));
+    }
+    if (s != reference) {
+      ++failed;
+    }
+
+    testSet -= { Test::D, Test::B };
+    reference.erase(static_cast<std::uint8_t>(Test::D));
+    reference.erase(static_cast<std::uint8_t>(Test::B));
+    s.clear();
+    for (auto e : testSet) {
+      s.insert(static_cast<std::uint8_t>(e));
+    }
+    if (s != reference) {
+      ++failed;
+    }
+  }
+  {
+    cm::enum_set<Test> testSet1{ Test::A, Test::C, Test::B };
+    cm::enum_set<Test> testSet2{ Test::A, Test::D, Test::E };
+    testSet1.insert(testSet2.cbegin(), testSet2.cend());
+
+    std::set<std::uint8_t> reference{ static_cast<std::uint8_t>(Test::A),
+                                      static_cast<std::uint8_t>(Test::B),
+                                      static_cast<std::uint8_t>(Test::C),
+                                      static_cast<std::uint8_t>(Test::D),
+                                      static_cast<std::uint8_t>(Test::E) };
+    std::set<std::uint8_t> s;
+    for (auto e : testSet1) {
+      s.insert(static_cast<std::uint8_t>(e));
+    }
+    if (s != reference) {
+      ++failed;
+    }
+
+    testSet1.erase(testSet2);
+
+    reference.erase(static_cast<std::uint8_t>(Test::A));
+    reference.erase(static_cast<std::uint8_t>(Test::D));
+    reference.erase(static_cast<std::uint8_t>(Test::E));
+    s.clear();
+    for (auto e : testSet1) {
+      s.insert(static_cast<std::uint8_t>(e));
+    }
+    if (s != reference) {
+      ++failed;
+    }
+  }
+  {
+    cm::enum_set<Test> testSet1{ Test::A, Test::C, Test::B };
+    cm::enum_set<Test> testSet2{ Test::C, Test::E };
+
+    testSet1.flip(Test::A);
+    if (testSet1.size() != 2 || testSet1.contains(Test::A)) {
+      ++failed;
+    }
+
+    testSet1.flip(testSet2);
+    std::set<std::uint8_t> reference{ static_cast<std::uint8_t>(Test::B),
+                                      static_cast<std::uint8_t>(Test::E) };
+    std::set<std::uint8_t> s;
+    for (auto e : testSet1) {
+      s.insert(static_cast<std::uint8_t>(e));
+    }
+    if (s != reference) {
+      ++failed;
+    }
+  }
+}
+}
+
+int testCMExtEnumSet(int /*unused*/, char* /*unused*/ [])
+{
+  testDeclaration();
+  testIteration();
+  testEdition();
+
+  return failed;
+}
diff --git a/Utilities/std/cmext/enum_set b/Utilities/std/cmext/enum_set
new file mode 100644
index 0000000..f97a04c
--- /dev/null
+++ b/Utilities/std/cmext/enum_set
@@ -0,0 +1,393 @@
+// -*-c++-*-
+// vim: set ft=cpp:
+
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <bitset>
+#include <cstddef>
+#include <initializer_list>
+#include <iterator>
+#include <limits>
+#include <utility>
+
+#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.
+//
+// The methods offered by 'enum_set' are close as possible to the 'std::set'
+// container plus some useful methods from 'std::bitset' like 'flip'.
+//
+// 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.
+//
+
+namespace cm {
+
+template <typename EnumSet>
+class enum_set_iterator
+{
+public:
+  enum_set_iterator() = default;
+  enum_set_iterator(const enum_set_iterator& other) = default;
+
+  using iterator_category = std::bidirectional_iterator_tag;
+  using value_type = typename EnumSet::value_type;
+  using difference_type = typename EnumSet::difference_type;
+  using reference = typename EnumSet::reference;
+  using pointer = typename EnumSet::pointer;
+
+  enum_set_iterator& operator++()
+  {
+    while (++this->Index < this->Set->max_size() &&
+           !this->Set->test(this->Index))
+      ;
+
+    return *this;
+  }
+  enum_set_iterator operator++(int)
+  {
+    auto retval = *this;
+    ++(*this);
+    return retval;
+  }
+
+  enum_set_iterator& operator--()
+  {
+    while (--this->Index >= 0 && !this->Set->test(this->Index))
+      ;
+
+    return *this;
+  }
+  enum_set_iterator operator--(int)
+  {
+    auto retval = *this;
+    --(*this);
+    return retval;
+  }
+
+  reference operator*() const { return static_cast<reference>(this->Index); }
+
+  bool operator==(enum_set_iterator other) const
+  {
+    return (this->Set == other.Set) && (this->Index == other.Index);
+  }
+
+  bool operator!=(enum_set_iterator other) const { return !(*this == other); }
+
+private:
+  friend EnumSet;
+
+  using size_type = typename EnumSet::size_type;
+
+  enum_set_iterator(EnumSet* set, bool at_end = false)
+    : Set(set)
+  {
+    if (at_end || this->Set->empty()) {
+      this->Index = this->Set->max_size();
+    } else {
+      while (!this->Set->test(this->Index) &&
+             ++this->Index < this->Set->max_size())
+        ;
+    }
+  }
+  enum_set_iterator(EnumSet* set, size_type pos)
+    : Index(pos)
+    , Set(set)
+  {
+  }
+
+  std::size_t Index = 0;
+  EnumSet* Set = nullptr;
+};
+
+template <
+  typename Enum,
+  typename cm::enable_if_t<
+    std::is_enum<Enum>::value &&
+      std::is_unsigned<typename std::underlying_type<Enum>::type>::value,
+    int> = 0>
+class enum_set
+{
+public:
+  using key_type = Enum;
+  using value_type = Enum;
+  using size_type = typename std::underlying_type<Enum>::type;
+  using difference_type = size_type;
+  using reference = Enum;
+  using const_reference = Enum;
+  using pointer = const Enum*;
+  using const_pointer = const Enum*;
+
+  using iterator = enum_set_iterator<enum_set>;
+  using const_iterator = enum_set_iterator<const enum_set>;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+  constexpr enum_set() noexcept = default;
+  enum_set(const enum_set& other) noexcept { this->insert(other); }
+  enum_set(std::initializer_list<value_type> list) { this->insert(list); }
+
+  enum_set& operator=(const enum_set& other) noexcept
+  {
+    this->Set.reset();
+    this->Set |= other.Set;
+    return *this;
+  }
+  enum_set& operator=(std::initializer_list<value_type> list)
+  {
+    this->Set.reset();
+    this->insert(list);
+  }
+
+  // Iterators
+  iterator begin() noexcept { return iterator(this); }
+  const_iterator begin() const noexcept { return const_iterator(this); }
+  const_iterator cbegin() const noexcept { return const_iterator(this); }
+
+  iterator end() noexcept { return iterator(this, true); }
+  const_iterator end() const noexcept { return const_iterator(this, true); }
+  const_iterator cend() const noexcept { return const_iterator(this, true); }
+
+  reverse_iterator rbegin() noexcept { return reverse_iterator(this->end()); }
+  const_reverse_iterator rbegin() const noexcept
+  {
+    return const_reverse_iterator(this->end());
+  }
+  const_reverse_iterator crbegin() const noexcept
+  {
+    return const_reverse_iterator(this->cend());
+  }
+
+  reverse_iterator rend() noexcept { return reverse_iterator(this->begin()); }
+  const_reverse_iterator rend() const noexcept
+  {
+    return const_reverse_iterator(this->begin());
+  }
+  const_reverse_iterator crend() const noexcept
+  {
+    return const_reverse_iterator(this->cbegin());
+  }
+
+  // Capacity
+  bool empty() const noexcept { return this->Set.none(); }
+
+  size_type size() const noexcept { return this->Set.count(); }
+
+  size_type max_size() const noexcept { return this->Set.size(); }
+
+  // Modifiers
+  void clear() noexcept { this->Set.reset(); }
+
+  enum_set& operator+=(key_type e)
+  {
+    this->insert(e);
+    return *this;
+  }
+  enum_set& operator+=(const enum_set& other) noexcept
+  {
+    this->erase(other);
+    return *this;
+  }
+  enum_set& operator+=(std::initializer_list<value_type> list)
+  {
+    this->insert(list);
+    return *this;
+  }
+
+  enum_set& operator-=(key_type e)
+  {
+    this->erase(e);
+    return *this;
+  }
+  enum_set& operator-=(const enum_set& other) noexcept
+  {
+    this->erase(other);
+    return *this;
+  }
+  enum_set& operator-=(std::initializer_list<value_type> list)
+  {
+    this->erase(list);
+    return *this;
+  }
+
+  std::pair<iterator, bool> insert(value_type value)
+  {
+    auto exist = this->contains(value);
+    if (!exist) {
+      this->Set.set(static_cast<size_type>(value));
+    }
+
+    return { iterator(this, static_cast<size_type>(value)), !exist };
+  }
+  template <typename InputIt>
+  void insert(InputIt first, InputIt last)
+  {
+    for (auto i = first; i != last; i++) {
+      this->insert(*i);
+    }
+  }
+  void insert(const enum_set& other) noexcept { this->Set |= other.Set; }
+  void insert(std::initializer_list<value_type> list)
+  {
+    for (auto e : list) {
+      this->Set.set(static_cast<size_type>(e));
+    }
+  }
+
+  size_type erase(key_type key)
+  {
+    if (this->contains(key)) {
+      this->Set.reset(static_cast<size_type>(key));
+      return 1;
+    }
+
+    return 0;
+  }
+  iterator erase(iterator pos)
+  {
+    this->erase(*pos++);
+    return pos;
+  }
+  iterator erase(const_iterator pos)
+  {
+    this->erase(*pos++);
+
+    return pos == this->cend() ? this->end()
+                               : iterator(this, static_cast<size_type>(*pos));
+  }
+  void erase(const enum_set& other) noexcept { this->Set &= ~other.Set; }
+  void erase(std::initializer_list<value_type> list)
+  {
+    for (auto e : list) {
+      this->Set.reset(static_cast<size_type>(e));
+    }
+  }
+
+  void swap(enum_set& other) noexcept
+  {
+    auto tmp = this->Set;
+    this->Set = other.Set;
+    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(const enum_set& 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; }
+
+  iterator find(key_type e)
+  {
+    if (this->contains(e)) {
+      return iterator(this, static_cast<size_type>(e));
+    } else {
+      return this->end();
+    }
+  }
+  const_iterator find(key_type e) const
+  {
+    if (this->contains(e)) {
+      return const_iterator(this, static_cast<size_type>(e));
+    } else {
+      return this->end();
+    }
+  }
+
+  bool contains(key_type e) const
+  {
+    return this->Set.test(static_cast<size_type>(e));
+  }
+
+private:
+  template <typename E, typename Predicate>
+  friend inline void erase_if(enum_set<E>& set, Predicate pred);
+
+  friend class enum_set_iterator<enum_set>;
+  friend class enum_set_iterator<const enum_set>;
+
+  bool test(size_type pos) const { return this->Set.test(pos); }
+
+  std::bitset<std::numeric_limits<size_type>::digits> Set;
+};
+
+// non-member functions for enum_set
+template <typename Enum>
+inline enum_set<Enum> operator+(const enum_set<Enum>& lhs, Enum rhs)
+{
+  return enum_set<Enum>(lhs) += rhs;
+}
+template <typename Enum>
+inline enum_set<Enum> operator+(const enum_set<Enum>& lhs,
+                                const enum_set<Enum>& rhs) noexcept
+{
+  return enum_set<Enum>(lhs) += rhs;
+}
+template <typename Enum>
+inline enum_set<Enum> operator+(const enum_set<Enum>& lhs,
+                                const std::initializer_list<Enum> rhs)
+{
+  return enum_set<Enum>(lhs) += rhs;
+}
+
+template <typename Enum>
+inline enum_set<Enum> operator-(const enum_set<Enum>& lhs, Enum rhs)
+{
+  return enum_set<Enum>(lhs) -= rhs;
+}
+template <typename Enum>
+inline enum_set<Enum> operator-(const enum_set<Enum>& lhs,
+                                const enum_set<Enum>& rhs) noexcept
+{
+  return enum_set<Enum>(lhs) -= rhs;
+}
+template <typename Enum>
+inline enum_set<Enum> operator-(const enum_set<Enum>& lhs,
+                                const std::initializer_list<Enum> rhs)
+{
+  return enum_set<Enum>(lhs) -= rhs;
+}
+
+template <typename Enum>
+inline bool operator==(const enum_set<Enum>& lhs,
+                       const enum_set<Enum>& rhs) noexcept
+{
+  return lhs == rhs;
+}
+
+template <typename Enum>
+inline bool operator!=(const enum_set<Enum>& lhs,
+                       const enum_set<Enum>& rhs) noexcept
+{
+  return !(lhs == rhs);
+}
+
+template <typename Enum>
+inline void erase(enum_set<Enum>& set, Enum value)
+{
+  set.erase(value);
+}
+
+template <typename Enum, typename Predicate>
+inline void erase_if(enum_set<Enum>& set, Predicate pred)
+{
+  for (std::size_t index = 0; index < set.Set.size(); ++index) {
+    if (set.Set.test(index) && pred(static_cast<Enum>(index))) {
+      set.Set.reset(index);
+    }
+  }
+}
+} // namespace cm
-- 
cgit v0.12