英語版
このページの英語版を見る

テンプレート再訪

ウォルター・ブライト著、http://www.digitalmars.com/dxml-ph-0000@deepl.internalこれからお話しすることは、当社がプログラミングの学生に教えている内容である。
ウォルター・ブライト著、http://www.digitalmars.com/d

これからお話しすることは、私たちが大学院の3年目か4年目のプログラミングの学生たちに教えていることだ。 私がすべきことは、あなたが理解できないからといって、目を背けないよう説得することだ。 私のプログラミングの学生たちも理解していない。 それは私が理解していないからだ。誰も理解していない

リチャード・ディーマン

要約

C++のテンプレートは、トークンの置換以上のものから、それ自体がプログラミング言語へと進化してきた。 C++のテンプレートの多くの有用な側面は、 設計されたというよりも発見されたものである。この副次的な効果として、C++のテンプレートは しばしば、厄介な構文、多くの難解なルール、そして 適切に実装するのが非常に難しいという理由で批判される。もし一歩下がって 一歩下がって、テンプレートが何ができ、どのような用途で使用されているのかをよく見て、 再設計するとしたら、テンプレートは強力で、見た目も美しく、説明しやすく、実装も簡単になるだろうか? この記事では、"プログラミング言語 D" [1] におけるテンプレートの代替設計について見ていく。 D languageD言語

類似点

引数構文

まず最初に思い浮かぶのは、パラメータリストと引数リストを囲むために < > を使用することだ。 しかし、 < > には重大な問題がいくつかある。 演算子 < > および &gt;&gt; と曖昧になることだ。つまり、 以下のような式は

a<b,c>d;
および:
a<b<c>>d;

コンパイラとプログラマーの両方にとって、構文的に曖昧である。 見慣れないコードでa<b,c>d; を見つけた場合、 膨大な量の宣言と.h ファイルを調べ、 それがテンプレートであるかどうかを判断しなければならない。 プログラマー、コンパイラ開発者、言語標準の作成者は、この問題にどれだけの労力を費やしてきただろうか?

もっと良い方法があるはずだ。Dは、!が二項演算子として使用されていないことに注目し、これを解決する。 つまり、置き換える。

a<b,c>

次のように置き換える。

a!(b,c)

文法的に曖昧な点はない。これにより、解析が容易になり、 妥当なエラーメッセージを生成しやすくなり、 コードを検査する人が、a はテンプレートに違いないと判断しやすくなる。

テンプレート定義の構文

C++では、2つの幅広いテンプレート型を定義できる。クラステンプレートと関数テンプレートである。 各テンプレートは、密接に関連している場合でも、それぞれ独立して記述される。

template<class T, class U> class Bar { ... };

template<class T, class U> T foo(T t, U u) { ... }

template<class T, class U> static T abc;

POD(Plain Old Data、"C言語のスタイル")構造体は 関連するデータ宣言をまとめ、クラスは 関連するデータと関数宣言をまとめるが、 一緒にインスタンス化されるテンプレートを論理的にまとめるものは何もない。 D言語では、次のように記述できる。

styleD
template Foo(T, U)
{
    class Bar { ... }

    T foo(T t, U u) { ... }

    T abc;

    typedef T* Footype; // どんな宣言もテンプレート化できる
}

Foo はテンプレート用の名前空間を形成し、これらは例えば以下のようにアクセスされる。 "example"例:"

xml-ph-0000@deepl.internalxml-ph-0000@deepl.internal
Foo!(int,char).Bar b;
Foo!(int,char).foo(1,2);
Foo!(int,char).abc = 3;

もちろん、これは少し面倒になる可能性があるので、特定のインスタンス化に対してエイリアスを使用することもできる。

alias f = Foo!(int,char);
f.Bar b;
f.foo(1,2);
f.abc = 3;

クラステンプレートでは、さらにシンプルな構文が使用できる。クラスは次のように定義される。

classclass
class Abc
{
    int t;
    ...
}

パラメータリストを追加するだけで、これをテンプレートに変換できる。

thisこれ
class Abc(T)
{
    T t;
    ...
}

テンプレートの宣言、定義、およびエクスポート

