列挙型のサブカテゴリをvariantで作ろう

Table of Contents

列挙型のサブカテゴリをvariantで作ろう

目的

c++で列挙型を使っていると列挙しているものが増えてきて 管理に苦しむことはないだろうか。 そこで増えてきた要素をカテゴライズできるサブグループをvariantを使い、 分類する方法を記載する。

古典的な方法

次のようにビットマスクを用いて管理されている列挙型が存在するとする。

  • before
#include <cstdint>

enum class Animal:uint16_t{
  Chicken=1<<8,
  Penguin,
  Dog=2<<8,
  Cat
};

この方法ではサブカテゴリをビット演算で取得できるが、 サブカテゴリの大きさが高々2のべき乗で示せることを保証しないといけないため、 ある程度サブカテゴリの大きさが既知でなければならない。

bool is_birds(Animal a){
  return static_cast<uint16_t>(a)>>8==1;
}

variantを使った方法

C++17から追加されたvariantを使うとサブカテゴリの大きさが 未知の状態でも次のようにかける。

  • after
#include <variant>

enum class Birds{
  Chicken,
  Penguin,
};

enum class Mammalians{
  Dog,
  Cat,
};
using Animal = std::variant<Birds, Mammalians>;// std::varintは比較演算子を提供する。

すると次のようにサブカテゴリを取得できる。

static inline bool is_bird(const Animal& a){
  return std::holds_alternative<Bird>(s);
}

また、サブカテゴリとその値を取得する場合は次のようにoptionalを 使って書くこともできる。

#include <optional>
template <class T>
static inline std::optional<T> try_to(const Animal& a){
  if (auto p=std::get_if<T*>(&a)){
    return *p;
  }else{
    return std::nullopt;
  }
}

最後にnlohmann-jsonmagic-enumを使い、 std::visitでjsonに変換する例を示す。

void to_json(nlohmann::json& j, const Animal& a){
  struct visiter{
    static json operator()(Birds b){return magic_enum::enum_name(b);}
    static json operator()(Mammalians m){return magic_enum::enum_name(m);}
  };
  j = std::visit(visiter{}, a);
}

Note

operator()をstatic属性にする機能はc++23以降であれば使えます。