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 <kwrobot@kitware.com>
Acked-by: buildbot <buildbot@kitware.com>
Merge-request: !10248
This commit is contained in:
Brad King
2025-01-30 13:44:13 +00:00
committed by Kitware Robot
4 changed files with 474 additions and 54 deletions

View File

@@ -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:

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

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

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