C++のテンプレートは、テンプレート宣言、テンプレート定義、およびエクスポートされたテンプレートの形式をとることができる。 Dには真のモジュールシステムがあるため、テキスト形式の#includeファイルではなく、 Dにはテンプレート定義のみが存在する。テンプレート宣言や エクスポートの必要性は無関係である。例えば、モジュールA 内のテンプレート定義を 与えると、

xml-ph-0000@deepl.internalxml-ph-0000@deepl.internal
module A;

template Foo(T)
{
    T bar;
}

B モジュールから次のようにアクセスできる。

xml-ph-0000@deepl.internalxml-ph-0000@deepl.internal
module B;

import A;

void test()
{
    A.Foo!(int).bar = 3;
}

通常通り、エイリアスを使用してアクセスを簡略化することもできる。

module B;

import A;
alias bar = A.Foo!(int).bar;

void test()
{
    bar = 3;
}

テンプレートパラメータ

C++のテンプレートパラメータは以下のいずれかである。

D テンプレートパラメータは以下のものを含むことができる。

それぞれにデフォルト値を設定でき、 型パラメータには(限定的な形の)制約を設定できる。

class B { ... }
interface I { ... }

class Foo(
    R,            // Rはどの型でもよい。
    P:P*,         // Pはポインタ型でなければならない
    T:int,        // Tはint型でなければならない
    S:T*,         // SはTへのポインタでなければならな
    C:B,          // CはクラスBまたは
                  // Bの派生クラスでなければならない
    U:I,          // Uは、インターフェイスIか、
                  // インターフェイスIを継承ツリーに持つインターフェイス
                  // またはクラスでなければならない
    string str = "hello",
                  // 文字列リテラル、
                  // デフォルトは"hello"である
    alias A = B   // Aは任意のシンボル
                  // (テンプレートシンボルを含む)、
                  // デフォルトはB
    )
{
    ...
}

特殊化

部分的な特殊化と明示的な特殊化は、C++の場合と同様に機能するが、 「主テンプレート」という概念はない。 同じ名前のすべてのテンプレートは、テンプレートのインスタンス化時に検査され、

部分的な特殊化と明示的な特殊化は、C++の場合と同様に機能するが、 「主テンプレート」という概念はない。すべてのテンプレートは、 テンプレートのインスタンス化時に同じ名前で検査され、 引数とパラメータの適合度が最も高いものがインスタンス化される。

template Foo(T) ...
template Foo(T:T*) ...
template Foo(T, U:T) ...
template Foo(T, U) ...
template Foo(T, U:int) ...

Foo!(long)       // Foo(T)を選ぶ
Foo!(long[])     // Foo(T)を選ぶ、Tはlong[]である
Foo!(int*)       // Foo(T*)を選び、Tはintである
Foo!(long,long)  // Foo(T, U:T)を選ぶ
Foo!(long,short) // Foo(T, U)を選ぶ
Foo!(long,int)   // Foo(T, U:int)を選ぶ
Foo!(int,int)    // 曖昧 - Foo(T, U:T)
                 // またはFoo(T, U:int)

テンプレートには、コンパイル時に評価されるブール値制約を 設定することもできる。

2段階の名前検索

C++には、テンプレート内の名前検索に関するいくつかの特殊なルールがある。例えば、 ベースクラス内を検索しない、テンプレートパラメータ名のスコープ付き再宣言を許可しない、 定義の時点以降に発生したオーバーロードを考慮しない (この例は C++98標準のものを基にしている):

int g(double d) { return 1; }

typedef double A;

template<class T> struct B
{
    typedef int A;
};

template<class T> struct X : B<T>
{
    A a;              // aはint型である
    int T;            // aはdouble型である
    int foo()
    {
        char T;       // エラー、Tの再宣言
        return g(1);  // エラー、Tの再宣言
    }
};

int g(int i) { return 2; }  // 常に1を返す

D言語のスコープ付き検索ルールは、他の言語のルールと一致している。

to match一致する
int g(double d) { return 1; }

alias A = double;

class B(T)
{
    alias A = int;
}

class X(T) : B!(T)
{
    A a;             // この定義はXでは見られない
    int T;           // OK、Tはintとして再宣言される
    int foo()
    {
        char T;      // OK,Tはcharとして再宣言される
        return g(1); // 常に2を返却する
    }
};

int g(int i) { return 2; }  // 関数は前方参照できる

テンプレート再帰

