C++11 has introduced class enum - strongly scoped and strongly typed enumerations with class enum
keywords. For example we may declare states of our FSA as the following:
class enum state { shutdown, initialization, running, maintenance, finalization, };
However, using class enum
to create masks is quite inconvenient:
constexpr unsigned allowed = (1<<static_cast<int>(state::running)) | (1<<static_cast<int>(state::maintenance))
std::bitset
does not help either on this matter.
To address this case, a template class setof is suggested (also available on Gist setof.hpp)
This class supports bitset type of operations & | &= |= set reset
and facilitates an iterator over populated bits.
#include <type_traits> #if __cplusplus > 201709L #include <bit> template<typename T> constexpr int countr_zero(T val) noexcept { return std::countr_zero(val); } template<typename T> constexpr int popcount(T val) noexcept { return std::popcount(val); } #else template<typename T, typename = std::enable_if_t<std::is_integral<T>::value,T>> constexpr int countr_zero(T val) noexcept { return __builtin_ctz(val); } template<typename T, typename = std::enable_if_t<std::is_integral<T>::value,T>> constexpr int popcount(T val) noexcept { return __builtin_popcount(val); } #endif template<typename T, typename = std::enable_if_t<std::is_integral<T>::value,T>> constexpr int bitwidth(const T&) noexcept { return sizeof(T) * 8; } template<template<std::size_t> class T, std::size_t N, typename = decltype(&T<N>::_Find_first)> constexpr int countr_zero(const T<N>& val) noexcept { return val._Find_first(); /* _Find_first is not in standard, its GCC extension */ } template<template<std::size_t> class T, std::size_t N> constexpr int popcount(const T<N>& val) noexcept { return val.count(); } template<template<std::size_t> class T, std::size_t N> constexpr int bitwidth(const T<N>& val) noexcept { return val.size(); } /** * setof - set of "named" bits, e.g. bit made from an enum * params: * Enum - enum type * Storage - storage type, integral or bitset<N> */ template<typename Enum, typename Storage = unsigned> class setof { public: static constexpr Storage lsh(Enum value) noexcept { return static_cast<Storage>(1) << static_cast<unsigned>(value); } static constexpr Storage bit_or() noexcept { return {}; } template<typename ... Enums> static constexpr Storage bit_or(Enum value, Enums ... values) noexcept { if constexpr (sizeof...(values) == 0 ) return lsh(value); else return lsh(value) | bit_or(values...); } template<typename ... Enums> constexpr setof(Enums ... values) noexcept : bits { bit_or(values...) } { static_assert(std::conjunction<std::is_same<Enum,Enums>...>::value, "Value type mismatch"); } constexpr setof(const setof& that) noexcept : bits { that.bits } {} constexpr setof operator&(const setof& that) const noexcept { return setof(bits & that.bits); } constexpr setof operator|(const setof& that) const noexcept { return setof(bits | that.bits); } constexpr setof& operator|=(const setof& that) noexcept { bits |= that.bits; return *this; } constexpr setof& operator&=(const setof& that) noexcept { bits &= that.bits; return *this; } constexpr setof& operator=(const setof& that) noexcept { bits = that.bits; return *this; } constexpr bool operator[](Enum val) const noexcept { return (bits & static_cast<unsigned>(val)) != Storage{}; } constexpr bool operator==(const setof& that) const noexcept { return bits == that.bits; } explicit constexpr operator Storage () const noexcept { return bits; } constexpr int capacity() const noexcept { return bitwidth(bits); } template<typename ... Enums> constexpr bool any(Enums ... values) const noexcept { static_assert(std::conjunction<std::is_same<Enum,Enums>...>::value, "Value type mismatch"); return Storage {} != (bits & setof(values...).bits); } template<typename ... Enums> constexpr bool all(Enums ... values) const noexcept { static_assert(std::conjunction<std::is_same<Enum,Enums>...>::value, "Value type mismatch"); return setof(values...).bits == (bits & setof(values...).bits); } template<typename ... Enums> constexpr bool none(Enums ... values) const noexcept { static_assert(std::conjunction<std::is_same<Enum,Enums>...>::value, "Value type mismatch"); return Storage {} == (bits & setof(values...).bits); } constexpr bool all(const setof& value) const noexcept { return value.bits == (bits & value.bits); } constexpr bool any(const setof& value) const noexcept { return Storage {} != (bits & value.bits); } constexpr bool none(const setof& value) const noexcept { return Storage {} == (bits & value.bits); } constexpr bool empty() const noexcept { return bits == Storage {}; } constexpr auto count() const noexcept { return popcount(bits); } setof& set(Enum val) noexcept { bits |= lsh(val); return *this; } setof& reset(Enum val) noexcept { bits &= ~lsh(val); return *this; } class iterator { public: constexpr iterator(const iterator&) = default; constexpr iterator& operator=(const iterator&) = default; constexpr Enum operator*() const noexcept { return static_cast<Enum>(pos); } constexpr iterator& operator++() { const auto rem = container.bits >> ++pos; if( rem == Storage{} ) pos = container.capacity(); else pos += countr_zero(rem); return *this; } constexpr bool operator==(const iterator& that) const noexcept { return &container == &that.container && pos == that.pos; } constexpr bool operator!=(const iterator& that) const noexcept { return not (that == *this); } private: friend class setof; constexpr iterator(const setof& cont) noexcept : container(cont), pos {cont.empty() ? cont.capacity() : countr_zero(cont.bits)} {} constexpr iterator(const setof& cont, unsigned) noexcept : container(cont), pos {cont.capacity()} {} const setof& container; int pos; }; constexpr iterator begin() const noexcept { return iterator(*this); } constexpr iterator end() const noexcept { return iterator(*this,0); } private: constexpr setof(Storage val) : bits { val } {} Storage bits; };
Usage
Creating a set of enums
//Our enum enum class Code { a, b, c, d, e, f, g }; // constexpr set constexpr auto fixed = setof<Code>(Code::a, Code::c, Code::d); // variable set setof<Code> variable(Code::c, Code::d); // result of a binary operation auto combined = fixed | variable;
Altering a set
variable |= Code::g; // adding individual value combined.set(Code::f); variable.reset(Code::a); // removing individual value
Querying a set
// Querying for listed values if( variable.any(Code::c, Code::e) ) { // .... } // Querying for another set if( combined.all(fixed) ) { // .... }
Iterating
for(auto i : combined) { // ... }
Specifying underlying storage
By default, setof
uses unsigned
data type for storing bit mask. This can be opted by providing another data type as the second parameter:
auto small = setof<Code,uint8_t>(Code::a, Code::c, Code::d); auto llong = setof<Code,uint64_t>(Code::c, Code::d); // Storage type can by integral setof<Code,std::bitset<1024>> vlong(Code::c, Code::d); // or bitset
Notes
std::bitset
based setof
iterator relays on bitset::_Find_first
, which is not in the standard and supported by GCC only.
Post a Comment