cmext/enum_set: add various enhancements to increase usability

This commit is contained in:
Marc Chevrier
2025-01-26 19:51:45 +01:00
parent a3ae58c9c5
commit 35dafcb5a1
2 changed files with 439 additions and 53 deletions

View File

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

View File

@@ -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);
}
std::pair<iterator, bool> insert(value_type value)
// 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(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 };
}