テンプレートの再帰と特殊化を組み合わせると、C++のテンプレートは 実際にはプログラミング言語を形成することになるが、確かに 奇妙な言語である。実行時に階乗を計算するテンプレートのセットを考えてみよう。 「hello world」プログラムと同様に、階乗はテンプレートメタプログラミングの典型的な例である。

template<int n> class factorial
{
    public:
        enum
        {
            result = n * factorial<n - 1>::result
        };
};

template<> class factorial<1>
{
    public:
        enum { result = 1 };
};

void test()
{
    // 24を表示する
    printf("%d\n", factorial<4>::result);
}

Dでは再帰も同様に動作するが、入力はかなり少なくなる。

DD
template factorial(int n)
{
    const factorial = n * factorial!(n-1);
}

template factorial(int n : 1)
{
    const factorial = 1;
}

void test()
{
    writeln(factorial!(4));  // 24を表示する
}

static if 構文を使用することで、たった1つのテンプレートで実現できる。

xml-ph-0000@deepl.internalxml-ph-0000@deepl.internal
template factorial(int n)
{
    static if (n == 1)
        const factorial = 1;
    else
        const factorial = n * factorial!(n-1);
}

13行のコードを、よりクリーンな7行に減らすことができる。static ifは、C++の#if に相当する。 しかし、#if はテンプレートの引数にアクセスできないため、 すべてのテンプレートの条件付きコンパイルは、部分的なテンプレートと明示的に特殊化されたテンプレートで処理しなければならない。 static if は、このような構造を劇的に簡素化する。

D を使用すれば、さらに簡潔に記述できる。 階乗のような値を生成するテンプレートも可能だが、 コンパイル時に計算できる関数を記述する方が簡単である。

int factorial(int n)
{
    if (n == 1)
        return 1;
    else
        return n * factorial(n - 1);
}

static int x = factorial(5);  // xは静的に120に初期化される

Substitution Failure Is Not An Error (SFINAE)

これは、テンプレートの引数の型が関数であるかどうかを決定する。 「C++ Templates: The Complete Guide」より Vandevoorde & Josuttis、353ページ:

これは、テンプレートの引数の型が関数であるかどうかを決定する。 「C++ Templates: The Complete Guide」より Vandevoorde & Josuttis、353ページ:

template<U> class IsFunctionT
{
    private:
        typedef char One;
        typedef struct { char a[2]; } Two;
        template static One test(...);
        template static Two test(U (*)[1]);
    public:
        enum
        {
            Yes = sizeof(IsFunctionT::test(0)) == 1
        };
};

void test()
{
    typedef int (fp)(int);

    assert(IsFunctionT<fp>::Yes == 1);
}

テンプレートIsFunctionT は、結果を達成するために2つの副作用に依存している。 まず、C++では無効な型である関数の配列に依存している。 したがって、U が関数型である場合、2番目のtest は選択されない。 なぜなら、そうするとエラーが発生するからだ(SFINAE(Substitution Failure Is Not An Error))。 1番目のtest が選択される。U が関数型でない場合、2番目のtest がより適合する。 次に、戻り値のサイズを調べることによって、test のどちらが選択されたかを判断する。 つまり、sizeof(One) またはsizeof(Two) である。 残念ながら、C++のテンプレートメタプログラミングは、 意図した通りに明示的にコード化できるというよりも、むしろ副作用に頼っているように見えることが多い。

Dでは、次のように記述できる。

thisこれ
template IsFunctionT(T)
{
    static if (is(T[]))
        const int IsFunctionT = 0;
    else
        const int IsFunctionT = 1;
}

void test()
{
    alias int fp(int);

    assert(IsFunctionT!(fp) == 1);
}

is(T[]) はSFINAE(Substitution Failure Is Not An Error)に相当する。T の配列を構築しようとし、T が関数型である場合は関数配列となる。これは 無効な型であるため、T[] は失敗し、is(T[]) はfalseを返す。

SFINAE(Substitution Failure Is Not An Error)を使用することもできるが、 is式は型を直接テストできる ため、型に関する質問をするためにテンプレートを使用する必要さえない。

SFINAESFINAE
void test()
{
    alias int fp(int);

    assert(is(fp == function));
}

浮動小数点型を用いたテンプレートメタプログラミング

