HDF5をC++で使う：(その3）複合データ型

# データセットに格納できるデータ型は一種類だけなのか？

これまでの説明[(その1)](https://qiita.com/zhidao/items/22ca97f7d2e55ece6a3f)[(その2)](https://qiita.com/zhidao/items/6594b2cf5c8eff2c5dc4)では、データセットに格納するデータ型は`H5::PredType::STD_I32LE`に限定していました。/usr/include/hdf5/serial/H5PredType.hを見ると他にも色々（例えばfloat型に相当するIEEE_F32BE/IEEE_F32LE、double型に相当するIEEE_F64BE/IEEE_F64LEなど）あるのですが、元のデータがある構造体の場合には、これを要素とした配列をそのまま格納したくなります。

この場合、Predefinedな型ではなく自分で複合型`H5::CompType`を定義することができます。

# 複合型のデータ配列書き出しサンプル

次のようなサンプルプログラムを作ってみました。
```C++
#include <iostream>
#include <H5Cpp.h>

#define DATASET_NAME "/dataset"

struct cdata_t{
  int    i_data;
  float  f_data;
  double d_data;
};

#define MEMBER1 "int_data"
#define MEMBER2 "float_data"
#define MEMBER3 "double_data"

#define N 5

int main(int argc, char *argv[])
{
  H5::Exception::dontPrint();
  H5::H5File file( argv[1], H5F_ACC_TRUNC );

  hsize_t dims[] = { N };
  H5::DataSpace dataspace( sizeof(dims) / sizeof(hsize_t), dims );
  H5::CompType comptype( sizeof(cdata_t) );
  comptype.insertMember( MEMBER1, HOFFSET(cdata_t,i_data), H5::PredType::NATIVE_INT );
  comptype.insertMember( MEMBER2, HOFFSET(cdata_t,f_data), H5::PredType::NATIVE_FLOAT );
  comptype.insertMember( MEMBER3, HOFFSET(cdata_t,d_data), H5::PredType::NATIVE_DOUBLE );
  H5::DataSet dataset = file.createDataSet( DATASET_NAME, comptype, dataspace );

  cdata_t *data = new cdata_t [dims[0]];
  for(int i=0; i<static_cast<int>(dims[0]); i++ ){
    data[i].i_data = i;
    data[i].f_data = i * i;
    data[i].d_data = 1.0 / (i+1);
  }
  dataset.write( data, comptype );
  delete data;
  dataset.close();

  file.close();
  return 0;
}
```
例によって処理を追っていきましょう。最初に構造体`cdata_t`を定義し、それぞれのメンバに対応する文字列を決めています。
```C++
struct cdata_t{
  int    i_data;
  float  f_data;
  double d_data;
};

#define MEMBER1 "int_data"
#define MEMBER2 "float_data"
#define MEMBER3 "double_data"
```
main()関数最初のファイルオープンは横着しています。
```C++
  H5::Exception::dontPrint();
  H5::H5File file( argv[1], H5F_ACC_TRUNC );
```
データスペース作成はこれまでと同様です。
```C++
  hsize_t dims[] = { N };
  H5::DataSpace dataspace( sizeof(dims) / sizeof(hsize_t), dims );
```
次が複合データ型を定義している部分になります。
```C++
  H5::CompType comptype( sizeof(cdata_t) );
  comptype.insertMember( MEMBER1, HOFFSET(cdata_t,i_data), H5::PredType::NATIVE_INT );
  comptype.insertMember( MEMBER2, HOFFSET(cdata_t,f_data), H5::PredType::NATIVE_FLOAT );
  comptype.insertMember( MEMBER3, HOFFSET(cdata_t,d_data), H5::PredType::NATIVE_DOUBLE );
```
何をしているかはおおよそご想像頂けると思います。コンストラクタに`sizeof( cdata_t )`を与えることで、これから定義する型のメモリサイズを指定し、その後にinsertMember()メソッドを使ってメンバーを一つ一つ登録しています。

`HOFFSET()`は構造体の各メンバーの相対アドレスを返すマクロで、H5Cpp.hに定義されています。標準のoffsetof()を使っても構いません。それぞれのメンバーが具体的にどの型であるのかを第3引数で指定しています。ここを自動判別させるのは難しいようです。

データセット作成時には、定義したcomptypeをそのまま`H5::DataType`インスタンスとして与えられます。
```C++
  H5::DataSet dataset = file.createDataSet( DATASET_NAME, comptype, dataspace );
```
続くforループで適当にデータを作成し、
```C++
  dataset.write( data, comptype );
```
として書き出します。最後にデータセットとファイルを閉じて終了です。
```C++
  dataset.close();

  file.close();
```

# 複合データ型配列の例

上記のプログラムを実行し、test.h5を作成しました。これをh5dumpで見ると次のように出力されます。
```
HDF5 "test.h5" {
GROUP "/" {
   DATASET "dataset" {
      DATATYPE  H5T_COMPOUND {
         H5T_STD_I32LE "int_data";
         H5T_IEEE_F32LE "float_data";
         H5T_IEEE_F64LE "double_data";
      }
      DATASPACE  SIMPLE { ( 5 ) / ( 5 ) }
      DATA {
      (0): {
            0,
            0,
            1
         },
      (1): {
            1,
            1,
            0.5
         },
      (2): {
            2,
            4,
            0.333333
         },
      (3): {
            3,
            9,
            0.25
         },
      (4): {
            4,
            16,
            0.2
         }
      }
   }
}
}
```
データ型自体はH5T_COMPOUNDと表現されています。そしてそれが、
```
  H5T_STD_I32LE "int_data";
  H5T_IEEE_F32LE "float_data";
  H5T_IEEE_F64LE "double_data";
```
という3つの型（と対応する文字列）を持つことが見て取れます。定義時にはNATIVE_INT、NATIVE_FLOAT、NATIVE_DOUBLEとしたものが、それぞれSTD_I32LE、IEEE_F32LE、IEEE_F64LEと解釈されていることにご注意下さい。これは実行したCPUに依存します。