日本語版
最新ニュース
科学&テクノロジー

C でジェネリックを記述する方法

C はベアボーンであり、ジェネリックを「サポートしていません」が、実際には、既存のツールを使用して実装するのは非常に簡単です。実際に実装されているのを見つける方法はたくさんあります。一般的なものとしては次のようなものがあります。 関数型マクロの使用 #define vector_push(vector, item) vector.buf[vector.idx++] = item; 短所: これにより、すべてがインライン化され、型付けが緩くなります。 void vector_push(Vector vec, void *item); 短所: ジェネリックスを実現するには型消去に依存しているため、ジェネリックスにアクセスするにはすべてを再キャストする必要があります。 UBに遭遇する可能性もあります。 関数のようなマクロを介して特殊化をディスパッチする #define DECLARE_VECTOR(type) \ void vector_push(Vector##_##type, type item) {\ // do stuff here \ } DECLARE_VECTOR(int) 短所: すべてがマクロでラップされているため、オートコンプリートが壊れる可能性があります 私が推奨するアプローチはそれほど目新しいものではありませんが、最も使いやすく、既存のツールとよりうまく連携できると私が発見したアプローチです。型の消去に依存しておらず、すべてがマクロでラップされていないためです。…

1763217685
2025-11-15 13:58:00

C はベアボーンであり、ジェネリックを「サポートしていません」が、実際には、既存のツールを使用して実装するのは非常に簡単です。実際に実装されているのを見つける方法はたくさんあります。一般的なものとしては次のようなものがあります。

  • 関数型マクロの使用
#define vector_push(vector, item) vector.buf[vector.idx++] = item;

短所: これにより、すべてがインライン化され、型付けが緩くなります。

void vector_push(Vector vec, void *item);

短所: ジェネリックスを実現するには型消去に依存しているため、ジェネリックスにアクセスするにはすべてを再キャストする必要があります。 UBに遭遇する可能性もあります。

  • 関数のようなマクロを介して特殊化をディスパッチする
#define DECLARE_VECTOR(type) \
  void vector_push(Vector##_##type, type item) {\
    // do stuff here \
    }

DECLARE_VECTOR(int)

短所: すべてがマクロでラップされているため、オートコンプリートが壊れる可能性があります

私が推奨するアプローチはそれほど目新しいものではありませんが、最も使いやすく、既存のツールとよりうまく連携できると私が発見したアプローチです。型の消去に依存しておらず、すべてがマクロでラップされていないためです。

長所:

  • タイプセーフ
  • ポインタを使用する必要はありません
  • マクロの使用を最小限に抑える
  • リンカーによって重複排除可能
  • 複数のファイルのコンパイルを引き起こす変更を減らすために、完全に 1 つのファイルに入れることができます。

短所:

  • マクロのトリックに依存する
  • 少し冗長になる可能性があります

ヘッダーのインスタンス化によるジェネリックスの実現

これがどのように機能するかというと、型を定義し、その型が識別子の有効な名前ではない場合はサフィックスを定義することによって、型の特殊化をインスタンス化します。これを行うには、小さなマクロのトリックに頼る必要がありますが、それ以外はすべて非常に簡単です。ヘッダーのインスタンス化は次のようになります。

#define VEC_ITEM_TYPE long long
#define VEC_SUFFIX num
#include "vector.h"

VEC_SUFFIX オプションであること。

インスタンス化されると、これにより、 vector_push_num 使える機能。これを行うには、追加できる必要があります _num シンボル名に。私たちが作成するのは、 G 関数のようなマクロ。

#define G(name) name##_##VEC_ITEM_TYPE

ただし、これは機能しません。このように単に展開して追加することはできません。最初に定義を展開してから追加する必要があるため、次のトリックに頼ることになります。

#define __G(name, type) name##_##type
#define _G(name, type) __G(name, type)
#define G(name) _G(name, VEC_ITEM_TYPE)

それは今すぐ機能します!これで、識別子名を G マクロを使用して、 名前が壊れている それぞれのタイプに合わせて。
これを使用して、次のように関数と構造体を定義できます。 struct G(something)void G(do_something)()

型定義の強制とカスタムサフィックスの許可

#ifndef VEC_ITEM_TYPE
  #error VEC_ITEM_TYPE was not defined
#endif

#ifndef VEC_SUFFIX
  #define VEC_SUFFIX VEC_ITEM_TYPE
#endif

次の場合にエラーを発行できるようになりました。 VEC_ITEM_TYPE そして、このヘッダーがコンパイルされているということは、 VEC_ITEM_TYPE 定義されています。

これによりヘッダーの間違った使用法が修正されましたが、次のようなものを使用したい場合はどうすればよいでしょうか。 long long または atomic int?それはうまくいきません。したがって、関数に追加されたサフィックスをプログラムでオーバーライドする方法を導入する必要があります。そこでご紹介するのは、 VEC_SUFFIX:

#ifndef VEC_ITEM_TYPE
  #error VEC_ITEM_TYPE was not defined
#endif

#ifndef VEC_SUFFIX
  #define VEC_SUFFIX VEC_ITEM_TYPE
#endif

#define __G(name, type) name##_##type
#define _G(name, type) __G(name, type)
#define G(name) _G(name, VEC_SUFFIX)

これで、ライブラリを自由に実装できるようになりました。たとえば、これは一般的なものです vec_pop 実装:

bool G(vec_pop)(G(vec_Vector) *vec, VEC_ITEM_TYPE *dest) {
   if (!vec->vec || vec->len  0 || vec->capacity  0) {
      return false;
   }

   vec->len--;
   if (dest) {
      *dest = vec->vec[vec->len];
   }
   G(vec_fit)(vec);
   return true;
}