C++のテンプレートでは実用的でないものについて見ていこう。 例えば、このテンプレートは バビロニアの方法を用いて実数xml-ph-0000@deepl.internalの平方根を返す。

C++のテンプレートでは非現実的なことについて見ていこう。 例えば、このテンプレートは バビロニアの方法を用いて実数x の平方根を返す:

xml-ph-0000@deepl.internalxml-ph-0000@deepl.internal
import std.stdio;

template sqrt(real x, real root = x/2, int ntries = 0)
{
    static if (ntries == 5)
        // 繰り返しで精度が2倍になる、
        // 5で十分である
        const sqrt = root;
    else static if (root * root - x == 0)
        const sqrt = root;  // 完全に一致する
    else
        // もう一度繰り返す
        const sqrt = sqrt!(x, (root+x/root)/2, ntries+1);
}

void main()
{
    real x = sqrt!(2);
    writefln("%.20g", x); // 1.4142135623730950487
}

速度上の理由から、他の実行時浮動小数点演算では、文字通りの平方根がしばしば必要となる。 例えばガンマ関数の計算などである。 これらのテンプレート浮動小数点アルゴリズムは、コンパイル時に計算されるため、効率的である必要はなく、 正確であるだけでよい。

はるかに複雑なテンプレートを構築することも可能であり、例えば、Don Clugstonは コンパイル時に円周率を計算するテンプレートを作成している。[2]

繰り返しになるが、これはコンパイル時に実行可能な関数で実現できる。

to canできる
real sqrt(real x)
{
    real root = x / 2;
    for (int ntries = 0; ntries < 5; ntries++)
    {
        if (root * root - x == 0)
            break;
        root = (root + x / root) / 2;
    }
    return root;
}
static y = sqrt(10);   // yは静的に3.16228に初期化される

文字列を用いたテンプレートメタプログラミング

文字列を使用すると、さらに興味深いことが可能になる。この例では、 コンパイル時に整数を文字列に変換する。

文字列については、さらに興味深いことが可能である。この例では、 コンパイル時に整数を文字列に変換する。

string整数
template decimalDigit(int n)    // [3]
{
    const string decimalDigit = "0123456789"[n..n+1];
}

template itoa(long n)
{
    static if (n < 0)
        const string itoa = "-" ~ itoa!(-n);
    else static if (n < 10)
        const string itoa = decimalDigit!(n);
    else
        const string itoa = itoa!(n/10L) ~ decimalDigit!(n%10L);
}

string foo()
{
    return itoa!(264);   // "264"を返却する
}

このテンプレートは文字列リテラルのハッシュを計算する:

thisこの
template hash(char [] s, uint sofar=0)
{
    static if (s.length == 0)
        const hash = sofar;
    else
        const hash = hash!(s[1 .. $], sofar * 11 + s[0]);
}

uint foo()
{
    return hash!("hello world");
}

正規表現コンパイラ

Dテンプレートは、正規表現コンパイラのような、より重要なものに対してどうだろうか? Eric NieblerはC++用のものを書いた が、これは式テンプレートに依存している。[4] 式テンプレートを使用する際に問題となるのは、 C++の演算子構文と優先順位のみを使用することに制限されることだ。 したがって、式テンプレートを使用する正規表現は、 正規表現には見えず、C++の式のように見える。 Eric Andertonは、テンプレートが文字列を解析する能力を利用したD用のものを書いている。 [5] つまり、文字列内では、期待される正規 表現の文法と演算子を使用できる。

正規表現コンパイラのテンプレートは、正規表現の文字列引数を解析し、 前方から順に トークンを1つずつ取り出し、各トークン述語に対してカスタムテンプレート関数を インスタンス化し、 最終的にそれらをすべて1つの関数にまとめ、正規表現を直接実装する。 さらに、正規表現の構文エラーに対して、適切なエラーメッセージを表示する

その関数に一致させる文字列を引数として渡すと、 一致する文字列の配列が返される。

array配列
import std.stdio;
import regex;

void main()
{
    auto exp = &regexMatch!(r"[a-z]*\s*\w*");
    writefln("matches: %s", exp("hello    world"));
}

以下は、Eric Anderton氏の正規表現コンパイラの簡略版である。 これは、上記の正規表現をコンパイルするのに十分なものであり、 その方法を説明するためのものである。

array配列
module regex;

const int testFail = -1;

