型安全なインデックス

数値計算プログラム内ではあらゆる場所でインデックスを使ったforループが使われています。C++11からrange-based forループが使えるようになったとはいえ、インデックス自体を必要とする場合も多々あるため、数値計算分野では未だにCスタイルのforループが一般的です。

数値計算分野でよく使われるインデックスとしては

- メッシュのセル・面・点
- メッシュのセル・面・点の全体または一部に各要素が紐付けられたベクトル

が挙げられます。一般的には整数型（intなど）をこれらのインデックスとして使用しますが、間違って使ってしまう（セルのインデックスに面のインデックスを代入してしまうなど）可能性があります。そこで、これらのインデックスを型安全を保証し、かつゼロオーバーヘッドで取り扱う方法を紹介します。

この記事は[OpenMesh](https://www.openmesh.org/)の各種Handleクラスを使用した経験、[Type safe handles in C++](http://www.ilikebigbits.com/blog/2014/5/6/type-safe-identifiers-in-c)というブログ、自作数値計算ライブラリでの実装経験を元に作成しました。

追記１：継承する場合Tagがいらないのを忘れてたので修正
追記２：継承する場合の実装にミスがあったので修正
追記３：継承する場合に各IDクラスごとに演算子を定義する必要のない方法を追加
追記４：Tag用structを作る必要がないので修正
追記５：ID型かどうかを判別する方法を追記

# 別名として定義する方法

まず別名で定義する方法を説明します。
別名で定義すると、テンプレートクラスに対して多重定義した非メンバーの演算子を使うことができますが、一方でコンパイルエラー時のエラーメッセージが複雑で読みにくくなります。

## IDクラス

全てのインデックスの元となるIDテンプレートクラスを作ります。
テンプレートパラメータのTagでインデックスの種類を識別し、Tでインデックスの型を与えます。
IDをT型に変換するための演算子とアクセス関数も定義します。

```cpp:ID.hpp
#ifndef ID_H
#define ID_H

template<class Tag, class T, T default_value>
class ID
{
public:
  ID() = default;

  // Explicit constructor
  explicit ID(T id) : id_(id) { }

  // Explicit conversion operator to T
  explicit operator T() const { return id_; }

  // Access function
  T id() const { return id_; }

  // Friend functions
  friend std::ostream& operator<<(std::ostream& os, const ID& id) {
    return os << id.id();
  }
  friend bool operator==(const ID a, const ID b) { return a.id_ == b.id_; }
  friend bool operator!=(const ID a, const ID b) { return a.id_ != b.id_; }
  // ... （その他、比較演算子などの定義）

protected:
  T id_ = default_value;
};

#endif
```

explicitをコンストラクタと変換演算子に与えているのは、前者がT型からID型への、後者がID型からT型への暗黙の型変換を行わせないためです。


## 各種IDクラス

`ID`クラスを使い、セル・面・点のインデックスを表す`CellID`・`FaceID`・`VertexID`クラスを定義します。
これらのIDクラスを使用する場合、各種関数は全てインライン化されるため、実質的にint型と変わらないパフォーマンスが得られます。


```cpp:IDs.hpp
#ifndef IDS_H
#define IDS_H

#include "ID.hpp"

// Tags for IDs
struct VertexTag {};
struct FaceTag {};
struct CellTag {};

// ID classes as aliases
using VertexID = ID<VertexTag, int, -1>;
using FaceID = ID<FaceTag, int, -1>;
using CellID = ID<CellTag, int, -1>;

#endif
```

# 継承を使って定義する方法

下記で紹介している実装だと、各IDクラスそれぞれで演算子をオーバロードする必要があります。
ただし、一つIDクラスを作れば以降は名前を置換するだけなので、それほど大変ではありませんし、むしろわかりやすいと思います。

## IDクラス

テンプレートパラメータからTagをなくします。
また演算子も定義しません。

```cpp:ID.hpp
#ifndef ID_H
#define ID_H

template<class T, T default_value>
class ID
{
public:
  ID() = default;

  // Explicit constructor
  explicit ID(T id) : id_(id) { }

  // Explicit conversion operator to T
  explicit operator T() const { return id_; }

  // Access function
  T id() const { return id_; }

protected:
  T id_ = default_value;
};

#endif
```

## 各種IDクラス

```cpp:VertexID.hpp
#ifndef VERTEX_ID_H
#define VERTEX_ID_H

#include "ID.hpp"

struct VertexID : ID<int, -1> {
  VertexID() = default;
  explicit VertexID(const int id) : ID(id) {}

  // Friend functions
  friend bool operator==(const VertexID& id1, const VertexID& id2) {
    return id1.id_ == id2.id_;
  }
  // ... （以下同様に演算子をオーバーロード）
};

#endif
```

`FaceID`・`CellID`クラスも同様に定義します。

## 補足

各種IDクラスで演算子をオーバーロードするのが助長だと思う場合は、次のように実装するといいでしょう。

```cpp:ID.hpp
#ifndef ID_H
#define ID_H

template<class Tag, class T, T default_value>
class ID
{ /* ...（別名を使う方法のIDクラスと同じ実装）*/ };

#endif
```

```cpp:VertexID.hpp
#ifndef VERTEX_ID_H
#define VERTEX_ID_H

#include "ID.hpp"

// この場合継承クラスをTagとしてわたせばよいので、Tag用structを作る必要はない
struct VertexID : ID<VertexID, int, -1> {
  VertexID() = default;
  explicit VertexID(const int id) : ID(id) {}
  // VertexIDがIDにアップキャストされた場合の互換性を保つために
  // コピーコンストラクタおよびコピー代入演算子を定義
  VertexID(const ID& id) : ID(id) {}
  VertexID& operator=(const ID& id) {
    this->id_ = id.id_;
    return *this;
 }
};

#endif
```

こうするとVertexID同士の演算に対して、自動的にVertexIDがIDへアップキャストされ、IDクラスの演算子が呼ばれます。

# IDクラスかどうかを判別する

別名で定義した場合のIDクラスを修正し、IDクラスかどうか判定できるテンプレートクラスを作成します。

```cpp:ID.hpp
#ifndef ID_H
#define ID_H

// IDクラス
template<class T>
class ID
{
public:
  using Index = typename T::Index;

  ID() = default;

  // Explicit constructor
  explicit ID(Index id) : id_(id) { }

  // Explicit conversion operator to T
  explicit operator Index() const { return id_; }

  // Access function
  Index id() const { return id_; }

  // Friend functions
  friend std::ostream& operator<<(std::ostream& os, const ID& id) {
    return os << id.id();
  }
  friend bool operator==(const ID a, const ID b) { return a.id_ == b.id_; }
  friend bool operator!=(const ID a, const ID b) { return a.id_ != b.id_; }
  // ... （その他、比較演算子などの定義）

private:
  Index id_{T::defaultValue};
};

// IDクラスを判別するためのテンプレートクラス
template <class T>
struct is_id {
  static constexpr bool value = false;
};

template <class T>
struct is_id<ID<T> > {
  static constexpr bool value = true;
};

template <class T>
constexpr bool is_id_v = is_id<T>::value;

#endif
```

`Index`型と`defaultValue`を内部で定義したクラスを`ID`クラスのテンプレートパラメータとして渡します。

```cpp:VertexID.hpp
#ifndef VERTEX_ID_H
#define VERTEX_ID_H

#include "ID.hpp"

struct VertexTag {
  using Index = int;
  static constexpr Index defaultValue = -1;
};

using VertexID = ID<VertexTag>;

#endif
```


# 結果

上の各種IDクラスを使うと次のように異なるID同士の代入に対してコンパイルエラーが起きます。

```cpp
FaceID face_1(1);
VertexID vertex_1(1);

vertex_1 = face_1;  // Oops! This line will cause a compilation error.
```