特性
特性 (trait) は、コンパイル時に、 プログラムからコンパイラの内部情報を取得できるようにする言語拡張です。 コンパイル時リフレクション と呼ばれることもあります。 構文は(pragmaに似た)簡単に拡張可能な形式で、 新しい機能が必要になったら すぐに追加できるようになっています。
TraitsExpression:
__traits ( TraitsKeyword , TraitsArguments )
TraitsKeyword:
isAbstractClass
isArithmetic
isAssociativeArray
isFinalClass
isFloating
isIntegral
isScalar
isStaticArray
isUnsigned
isVirtualFunction
isVirtualMethod
isAbstractFunction
isFinalFunction
isStaticFunction
isRef
isOut
isLazy
hasMember
identifier
parameterNames
getMember
getOverloads
getVirtualFunctions
getVirtualMethods
parent
classInstanceSize
allMembers
derivedMembers
isSame
compiles
TraitsArguments:
TraitsArgument
TraitsArgument , TraitsArguments
TraitsArgument:
AssignExpression
Type
isArithmetic
引数が全て、算術型であるか算術型の式である場合、 true を返します。 それ以外の場合は false を返します。 引数が無い場合は false を返します。
import std.stdio;
void main() {
int i;
writeln(__traits(isArithmetic, int));
writeln(__traits(isArithmetic, i, i+1, int));
writeln(__traits(isArithmetic));
writeln(__traits(isArithmetic, int*));
}
出力:
true
true
false
false
isFloating
isArithmetic と同じように、浮動小数点数型 (虚数型と複素数型を含む) かどうかを判定します。
isIntegral
isArithmetic と同じように、整数型 (文字型を含む) かどうかを判定します。
isScalar
isArithmetic と同じように、スカラ型 かどうかを判定します。
isUnsigned
isArithmetic と同じように、符号無し型 かどうかを判定します。
isStaticArray
isArithmetic と同じように、静的配列型 かどうかを判定します。
isAssociativeArray
isArithmetic と同じように、連想配列型 かどうかを判定します。
isAbstractClass
引数が全て、abstractクラス型であるかabstractクラス型の式である場合、 true を返します。 それ以外の場合は false を返します。 引数が無い場合は false を返します。
import std.stdio;
abstract class C { int foo(); }
void main() {
C c;
writeln(__traits(isAbstractClass, C));
writeln(__traits(isAbstractClass, c, C));
writeln(__traits(isAbstractClass));
writeln(__traits(isAbstractClass, int*));
}
出力:
true
true
false
false
isFinalClass
isAbstractClass と同じように、finalクラス かどうかを判定します。
isVirtualFunction
isVirtualMethod と同様ですが、 他の関数のオーバーライドでない final 関数に対しては true を返します。
isVirtualMethod
引数を1つ取ります。その引数が仮想関数なら true、そうでなければ false を返します。 他の関数のオーバーライドでない final 関数に対しては false を返します。
import std.stdio;
struct S {
void bar() { }
}
class C {
void bar() { }
}
void main() {
writeln(__traits(isVirtualMethod, C.bar)); // true
writeln(__traits(isVirtualMethod, S.bar)); // false
}
isAbstractFunction
引数を1つ取ります。その引数がabstract関数なら true、そうでなければ false を返します。
import std.stdio;
struct S {
void bar() { }
}
class C {
void bar() { }
}
class AC {
abstract void foo();
}
void main() {
writeln(__traits(isAbstractFunction, C.bar)); // false
writeln(__traits(isAbstractFunction, S.bar)); // false
writeln(__traits(isAbstractFunction, AC.foo)); // true
}
isFinalFunction
引数を1つ取ります。その引数がfinal関数なら true、そうでなければ false を返します。
import std.stdio;
struct S {
void bar() { }
}
class C {
void bar() { }
final void foo();
}
final class FC {
void foo();
}
void main() {
writeln(__traits(isFinalFunction, C.bar)); // false
writeln(__traits(isFinalFunction, S.bar)); // false
writeln(__traits(isFinalFunction, C.foo)); // true
writeln(__traits(isFinalFunction, FC.foo)); // true
}
isStaticFunction
引数を一つ取り、それが静的関数ならば、 つまり context pointer を持たないならば true、そうでなければ false を返します。
isRef, isOut, isLazy
引数を1つとります。引数が宣言であった場合、 それぞれ ref, out, lazy ならば true を返し、それ以外では false になります。
void fooref(ref int x) {
static assert(__traits(isRef, x));
static assert(!__traits(isOut, x));
static assert(!__traits(isLazy, x));
}
void fooout(out int x) {
static assert(!__traits(isRef, x));
static assert(__traits(isOut, x));
static assert(!__traits(isLazy, x));
}
void foolazy(lazy int x) {
static assert(!__traits(isRef, x));
static assert(!__traits(isOut, x));
static assert(__traits(isLazy, x));
}
hasMember
第一引数には、メンバがあるか調べたい型か、 メンバがあるか調べたい型を持つ式を指定します。 第二引数は文字列です。 その文字列が指定された型の有効なプロパティだった場合 true、そうでなければ false を返します。
import std.stdio;
struct S {
int m;
}
void main() {
S s;
writeln(__traits(hasMember, S, "m")); // true
writeln(__traits(hasMember, s, "m")); // true
writeln(__traits(hasMember, S, "y")); // false
writeln(__traits(hasMember, int, "sizeof")); // true
}
identifier
シンボルを1つ引数としてとり、 そのシンボルの識別子を文字列として返します。
parameterNames
引数を一つ、関数を受け取ります。 関数の仮引数名を文字列リテラルとして格納した配列を返します。
import std.stdio;
void foo(int x, int y) {}
void main() {
foreach (i; __traits(parameterNames, foo)) {
writeln(i);
}
}
以下のように表示されます:
x
y
getMember
二つ引数を取り、 第二引数は文字列です。 "第一引数 . 第二引数を識別子化したもの" という形の式を返します。
import std.stdio;
struct S {
int mx;
static int my;
}
void main() {
S s;
__traits(getMember, s, "mx") = 1; // s.mx=1; と同じ
writeln(__traits(getMember, s, "m" ~ "x")); // 1
__traits(getMember, S, "mx") = 1; // エラー、S.mx のための this がない
__traits(getMember, S, "my") = 2; // ok
}
getOverloads
第一引数にはクラス型か、 クラス型を持つ式を指定します。 第二引数には、 そのクラスの関数名にマッチする文字列を指定します。 結果は、その名前で定義されている関数全ての配列です。
import std.stdio;
class D {
this() { }
~this() { }
void foo() { }
int foo(int) { return 2; }
}
void main() {
D d = new D();
foreach (t; __traits(getOverloads, D, "foo"))
writeln(typeid(typeof(t)));
alias typeof(__traits(getOverloads, D, "foo")) b;
foreach (t; b)
writeln(typeid(t));
auto i = __traits(getOverloads, d, "foo")[1](1);
writeln(i);
}
出力:
void()
int()
void()
int()
2
getVirtualFunctions
getVirtualMethods と同様ですが、 他の関数のオーバーライドでない final 関数も含まれます。
getVirtualMethods
第一引数にはクラス型か、 クラス型を持つ式を指定します。 第二引数には、 そのクラスの関数名にマッチする文字列を指定します。 結果は、その名前で定義されている仮想関数全ての配列です。 他の関数のオーバーライドでない final 関数が含まれません。
import std.stdio;
class D {
this() { }
~this() { }
void foo() { }
int foo(int) { return 2; }
}
void main() {
D d = new D();
foreach (t; __traits(getVirtualMethods, D, "foo"))
writeln(typeid(typeof(t)));
alias typeof(__traits(getVirtualMethods, D, "foo")) b;
foreach (t; b)
writeln(typeid(t));
auto i = __traits(getVirtualMethods, d, "foo")[1](1);
writeln(i);
}
出力:
void()
int()
void()
int()
2
parent
シンボル一つに評価される引数を一つとり、 その親シンボルを返します。
classInstanceSize
クラス型かクラス型を持つ式一つを 引数に取ります。 返値は size_t 型で、 値はそのクラスのインスタンスが消費するバイト数になります。 この値は、クラスの静的型に基づいたサイズです。 多態は考慮されません。
allMembers
型か、 型を持つ式一つを引数にとります。 返値は文字列リテラルのタプルで、 指定された型及びその基底クラスの全てのメンバの 名前が格納されています。 名前が重複することはありません。 また、組み込みのプロパティは含まれません。
import std.stdio;
class D {
this() { }
~this() { }
void foo() { }
int foo(int) { return 0; }
}
void main() {
auto b = [ __traits(allMembers, D) ];
writeln(b);
// ["__ctor", "__dtor", "foo", "toString", "toHash", "opCmp", "opEquals", "Monitor", "factory"]
}
結果の文字列配列がどのような順番になっているかは 特に定義されていません。
derivedMembers
型か、 型を持つ式一つを引数にとります。 返値は文字列リテラルのタプルで、 指定された型の全てのメンバの名前が格納されています。 名前が重複することはありません。 基底クラスのメンバは含まれません。 また、組み込みのプロパティは含まれません。
import std.stdio;
class D {
this() { }
~this() { }
void foo() { }
int foo(int) { return 0; }
}
void main() {
auto a = [__traits(derivedMembers, D)];
writeln(a); // ["__ctor", "__dtor", "foo"]
}
結果の文字列配列がどのような順番になっているかは 特に定義されていません。
isSame
二つの引数を受け取り、同じシンボルならば true、 そうでなければ false を返します。
import std.stdio;
struct S { }
int foo();
int bar();
void main() {
writeln(__traits(isSame, foo, foo)); // true
writeln(__traits(isSame, foo, bar)); // false
writeln(__traits(isSame, foo, S)); // false
writeln(__traits(isSame, S, S)); // true
writeln(__traits(isSame, std, S)); // false
writeln(__traits(isSame, std, std)); // true
}
二つの引数がリテラルとenumで構成された式であって、 同じ値に評価されれば、true が返ります。
compiles
全ての引数がコンパイルが通る(意味的に正しい) ならば、 true を返します。 引数としては、 文法的に正しいシンボル,型,式のいずれかを指定できます。 文や宣言を引数として指定することはできません。
ゼロ引数で呼び出した場合 false となります。
import std.stdio;
struct S {
static int s1;
int s2;
}
int foo();
int bar();
void main() {
writeln(__traits(compiles)); // false
writeln(__traits(compiles, foo)); // true
writeln(__traits(compiles, foo + 1)); // true
writeln(__traits(compiles, &foo + 1)); // false
writeln(__traits(compiles, typeof(1))); // true
writeln(__traits(compiles, S.s1)); // true
writeln(__traits(compiles, S.s3)); // false
writeln(__traits(compiles, 1,2,3,int,long,std)); // true
writeln(__traits(compiles, 3[1])); // false
writeln(__traits(compiles, 1,2,3,int,long,3[1])); // false
}
使い方としては、例えばこんなものがあります:
- 複雑なテンプレート定義の内部で、 読みやすいエラーメッセージを提供する
- テンプレートの部分特殊化よりも 粒度の細かい特殊化を行う