/**
 * pattern[]をコンパイルし、
 * 文字列str[]を受け取って正規表現を適用し、
 * マッチした関数の配列を返すカスタム生成関数に展開する。
 */

template regexMatch(string pattern)
{
    string[] regexMatch(string str)
    {
        string[] results;
        int n = regexCompile!(pattern).fn(str);
        if (n != testFail && n > 0)
            results ~= str[0..n];
        return results;
    }
}

/******************************
 * testXxxx()関数は、
 * 正規表現の各述語にマッチするようにテンプレートによってカスタム生成される。
 *
 * Params:
 *      string str      照合する入力文字列
 *
 * Returns:
 *      testFail        マッチを得られなかった
 *      n >= 0          n文字にマッチした
 */

/// 常にマッチする
template testEmpty()
{
    int testEmpty(string str) { return 0; }
}

/// testFirst(str)とtestSecond(str)がマッチすればマッチする
template testUnion(alias testFirst, alias testSecond)
{
    int testUnion(string str)
    {
        int n1 = testFirst(str);
        if (n1 != testFail)
        {
            int n2 = testSecond(str[n1 .. $]);
            if (n2 != testFail)
                return n1 + n2;
        }
        return testFail;
    }
}

/// str[]の最初の部分がtext[]にマッチすればマッチする
template testText(string text)
{
    int testText(string str)
    {
        if (str.length &&
            text.length <= str.length &&
            str[0..text.length] == text)
        {
            return text.length;
        }
        return testFail;
    }
}

/// testPredicate(str)が0回以上マッチする場合にマッチする
template testZeroOrMore(alias testPredicate)
{
    int testZeroOrMore(string str)
    {
        if (str.length == 0)
            return 0;
        int n = testPredicate(str);
        if (n != testFail)
        {
            int n2 = testZeroOrMore!(testPredicate)(str[n .. $]);
            if (n2 != testFail)
                return n + n2;
            return n;
        }
        return 0;
    }
}

/// term1[0] <= str[0] <= term2[0]の場合にマッチする
template testRange(string term1, string term2)
{
    int testRange(string str)
    {
        if (str.length && str[0] >= term1[0]
                       && str[0] <= term2[0])
            return 1;
        return testFail;
    }
}

/// ch[0]==str[0]の場合にマッチする
template testChar(string ch)
{
    int testChar(string str)
    {
        if (str.length && str[0] == ch[0])
            return 1;
        return testFail;
    }
}

/// str[0]が単語文字ならマッチする
template testWordChar()
{
    int testWordChar(string str)
    {
        if (str.length &&
            (
             (str[0] >= 'a' && str[0] <= 'z') ||
             (str[0] >= 'A' && str[0] <= 'Z') ||
             (str[0] >= '0' && str[0] <= '9') ||
             str[0] == '_'
            )
           )
        {
            return 1;
        }
        return testFail;
    }
}

/*****************************************************/

/**
 * pattern[]の先頭から
 * 末尾または特殊文字までを返す。
 */

template parseTextToken(string pattern)
{
    static if (pattern.length > 0)
    {
        static if (isSpecial!(pattern))
            const string parseTextToken = "";
        else
            const string parseTextToken =
                pattern[0..1] ~ parseTextToken!(pattern[1..$]);
    }
    else
        const string parseTextToken="";
}

/**
 * pattern[]を終端までを含めて解析する。
 * Returns:
 *      token[]         終端まですべて。
 *      consumed        解析されたpattern[]の文字数
 */
template parseUntil(string pattern,char terminator,bool fuzzy=false)
{
    static if (pattern.length > 0)
    {
        static if (pattern[0] == '\\')
        {
            static if (pattern.length > 1)
            {
                const string nextSlice = pattern[2 .. $];
                alias next = parseUntil!(nextSlice,terminator,fuzzy);
                const string token = pattern[0 .. 2] ~ next.token;
                const uint consumed = next.consumed+2;
            }
            else
            {
                pragma(msg,"Error: expected character to follow \\");
                static assert(false);
            }
        }
        else static if (pattern[0] == terminator)
        {
            const string token="";
            const uint consumed = 1;
        }
        else
        {
            const string nextSlice = pattern[1 .. $];
            alias next = parseUntil!(nextSlice,terminator,fuzzy);
            const string token = pattern[0..1] ~ next.token;
            const uint consumed = next.consumed+1;
        }
    }
    else static if (fuzzy)
    {
        const string token = "";
        const uint consumed = 0;
    }
    else
    {
        pragma(msg, "Error: expected " ~
                    terminator ~
                    " to terminate group expression");
        static assert(false);
    }
}

