m_nukazawaの日記: Cに欲しい機能 インデックス番号付き構造体配列 7
この前スラドの日記で、「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はそのまま配列添字に使えます。 (スコア:1)
enumは0から順番に割り振られることが保証されてるので、そのまま配列indexに使えると思いますが…
って感じで。enum定義とColorInfo配列インデックスとの対応が非明示的なのが気になるなら、
と、明示的に割り当てた方が、対応がわかりやすいですかね。
あと、
> 実際、こういうコードを書き出すジェネレータを自分で書こうかとも思ったのですが、エラー行番号がコンパイラと元コードで一致しないなど、諸々面倒でやっていません。
ジェネレータ側で、#line ディレクティブを使って、ジェネレート元のファイル名・行番号を
という形式で出力しておけば、コンパイラエラーなどは#lineで指定したもので表示されるようになりますよ。
Re:enumはそのまま配列添字に使えます。 (スコア:2)
コメントで頂いた通り、enumと構造体配列の対応が非明示的なのが、今のやり方では満足いかない理由のひとつです。
#line ディレクティブは思いつきませんでした。
でもコンパイラの行番号表示をこちらから指示するのはちょっと不安に感じますね。
Re: (スコア:0)
> > enum{
> > ColorId_Info=0,
> > ColorId_Warn=1,
> > ColorId_Error=2,
> > }ColorId;
> と、明示的に割り当てた方が、対応がわかりやすいですかね。
構造体配列の方が連番になっていることが前提なので、明示的に割り当てると逆にトラブルの元じゃないですかね。
うっかり数字を抜かしたり入れ替えてしまったりした場合に分かりにくい。
これに加えてさらに、C99で導入された配列初期化の拡張を使うと良いと思います。
// 引きたい内容の実態
static const ColorInfo colorInfos[] = {
[ColorId_Info] = {0, 32, 64, "In
Re:enumはそのまま配列添字に使えます。 (スコア:2)
C99の配列初期化の構文自体は知っていましたが、enum値を入れる発想はなかったです。
やはり対応関係が問題ですね。
私は、構造体からインデックスを生成する、それもゼロから始まって抜けがないようなもの、という方向で考えています。
awkとかperlでソースコードを自動生成 (スコア:0)
$ 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分もあれば書けますよ
Re:awkとかperlでソースコードを自動生成 (スコア:2)
なるほどジェネレータ自体は簡単に簡単に書けるのですね。
ありがとうございます。
// 私はまだawk使えていませんが。最近やっとsedの便利さがわかってきた
Re:awkとかperlでソースコードを自動生成 (スコア:1)
流れ作業でせいぜいパイプに渡す前に一つのことしかしないプリミティブな処理をえんえんと並べて得られたものを見て
「あたま悪いな。でも使い捨てだからこれで構わないし。」
と自己評価を現状維持以上に肯定してくれるお友達扱いできます。
// あくまでわたしの場合。