テンプレートを用いてc++11で列挙型をカスタマイズする

掲題の通りです。
Javaの列挙型みたいにC++でenumを使いたいなって思うことがよくあるんですけど、デフォルトのenum型では数値以外扱うことができません。
それに数値として扱う場合もキャストが必要だったりと何かと制限もあるんで、似たようなふるまいができるものを作成してみました。

# 具体的に何をしたいか
要は列挙型で以下のようなことをしたいわけです。

* 通常のenumと同じように、定義した順に数値が割り振られる
* 数値として認識する
* 文字列に変換できる
* 列挙子同士の比較ができる
* values()みたいに列挙型のリストをまとめてとれる
* valueOf()みたいに数値から値を取得できる
* 拡張できる

結論からいうと、templateでクラスを作成することで比較的簡単に実装できます。
※ ただぼくのc++の理解はそんな深くなく、むしろ浅いので間違えてる部分あったらご指摘ください。

# ソース

四の五の言わずにまずはソース書きますね。はい。
### 疑似列挙クラス

```cpp:c++11(Enum.h)
#ifndef Enum_h
#define Enum_h

#include <string>
#include <vector>

template <class _Ty> // -- (1)
class Enum {
   public:
    // operatorのオーバーロード
    operator const std::string&() const {
        return m_name;
    }

    operator const char*() const {
        return m_name.c_str();
    }

    operator int() const {
        return m_id;
    }

    bool operator<(const _Ty& v) const {
        return m_id < v.m_id;
    }

    bool operator<=(const _Ty& v) const {
        return m_id <= v.m_id;
    }

    bool operator>(const _Ty& v) const {
        return m_id > v.m_id;
    }

    bool operator>=(const _Ty& v) const {
        return m_id > v.m_id;
    }

    bool operator==(const _Ty& v) const {
        return m_id == v.m_id && m_name == v.m_name;
    }

    bool operator!=(const _Ty& v) const {
        return m_id != v.m_id || m_name != v.m_name;
    }

    int id() const {
        return m_id;
    }

    std::string value() const {
        return m_name;
    }

    bool is(const _Ty& v) {
        return *this == v;
    }

    bool is(int i) {
        return m_id == i;
    }

    bool is(const std::string& value) {
        return m_name == value;
    }

    static const std::vector<_Ty*>& values() {
        return s_vector;
    }

    static _Ty* valueOf(int num) {
        auto it = std::find_if(s_vector.begin(), s_vector.end(),
                               [num](const _Ty* t) { return t->m_id == num; });
        return it != s_vector.end() ? *it : nullptr;
    }

    static _Ty* valueOf(const std::string& value) {
        auto it =
            std::find_if(s_vector.begin(), s_vector.end(),
                         [value](const _Ty* t) { return t->m_name == value; });
        return it != s_vector.end() ? *it : nullptr;
    }

   protected:
    virtual ~Enum(){};
    Enum() : m_id(0), m_name("") {
    }

    Enum(const std::string& name) : m_id(s_size), m_name(name) {
        s_size++;　// -- (2)
        LOG("enum : %d name: %s", m_id, name.c_str());
        s_vector.push_back(static_cast<_Ty*>(this)); // -- (3)
    }

    Enum(int num, const std::string& name) : m_id(num), m_name(name) {
        s_size = num + 1; 
        LOG("enum : %d name: %s", m_id, name.c_str());
        s_vector.push_back(static_cast<_Ty*>(this));
    }

   private:
    std::string m_name;
    unsigned int m_id;
    static int s_size;
    static std::vector<_Ty*> s_vector;
};

// -- (4)
template <class _Ty>
int Enum<_Ty>::s_size = 0;
template <class _Ty>
typename std::vector<_Ty*> Enum<_Ty>::s_vector = std::vector<_Ty*>();

#endif /* Enum_h */
```

### 列挙型の定義クラス
何してるかはあとで書くとして、こんな感じで列挙型を定義します。

```cpp:c++11(Event.hpp)
#include "Enum.h"

class EVENT : public Enum<EVENT> { 
   public:
    static const EVENT NONE;
    static const EVENT EVENT1;
    static const EVENT EVENT2;
    static const EVENT EVENT3;

   private:
    using Enum::Enum; // -- (5)
};

class EVENT2 : public Enum<EVENT2> {
   public:
    static const EVENT2 NONE;
    static const EVENT2 TEST;
    static const EVENT2 TEST2;

    void execute() const {
        if (m_func) m_func();
    };

   private:
    std::function<void()> m_func;
    using Enum::Enum;
    EVENT2(const std::string& name, const std::function<void()>& func)
        : Enum<EVENT2>(name), m_func(func) {
    }
};
```

