式
CやC++プログラマの方は、Dの式を非常に親しみやすく感じるでしょう。 いくつか面白い追加もあります。
式は、結果の値を計算するために使用されます。 この値は、変数へ代入したり、判定に用いたり、あるいは無視して捨てることもできます。 式には副作用が伴うことがあります。
評価順序
以下の二項演算子式は、かならず 左から右に評価されます:
CommaExpression, OrOrExpression, AndAndExpression
以下の二項演算子式は、 実装定義の順序で実行されます:
AssignExpression, OrExpression, XorExpression, AndExpression, CmpExpression, ShiftExpression, AddExpression, CatExpression, MulExpression, 関数引数
未定義の評価順序に依存するようなコードは誤りです。 例えば次のコードは不正です:
i = i++; c = a + (a = b); func(++i, ++i);
式の値が評価順序に依存しているとコンパイラが判断したら、 コンパイラはその式をエラーとすることができます。 (必ずエラーになるとは限りませんが。) この種のエラー検知の精度は、実装の質の問題です。
式
Expression: AssignExpression AssignExpression , Expressionまず最初に , の左の式が評価され、 次に右が評価されます。 この式全体の型、及び値は、右の式の型と値となります。
代入式
AssignExpression: ConditionalExpression ConditionalExpression = AssignExpression ConditionalExpression += AssignExpression ConditionalExpression -= AssignExpression ConditionalExpression *= AssignExpression ConditionalExpression /= AssignExpression ConditionalExpression %= AssignExpression ConditionalExpression &= AssignExpression ConditionalExpression |= AssignExpression ConditionalExpression ^= AssignExpression ConditionalExpression ~= AssignExpression ConditionalExpression <<= AssignExpression ConditionalExpression >>= AssignExpression ConditionalExpression >>>= AssignExpression右辺の結果は暗黙に左辺式の型へ変換され、 代入されます。 この式全体の結果型は左辺の式の型で、 値は代入後の左辺値です。
左辺は、lvalue でなくてはなりません。
代入演算子式
代入演算子式、例えば:a op= bは、意味的には
a = a op bと同等です、違いは、オペランド a が一度しか評価されない点です。
条件式
ConditionalExpression: OrOrExpression OrOrExpression ? Expression : ConditionalExpressionまず、一つ目の式を評価し、 bool値へ変換します。 結果がtrueなら、二番目の式が評価され、 式全体の値はその式の評価結果となります。 結果がfalseなら、三番目の式が評価され、 式全体の値は三番目の式の評価結果です。 もし二番目の式と三番目の式のどちらか一方でもvoid型ならば、 全体の型もvoidになります。そうでなければ、全体の型は 二番目と三番目の式の型から暗黙に変換できる共通の型です。
OrOr 式
OrOrExpression: AndAndExpression OrOrExpression || AndAndExpressionOrOr式の結果は、 右の式の型がvoidならばvoid、 そうでなければboolです。
まず、左の式が評価されます。 評価結果をboolに変換した結果がtrueだったならば、 右の式は評価されません。 全体の型がboolの場合は、 この時は式の値はtrueとなります。 左の式がfalseだった時は、 右の式が評価されます。 全体の型がboolの場合は、 式の値は右の式の評価結果を bool に変化したものです。
AndAnd 式
AndAndExpression: OrExpression AndAndExpression && OrExpression
AndAnd式の結果は、右の式の型がvoidならばvoid、 そうでなければboolです。
まず、左の式が評価されます。
評価結果をboolに変換した結果がfalseだったならば、 右の式は評価されません。 全体の型がboolの場合は、 この時は式の値はfalseとなります。
左の式がtrueだった時は、 右の式が評価されます。 全体の型がboolの場合は、 式の値は右の式の評価結果を bool に変換したものです。
ビット演算式
ビット演算式では、オペランドどうしのビット毎の演算が行われます。 オペランドどうしは整数型でなくてはなりません。 まずデフォルトの整数型の昇格が行われ、 その後にビット演算がなされます。Or 式
OrExpression: XorExpression OrExpression | XorExpressionビット毎のOR演算です。
Xor 式
XorExpression: AndExpression XorExpression ^ AndExpressionビット毎のXOR演算です。
And 式
AndExpression: CmpExpression AndExpression & CmpExpressionビット毎のAND演算です。
比較式
CmpExpression: ShiftExpression EqualExpression IdentityExpression RelExpression InExpression
等値式
EqualExpression: ShiftExpression == ShiftExpression ShiftExpression != ShiftExpression等値式では、2つのオペランドが等しいかどうか (==), あるいは等しくないか (!=) がチェックされます。 結果の型はboolで、 比較の前に双方のオペランドは共通の型へと変換されます。
整数かポインタの比較であれば、"等しい" というのは、 "ビットパターンが一致すること" として定義されます。 構造体どうしの"等しい"も、 オブジェクト全体のビットパターンが一致すること、 です。(整列の際の穴の存在も、 コンパイラが初期化時に0で埋めておくなどの方法で 考慮されています)。 浮動小数点数の場合は多少複雑です。-0 と +0 は等しいと判定されます。 どちらか一方でも NaN であれば、 == はfalse、!=はtrueになります。 それ以外の場合は、ビットパターンで比較されます。
複素数の場合、x == y は次と同じです:
x.re == y.re && x.im == y.im!= は次と同じです:
x.re != y.re || x.im != y.im
クラスや構造体オブジェクトであれば、 (a == b) という式は、 a.opEquals(b) と書き換えられ、(a != b) は !a.opEquals(b) と書き換えられます。
演算子 == と != は、 オブジェクトの内容を比較するものです。従って、 内容を持たない null とオブジェクトを比較するのは不正なコードです。 null かどうかの検査には、is と !is 演算子を代わりに使用してください。
class C; C c; if (c == null) // エラー ... if (c is null) // ok ...
静的/動的な配列の等しさは、配列の長さが等しく、 かつ、全ての要素が等しいこと、と定義されます。
同一性式
IdentityExpression: ShiftExpression is ShiftExpression ShiftExpression !is ShiftExpression
is は二つのオブジェクトの同一性をチェックします。 同一でないことのチェックは、 e1 !is e2 を使います。 結果の型はboolで、 比較の前に双方のオペランドは共通の型へと変換されます。
クラスオブジェクトの場合は、二つの参照が同じオブジェクトを 指しているかどうかのチェックになります。is では、null 参照との比較も可能です。
構造体オブジェクトの場合は、構造体のビット列が同一であること、 として定義されています。
静的あるいは動的な配列の場合は、二つとも同じ配列を指しているかどうか、 で同一性が判定されます。
その他の型に関しては、 同一性は同値性と同じように定義されています。
同一性演算子 is はオーバーロードできません。
関係式
RelExpression: ShiftExpression < ShiftExpression ShiftExpression <= ShiftExpression ShiftExpression > ShiftExpression ShiftExpression >= ShiftExpression ShiftExpression !<>= ShiftExpression ShiftExpression !<> ShiftExpression ShiftExpression <> ShiftExpression ShiftExpression <>= ShiftExpression ShiftExpression !> ShiftExpression ShiftExpression !>= ShiftExpression ShiftExpression !< ShiftExpression ShiftExpression !<= ShiftExpressionまず、オペランド双方に整数の昇格が行われます。 結果の型はboolです。
クラスオブジェクトの場合は、左オペランドに対して Object.opCmp() の呼び出し結果と整数0、を演算子で比較した結果が式全体の値となります。 つまり、関係式 (o1 op o2) の結果は次のように定義されます:
(o1.opCmp(o2) op 0)どちらかがnullの場合、エラーになります。
静的/動的な配列の場合、 "二つの配列の中で等しくない最初の要素" に op を適用した結果が、配列どうしの関係式opの結果となります。 有効な全要素が等しい時は、サイズが小さい配列の方がより"小さい" とします。
整数比較
オペランドがどちらも整数型だった場合、 整数比較が行われます。
演算子 | 関係 |
---|---|
< | 小さい |
> | 大きい |
<= | 以下 |
>= | 以上 |
== | 等しい |
!= | 等しくない |
<, <=, >, >= 式の時は、 符号付き整数と符号なし整数の比較はエラーとなります。 明示的にどちらかのオペランドをキャストしてください。
浮動小数点数比較
オペランドがどちらも浮動小数点数型だった場合、 浮動小数点数比較が行われます。実用に値する浮動小数点数演算を定義するには、 NaN を考慮に入れなくてはいけません。 特に、関係演算のオペランドとしては NaN を取ることができます。 二つの浮動小数点数の関係としては、 より小さい、より大きい、等しい、順序づけ不可能(一方がNaN) の4種類があり、 演算子もこれに応じて14種類の条件を表現しています:
演算子 | より大きい | より小さい | 等しい | 順序づけ不可能 | 例外 | 関係 |
---|---|---|---|---|---|---|
== | F | F | T | F | no | 等しい |
!= | T | T | F | T | no | 順序づけ不可能, より小さい, より大きい |
> | T | F | F | F | yes | より大きい |
>= | T | F | T | F | yes | 以上 |
< | F | T | F | F | yes | より小さい |
<= | F | T | T | F | yes | 以下 |
!<>= | F | F | F | T | no | 順序づけ不可能 |
<> | T | T | F | F | yes | より小さい, より大きい |
<>= | T | T | T | F | yes | より小さい, より大きい, 等しい |
!<= | T | F | F | T | no | 順序づけ不可能か、より大きい |
!< | T | F | T | T | no | 順序づけ不可能か、以上 |
!>= | F | T | F | T | no | 順序づけ不可能か、より小さい |
!> | F | T | T | T | no | 順序づけ不可能か、以下 |
!<> | F | F | T | T | no | 順序づけ不可能か、等しい |
注:
- 浮動小数点数の場合、(a !op b) は !(a op b) と同義ではありません。
- "順序づけ不可能" とは、オペランドの一方または両方が NaN という意味です。
- "例外" とは、どちらかがNaNだった時に Invalid Exception が発生することを意味します。これは、例外がthrowされるのとは異なります。 Invalid Exception が発生したかどうかのチェックは、 std.c.fenv の関数で行います。
クラスの比較
クラスオブジェクトに対しては、 比較演算子はオブジェクトの内容を比較するものです。 従って、 内容を持たない null とオブジェクトを比較するのは不正なコードです。
class C; C c; if (c < null) // エラー ...
In 式
InExpression: ShiftExpression in ShiftExpression
連想配列では、値が配列の要素かどうかを調べることができます:
int foo[char[]]; ... if ("hello" in foo) ...
in 式は、 <, <= などの関係式と 同じ優先度を持ちます。 要素が配列中に存在しなければ InExpression は null になります。存在すれば、 配列中の要素へのポインタになります。
Shift 式
ShiftExpression: AddExpression ShiftExpression << AddExpression ShiftExpression >> AddExpression ShiftExpression >>> AddExpressionオペランドは整数型でなくてはならず、 演算前に昇格が行われます。 結果は昇格後の左オペランドと同じ型を持ち、 値は左オペランドのビットを右オペランド分だけシフトした値です。
<< は左シフト >> は符号付き右シフト >>> は符号無し右シフト
シフトされる型のビット数より多くシフトしようとするのは 不正です:
int c; c << 33; // 誤り
Add 式
AddExpression: MulExpression AddExpression + MulExpression AddExpression - MulExpression CatExpression
オペランドが整数型ならば昇格が行われます。 さらに、 共通の型へと通常の算術変換がなされます。
もしどちらか一方が浮動小数点数ならば、 もう片方も暗黙に浮動小数点数に変換され、 共通の型へと変換されます。
演算子が + が - で、 第一オペランドがポインタで第二オペランドが整数型の時は、 結果の型は第一オペランドの型と同じになります。式の値は、 第一オペランドのポインタに、第二オペランドに型のサイズを掛けた結果を 足した(引いた)ポインタとなります。
第二オペランドがポインタで第一引数が整数型、そして 演算子が + の時は、 オペランドを逆転して、 上で述べた演算が行われます。
どちらのオペランドもポインタで、演算子が + の場合、これは不正です。演算子が - の場合、ポインタのアドレス値同士が減算され、 結果がオペランドの指す型のサイズで割り算されます。 二つのポインタの型が違う場合エラーです。
浮動小数点数に対する加算は結合則を満たしません。
Cat 式
CatExpression: AddExpression ~ MulExpression
CatExpression は配列同士を結合し、 結果の動的配列を返します。結合される配列は、 同じ型の要素を持つ配列である必要があります。片方のオペランドが配列で、 もう一方がその配列の要素型だった場合は、 その要素だけを含む長さ1の配列に変換されてから、 結合が行われます。
Mul 式
MulExpression: UnaryExpression MulExpression * UnaryExpression MulExpression / UnaryExpression MulExpression % UnaryExpression
オペランドが整数型ならば昇格が行われます。 さらに、 共通の型へと通常の算術変換がなされます。
整数型の場合、*, /, %, がそれぞれ 掛け算、割り算, 余り に対応しています。 掛け算ではオーバーフローは無視され、 整数型に収まる下位ビットだけが結果として残ります。
整数の / と % 演算については、 商は 0 に向かって丸められ、余りは割られる数と同じ符号になります。 割る数が 0 だった場合、 例外が送出されます。
浮動小数点数では、* と / は 各演算は IEEE 754 で規定された演算に対応します。 % は IEEE754 の剰余とは一致しません。 例えば、15.0 % 10.0 == 5.0 ですが、 IEEE 754 では remainder(15.0,10.0) == -5.0 です。
浮動小数点数に対するMul式は結合則を満たしません。
単項演算式
UnaryExpression: PostfixExpression & UnaryExpression ++ UnaryExpression -- UnaryExpression * UnaryExpression - UnaryExpression + UnaryExpression ! UnaryExpression ~ UnaryExpression ( Type ) . Identifier NewExpression DeleteExpression CastExpression NewAnonClassExpression
New 式
NewExpression: NewArguments Type [ AssignExpression ] NewArguments Type ( ArgumentList ) NewArguments Type NewArguments ClassArguments BaseClasslistopt { DeclDefs } NewArguments: new ( ArgumentList ) new ( ) new ClassArguments: class ( ArgumentList ) class ( ) class ArgumentList: AssignExpression AssignExpression , ArgumentList
new式は、ガベージコレクタのヒープ(デフォルト)、 もしくはクラス/構造体特有のアロケータを使ってメモリを確保するのに使われます。
多次元配列を確保するには、 宣言は前置の型宣言と同じ順番で記述します。
char[][] foo; // 文字列の動的配列 ... foo = new char[][30]; // 文字列 30要素の配列 を割付
上のコードは次のように書くこともできます:
foo = new char[][](30); // 30個の文字列を割り当て
多重に配列を割り当てるには、引数を複数書きます:
int[][][] bar; ... bar = new int[][][](5,20,30);
このコードは、次と同等の意味になります。
bar = new int[][][5]; foreach (ref a; bar) { a = new int[][20]; foreach (ref b; a) { b = new int[30]; } }
new ( ArgumentList ) がある場合、 その引数は、 クラス/構造体特有のアロケータ関数へと、 サイズ引数に続けて引き渡されます。
NewExpression が、記憶クラス scope つきの関数ローカル変数の初期化式で使用された場合で、 しかも new の ArgumentList が空ならば、 インスタンス用のメモリは、ヒープやクラス専用のアロケータではなく、 スタック上から割り当てられます。
Delete 式
DeleteExpression: delete UnaryExpression
まず、クラスオブジェクトへの参照を指定した場合で、 そのクラスにデストラクタがある場合は、 インスタンスに対してデストラクタが呼び出されます。
次に、クラスオブジェクトへの参照か構造体へのポインタを指定した場合で、 そのクラスか構造体で delete 演算子がオーバーロードされている場合、 インスタンスに対してそのdelete演算子が呼び出されます。
それ以外の場合はガベージコレクタが呼び出され、 インスタンスに割り当てたメモリは直ちに解放されます。 ガベージコレクタ以外で割り当てたメモリが使われていた場合、 動作は未定義になります。
ポインタや動的配列が指定された場合は、 ガベージコレクタが呼び出され、 メモリ領域は直ちに解放されます。 ガベージコレクタ以外で割り当てたメモリが使われていた場合、 動作は未定義になります。
ポインタ、動的配列、参照変数は、 delete のあと null にセットされます。
UnaryExpression がスタックに割り当てられた変数だった場合、 クラスのデストラクタが(もしあれば)呼び出されます。 ガベージコレクタやクラス専用のアロケータは 呼び出されません。
Cast 式
CastExpression: cast ( Type ) UnaryExpression cast ( CastParam ) UnaryExpression CastParam: Type const const shared shared const inout inout shared shared inout immutable shared
キャスト式は、UnaryExpression を別の型へと変換します。
cast(foo) -p; // (-p) をfoo型にキャスト (foo) - p; // fooからpを引き算
クラスオブジェクトへの参照を派生クラスへとキャストするときは常に、 そのダウンキャストが適切なものであるか実行時のチェックが入ります。 不適切なキャストのときは結果は null になります。 注: これはC++で言うdynamic_cast演算子と同等の動作をしている、 と言えるでしょう。
class A { ... } class B : A { ... } void test(A a, B b) { B bx = a; // エラー、キャストが必要 B bx = cast(B) a; // a が B型ではない場合、bx は null となる A ax = b; // キャスト不要 A ax = cast(A) b; // アップキャストには実行時チェックは不要 }
オブジェクト o がクラス B のインスタンスであることを判定するためには、キャストを使います:
if (cast(B) o) { // o は B のインスタンス } else { // o は B のインスタンスではない }
浮動小数点数リテラルを別の型へとキャストすると型は変わりますが、 内部的には、 定数畳み込みのために、元々の精度を保って保持されます。
void test() { real a = 3.40483L; real b; b = 3.40483; // リテラルはdouble精度に切り詰められない assert(a == b); assert(a == 3.40483); assert(a == 3.40483L); assert(a == 3.40483F); double d = 3.40483; // 変数に代入されると、精度が切り詰められる assert(d != a); // このため、完全に同じ値ではなくなる const double x = 3.40483; // constへの代入は assert(x == a); // 初期化子が可視な場合は切り捨てられない }
値 v から構造体 S へのキャストは、 v の型がSと同じではないときに限り、以下のコードと同等です:
S(v)
後置演算式
PostfixExpression: PrimaryExpression PostfixExpression . Identifier PostfixExpression . TemplateInstance PostfixExpression . NewExpression PostfixExpression ++ PostfixExpression -- PostfixExpression ( ) PostfixExpression ( ArgumentList ) IndexExpression SliceExpression
インデクス式
IndexExpression: PostfixExpression [ ArgumentList ]
まず PostfixExpression が評価されます。 PostfixExpression が静的配列か動的配列だった場合、 シンボル $ が配列の長さとして使えます。 PostfixExpression が ExpressionTuple だった場合は、 シンボル $ はタプルの要素数になります。 ArgumentList の評価の際には新しい宣言スコープが作られ、 $ はこの範囲でのみ使用できます。
PostfixExpression が ExpressionTuple の場合は、 ArgumentList の要素は必ず1つで、 コンパイル時に整数定数へと評価される式でなければいけません。 その整数定数 n に応じて、ExpressionTuple の 第n番目の要素が選択され、 その値が IndexExpression 全体の値となります。 n が ExpressionTuple の範囲を外れているとエラーです。
スライス式
SliceExpression: PostfixExpression [ ] PostfixExpression [ AssignExpression .. AssignExpression ]
まず PostfixExpression が評価されます。 PostfixExpression が静的配列か動的配列だった場合、 変数 length (と特殊変数 $) が宣言され、 その値は配列の長さになります。 AssignExpression..AssignExpression の評価の際にはこの新しい宣言スコープが作られ、 length (と $) はこの範囲でのみ使用できます。
式の結果は、 PostfixExpressionの指す配列の、 先頭の AssignExpression 以上、 二番目の AssignExpression 未満、 の範囲の スライスとなります。
[ ] 形式を使うと、 スライスは配列全体になります。
スライスの型は、 PostfixExpression の要素の動的配列型となります。
PostfixExpression が ExpressionTuple のときは、 スライス式の結果は新しく指定された上限と下限の範囲の ExpressionTuple になります。 上限と下限はコンパイル時に整数定数へと評価される式でなければなりません。 範囲の外の値が指定された場合は、 エラーになります。
原始式
PrimaryExpression: Identifier .Identifier TemplateInstance this super null true false $ IntegerLiteral FloatLiteral CharacterLiteral StringLiterals ArrayLiteral AssocArrayLiteral FunctionLiteral AssertExpression MixinExpression ImportExpression BasicType . Identifier TypeidExpression Typeof IsExpression ( Expression )
.Identifier
Identifier はその時点のネストされたスコープではなく、 モジュールスコープから探索されます。this
非staticメンバ関数の中では、this はその関数を呼んだ オブジェクトへの参照となっています。 構造体での this は、 その構造体インスタンスへのポインタとなります。 typeof(this) を明示的に参照してメンバ関数を呼び出した場合は、 非仮想の関数呼び出しが行われます:
class A { char get() { return 'A'; } char foo() { return typeof(this).get(); } char bar() { return this.get(); } } class B : A { char get() { return 'B'; } } void main() { B b = new B(); b.foo(); // 'A' を返す b.bar(); // 'B' を返す }
super
super は、this を基底クラスへとキャストしたものです。基底クラスが存在しない場合はエラーになります。 (クラス Object のみが基底クラスを持ちません。) 基底クラスを持たない構造体の場合は、super をメンバ関数内で使うのは禁じられています。 super を明示的に参照してメンバ関数を呼び出した場合は、 非仮想の関数呼び出しが行われます:
null
null は、 ポインタ、関数ポインタ、デリゲート、 動的配列、連想配列、 クラスオブジェクトの null 値を表します。 特定の型にキャストされていないときは、 (void*) 型となり、 ポインタ、 関数ポインタ等々への完全一致の変換が可能です。 特定の型にキャストされた後も暗黙の変換は可能ですが、 完全一致扱いにはなりません。
true, false
bool型の値で、 他の整数型にキャストされた場合はそれぞれ 1 と 0 になります。文字リテラル
文字リテラルは、文字1文字を表していて、 char, wchar, dchar のいずれかの型を持ちます。 \u エスケープシーケンスならば wchar 型、 \U エスケープシーケンスならば dchar となります。 それ以外の場合は、その文字を格納できる最小の型になります。文字列リテラル
StringLiterals: StringLiteral StringLiterals StringLiteral
文字列リテラルは、 以下の型に(同じ優先度で)暗黙変換されます:
char* |
wchar* |
dchar* |
char[] |
wchar[] |
dchar[] |
文字列リテラルの末尾には 0 が必ず入っています。 これは const char* 文字列を期待する C/C++ の関数にリテラルを渡しやすくするためです。 この 0 は文字列リテラルの .length には含まれません。
配列リテラル
ArrayLiteral: [ ArgumentList ]
配列リテラルは、[ と ] で挟んで、カンマで区切った AssignExpression のリストです。 各 AssignExpression は配列の要素をあらわし、 配列の長さは要素の個数になります。 配列リテラル全体の型としては、先頭要素の型が採用されます。 ほかの要素は全て、 その型へと暗黙変換されます。
[1,2,3]; // int[3] 型。要素は 1, 2, 3 [1u,2,3]; // uint[3] 型。引数は 1u, 2u, 3u
ArgumentList の引数のどれかが ExpressionTuple であった場合は、 そのタプルの場所に引数として ExpressionTuple の要素が挿入されます。
配列リテラルはGC管理されたヒープに配置されます。 従って、安全に関数から返すことができます:
int[] foo() { return [1, 2, 3]; }
配列リテラルが別の配列型にキャストされた場合、 要素それぞれが新しい要素型にキャストされます。 リテラルでない配列がキャストされた場合、 配列が新しい型として再解釈され、長さが再計算されます:
import std.stdio; void main() { // 配列リテラルのキャスト const short[] ct = cast(short[]) [cast(byte)1, 1]; writeln(ct); // writes [1 1] // それ以外の配列のキャスト short[] rt = cast(short[]) [cast(byte)1, 1].dup; writeln(rt); // writes [257] }
連想配列リテラル
AssocArrayLiteral: [ KeyValuePairs ] KeyValuePairs: KeyValuePair KeyValuePair , KeyValuePairs KeyValuePair: KeyExpression : ValueExpression KeyExpression: AssignExpression ValueExpression: AssignExpression
連想配列リテラルは、角括弧 [ と ] の間で コンマで区切られた key:value ペアのリストです。 空リストであってはいけません。 1個目のkeyの型が全体のkeyの型として使用され、 残りの key はその型へと暗黙変換されます。 1個目のvalueの型が全体のvalueの型として使用され、 残りの value はその型へと暗黙変換されます。 連想配列リテラルは、AssocArrayLiteral 何かを静的初期化するのには使用できません
[21u:"he",38:"ho",2:"hi"]; // 型は char[2][uint] で、keyが 21u, 38u, 2u。 // valueが "he", "ho", "hi"
KeyValuePairs のどれかが ExpressionTuple であった場合は、 そのタプルの場所に引数として ExpressionTuple の要素が挿入されます。
関数リテラル
FunctionLiteral: function Typeopt ParameterAttributes opt FunctionBody delegate Typeopt ParameterAttributes opt FunctionBody ParameterAttributes FunctionBody FunctionBody ParameterAttributes: Parameters関数リテラルによって、式の中に直接 無名関数や無名デリゲートを埋め込むことが可能になります。 Type は関数やデリゲートの返値型です。 FunctionBody の return文 から推論できる場合は、 省略してもかまいません。 ( ArgumentList ) は関数の引数リストです。 省略した場合は、空の引数リスト () として処理されます。 関数リテラル式の型は、関数へのポインタか デリゲートへのポインタになります。 キーワード function や delegate が省略された場合のデフォルトは delegate です。
例として:
int function(char c) fp; // 関数へのポインタを宣言 void test() { static int foo(char c) { return 6; } fp = &foo; }これは次と等価です:
int function(char c) fp; void test() { fp = function int(char c) { return 6;} ; }あるいは、次のコードは:
int abc(int delegate(long i)); void test() { int b = 3; int foo(long c) { return 6 + b; } abc(&foo); }以下と全く同等です:
int abc(int delegate(long i)); void test() { int b = 3; abc( delegate int(long c) { return 6 + b; } ); }
次の例では、返値型 int が自動推論されています:
int abc(int delegate(long i)); void test() { int b = 3; abc( (long c) { return 6 + b; } ); }無名デリゲートは、文リテラルのように使うことができます。 例えば、以下は任意の文をループ実行する例です:
double test() { double d = 7.6; float f = 2.3; void loop(int k, int j, void delegate() statement) { for (int i = k; i < j; i++) { statement(); } } loop(5, 100, { d += 1; } ); loop(3, 10, { f += 3; } ); return d + f; }ネスト関数 と対比すると、 function を使う方は static ないしはネストしていない関数と似ていて、 delegate を使う方は、 非staticないしはネストした関数と似ています。言い換えると、 delegateリテラルは周囲の関数のスタック変数にアクセスできますが、 関数リテラルはできません。
Assert 式
AssertExpression: assert ( AssignExpression ) assert ( AssignExpression , AssignExpression )
expression の部分が評価されます。 もし結果がfalseだったらならば、 AssertError例外が投げられます。 結果がtrueならば例外は発生しません。 expression で、その挙動にプログラムが依存するような副作用が発生すると、 それはエラーです。 コンパイラーはassert式を全く評価しないことも許されています。 assert式の結果型は void です。 このassertは、Dの契約プログラミングサポートの 重要な一部をなしています。
assert(0) は特殊な場合です。この式は、 その部分が到達不能コードであることを示します。 到達してしまった場合、AssertError が送出されるか、 プログラムが実行停止します。 (x86 プロセッサでは、実行停止には HLT 命令が使われます) コンパイラは、最適化やコード生成の段階で、 実行がassert(0)の部分には到達しないことを仮定してよいものとします。
第二引数の Expression がもしあれば、 暗黙に char[] へ変換できる型である必要があります。この値は 第一引数の結果がfalseの時に評価され、 結果の文字列が AssertError のメッセージに追記されます。
void main() { assert(0, "an" ~ " error message"); }
これをコンパイルして実行すると、次のメッセージが表示されます:
Error: AssertError Failure test.d(3) an error message
mixin 式
MixinExpression: mixin ( AssignExpression )
この AssignExpression はコンパイル時定数へと評価される文字列である必要があります。 その文字列の内容は正当な AssignExpression としてコンパイルできるものでなければならず、 その場合、その通りにコンパイルされます。
int foo(int x) { return mixin("x + 1") * 7; // ((x + 1) * 7) と同じ }
import 式
ImportExpression: import ( AssignExpression )
AssignExpression はコンパイル時定数へと評価される文字列である必要があります。 文字列の内容はファイル名として解釈されます。 その名前のファイルが読み込まれ、 ファイルの内容が文字列リテラルとなります。
void foo() { // ファイル foo.txt の内容を表示 writefln( import("foo.txt") ); }
処理系は、ディレクトリトラバーサル攻撃を防止するために、 importに指定できるファイル名を制限する可能性があります。 典型的な制約としては、ファイル名の中にパス区切り文字を含めない、 などがあります。
Typeid式
TypeidExpression: typeid ( Type )
型 Type に対応した、 TypeInfo クラスのインスタンスを返します。
Is式
IsExpression: is ( Type ) is ( Type : TypeSpecialization ) is ( Type == TypeSpecialization ) is ( Type Identifier ) is ( Type Identifier : TypeSpecialization ) is ( Type Identifier == TypeSpecialization ) TypeSpecialization: Type typedef struct union class interface enum function delegate superIsExpression はコンパイル時に評価され、 型の正当性を検査したり、型の同値性を比較したり、 ある型から別の型へ暗黙変換できるかどうか確かめたり、 型の部分型を推論したりするために使用します。 IsExpression の結果はint型で、条件が不成立の場合0、 成立した場合1になります。
Type はテストする型です。文法的には正しくなければなりませんが、 意味的に正しい必要は必ずしもありません。 意味的に不正な型だった場合、条件が不成立となります。
Identifier は、条件が成立した場合、結果の型への alias となります。Identifier 形式は、 IsExpression が StaticIfCondition の中に現れた時のみ使用できます。
TypeSpecialization は Type と比較して検査するために指定する対象の型式です。
IsExpression には以下のような種類があります:
- is ( Type )
Type が意味的に正当な型の時に、条件が成立します。 (どの場合も文法的には正しい型を指定する必要があります)alias int func(int); // func は関数型への alias void foo() { if ( is(func[]) ) // 関数の配列は作れないので、 // 条件は満たされない writefln("satisfied"); else writefln("not satisfied"); if (is([][])) // エラー。[][] は文法的に正しい型でない ... }
- is ( Type : TypeSpecialization )
Type が意味的に正しく、 TypeSpecialization と同じかまたは暗黙に変換できる場合、 条件成立となります。 TypeSpecialization の部分には型名のみ使用できます。alias short bar; void foo(bar x) { if ( is(bar : int) ) // shortはintに暗黙変換されるので // 条件は満たされる writefln("satisfied"); else writefln("not satisfied"); }
- is ( Type == TypeSpecialization )
Type が意味的に正当な型で、 TypeSpecialization と同じな時に条件成立です。TypeSpecialization が typedef struct union class interface enum function delegate のいずれかの時は、Type が指定の種類の型であれば条件成立となります。
alias short bar; typedef char foo; void test(bar x) { if ( is(bar == int) ) // shortはintと同じ型ではないので、 // 条件は満たされない writefln("satisfied"); else writefln("not satisfied"); if ( is(foo == typedef) ) // fooはtypedef型なので条件が満たされる writefln("satisfied"); else writefln("not satisfied"); }
- is ( Type Identifier )
Type が意味的に正当な型であれば条件成立です。 その場合、Identifier が Type のaliasとして宣言されます。alias short bar; void foo(bar x) { static if ( is(bar T) ) alias T S; else alias long S; writefln(typeid(S)); // "short" と表示 if ( is(bar T) ) // エラー、Identifier T の形式は // StaticIfCondition にしか使えない ... }
- is ( Type Identifier : TypeSpecialization )
Type が TypeSpecialization と同じか、またはTypeがクラスでTypeSpecializationが その基底クラス/インターフェイスの場合、 条件成立となります。 Identifier はTypeSpecializationへの alias として宣言されるか、または、TypeSpecialization が Identifier に依存しているときは、推論された型へのaliasとなります。
alias int bar; alias long* abc; void foo(bar x, abc a) { static if ( is(bar T : int) ) alias T S; else alias long S; writefln(typeid(S)); // "int" と表示 static if ( is(abc U : U*) ) U u; writefln(typeid(typeof(u))); // "long" と表示 }
Identifier の型を決定する方法は、 TemplateTypeParameterSpecialization. の時にテンプレート引数型が決定されるのと同様の方法です。
- is ( Type Identifier == TypeSpecialization )
Type が意味的に正当な型で、 TypeSpecialization と同じな時に条件成立です。 Identifier はTypeSpecializationへのaliasとして宣言されるか、 または、TypeSpecialization が Identifier に依存しているときは、 推論された型へのaliasとなります。
TypeSpecialization が typedef struct union class interface enum function delegate のいずれかの時は、Type が指定の種類の型であれば条件成立となります。 さらに、Identifier は以下の型のaliasとなります:
キーワード Identifierがaliasとなる型 typedef typedefの元の型 struct Type union Type class Type interface Type super 基底クラスとインターフェイスからなる型タプル enum enumの基底型 function 関数の引数の TypeTuple delegate delegateの返値型 return 関数、delegate、関数ポインタの返値型 alias short bar; enum E : byte { Emember } void foo(bar x) { static if ( is(bar T == int) ) // 満たされない。shortはintではない alias T S; alias T U; // エラー、T は未定義 static if ( is(E V == enum) ) // 満たされる。Eはenum V v; // v はbyte型として宣言される }
例えば、X が typedef で 基底型が int であることを確かめるにはこう書きます:
typedef int X; static if (is(X base == typedef)) { static assert(is(base == int), "base of typedef X is not int"); } else { static assert(0, "X is not a typedef"); }
結合性と可換性
実装によっては、 一連の実行による結果が変化しない範囲で、 演算子の算術的な結合性と可換性の規則にしたがって 式を変形することがあります。
浮動小数点数は結合性/可換性を持たないため、 この規則に基づいた式変形の対象からは除かれます。