D言語 Advent Calendar 2013の七日目の記事です。
なんと、D言語にはテンプレートがある一方、 SFINAEがありません!! これについて、多くのC++erの方々が疑問に思うはずです。では、C++でSFINAEを使って実現できることは、D言語ではどのように実現されているのでしょう?
__traits(compiles)
D言語のTraitには、compilesなる命令があります。これは、 意味的に正しい(コンパイルが通る)シンボルや型や式が渡された時、trueを返す というものです。文を渡したい時はラムダ式が、宣言を渡したいときには無名クラスnew式が、それぞれイディオム的に使われます。
pragma(msg, __traits(compiles, hoge));
pragma(msg, __traits(compiles, 3[1]));
pragma(msg, __traits(compiles, { const int i = 1; i = 2; }));
pragma(msg, __traits(compiles, new class{ void f(){ return 0; } }()));
pragma(msg, __traits(compiles, new class{ void f(){} void f(){} }()));
pragma(msg)は、コンパイル時にメッセージを出力するものです。
$ dmd -main invalid.d
false
false
false
false
false
正しくないコードが渡されると、falseになっていることが分かります。
テンプレートの制約
テンプレートには、制約と呼ばれる式を付けることが出来ます。制約がtrueになる時のみ、そのテンプレートにマッチします。
import std.stdio;
template abs(int i) if(i >= 0)
{
enum abs = i;
}
template abs(int i) if(i < 0)
{
enum abs = -i;
}
void main()
{
writeln(abs!(-1));
writeln(abs!(1));
}
D言語のテンプレートは、C++で例えるとテンプレート名前空間なので、気をつけてください。さらに、テンプレート内部でテンプレート名と同名のシンボルがあった場合、そのシンボルの指定を省略する必要があります。上のコードの場合、本来はabs!(-1).abs
と書く場所を、abs!(-1)
と省略する必要があるというわけです。
$ rdmd constraint.d
1
1
ちゃんとマッチするテンプレートを制御できています。
組み合わせる
上の2つの機能を組み合わせると、C++のSFINAEで実現していることが、D言語でも実現できます。例として、引数なしで呼べる関数が渡されたらそれを呼び、そうでなければ適当になにかをするテンプレート関数を作ってみます。
import std.stdio;
template call(alias func) if(__traits(compiles, func()))
{
void call()
{
func();
}
}
template call(alias func) if(!__traits(compiles, func()))
{
void call()
{
writeln("cannot call");
}
}
void func1(int i)
{
writeln("func1");
}
void func2()
{
writeln("func2");
}
void main()
{
call!(func1)();
call!(func2)();
}
上と同様に、call!(func1).call()
と書く部分をcall!(func1)()
と省略して書く必要があります。
$ rdmd call.d
cannot call
func2
ちゃんと動いています。
まとめ
まとめると、__traits(compiles)とテンプレート制約を組み合わせて使うと、 まさにC++のSFINAEのようなことが可能になります!!やった!!!