2013年2月14日木曜日

Templateの基礎


Templateの基礎です。
なんだかんだで、勉強不足で知らないことが多かったのでまとめたいと思います。

まず、引数の型からテンプレートパラメータが自動決定される、よくあるテンプレート関数です。

上記の場合、func1(100)などとすれば型が自動確定しますが、func1<char>(100)などとして、明示的にテンプレートパラメータで指定することもできます。

次は明示的なテンプレートパラメータ指定が必須な場合です。

戻り値にテンプレート型が使用されており、引数による型の自動特定はできません。 上記を使用する場合、func2<char>(100)などのようにして明示的にテンプレートパラメータを示さないとエラーになります。
また、下記のようにテンプレートパラメータが2つ以上ある場合においては、明示と省略表現を混在できます。

例えば、func3<char>(100)とも、func3<char,int>(100)とも書けます。
さらに、次のような型を指定したテンプレート関数を作ることもできます。

使うときにはfunc4<2>(10)などとします。 当然ですが、テンプレート関数はコンパイル時に静的に生成されるため、func4<a>(10)のように、テンプレートパラメータに変数を指定することはできません。 動的なパラメータは大人しく引数で渡して下さい。

しかし、ここで挙げたサンプルだけ見ると、テンプレートの利点がよく飲み込めないですね。 #defineなどのプリプロセッサと本質何が違うのでしょうか?なんとなくかっこ良く見えるとか? 実際書いててよくわからなくなってきました。
例えば、テンプレートの場合は、型がより安全に扱えるという利点があると思います (プリプロセッサは単なる文字列の置換にすぎないので)。 しかし、Policy-based Designのような技巧的な手法を使わないとtemplateの本領は見えてこないのかもしれないです。

プログラム中にはなるべく即値(イミディエイト)を書くべからずとはよく言われますが、そういう意味では#defineもtemplateも発想の着眼点は同じなのかもしれません。