```cpp:c++11(Event.cpp)
#include "Event.hpp"

const EVENT EVENT::NONE("NONE");　// 最初0の値が入る
const EVENT EVENT::EVENT1("EVENT1"); // 1が入ることを期待
const EVENT EVENT::EVENT2(5, "EVENT2"); // 列挙子らしく途中5を定義してみる
const EVENT EVENT::EVENT3("EVENT3"); // その後6の値が入るのを期待

const EVENT2 EVENT2::NONE("NONE", []() { LOG("exec none"); }); // 0が入ることを期待
const EVENT2 EVENT2::TEST("TEST", []() { LOG("exec test1"); }); 
const EVENT2 EVENT2::TEST2("TEST2", nullptr);
```
### 使い方
使い方はこんな感じです。

```cpp:c++:c++11(使い方)
    // (a)全部取得する
    for (auto& t : EVENT::values()) {
        LOG("%d %s", t->id(), t->value().c_str());
    }
    LOG("----");

    // (b) 最初に空の型を定義して代入する
    EVENT evt;
    evt = EVENT::EVENT1;
    LOG("%d %s", evt.id(), evt.value().c_str());

    LOG("----");
    // 引数に入れても問題ないか確認
    auto function1 = [](const EVENT& evt) {
        // キャストしてみたり
        LOG("%d %s", (int)evt, ((const char*)evt));
    };
    
    // 引数に入れられた際に代入演算子が呼ばれる(だったと思う)けど問題ないか確認
    auto function2 = [](EVENT evt) {
        LOG("%d %s", evt.id(), evt.value().c_str());
    };

    // 引数での型変換
    auto function3 = [](int id) { LOG("%d", id); };
    auto function4 = [](const std::string& value) {
        LOG("%s", value.c_str());
    };

    // (c)
    function1(EVENT::EVENT1);
    function2(EVENT::EVENT1);
    function3(EVENT::EVENT1);
    function4(EVENT::EVENT1);

    LOG("----");
    for (auto& t : EVENT::values()) {
        // (d) 壊れてないか確認
        LOG("%d %s", t->id(), t->value().c_str());
    }
    LOG("----");
    // autoで代入
    auto v = EVENT2::NONE;
    // (e) 
    v.execute();
    v = EVENT2::TEST;
    v.execute();
    // 直接メソッドを呼ぶ
    EVENT2::NONE.execute();
    // リストからメソッドを呼ぶ
    for (auto& t : EVENT2::values()) {
        // (f)
        t->execute();
    }
    // (g)
    // valueofで取得
    auto vv = EVENT2::valueOf(1);
    vv->execute();
    vv = EVENT2::valueOf("NONE");
    vv->execute();

```
### 出力ログ
すると、以下のように出力されます。

```bash
#以下はEnum.h に仕込んだログ出力です
#以下はEVENT列挙クラスの出力
enum : 0 name: NONE
enum : 1 name: EVENT1
enum : 5 name: EVENT2
enum : 6 name: EVENT3
#以下はEVENT2列挙クラスの出力
enum : 0 name: NONE
enum : 1 name: TEST
enum : 2 name: TEST2
# (a)
0 NONE
1 EVENT1
5 EVENT2
6 EVENT3
----
# (b)
1 EVENT1
----
# (c)
1 EVENT1
1 EVENT1
1
EVENT1
----
# (d)
0 NONE
1 EVENT1
5 EVENT2
6 EVENT3
----
# (e)
exec none
exec test1
# (f)
exec none
exec test1
# (g)
exec test1
exec none
```
わかりづらいかもですが、とりあえずうまくいってます。
やったね(๑•̀ㅂ•́)و✧

# 説明

とりあえず、Enumのクラスはテンプレートを使って定義してます。

```cpp
template <class _Ty> // -- (1)
```
テンプレートについて詳しいことはぼくよりもずっと詳しい方々に任せるとして(こことか分かりやすかったです)、動作を知るために知っとくといいのは**「c++の場合テンプレートは文字通りテンプレートにすぎない」**ってことかと思います。
要はテンプレートだけあってもクラスの実体はコンパイルされないという感じですね。(もっと詳細をいうと違うのかもしれませんが、ぼくはそう理解してます)。
ではいつ実体のクラスがコンパイルされるかというと、こんな感じで型が定義された際ですね。

