パスワードを忘れた? アカウント作成
12978323 journal
日記

m_nukazawaの日記: Cに欲しい機能 インデックス番号付き構造体配列 7

日記 by m_nukazawa

この前スラドの日記で、「Cにこんな機能があるといいな」というのを書いていた方がいて、残念ながら日記そのものは見つからないのですが、それへのコメント代わりに。

C言語に欲しい機能というか、プリプロセッサを用意すれば済む内容なのですが。

最近、家で書いている趣味のCコードでは、
・enumでインデックスをIDとして定義
・内容をenumをキーとした構造体配列としてenumと別に定義
・それを引く関数を別途用意
という書き方をよくしています。

====== 最近よく書いているコード ======
// indexをIDとして定義
enum{
  ColorId_Info,
  ColorId_Warn,
  ColorId_Error,
}ColorId;

// 引きたい内容の構造体
typedef struct{
  ColorId colod_id,
  int r, g, b,
  const char *name,
}ColorInfo;

// 引きたい内容の実態
static const ColorInfo = colorInfos[] = {
  {ColorId_Info, 0, 32, 64, "Info"},
  {ColorId_Warn, 0, 64, 128, "Warn"},
  {ColorId_Error, 0, 128, 255, "Error"},
};

// 以下、引く機能は概念コード
size_t getNumColorInfos(){ return sizeofを使ったいつものトリックで要素数 };
ColorInfo *getcolor(color_id)
{
  for( getNumで取った個数でループ ){return IDがマッチしたcolorInfo}
  return IDマッチしなかったのでNULL;
}
// 欲しければnameでマッチする版などを別途用意
======

みたいなことになっている。
かなり定形になっているので、自動生成できるはず。
次のように書きたい。

====== こう書きたいコード ======
typedef struct_with_index{
  // インデックスがデフォルトで Enum ColorId id (またはindex)で作成される
  Member_Of_Index ColorId color_id;
  // デフォルトで指定したID文字列が const char *name メンバに格納される
  Member_Of_Name;
  // その他メンバが続く
  int r, g, b;
}ColorInfo;

// 以下のように書くと、color_id, nameメンバが自動生成される
static const ColorInfos colorInfos[] = {
  {Info, 0, 32, 64},
  {Warn, 0, 64, 128},
  {Error, 0, 128, 255},
};

/*
さらに、
  #define colorInfos_Num (3)
相当のものが自動で用意される

あと、colorInfos[ColorId_Warn] // 存在しないIdを指定されたらビルドエラー、とか。
*/

======

実際、こういうコードを書き出すジェネレータを自分で書こうかとも思ったのですが、エラー行番号がコンパイラと元コードで一致しないなど、諸々面倒でやっていません。
今日もコード生成ボットのようにコーディングしています。

Cコードのデザインパターンが欲しい。

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
  • enumは0から順番に割り振られることが保証されてるので、そのまま配列indexに使えると思いますが…

    // indexをIDとして定義
    enum{
      ColorId_Info,
      ColorId_Warn,
      ColorId_Error,
    }ColorId;
     
    // 引きたい内容の構造体
    typedef struct{
      int r, g, b,
      const char *name,
    }ColorInfo;
     
    // 引きたい内容の実態
    static const ColorInfo = colorInfos[] = {
      {0, 32, 64, "Info"},
      {0, 64, 128, "Warn"},
      {0, 128, 255, "Error"},
    };
     
    // 以下、引く機能は概念コード
    size_t getNumColorInfos(){ return sizeofを使ったいつものトリックで要素数 };
    ColorInfo *getcolor(color_id)
    {
      if (0 <= color_id && color_id < getNumColorInfos()) return &(colorInfos[color_id]);
      return NULL;
    }

    って感じで。enum定義とColorInfo配列インデックスとの対応が非明示的なのが気になるなら、

    enum{
      ColorId_Info=0,
      ColorId_Warn=1,
      ColorId_Error=2,
    }ColorId;

    と、明示的に割り当てた方が、対応がわかりやすいですかね。

    あと、
    > 実際、こういうコードを書き出すジェネレータを自分で書こうかとも思ったのですが、エラー行番号がコンパイラと元コードで一致しないなど、諸々面倒でやっていません。
    ジェネレータ側で、#line ディレクティブを使って、ジェネレート元のファイル名・行番号を

    #line 行番号 "ファイル名"

    という形式で出力しておけば、コンパイラエラーなどは#lineで指定したもので表示されるようになりますよ。

    • コメントで頂いた通り、enumと構造体配列の対応が非明示的なのが、今のやり方では満足いかない理由のひとつです。

      #line ディレクティブは思いつきませんでした。
      でもコンパイラの行番号表示をこちらから指示するのはちょっと不安に感じますね。

      親コメント
    • by Anonymous Coward

      > > enum{
      > > ColorId_Info=0,
      > > ColorId_Warn=1,
      > > ColorId_Error=2,
      > > }ColorId;
      > と、明示的に割り当てた方が、対応がわかりやすいですかね。

      構造体配列の方が連番になっていることが前提なので、明示的に割り当てると逆にトラブルの元じゃないですかね。
      うっかり数字を抜かしたり入れ替えてしまったりした場合に分かりにくい。

      これに加えてさらに、C99で導入された配列初期化の拡張を使うと良いと思います。

      // 引きたい内容の実態
      static const ColorInfo colorInfos[] = {
          [ColorId_Info] = {0, 32, 64, "In

  • by Anonymous Coward on 2016年11月12日 1時31分 (#3113124)

    $ cat template.txt
    Info 0 32 64
    Warn 0 64 128
    Error 0 128 255

    $ awk '{printf "#define %s %d\n", $1, NR }' < /tmp/template.txt
    #define Info 1
    #define Warn 2
    #define Error 3

    $ awk 'BEGIN{ print "static const ColorInfos colorInfos[] = {"}{printf "{%s,%s,%s},\n", $1,$2,$3} END{print "};";}' < /tmp/template.txt
    static const ColorInfos colorInfos[] = {
    {Info,0,32},
    {Warn,0,64},
    {Error,0,128},
    };

    と言う感じで後は Makefile で

    header.h: template.txt
            awk '....' > header.h
          awk '....' > > header.h

    するだけでは?5分もあれば書けますよ

typodupeerror

あと、僕は馬鹿なことをするのは嫌いですよ (わざとやるとき以外は)。-- Larry Wall

読み込み中...