7 件のコメント:

  1. ヤンバル戦線の更新楽しみにしています!

    今回のテンプレートの基礎のお話、参考になりました。
    話の中で言及されているテンプレートとdefineの類似点という観点、なるほどなと思いました。

    考えてみれば、defineなどのプリプロセッサを使えばC言語でもC++のテンプレートライクな記述はできそうだなと。

    試しに以下のテストプログラムを作ってみました。

    ■ a.h

    #include

    HOGE * new_mem(void)
    {
    HOGE * h;
    h = (HOGE *)malloc(sizeof(HOGE));
    return h;
    }

    void rel_mem(HOGE ** h)
    {
    free(*h);
    *h = NULL;
    }

    ■ main.c


    #include

    typedef struct
    {
    int a;
    }s_a;

    #define HOGE s_a
    #include "a.h"

    void main(void)
    {
    s_a * v1;

    v1 = new_mem();

    v1->a = 100;
    printf("%d\n", v1->a);

    rel_mem(&v1);
    }


    ほうほう。
    確かにこれなら型を目的に応じて変更したい汎用関数に対して、void * ではなく、コンパイル時に静的に型を決定することができますね。
    (まぁ、C言語の型安全は弱いので、ここまでする意味はあるのかという疑問はありますが。)

    また関数にstaticを指定してファイル内でのみ有効の関数にすれば、プロジェクト内に型別に応じた複数の関数を定義することもできますね。

    今回の投稿はC言語の奥深さを再認識させてもらえた大変有意義なものでした。

    返信削除
  2. しかしながら、同じファイル内に2つ以上の型が異なる関数をC言語のdefineで作る場合にはどうすればいいんでしょうね。

    C++のように関数のオーバーロードや名前空間などがあればできるんですけどね。
    残念ながらこれがC言語の限界なんだろうか。

    返信削除
  3. 同じファイル内に2つ以上の型が異なる関数をC言語のdefineで作る1つの方法として、関数名を装飾するようなマクロを作ればいいのではないかという結論に達しました。

    サンプルプログラム:

    ■ a.h

    #include

    #define newmem(x) new_mem##x
    #define relmem(x) rel_mem##x

    static HOGE * NEWMEM(void)
    {
    HOGE * h;
    h = (HOGE *)malloc(sizeof(HOGE));
    return h;
    }

    static void RELMEM(HOGE ** h)
    {
    free(*h);
    *h = NULL;
    }


    ■ main.c

    #include

    typedef struct
    {
    int a;
    }s_a;

    typedef struct
    {
    double a;
    }s_b;

    #define newmem_a newmem(a)
    #define relmem_a relmem(a)
    #define NEWMEM newmem_a
    #define RELMEM relmem_a
    #define HOGE s_a
    #include "a.h"
    #undef NEWMEM
    #undef RELMEM
    #undef HOGE

    #define newmem_b newmem(b)
    #define relmem_b relmem(b)
    #define NEWMEM newmem_b
    #define RELMEM relmem_b
    #define HOGE s_b
    #include "a.h"
    #undef NEWMEM
    #undef RELMEM
    #undef HOGE

    void main(void)
    {
    s_a * v1;
    s_b * v2;

    v1 = newmem_a();
    v2 = newmem_b();

    v1->a = 100;
    printf("%d\n", v1->a);
    v2->a = 1.5;
    printf("%lf\n", v2->a);

    relmem_a(&v1);
    relmem_b(&v2);
    }


    うむうむ。
    多少まどろっこしいところがありますが、一応目的は達成できましたね。
    まあでも、やはり言語レベルでテンプレートといった仕組みを備えているものには、記述の簡潔さという点では勝てませんね。

    返信削除
  4. あれ、#include の一部が消えている。。。

    a.h の#include は malloc.hで、
    main.c の#include は stdio.h です。

    HTMLのタグとして解釈された・・・?

    返信削除
  5. このコメントは投稿者によって削除されました。

    返信削除
  6. アイディアをコメントいただきありがとうございます。
    確かにこれなら、Cでも似たようなことができますね。

    以下のようにするとさらに使いやすくなると思いました。

    ■a.h -----------------------------------
    #if defined(HOGE) && defined(NEWMEM) && defined(RELMEM)
    #include <stdio.h>

    static HOGE * NEWMEM(void)
    {
    HOGE * h;
    h = (HOGE *)malloc(sizeof(HOGE));
    return h;
    }

    static void RELMEM(HOGE ** h)
    {
    free(*h);
    *h = NULL;
    }

    #undef NEWMEM
    #undef RELMEM
    #undef HOGE

    #endif

    ■main.c --------------------------------
    #include <stdio.h>

    typedef struct
    {
    int a;
    }s_a;

    typedef struct
    {
    double a;
    }s_b;


    #define HOGE s_a
    #define NEWMEM newmem_a
    #define RELMEM relmem_a
    #include "a.h"

    #define HOGE s_b
    #define NEWMEM newmem_b
    #define RELMEM relmem_b
    #include "a.h"

    int main(void)
    {
    s_a * v1;
    s_b * v2;

    v1 = newmem_a();
    v2 = newmem_b();

    v1->a = 100;
    printf("%d\n", v1->a);
    v2->a = 1.5;
    printf("%lf\n", v2->a);

    relmem_a(&v1);
    relmem_b(&v2);

    return 0;
    }

    -----------------------------------------

    しかし、Cでやろうとするとやはりゴリ押し感は隠し切れないですね。
    それと、トークン連結演算子(##)なるものがあることは知りませんでした・・・

    やっぱ知ってるつもりで知らないことって結構あるものですねぇ

    返信削除
  7. なるほど。
    使いやすくするための改良ありがとうございます。

    HOGEなどが定義されていない場合は関数自体定義されないようにしてある&自動的にundefするようにしたわけですね。

    返信削除