/**
 * 文字クラスの内容を解析する。
 * Params:
 *   pattern[] = コンパイルする残りのパターン
 * Output:
 *   fn       = 生成された関数
 *   consumed = 解析されたpattern[]の文字数
 */

template regexCompileCharClass2(string pattern)
{
    static if (pattern.length > 0)
    {
        static if (pattern.length > 1)
        {
            static if (pattern[1] == '-')
            {
                static if (pattern.length > 2)
                {
                    alias termFn = testRange!(pattern[0..1], pattern[2..3]);
                    const uint thisConsumed = 3;
                    const string remaining = pattern[3 .. $];
                }
                else // 長さが2である
                {
                    pragma(msg,
                      "Error: expected char following '-' in char class");
                    static assert(false);
                }
            }
            else // '-'ではない
            {
                alias termFn = testChar!(pattern[0..1]);
                const uint thisConsumed = 1;
                const string remaining = pattern[1 .. $];
            }
        }
        else
        {
            alias termFn = testChar!(pattern[0..1]);
            const uint thisConsumed = 1;
            const string remaining = pattern[1 .. $];
        }
        alias recurse = regexCompileCharClassRecurse!(termFn,remaining);
        alias fn = recurse.fn;
        const uint consumed = recurse.consumed + thisConsumed;
    }
    else
    {
        alias fn = testEmpty!();
        const uint consumed = 0;
    }
}

/**
 * 文字クラスを再帰的に解析する。
 * Params:
 *  termFn = ここまでの生成された関数
 *  pattern[] = コンパイルする残りのパターン
 * Output:
 *  fn = termFnと解析された文字クラス
 *       を含む生成された関数
 *  consumed = 解析されたpattern[]の文字数
 */

template regexCompileCharClassRecurse(alias termFn,string pattern)
{
    static if (pattern.length > 0 && pattern[0] != ']')
    {
        alias next = regexCompileCharClass2!(pattern);
        alias fn = testOr!(termFn,next.fn,pattern);
        const uint consumed = next.consumed;
    }
    else
    {
        alias fn = termFn;
        const uint consumed = 0;
    }
}

/**
 * キャラクタークラスの開始時に。それをコンパイルする。
 * Params:
 *  pattern[] = コンパイルする残りのパターン
 * Output:
 *  fn = 生成された関数
 *  consumed = 解析されたpattern[]の文字数
 */

template regexCompileCharClass(string pattern)
{
    static if (pattern.length > 0)
    {
        static if (pattern[0] == ']')
        {
            alias fn = testEmpty!();
            const uint consumed = 0;
        }
        else
        {
            alias charClass = regexCompileCharClass2!(pattern);
            alias fn = charClass.fn;
            const uint consumed = charClass.consumed;
        }
    }
    else
    {
        pragma(msg,"Error: expected closing ']' for character class");
        static assert(false);
    }
}

/**
 * ポストフィックス'*'を探し、解析する。
 * Params:
 *  test = 関数はここまで正規表現をコンパイルする
 *  pattern[] = コンパイルする残りのパターン
 * Output:
 *  fn = 生成された関数
 *  consumed = 解析されたpattern[]の文字数
 */

template regexCompilePredicate(alias test, string pattern)
{
    static if (pattern.length > 0 && pattern[0] == '*')
    {
        alias fn = testZeroOrMore!(test);
        const uint consumed = 1;
    }
    else
    {
        alias fn = test;
        const uint consumed = 0;
    }
}

/**
 * エスケープシーケンスを解析する。
 * Params:
 *  pattern[] = コンパイルする残りのパターン
 * Output:
 *  fn = 生成された関数
 *  consumed = 解析されたpattern[]の文字数
 */

template regexCompileEscape(string pattern)
{
    static if (pattern.length > 0)
    {
        static if (pattern[0] == 's')
        {
            // 空白文字
            alias fn = testRange!("\x00","\x20");
        }
        else static if (pattern[0] == 'w')
        {
            // 単語文字
            alias fn = testWordChar!();
        }
        else
        {
            alias fn = testChar!(pattern[0 .. 1]);
        }
        const uint consumed = 1;
    }
    else
    {
        pragma(msg,"Error: expected char following '\\'");
        static assert(false);
    }
}

