LoginSignup
6
6

More than 1 year has passed since last update.

[c++]std::bitset と scoped enumでビットフラグ

Last updated at Posted at 2021-09-08

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

ソース

EnumBitSet.hpp
#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

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6