茂加部珈琲店

主にtech関連のメモ置き場です

フレキシブル配列メンバをC++で

こんにちは。c++ネタ。

flexible array member

プログラミングをしていると、大きさのわからない配列を扱いたい時があります。
間接参照を挟まず、構造体に直接配列を埋め込む手法はCなどではよく使われています
こういった配列を扱うため、C99からはフレキシブル配列flexible array memberという機能が追加されました。 使い方は以下のようになります

struct flexArrayStruct {
  int num;
  int data[];
};

末尾のdata[]がフレキシブル配列で、要素数の不明な配列を表します。プログラムは実行時に任意の長さのペイロードを含むオブジェクトをメモリ上に確保してアクセスすることができます。

フレキシブル配列の実体は大雑把に言えば大きさ0の配列です。つまり、事前に「次に来る要素」にアクセスする手段を提供する手段ということです。

C++でフレキシブル配列メンバを模倣する その1

本題に入りますが、C++はC99ベースに作られていないので、フレキシブル配列を使用することはできません。
いくつかのコンパイラは独自拡張としてフレキシブル配列をサポートしています

struct flexArrayStruct {
    int num;
    int data[]; // gcc/clangでコンパイル可能
};

しかし、フレキシブル配列を持つクラスは継承することができません。

  struct Derived : flexArrayStruct {}; // error: base class 'flexArrayStruct' has a flexible array member

これは異論がある人もいると思いますが、フレキシブル配列を持つ構造体を継承すれば、ポインタがキャスト可能になり若干嬉しいです。
そこで、フレキシブル配列ではなく、長さゼロの配列を使用します

// gcc/clangでコンパイル可能
struct flexArrayStruct {
    int num;
    int data[0] = {};
};

struct Derived : flexArrayStruct {
    Derived() :flexArrayStruct{8} {}
    int data[8] = {1,2,3,4,5,6,7,8};
};

flexArrayStruct::dataDerived::dataはメモリ上で同一の場所を表します。
C++の規格上は、長さゼロの配列も、固有のアドレスを持たないオブジェクトも違法です。
そもそも配列の要素数を超えたアクセスはUBですが、耳から天使を召喚して何とかしてください
(ちなみにstd::array<0>は許されていますが、オブジェクトのサイズはゼロではないので注意) また、clang/gccではコンパイルできますが、ゼロ長配列を持つ基底クラスを許さないmsvcではコンパイルできません。

C++でフレキシブル配列メンバを模倣する その2

MSVCでもコンパイルしたい場合は上記の手法は使えませんでした。
こういうときは何も考えずにテンプレートを使ってみるのがC++

template <int N=1>
struct A : A<N-1> {
    int _data_element= {};
};
template <>
struct A<1> {
    int num;
    int data[1] = {};
};
struct D : A<8> {
  D() :A<8>{8,1,2,3,4,5,6,7,8} {}
};

うーん… 非常に危険な香りがしますね

いずれにせよ、C++でフレキシブル配列を行儀よくやるのは無理そうというのが感想です。 より行儀が良い手法としてはポインタ演算でゴリゴリやるしかなさそうです。 フレキシブル配列メンバが規格に入れば良いんですが、忘れられてしまったんでしょうか