#enum
をビットフラグとして使いたい!
という欲求は誰にでもあるようで検索すると様々な答えがあります。
プレフィクスを付けてunscoped enum
を使う
マクロを使って演算子をオーバーロードする
名前空間、クラススコープに閉じ込める
別の型として自作
...
参考:https://jumble-note.blogspot.com/2017/12/c.html
それぞれ一長一短あって自分もSFINAE
の練習で作ったoperator overload
で我慢していました。
enum class Type {
A = 1 << 0,
B = 1 << 1,
C = 1 << 2,
... ↑ここが面倒!
};
##std::bitset
という選択肢
(とくに使うわけでもないのに)std::vector<bool>
の特殊化について調べていたある日のこと。
std::bitset
という見慣れぬコンテナを見つけ、さらに検索をかけた結果ビットフラグだと判明、これでscoped enum
がキャストなしで使えたら完ぺきでは?
ただラッパー作るの面倒だから他にいい方法ないものかと調べたけどとくに見つからず。
あきらめきれずにソースとリファレンスを眺めていると、ビット位置を引数にとるメンバ関数は意外と少ないのがわかったので(set,reset,flip,test,operator[]くらい)
これくらいなら作っちゃおうか、ということでできたのがこれです。
ついでに便利関数も追加してます。
##環境
visual stdio 2019 16.11.2 / c++ latest
#ソース
#pragma once
#include <bitset>
#include <initializer_list>
#include <type_traits>
#include <concepts>//C++20
/// <summary>
/// std::bitsetの引数にenum classを使うためのラッパー
/// </summary>
template <auto ENUM_MAX>
requires std::is_enum_v<decltype(ENUM_MAX)>//c++20
class EnumBitSet :public std::bitset<static_cast<size_t>(ENUM_MAX)> {
public:
using Enum = decltype(ENUM_MAX);
using Base = std::bitset<static_cast<size_t>(ENUM_MAX)>;
static constexpr ptrdiff_t _Bits = static_cast<size_t>(ENUM_MAX);
//継承元と同じ名前で定義してオーバーロード
using Base::bitset;
using Base::set;
using Base::reset;
using Base::flip;
using Base::test;
using Base::any;
using Base::none;
using Base::all;
using Base::operator[];
using _Ty = Base::_Ty;
constexpr EnumBitSet() noexcept : Base::bitset() {}
constexpr EnumBitSet(_Ty ty) noexcept : Base::bitset(ty) {}
/// <summary>
/// 全て 0/1で作成
/// </summary>
constexpr EnumBitSet(bool sw) noexcept : Base::bitset(sw ? get_max_ty() : 0) {}
/// <summary>
/// posのフラグを立てて作成
/// </summary>
constexpr EnumBitSet(Enum pos) noexcept : EnumBitSet({ pos }) {}
/// <summary>
/// posListのすべてのフラグを立てて作成
/// </summary>
constexpr EnumBitSet(std::initializer_list<Enum> posList) noexcept : Base::bitset(to_ty(posList)) {}
//Enum用にラップ
EnumBitSet& set(Enum _Pos, bool _Val = true) {
Base::set(EToS(_Pos), _Val);
return *this;
}
EnumBitSet& reset(Enum _Pos) {
Base::reset(EToS(_Pos));
return *this;
}
EnumBitSet& flip(Enum _Pos) {
Base::flip(EToS(_Pos));
return *this;
}
_NODISCARD bool test(Enum _Pos) const {
return Base::test(EToS(_Pos));
}
_NODISCARD constexpr bool operator[](Enum _Pos) const {
return Base::operator[](EToS(_Pos));
}
_NODISCARD Base::reference operator[](Enum _Pos) {
return Base::operator[](EToS(_Pos));
}
//EnumBitSet同士 Baseにすることで別クラスで実体化したやつも同じ大きさなら演算できるようになる
EnumBitSet& set(const Base& mask, bool _Val = true)noexcept {
if(!_val){
return reset(mask);
}
*this |= mask;
return *this;
}
EnumBitSet& reset(const Base& mask)noexcept {
*this &= ~mask;
return *this;
}
EnumBitSet& flip(const Base& mask)noexcept {
*this ^= mask;
return *this;
}
/// <summary>
/// 引数の1のビットを対象に any
/// </summary>
_NODISCARD bool any(const Base& mask) const noexcept {
return (*this & mask).any();
}
/// <summary>
/// 引数の1のビットを対象に none
/// </summary>
_NODISCARD bool none(const Base& mask) const noexcept {
return !any(mask);
}
/// <summary>
/// 引数の1のビットを対象に all
/// </summary>
_NODISCARD bool all(const Base& mask) const noexcept {
return (*this & mask) == mask;
}
//initializer_list用これがないと set({ Enum::A,Enum::B }) でエラー
inline EnumBitSet& set(std::initializer_list<Enum> posList, bool _Val = true)noexcept {
return set(EnumBitSet(posList),_val);
}
inline EnumBitSet& reset(std::initializer_list<Enum> posList)noexcept {
return reset(EnumBitSet(posList));
}
inline EnumBitSet& flip(std::initializer_list<Enum> posList)noexcept {
return flip(EnumBitSet(posList));
}
_NODISCARD inline bool any(std::initializer_list<Enum> posList) const noexcept {
return any(EnumBitSet(posList));
}
_NODISCARD inline bool none(std::initializer_list<Enum> posList) const noexcept {
return none(EnumBitSet(posList));
}
_NODISCARD inline bool all(std::initializer_list<Enum> posList) const noexcept {
return all(EnumBitSet(posList));
}
//変換
_NODISCARD static constexpr size_t EToS(Enum e) noexcept {
return static_cast<size_t>(e);
}
//ビット列が全部1の場合の数を_Ty型に変換して返す
_NODISCARD static constexpr _Ty get_max_ty() {
_Ty ty = 0;
for (auto n = _Bits - 1; n >= 0; n--) {
ty += _Ty(1) << n;
}
return ty;
}
//ビット列を_Ty型に変換して返す
_NODISCARD static constexpr _Ty to_ty(std::initializer_list<Enum> posList) {
_Ty ty = 0;
std::array<bool, _Bits> added{};
for (auto&& pos : posList) {
auto n = EToS(pos);
if (!added[n]) {
ty += _Ty(1) << n;
added[n] = true;
}
}
return ty;
}
};
##使い方
enum class Type {
A,
B,
C,
MAX
};
using TypeSet = EnumBitSet<Type::MAX>; //準備はこれだけ!
constexpr TypeSet AB = { Type::A,Type::B }; //複合ビットもconstexprで書ける
int main() {
TypeSet typeSet{ Type::B,Type::C }; //BとCが1の状態で初期化
typeSet.set(Type::A); //Aを1に
typeSet.flip(AB); //AとBを反転
auto b = typeSet[Type::B]; //Bにアクセス autoは std::bitset<Type::MAX>::reference
if (!b) {
b = true;
if (typeSet.any({ Type::A,Type::C })) { //AかCが1ならtrue
printf(typeSet.to_string().c_str()); //"110" -> C:1, B:1, A:0
}
}
}
##説明
・std::bitset
を継承したラッパークラスです。
・どうせstd::bitset
に渡す際size_t
になるため、underlying_type
は使ってません。
・concepts
、非型テンプレートパラメータのauto宣言
など新しめのがありますが特に難しいところはないと思います。
・//c++20
のコメントがある行を消すとc++17環境でも動くと思いますが、enum
以外も型引数に入るようになっちゃいます。
・演算は関数(set,reset,flip...)
に一本化しましたが、コンストラクタでEnumBitSet
に変換すればビット演算子も使えます。
・constexpr、コピーとムーブ、完全転送あたりいまいちわかってないのでツッコミあればお願いします。
#作り終わってから
勢いで作ったためc++ bitset enum
で検索すれば良かったのでは・・・?
と思ったけど継承して便利なのまで作ってるのは無かったので記事にしました。
死ぬほど便利なのでc++enumビットフラグ論争を終わらせます!
#参考
bitset
https://cpprefjp.github.io/reference/bitset/bitset.html
concepts
https://cpprefjp.github.io/lang/cpp20/concepts.html
非型テンプレートパラメータのauto宣言
https://cpprefjp.github.io/lang/cpp17/declaring_non-type_template_arguments_with_auto.html
bitset+enumの実装例 メンバ変数にbitsetを持つのが多いみたいです
https://m-peko.github.io/craft-cpp/posts/different-ways-to-define-binary-flags/
https://stackoverflow.com/questions/17350214/using-enum-class-with-stdbitset