Skip to content

setof – an iterable set of strongly typed enumerations

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

Your email is never published nor shared. Required fields are marked *
*
*

*