7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

D言語Advent Calendar 2013

Day 7

なぜD言語にはSFINAEが無いのか

Last updated at Posted at 2013-12-07

D言語 Advent Calendar 2013の七日目の記事です。

なんと、D言語にはテンプレートがある一方、 SFINAEがありません!! これについて、多くのC++erの方々が疑問に思うはずです。では、C++でSFINAEを使って実現できることは、D言語ではどのように実現されているのでしょう?

__traits(compiles)

D言語のTraitには、compilesなる命令があります。これは、 意味的に正しい(コンパイルが通る)シンボルや型や式が渡された時、trueを返す というものです。文を渡したい時はラムダ式が、宣言を渡したいときには無名クラスnew式が、それぞれイディオム的に使われます。

invalid.d
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)は、コンパイル時にメッセージを出力するものです。

shell
$ dmd -main invalid.d
false
false
false
false
false

正しくないコードが渡されると、falseになっていることが分かります。

テンプレートの制約

テンプレートには、制約と呼ばれる式を付けることが出来ます。制約がtrueになる時のみ、そのテンプレートにマッチします。

constrait.d
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)と省略する必要があるというわけです。

shell
$ rdmd constraint.d
1
1

ちゃんとマッチするテンプレートを制御できています。

組み合わせる

上の2つの機能を組み合わせると、C++のSFINAEで実現していることが、D言語でも実現できます。例として、引数なしで呼べる関数が渡されたらそれを呼び、そうでなければ適当になにかをするテンプレート関数を作ってみます。

call.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)()と省略して書く必要があります。

shell
$ rdmd call.d
cannot call
func2

ちゃんと動いています。

まとめ

まとめると、__traits(compiles)とテンプレート制約を組み合わせて使うと、 まさにC++のSFINAEのようなことが可能になります!!やった!!!

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?