ご覧のとおり、ジェネリックなものを呼び出すたびに、それをラップする必要があります。 G これにより、特殊化して名前を適切に変換できるようになります。

部屋の中の象たち

再宣言エラー

この実装を試して使用することはできますが、C ファイルが 1 つしかなく、他に何もない場合を除き、特にこの汎用ライブラリを .c そして .h ファイル。

潜在的な解決策は、すべてを作成することです static しかし、その場合、プログラム内の多くの翻訳単位全体ですべてを複製することになります。

実際の修正は、いつ前方宣言するか、いつ実装を使用するかを選択できるようにすることです。

#ifndef VEC_IMPLEMENTATION
  bool G(vec_pop)(G(vec_Vector) *vec, VEC_ITEM_TYPE *dest);
#else
  bool G(vec_pop)(G(vec_Vector) *vec, VEC_ITEM_TYPE *dest) {
     if (!vec->vec || vec->len  0 || vec->capacity  0) {
        return false;
     }

     vec->len--;
     if (dest) {
        *dest = vec->vec[vec->len];
     }
     G(vec_fit)(vec);
     return true;
  }
#endif

その後、ヘッダー ファイルにヘッダーを含めることができます。

// included like any other header
#define VEC_ITEM_TYPE int
#include "vector.h"
#include 
#include 

そして、C ファイル上で、実装をインスタンス化する必要があることを伝えることができます。

#define VEC_IMPLEMENTATION
#define VEC_ITEM_TYPE int
#include "vector.h"

補足: ヘッダーを別のヘッダーに含めた場合でも、単に呼び出すことができます。 #define VEC_IMPLEMENTION 実装を取得するためにファイルを再インクルードする必要はありません。これはこのメソッドの良い副作用ですが、インクルードが別のインクルード内に隠されているため、少し暗黙的に見える可能性があります。何が好きかはあなた次第です。

例:

#define VEC_IMPLEMENTATION
#include "flag.h"

複数には継承型が含まれます

型を定義するためにプリプロセッサ定義に依存しているためです。そうする場合:

#define VEC_ITEM_TYPE int
#include "vector.h"
#include "vector.h"

両方に「参照」が含まれているため、再宣言エラーが発生します。 VEC_ITEM_TYPE として int。これは、汎用ベクトル ヘッダーの最後で定義を解除することで修正できます。

#undef VEC_ITEM_TYPE
#undef VEC_SUFFIX

この方法では、vector を再度インクルードし、インスタンス化したい型を伝えるのを忘れた場合でも、「undeclared VEC_ITEM_TYPE」エラーが発生します。

注: 定義を解除することはありません VEC_IMPLEMENTATION で一度宣言するだけで済むように、 .c 便宜上。

ガードを含める

インクルード ガードが欠落していることについて尋ねているかもしれませんが、それは必要ありません。その理由は、実際には同じヘッダーを複数回インクルードできるようにしたいためです。毎回違うタイプに。

すべてをまとめる

最終的なヘッダーは次のようになります。

#ifndef VEC_ITEM_TYPE
   #error VEC_ITEM_TYPE was not defined
#endif

#ifndef VEC_SUFFIX
   #define VEC_SUFFIX VEC_ITEM_TYPE
#endif

#define __G(name, type) name##_##type
#define _G(name, type) __G(name, type)
#define G(name) _G(name, VEC_SUFFIX)

#include 
#include 
#include 
#include 
#include 
#include 

typedef struct G(vec_vector) {
   size_t capacity;
   size_t len;
   VEC_ITEM_TYPE *vec;
} G(vec_Vector);

#ifndef VEC_IMPLEMENTATION

G(vec_Vector) * G(vec_new)(void);
bool G(vec_push)(G(vec_Vector) *vec, VEC_ITEM_TYPE item);

#else

// Initializes a new vector with items of sizeof(T)
G(vec_Vector) * G(vec_new)(void) {
   G(vec_Vector) *vector = malloc(sizeof(*vector));
   if (!vector) {
      return NULL;
   }

   *vector = (G(vec_Vector)) {
      .capacity = 0,
      .len = 0,
      .vec = NULL,
   };

   return vector;
}

// Resizes vector to fit length
bool G(vec_fit)(G(vec_Vector) *vec) {
   const size_t power = ceilf(log2f(vec->len + 1));
   const size_t new_capacity = sizeof(vec->vec[0]) * powf(2, power);

   if (new_capacity  vec->capacity || new_capacity > vec->capacity) {
      void *tmp = realloc(vec->vec, new_capacity);
      if (!tmp) {
         return false;
      }
      vec->vec = tmp;
      vec->capacity = new_capacity;
   }
   return true;
}

// Pushes a value to vector
bool G(vec_push)(G(vec_Vector) *vec, VEC_ITEM_TYPE item) {
   if (vec->len == 0 && vec->capacity == 0) {
      vec->vec = malloc(sizeof(vec->vec[0]));
      if (!vec->vec) {
         return false;
      }

      vec->vec[0] = item;
      vec->len = 1;
      vec->capacity = sizeof(vec->vec[0]);
      return true;
   }

   G(vec_fit)(vec);
   vec->vec[vec->len] = item;
   vec->len++;
   return true;
}

#endif
#undef VEC_ITEM_TYPE
#undef VEC_SUFFIX

#でジェネリックを記述する方法

執筆者について: nipponese

Nipponese News編集部は、国内外のニュースを日本語で分かりやすくお届けします。