diff options
author | Marc Chevrier <marc.chevrier@gmail.com> | 2022-04-27 17:24:57 (GMT) |
---|---|---|
committer | Marc Chevrier <marc.chevrier@gmail.com> | 2022-04-27 17:40:40 (GMT) |
commit | b13eef5a909ba038e71cf5e788930bceb4ca3e0d (patch) | |
tree | cd6388466beb502522bc8a303cf40a6f48d963fb | |
parent | 1d82670bd4daff26d0d0169820b289bc401f4943 (diff) | |
download | CMake-b13eef5a909ba038e71cf5e788930bceb4ca3e0d.zip CMake-b13eef5a909ba038e71cf5e788930bceb4ca3e0d.tar.gz CMake-b13eef5a909ba038e71cf5e788930bceb4ca3e0d.tar.bz2 |
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.
-rw-r--r-- | Help/dev/source.rst | 5 | ||||
-rw-r--r-- | Tests/CMakeLib/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Tests/CMakeLib/testCMExtEnumSet.cxx | 204 | ||||
-rw-r--r-- | Utilities/std/cmext/enum_set | 393 |
4 files changed, 603 insertions, 0 deletions
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 |