forked from dlang/dlang.org
-
Notifications
You must be signed in to change notification settings - Fork 4
/
function.dd
1795 lines (1441 loc) · 52.1 KB
/
function.dd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Ddoc
$(SPEC_S 関数,
$(GRAMMAR
$(GNAME FunctionBody):
$(GLINK2 statement, BlockStatement)
$(GLINK BodyStatement)
$(GLINK InStatement) $(GLINK BodyStatement)
$(GLINK OutStatement) $(GLINK BodyStatement)
$(GLINK InStatement) $(GLINK OutStatement) $(GLINK BodyStatement)
$(GLINK OutStatement) $(GLINK InStatement) $(GLINK BodyStatement)
$(GNAME InStatement):
$(B in) $(GLINK2 statement, BlockStatement)
$(GNAME OutStatement):
$(B out) $(GLINK2 statement, BlockStatement)
$(B out) $(B $(LPAREN)) $(I Identifier) $(B $(RPAREN)) $(GLINK2 statement, BlockStatement)
$(GNAME BodyStatement):
$(B body) $(GLINK2 statement, BlockStatement)
)
<h3>関数の返値</h3>
$(P 関数の返値は右辺値とみなされます。
つまり、返値を他の関数に参照渡しすることはできません。
)
<h3>本体なしの関数</h3>
$(P 本体なしの関数)
---
int foo();
---
$(P で $(D abstract) と宣言されていないものは、どこか他に実装があり、
リンク時に提供されるものと仮定されます。
これによって、ユーザーから関数の実装を完全に隠すことや、
C やアセンブラなど別言語での実装を使うことができます。
)
$(V2
<h3>$(LNAME2 pure-functions, pure関数)</h3>
$(P pure 関数は、
グローバルやstaticの記憶域への書き換えアクセスができず、状態を保存しない関数です。これによって、
渡されたもの以外何も書き換えないことが保証されることを利用した最適化が可能になります。
さらに、コンパイラが pure 関数がその引数を置き換えないと保証できる場合、
完全な、関数としての純粋性 (つまり、
同じ引数に対して常に同じ結果を返す関数であるという保証) が得られます。
そのために、pure関数は以下を満たす必要があります:
)
$(UL
$(LI グローバル や static の mutable な状態を読み書きしない)
$(LI pure関数はpureでない関数を呼び出すことはできません)
$(LI pure関数によるpureでない関数のオーバーライドはできますが、
逆はできません。)
$(LI pureでない関数に対し共変です)
$(LI I/Oは行えません)
)
$(P 実用性を考慮して、pure関数では:)
$(UL
$(LI メモリを $(GLINK2 expression, NewExpression) で割り当てられます)
$(LI プログラムを終了できます)
$(LI 浮動小数点数の例外フラグを読み書きできます)
$(LI 浮動小数点数のモードフラグは、
関数を抜けるときに元の状態に戻すことが保証されている限り、読み書きできます。)
$(LI $(GLINK2 version, DebugCondition) で制御された
$(GLINK2 version, ConditionalStatement) の中では、
pure でない操作を行えます。)
)
$(P pure関数は例外を投げることができます。)
---
import std.stdio;
int x;
immutable int y;
const int* pz;
pure int foo(int i,
char* p,
const char* q,
immutable int* s)
{
debug writeln("in foo()"); // ok, debug文の中ではpureでない関数も可
x = i; // エラー。グローバル状態を書き換えている
i = x; // エラー。mutableなグローバル状態を読み取っている
i = y; // ok。immutableなグローバル状態の読み取り
i = *pz; // エラー。constなグローバル状態を読み取っている
return i;
}
---
<h3>$(LNAME2 nothrow-functions, nothrow関数)</h3>
$(P nothrow関数は、
クラス $(I Exception) から派生した例外を一切投げない関数です。
)
$(P nothrow関数はそうでない関数に対して共変です。)
<h3>$(LNAME2 ref-functions, ref関数)</h3>
$(P ref関数は、返値を参照で返します。
ref引数の返値バージョンです。
)
---
ref int foo() {
auto p = new int;
return *p;
}
...
foo() = 3; // ref返値は左辺値となる
---
<h3>$(LNAME2 auto-functions, auto 関数)</h3>
$(P auto関数は、関数定義にある
$(GLINK2 statement, ReturnStatement)
から返値の型を推論します。
)
$(P auto 関数は返値型なしで宣言されます。
他に特に記憶域クラスのない関数では、
$(D_KEYWORD auto) 記憶域クラスを指定します。
)
$(P 複数の $(I ReturnStatement) がある場合は、
それらの型は完全にマッチしなければいけません。$(I ReturnStatement) がない場合は、
返値型は $(D_KEYWORD void) と推論されます。
)
---
auto foo(int i) {
return i + 3; // 返値型はintと推論される
}
---
$(V2
<h3>$(LNAME2 auto-ref-functions, auto ref 関数)</h3>
$(P auto ref 関数は、
$(LINK2 #auto-functions, auto 関数) と同様に関数の返値型を推論します。
加えて、return される式が左辺値であった場合、
自動的に $(LINK2 #ref-functions, ref 関数)
となります。ただし、値引数やローカル変数への参照を返すようにはなりません。
)
---
auto ref foo(int x) { return x; } // 値をreturn
auto ref foo() { return 3; } // 値をreturn
auto ref foo(ref int x) { return x; } // refをreturn
auto ref foo(out int x) { return x; } // refをreturn
auto ref foo() { static int x; return x; } // refをreturn
---
$(P 字句的に最初の $(GLINK2 statement, ReturnStatement)
が関数のref性を決めます:
)
---
auto ref foo(ref int x) { return 3; return x; } // ok, 値return
auto ref foo(ref int x) { return x; return 3; } // エラー。3は左辺値でない。
---
)
$(V2
<h3>$(LNAME2 inout-functions, inout 関数)</h3>
$(P mutable, const, immutable のどれに対しても同じように適用したい関数を扱う場合、
その型を返値に伝搬させたいケースが多くあります。
)
---
int[] foo(int[] a, int x, int y) { return a[x .. y]; }
const(int)[] foo(const(int)[] a, int x, int y) { return a[x .. y]; }
immutable(int)[] foo(immutable(int)[] a, int x, int y) { return a[x .. y]; }
---
$(P これらの関数から生成されるコードは完全に一致します。
これらを一つの関数にまとめるには、 $(D_KEYWORD inout)
型コンストラクタを使います:
)
---
inout(int)[] foo(inout(int)[] a, int x, int y) { return a[x .. y]; }
---
$(P $(D_KEYWORD inout) は、
mutable, const, immutable のいずれかにマッチするワイルドカードとして働きます。
関数が呼ばれたときに、返値型の inout は、
引数の mutable, const, immutable
に合わせて変化します。
)
$(P inout 型は const に暗黙変換できますが、他の型になることはありません。
他の型を暗黙にinoutに変換することもできません。
inoutへの/からのキャストは @safe 関数では不可能です。
)
$(P inout が関数引数リストに現れる場合、
返値型にも現れなければいけません。
)
$(P inout仮引数を持つ関数へと実引数が与えられたとき、以下のケースでマッチしたと判定されます。
実引数も完全に同じinoutを持つか、または:)
$(OL
$(LI 実引数がどれもinout型を持たない)
$(LI 仮引数でinout型となっている部分に、実引数側で mutable, const, immutable
のいずれかがマッチする。)
)
$(P このようなマッチがあった場合、全てのマッチがmutableなら、inoutはmutalbeとして扱われます。
全てのマッチがimmutableなら、inoutはimmutalbeとして扱われます。
それ以外のケースでは、inoutはconstとして扱われます。
関数の返値の型は、
マッチした属性でinoutを置き換えたものとなります。
)
$(P グローバルやstaticな変数の型にはinoutを使うことはできません。
)
$(P $(B 注:) sharedについて書き忘れたているわけではありません。
shared型はinoutとはマッチしません。
)
)
<h3>$(LNAME2 property-functions, プロパティ関数)</h3>
$(P $(CODE @property) 属性のついた関数が、プロパティ関数です。
この関数は括弧無しで(つまりプロパティのように)
呼び出すことができます。
)
---
struct S {
int m_x;
@property {
int x() { return m_x; }
int x(int newx) { return m_x = newx; }
}
}
void foo() {
S s;
s.x = 3; // s.x(int) を呼ぶ
bar(s.x); // bar(s.x()) を呼ぶ
}
---
)
<h3>$(LNAME2 virtual-functions, 仮想関数)</h3>
$(P 仮想関数とは、
直接呼び出されるのではなく、
vtbl[] と呼ばれるポインタテーブル経由で間接的に呼び出される関数のことをいいます。
全ての非staticかつ非privateかつ非templateなメンバ関数は、
仮想関数です。
これは非効率的に聞こえるかもしれませんが、
コード生成時にDはクラス階層を全て把握していますので、
オーバーライドされていない関数への呼び出しは全て最適化されて
non-virtual になります。
C++プログラマは "疑わしきはvirtualにせよ"
でコードを書く傾向にありますが、
Dの流儀である "non-virtualにできると証明できない限りvirtualにせよ"
の方が、結果的には多くの関数を直接呼び出せます。
オーバーライドしたい関数を
仮想関数にし忘れる、
というバグを減らすことにもつながります。
)
$(P Dのリンケージを持たない関数はvirtualにはできず、
それゆえオーバーライドできません。
)
$(P メンバテンプレート関数はvirtualにはできず、
それゆえオーバーライドできません。
)
$(P $(D final) という印のついた関数は、
$(D private) でない限り、
派生クラスでオーバーライドできません。例えば:
)
------
class A {
int def() { ... }
final int foo() { ... }
final private int bar() { ... }
private int abc() { ... }
}
class B : A {
int def() { ... } // ok。A.defをオーバーライドする。
int foo() { ... } // エラー。A.fooはfinal
int bar() { ... } // ok。A.bar は final private だが virtual でない
int abc() { ... } // ok。A.abc は virtual でない。B.abc は virtual
}
void test(A a) {
a.def(); // B.def を呼ぶ
a.foo(); // A.foo を呼ぶ
a.bar(); // A.bar を呼ぶ
a.abc(); // A.abc を呼ぶ
}
void func() {
B b = new B();
test(b);
}
------
$(P 共変の返値型がサポートされています。
すなわち、派生クラスでオーバーライドした関数では、
基底クラスでの返値型の
派生型オブジェクトを返すことができます。
)
------
class A { }
class B : A { }
class Foo {
A test() { return null; }
}
class Bar : Foo {
B test() { return null; } // Foo.test() と共変なオーバーライド
}
------
$(P 仮想関数は全て隠し引数として
$(I this) 参照を受け取り、
これは関数が呼ばれた対象オブジェクトを指しています。
)
<h3>$(LNAME2 function-inheritance, 関数の継承とオーバーライド)</h3>
派生クラスの関数で、基底クラスの関数と名前と引数の同じものは、
基底の関数を上書きします:
------
class A {
int foo(int x) { ... }
}
class B : A {
override int foo(int x) { ... }
}
void test() {
B b = new B();
bar(b);
}
void bar(A a) {
a.foo(1); // B.foo(int) を呼ぶ
}
------
$(P しかしながら、オーバーロードの解決の際には、
基底クラスのメンバ関数は考慮されません。
)
------
class A {
int foo(int x) { ... }
int foo(long y) { ... }
}
class B : A {
override int foo(long x) { ... }
}
void test() {
B b = new B();
b.foo(1); // B.foo(long) を呼ぶ。A.foo(int) は考慮しない
A a = b;
$(V1 a.foo(1); // A.foo(int) を呼ぶ)
$(V2 a.foo(1); // 実行時エラー (A.foo(int) は呼ばれない))
}
------
$(P オーバーロードの解決の過程で基底クラスの関数も考慮に入れたい場合は、
$(I AliasDeclaration) を使います:
)
------
class A {
int foo(int x) { ... }
int foo(long y) { ... }
}
class B : A {
$(B alias A.foo foo;)
override int foo(long x) { ... }
}
void test() {
B b = new B();
bar(b);
}
void bar(A a) {
a.foo(1); // A.foo(int) を呼ぶ
B b = new B();
b.foo(1); // A.foo(int) を呼ぶ
}
------
$(V2
$(P $(I AliasDeclaration) を使わなかった場合、
派生クラスの関数は、
例え引数の型が違っていたとしても基底クラスの同名の関数を全てオーバーライドします。
ただし、
実際に基底クラスへの参照を通してそれらの引数型違いの関数が実際に呼び出された場合は、
$(CODE core.exception.HiddenFuncError) 例外が発生します
)
---
import core.exception;
class A {
void set(long i) { }
void $(B set)(int i) { }
}
class B : A {
void set(long i) { }
}
void foo(A a) {
int i;
try {
a.$(B set)(3); // エラー。A.set(int)がBでは定義されていないので
// 実行時に例外を投げる
}
catch ($(B HiddenFuncError) o) {
i = 1;
}
assert(i == 1);
}
void main() {
foo(new B);
}
---
$(P 自分のコードを実行してみて $(CODE HiddenFuncError)
が送出された場合、
関係するクラスでのオーバーロードやオーバーライドをチェックするようにしましょう。)
$(P ただし、
継承階層中の他の全ての仮想関数とオーバーロードに関して排反な場合は、
$(CODE HiddenFuncError) は投げられません。)
)
$(P 関数のデフォルト引数値は継承されません:)
------
class A {
void foo(int $(B x = 5)) { ... }
}
class B : A {
void foo(int $(B x = 7)) { ... }
}
class C : B {
void foo(int $(B x)) { ... }
}
void test() {
A a = new A();
a.foo(); // A.foo(5) を呼ぶ
B b = new B();
b.foo(); // B.foo(7) を呼ぶ
C c = new C();
c.foo(); // エラー、C.fooには引数が必要
}
------
<h3>インライン関数</h3>
inlineという予約語はありません。
関数をインライン化するかどうかは、
コンパイラが決定します。Cでの予約語registerに対し、
実際に変数をレジスタに配置するかどうかはコンパイラにゆだねられているのと
同様です。(Dにはregisterという予約語もありません。)
<h2>$(LNAME2 function-overloading, 関数オーバーロード)</h2>
$(P 関数は、仮引数の型と実引数の型がどれだけよくマッチするかという基準によって
オーバーロードされます。
$(I もっともよく) マッチする関数が選択されます。
マッチの適合度は以下の4段階あります:
)
$(OL
$(LI マッチしない)
$(LI 暗黙変換によるマッチ)
$(V2 $(LI constへの変換によるマッチ))
$(LI 正確なマッチ)
)
$(P 実引数それぞれ ($(CODE this) ポインタも含む) が
関数の対応する仮引数と比較され、
その実引数に関するマッチ適合度が決定されます。
引数のうち$(I 一番悪い)もののマッチ適合度が、
関数全体としてのマッチ適合度と定義されます。)
$(V2
$(P リテラルは $(CODE ref) や $(CODE out) 引数とはマッチしません)
)
$(V1
$(P 複数の関数が同じマッチ適合度にある場合、
曖昧でエラーとなります。
)
)
$(V2
$(P 複数の関数が同じマッチ適合度にある場合、
$(LNAME2 partial-ordering, $(I partial ordering))
iによって最適なマッチが決定されます。
Partial ordering では、もっとも特殊化された関数が選択されます。
他と比べてより特殊化されていると言える関数が無い場合、
曖昧な関数呼び出しとしてエラーになります。
関数 $(CODE f()) と
$(CODE g()) の partial ordering は、まず $(CODE f()) の引数型を取り出し、
それぞれの型のデフォルト値によって
$(CODE g()) の呼び出しの型チェックを試みることで判定されます。
成功すれば、$(CODE f()) は少なくとも
$(CODE g()) と同等以上に特殊化されていることになります。
例えば:
)
---
class A { }
class B : A { }
class C : B { }
void foo(A);
void foo(B);
void test() {
C c;
/* foo(A) と foo(B) のどちらも暗黙変換によるマッチなので、
* partial ordering を適用する。
* foo(B) は引数 A では呼び出せないが、foo(A) は
* 引数 B で呼び出せる。従って foo(B) の方がより特殊化されていることになり、選択される。
*/
foo(c); // foo(B) を呼び出す
}
---
$(P 可変個引数関数は、
そうでない関数よりも特殊化の度合いが低いと見なされます。
)
)
$(P D
のリンケージを持たない関数はオーバーロードできません。
名前マングリングが引数の型を考慮していないためです。
)
$(V2
<h2><a name="overload-sets">オーバーロード集合</a></h2>
$(P 同じスコープで宣言された関数同士はお互いオーバーロードしあいます、
このような関数のあつまりを $(I オーバーロード集合) と呼びます。
典型的な例は、
モジュールレベルで定義された関数によるオーバーロード集合です:
)
---
module A;
void foo() { }
void foo(long i) { }
---
$(P $(CODE A.foo()) と $(CODE A.foo(long)) がオーバーロード集合を形成します。
別のモジュールで同名の関数を宣言することも可能で:
)
---
module B;
class C { }
void foo(C) { }
void foo(int i) { }
---
$(P さらに別のモジュール C で A と B を同時に import しても構いません。
C では二つのオーバーロード集合、つまり $(CODE A.foo) oのオーバーロード集合と $(CODE B.foo)
oのオーバーロード集合が存在することになります。そして、ちょうど1個のオーバーロード集合に
マッチした場合のみ、実際に呼び出される $(CODE foo) のインスタンスが選択されます。
)
---
import A;
import B;
void bar(C c) {
foo(); // A.foo() を呼ぶ
foo(1L); // A.foo(long) を呼ぶ
foo(c); // B.foo(C) を呼ぶ
foo(1,2); // エラー。どの foo にもマッチしない
foo(1); // エラー。A.foo(long) と B.foo(int) の両方にマッチ
A.foo(1); // A.foo(long) を呼ぶ
}
---
$(P $(CODE foo(1)) に対しては $(CODE B.foo(int)) の方が $(CODE
A.foo(long)) よりも適合度は高いですが、
違う2つのオーバーロード集合両方にマッチするので、
これはエラーとなります。
)
$(P alias宣言を使うことで、オーバーロード集合を併合することができます:)
---
import A;
import B;
alias A.foo foo;
alias B.foo foo;
void bar(C c) {
foo(); // A.foo() を呼ぶ
foo(1L); // A.foo(long) を呼ぶ
foo(c); // B.foo(C) を呼ぶ
foo(1,2); // エラー。どの foo にもマッチしない
foo(1); // B.foo(int) を呼ぶ
A.foo(1); // A.foo(long) を呼ぶ
}
---
)
<h3><a name="parameters">関数の引数</a></h3>
$(V1
$(P 関数引数のには $(B in), $(B out), $(B inout), $(B lazy)が指定できます。
$(B in) がデフォルトです。そのほかは記憶域クラスのように動作します。
例:
)
------
int foo(in int x, out int y, inout int z, int q);
------
$(P x は $(B in), y は $(B out), z は $(B inout), q は $(B in) です。
)
)
$(V2
$(P 関数引数の記憶クラスには $(B in), $(B out),
$(B ref), $(B lazy), $(B const), $(B immutable), $(B shared),
$(B inout),
$(B scope) が指定できます。
例:
)
------
int foo(in int x, out int y, ref int z, int q);
------
$(P x は $(B in), y は $(B out), z は $(B ref), q はそのいずれでもありません。
)
)
$(UL
$(LI 関数宣言にどれが入力でどれが出力か明示することで、
理解しやすくなります。)
$(LI IDLを別の言語として用意する必要がなくなります。)
$(LI コンパイラに多くの情報を与えることで、
よりよいエラーチェックやコード生成が
可能になります。)
)
$(TABLE2 仮引数の記憶域クラス,
$(THEAD 記憶域クラス, 説明)
$(TROW $(I none), 仮引数は実引数の書き換え可能なコピーとなる)
$(V1 $(TROW $(D in), $(I none) と同じ))
$(V2 $(TROW $(D in), $(D const scope) と同じ))
$(TROW $(D out), 仮引数は関数に入る際に、
その型のデフォルト値で初期化される)
$(V1 $(TROW $(D inout), 引数は参照渡しされる))
$(V2 $(TROW $(D ref), 引数は参照渡しされる))
$(V2 $(TROW $(D scope), 仮引数への参照は関数スコープの外に出すこと
(グローバル変数への代入など) はできない))
$(TROW $(D lazy), 実引数は呼び出し側ではなく、呼ばれた関数の中で使った時に評価される)
$(V2
$(TROW $(D const), 実引数が暗黙にconst型に変換される)
$(TROW $(D immutable), 実引数が暗黙にimmutable型に変換される)
$(TROW $(D shared), 実引数が暗黙にshared型に変換される)
$(TROW $(D inout), 実引数が暗黙にinout型に変換される)
)
)
------
void foo(out int x) {
// x は int.init、つまり 0 に
// 関数の最初でセットされる
}
int a = 3;
foo(a);
// a は 0
void abc(out int x) {
x = 2;
}
int y = 3;
abc(y);
// y は 2
void def($(V1 inout)$(V2 ref) int x) {
x += 1;
}
int z = 3;
def(z);
// z は 4
------------
$(P 動的配列やオブジェクト引数は元々参照渡しされますが、
これらについては、
in/out/ref は参照に対して適用され、中身には影響しません。
)
$(P $(D lazy) 引数は関数が呼び出されるときに評価されるのではなく、
関数の中で引数が使われる時点で初めて評価されます。このため、
$(D lazy) 引数は一度も実行されないこともあれば複数回実行される可能性もあります。 $(D lazy)
引数は左辺値とすることはできません。)
---
void dotimes(int n, lazy void exp) {
while (n--)
exp();
}
void test() {
int x;
dotimes(3, writefln(x++));
}
---
$(P 次のように表示されます:)
$(CONSOLE
0
1
2
)
$(P $(D void) 型の $(D lazy)
引数は、任意の型の引数を受け付けます。)
<h3>デフォルト引数</h3>
$(P 関数のパラメタ宣言にはデフォルト値を設定することが可能です:)
---
void foo(int x, int y = 3) {
...
}
...
foo(4); // foo(4, 3); と同じ
---
$(P デフォルト引数は、
関数宣言のコンテキストで評価されます。
引数にデフォルト値を設定する場合、
それよりも後ろの引数にも全てデフォルト値を設定する必要があります。
)
<a name="variadic"><h2>可変個関数引数</h2></a>
引数の個数が定まっていな関数のことを、可変個引数関数と呼びます。
可変個引数関数には、
以下の3つの形式があります:
$(OL
$(LI Cスタイル可変個引数関数)
$(LI Dスタイル可変個引数関数)
$(LI 型安全可変個引数関数)
)
<h3>Cスタイル可変個引数関数</h3>
Cスタイル可変個引数関数は、必須の引数の後ろに
... をつけた形で宣言されます。
$(D extern (C)) のような、D以外のリンケージを持ちます:
------
extern (C) int foo(int x, int y, ...);
foo(3, 4); // ok
foo(3, 4, 6.8); // ok, 可変引数が1つ。
foo(2); // エラー。引数yは必須。
------
この形式の関数は、最低1つの非変引数を受け取る必要があります。
------
extern (C) int def(...); // エラー、最低一つの引数が必要
------
$(P
Cスタイル可変個引数関数は、Cの可変個引数関数の呼び出し規約と一致していて、
$(D printf) のような
Cのライブラリ関数を呼び出すのに利用できます。
)
$(P 可変個引数へのアクセスは、
標準ライブラリモジュール $(V1 $(B std.c.stdarg))$(V2 $(B core.stdc.stdarg)) を使います。
)
------
import $(V1 std.c.stdarg)$(V2 core.stdc.stdarg);
void test() {
foo(3, 4, 5); // 可変個引数の先頭は、5
}
int foo(int x, int y, ...) {
va_list ap;
version (X86_64)
va_start(ap, __va_argsave);
else version (X86)
va_start(ap, y); // y は最後の名前付きメンバ
int z;
va_arg(ap, z); // z は 5 になる
va_end(ap);
}
------
<h3>Dスタイル可変個引数関数</h3>
型情報付き可変個引数関数は、必須の引数の後ろに
... をつけた形で宣言します。
この種類の関数はDリンケージを持ち、
固定の引数が一個もなくても構いません:
------
int abc(char c, ...); // 必須引数が一つ: c
int def(...); // ok
------
引数にアクセスするには、以下の import が必要です:
------
import $(V1 std.stdarg)$(V2 core.vararg);
------
可変個引数関数の中では、
$(V1 $(D std.stdarg.va_list))$(V2 $(D core.vararg)) で参照できる
特別なローカル変数 $(B _argptr)
が使えます。
引数にアクセスするには、$(B _argptr) を
$(D va_arg) と合わせて使います。
------
import $(V1 std.stdarg)$(V2 core.vararg);
void test() {
foo(3, 4, 5); // 可変個引数の先頭は、5
}
int foo(int x, int y, ...) {
int z;
z = va_arg!int(_argptr); // z に 5 が入る
}
------
Dのリンケージを持つ可変個引数関数には、さらに $(D TypeInfo[])
型の隠れた引数 $(B _arguments) が渡されます。
$(B _arguments) によって、
実際に渡された引数の個数とそれぞれ型がわかります。
この情報を元に、型安全な可変個引数関数を作ることができます。
------
import std.stdio;
import $(V1 std.stdarg)$(V2 core.vararg);
class Foo { int x = 3; }
class Bar { long y = 4; }
void printargs(int x, ...) {
writefln("%d arguments", $(B _arguments).length);
for (int i = 0; i < $(B _arguments).length; i++)
{
$(V1 $(B _arguments)[i].print();)$(V2 writeln($(B _arguments)[i]);)
if ($(B _arguments)[i] == typeid(int))
{
int j = va_arg!(int)(_argptr);
writefln("\t%d", j);
}
else if ($(B _arguments)[i] == typeid(long))
{
long j = va_arg!(long)(_argptr);
writefln("\t%d", j);
}
else if ($(B _arguments)[i] == typeid(double))
{
double d = va_arg!(double)(_argptr);
writefln("\t%g", d);
}
else if ($(B _arguments)[i] == typeid(Foo))
{
Foo f = va_arg!(Foo)(_argptr);
writefln("\t%s", f);
}
else if ($(B _arguments)[i] == typeid(Bar))
{
Bar b = va_arg!(Bar)(_argptr);
writefln("\t%s", b);
}
else
assert(0);
}
}
void main() {
Foo f = new Foo();
Bar b = new Bar();
writefln("%s", f);
printargs(1, 2, 3L, 4.5, f, b);
}
------
これは次のように表示されます:
------
00870FE0
5 arguments
int
2
long
3
double
4.5
Foo
00870FE0
Bar
00870FD0
------
<h3>型安全可変個引数関数</h3>
型安全可変個引数関数は、
引数の可変部分が配列やクラスオブジェクトの生成に使われる、
という形で実現されています。
<p>
配列として可変部分を受け取る場合:
------
int test() {
return sum(1, 2, 3) + sum(); // 6+0 を返す
}
int func() {
int[3] ii = [4, 5, 6];
return sum(ii); // 15を返す
}
int sum(int[] ar ...) {
int s;
foreach (int x; ar)
s += x;
return s;
}
------
静的配列の場合: