【D言語】文字列mixinと#lineを組み合わせる

D言語を使うと、文字列mixinと#lineが両方そなわり最強に見える

とよく聞きます。 今回は、文字列mixinと#lineの組み合わせが、どう最強に見えるか解説します。

defBothという、同じ内容でかつstructとclassの両方を定義する関数を定義したとします。


string defBoth(string name, string src){
    return "struct S" ~ name ~ "{" ~ src ~ "}final class C" ~ name ~ "{" ~ src ~ "}";
}
mixin(defBoth("Hoge", q{
    int[3] ints;
    const int get(size_t index){ return ints[index]; }
}));

defBoth自体がclassとstructを定義するわけではなく、返ってきた文字列をmixinすることによって、両方定義されます。 このコードはとてもうまく動くように見えます。が、問題が残っています。

問題

問題とは、エラーメッセージの行数です。 実際に問題を起こしてみると、下のようになります。


string defBoth(string name, string src){
    return "struct S" ~ name ~ "{" ~ src ~ "}final class C" ~ name ~ "{" ~ src ~ "}";
}
mixin(defBoth("Hoge", q{
    int[3] ints;
    const int get(size_t index){ return ints[index]; }
}));
void main(){
    auto hoge = new CHoge;
    hoge.get(3);
}

このコードは例外、RangeErrorを投げますが、行数が9と表示されます。 文字列mixinでmixinした文字列と実際のソースコードがうまく噛み合っていないので、このような行数のズレが起きます。

解決策

この問題は、#lineを使うことによって解決することができます。

lineとは、行数を調整することが出来る、D言語の機能の一つです。


void main(){
    #line 2520
    throw new Exception("ここが2520行目として扱われる");
}

この#lineを、defBothが返す文字列の中にうまく組み込めば、エラーメッセージの行数のズレを無くすことができます。


import std.metastring;
string defBoth(int line = __LINE__)(string name, string src){ // lineにはdefBothの呼び出し元の行数が代入される
    return "struct S" ~ name ~ "{" ~ src ~ "}\n #line " ~ toStringNow!line ~ "\n final class C" ~ name ~ "{" ~ src ~ "}";
}

まとめ

このように、#lineを使うことによってmixinの欠点の一つを補うことができます。 最強に見えますね。

lineはmixinと一緒に使われることでしか活躍しないので、皆さんもmixinを使う機会があったらぜひ#lineを使ってみてください。