/**
 * pattern[]で表された正規表現を解析し、コンパイルする。
 * Params:
 *  pattern[] = コンパイルする残りのパターン
 * Output:
 *  fn = 生成された関数
 */

template regexCompile(string pattern)
{
    static if (pattern.length > 0)
    {
        static if (pattern[0] == '[')
        {
            const string charClassToken =
                parseUntil!(pattern[1 .. $],']').token;
            alias charClass = regexCompileCharClass!(charClassToken);
            const string token = pattern[0 .. charClass.consumed+2];
            const string next = pattern[charClass.consumed+2 .. $];
            alias test = charClass.fn;
        }
        else static if (pattern[0] == '\\')
        {
            alias escapeSequence =
                regexCompileEscape!(pattern[1 .. pattern.length]);
            const string token = pattern[0 .. escapeSequence.consumed+1];
            const string next =
                pattern[escapeSequence.consumed+1 .. $];
            alias test = escapeSequence.fn;
        }
        else
        {
            const string token = parseTextToken!(pattern);
            static assert(token.length > 0);
            const string next = pattern[token.length .. $];
            alias test = testText!(token);
        }

        alias term = regexCompilePredicate!(test, next);
        const string remaining = next[term.consumed .. next.length];

        alias fn = regexCompileRecurse!(term,remaining).fn;
    }
    else
        alias fn = testEmpty!();
}

template regexCompileRecurse(alias term,string pattern)
{
    static if (pattern.length > 0)
    {
        alias next = regexCompile!(pattern);
        alias fn = testUnion!(term.fn, next.fn);
    }
    else
        alias fn = term.fn;
}

/// 解析用ユーティリティ関数
template isSpecial(string pattern)
{
    static if (
        pattern[0] == '*' ||
        pattern[0] == '+' ||
        pattern[0] == '?' ||
        pattern[0] == '.' ||
        pattern[0] == '[' ||
        pattern[0] == '{' ||
        pattern[0] == '(' ||
        pattern[0] == ')' ||
        pattern[0] == '$' ||
        pattern[0] == '^' ||
        pattern[0] == '\\'
    )
        const isSpecial = true;
    else
        const isSpecial = false;
}

テンプレートメタプログラミングの詳細

Tomasz Stachowiakのコンパイル時レイトレーサー。
  1. Tomasz Stachowiakのコンパイル時レイトレーサー
  2. ドン・クラッグストンのコンパイル時間99 Bottles of Beer

参考文献

[1] Dプログラミング言語、 http://www.digitalmars.com/d/を参照

[1] プログラミング言語 D、 http://www.digitalmars.com/d/を参照。

[2] ドン・クラッグストンの円周率計算機、 http://trac.dsource.org/projects/ddl/browser/trunk/meta/demo/calcpi.d

[2] Don Clugstonの円周率計算機、 http://trac.dsource.org/projects/ddl/browser/trunk/meta/demo/calcpi.dを参照

[3] ドン・クラッグストンの小数桁とITOAについては、 http://trac.dsource.org/projects/ddl/browser/trunk/meta/conv.dを参照

[3] ドン・クラッグストンの小数桁と itoa、 http://trac.dsource.org/projects/ddl/browser/trunk/meta/conv.dを参照

[4] Eric Niebler氏のBoost.Xpressive正規表現テンプレートライブラリは https://boost-sandbox.sourceforge.net/libs/xpressive/doc/html/index.htmlにある。

[4] Eric Niebler氏のBoost.Xpressive正規表現テンプレート ライブラリは、https://boost-sandbox.sourceforge.net/libs/xpressive/doc/html/index.htmlにある。

[5] Eric Anderton氏のD用正規表現テンプレートライブラリは http://trac.dsource.org/projects/ddl/browser/trunk/meta/regex.dにある。

[5] Eric Anderton 氏の D 用正規表現テンプレートライブラリは http://trac.dsource.org/projects/ddl/browser/trunk/meta/regex.d

謝辞

ドン・クラッグストン、エリック・アンドートン、マシュー・ウィルソンから受けたインスピレーションと支援に感謝する。

orgorg