```cpp
class EVENT : public Enum<EVENT> {
//(省略)
}
```
そしてその時に**「指定された型が補完されたクラスが生成されてコンパイル」**されるっぽいです。
これが何を意味しているかというと、**指定された種類の分だけ別クラスが生成される**ってことです。つまり、上記の例だと `Enum<EVENT>` と `Enum<EVENT2>` というクラスがそれぞれ**別のクラスとして**テンプレートにもとづいて作成されます。

※　余談ですが、テンプレートメソッドもプログラム全体で指定された数だけ似たようなメソッドが生成されるっぽいです(確かめてないから本当か知らないけど)。

### 何を意味してるかというと
それぞれ別クラスが生成されるおとで、この部分がちゃんと動作するわけですね。

```cpp
    Enum(const std::string& name) : m_id(s_size), m_name(name) {
        s_size++;　// -- (2)
        LOG("enum : %d name: %s", m_id, name.c_str());
        s_vector.push_back(static_cast<_Ty*>(this)); // -- (3)
    }

    Enum(int num, const std::string& name) : m_id(num), m_name(name) {
        s_size = num + 1; 
        LOG("enum : %d name: %s", m_id, name.c_str());
        s_vector.push_back(static_cast<_Ty*>(this));
    }
```
上で定義した `s_size` と `s_vector` は、staticな変数でありクラスの中で静的に定義されたものになります。 クラスが同じ場合、インスタンスに関係なくひとつの変数が共有されるわけですが、上で述べた通り`Enum<EVENT>` と `Enum<EVENT2>`は別のクラスになるので、それぞれの`s_size` と `s_vector`が生成されます。
その結果、下記のように別々に0から番号が割り振られることになります。

```bash
#以下はEVENT列挙クラスの出力
enum : 0 name: NONE
enum : 1 name: EVENT1
enum : 5 name: EVENT2
enum : 6 name: EVENT3
#以下はEVENT2列挙クラスの出力
enum : 0 name: NONE
enum : 1 name: TEST
enum : 2 name: TEST2
```
また、 `s_vector` (命名が下手でごめんなさい)にもそれぞれのクラスのインスタンスが格納されるので、values()で呼び出した際にちゃんと期待する結果となるわけですね。

なお、Enum.hの一番下にあった

```cpp
// -- (4)
template <class _Ty>
int Enum<_Ty>::s_size = 0;
template <class _Ty>
typename std::vector<_Ty*> Enum<_Ty>::s_vector = std::vector<_Ty*>();
```
こちらでstatic変数の初期化をしています。
本来はstatic変数をヘッダ内で定義するとエラーが出たりするのですが、テンプレートの場合はエラーとなりません。
理由
はたぶん同じで、テンプレートはただ定義しただけではコンパイルの対象にはならず、型も定義されてはじめてコンパイルされるからだと思います。
(そのあたりまだあまり詳しく知らないからもしかしたら別の理由かもしれないけど)

### その他のこと

あとはまあ、やってることはオペレーターのオーバーロードとかメソッド定義とかだけで、そんな難しくないかなと思いますので説明は割愛しますが、 `Event.hpp` で定義してたここだけ

```
    using Enum::Enum; // -- (5)
```

上記はEnumのコンストラクタをそのまま継承するよって意味になります。
意外と知らない方もいると思いますので(とっても便利ですよ)。

# ちなみに

列挙の定義の箇所ですが、こんな感じにマクロ作るとより使いやすいかもです。
以下に置き換えても同じ内容になります。

```cpp:c++11(Event.cpp)
#include "Event.hpp"
#define ENUM(__TYPE__,__VALUE__) const __TYPE__ __TYPE__::__VALUE__(##__VALUE__)
#define ENUM_SETNUM(__TYPE__,__VALUE__,__NUM__) const __TYPE__ __TYPE__::__VALUE__(__NUM__,##__VALUE__)


ENUM(EVENT , NONE);
ENUM(EVENT , EVENT1);
ENUM_SETNUM(EVENT , EVENT2 , 5);
ENUM(EVENT , EVENT3);
```
ただ、マクロだと中でラムダ式の定義とかはできないんですけどね。

# まとめ
テンプレートおもしろいね。
あと天ぷら食べたい。