forked from DlangRen/Programming-in-D
-
Notifications
You must be signed in to change notification settings - Fork 1
/
operator_overloading.d
1902 lines (1470 loc) · 55.2 KB
/
operator_overloading.d
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
$(DERS_BOLUMU $(IX operator overloading) $(IX overloading, operator) Operator Overloading)
$(P
The topics covered in this chapter apply mostly for classes as well. The biggest difference is that the behavior of assignment operation $(C opAssign()) cannot be overloaded for classes.
)
$(P
Operator overloading involves many concepts, some of which will be covered later in the book (templates, $(C auto ref), etc.). For that reason, you may find this chapter to be harder to follow than the previous ones.
)
$(P
Operator overloading enables defining how user-defined types behave when used with operators. In this context, the term $(I overload) means providing the definition of an operator for a specific type.
)
$(P
We have seen how to define structs and their member functions in previous chapters. As an example, we have defined the $(C increment()) member function to be able to add $(C Duration) objects to $(C TimeOfDay) objects. Here are the two structs from previous chapters, with only the parts that are relevant to this chapter:
)
---
struct Duration {
int minute;
}
struct TimeOfDay {
int hour;
int minute;
void $(HILITE increment)(in Duration duration) {
minute += duration.minute;
hour += minute / 60;
minute %= 60;
hour %= 24;
}
}
void main() {
auto lunchTime = TimeOfDay(12, 0);
lunchTime$(HILITE .increment)(Duration(10));
}
---
$(P
A benefit of member functions is being able to define operations of a type alongside the member variables of that type.
)
$(P
Despite their advantages, member functions can be seen as being limited compared to operations on fundamental types. After all, fundamental types can readily be used with operators:
)
---
int weight = 50;
weight $(HILITE +=) 10; // by an operator
---
$(P
According to what we have seen so far, similar operations can only be achieved by member functions for user-defined types:
)
---
auto lunchTime = TimeOfDay(12, 0);
lunchTime$(HILITE .increment)(Duration(10)); // by a member function
---
$(P
Operator overloading enables using structs and classes with operators as well. For example, assuming that the $(C +=) operator is defined for $(C TimeOfDay), the operation above can be written in exactly the same way as with fundamental types:
)
---
lunchTime $(HILITE +=) Duration(10); // by an operator
// (even for a struct)
---
$(P
Before getting to the details of operator overloading, let's first see how the line above would be enabled for $(C TimeOfDay). What is needed is to redefine the $(C increment()) member function under the special name $(C opOpAssign(string op)) and also to specify that this definition is for the $(C +) character. As it will be explained below, this definition actually corresponds to the $(C +=) operator.
)
$(P
The definition of this member function does not look like the ones that we have seen so far. That is because $(C opOpAssign) is actually a $(I function template). Since we will see templates in much later chapters, I will have to ask you to accept the operator overloading syntax as is for now:
)
---
struct TimeOfDay {
// ...
ref TimeOfDay opOpAssign(string op)(in Duration duration)//(1)
if (op == "+") { //(2)
minute += duration.minute;
hour += minute / 60;
minute %= 60;
hour %= 24;
return this;
}
}
---
$(P
The template definition consists of two parts:
)
$(OL
$(LI $(C opOpAssign(string op)): This part must be written as is and should be accepted as the $(I name) of the function. We will see below that there are other member functions in addition to $(C opOpAssign).
)
$(LI $(C if (op == $(STRING "+"))): $(C opOpAssign) is used for more than one operator overload. $(C $(STRING "+")) specifies that this is the operator overload that corresponds to the $(C +) character. This syntax is a $(I template constraint), which will also be covered in later chapters.
)
)
$(P
Also note that this time the return type is different from the return type of the $(C increment()) member function: It is not $(C void) anymore. We will discuss the return types of operators later below.
)
$(P
Behind the scenes, the compiler replaces the uses of the $(C +=) operator with calls to the $(C opOpAssign!$(STRING "+")) member function:
)
---
lunchTime += Duration(10);
// The following line is the equivalent of the previous one
lunchTime.opOpAssign!"+"(Duration(10));
---
$(P
The $(C !$(STRING "+")) part that is after $(C opOpAssign) specifies that this call is for the definition of the operator for the $(C +) character. We will cover this template syntax in later chapters as well.
)
$(P
Note that the operator definition that corresponds to $(C +=) is defined by $(STRING "+"), not by $(STRING "+="). The $(C Assign) in the name of $(C opOpAssign()) already implies that this name is for an assignment operator.
)
$(P
Being able to define the behaviors of operators brings a responsibility: The programmer must observe expectations. As an extreme example, the previous operator could have been defined to decrement the time value instead of incrementing it. However, people who read the code would still expect the value to be incremented by the $(C +=) operator.
)
$(P
To some extent, the return types of operators can also be chosen freely. Still, general expectations must be observed for the return types as well.
)
$(P
Keep in mind that operators that behave unnaturally would cause confusion and bugs.
)
$(H5 Overloadable operators)
$(P
There are different kinds of operators that can be overloaded.
)
$(H6 $(IX unary operator) $(IX operator, unary) $(IX opUnary) Unary operators)
$(P
An operator that takes a single operand is called a unary operator:
)
---
++weight;
---
$(P
$(C ++) is a unary operator because it works on a single variable.
)
$(P
Unary operators are defined by member functions named $(C opUnary). $(C opUnary) does not take any parameters because it uses only the object that the operator is being executed on.
)
$(P
$(IX -, negation)
$(IX +, plus sign)
$(IX ~, bitwise complement)
$(IX *, pointee access)
$(IX ++, pre-increment)
$(IX --, pre-decrement)
The overloadable unary operators and the corresponding operator strings are the following:
)
$(TABLE full,
$(HEAD3 Operator, Description, Operator String)
$(ROW3 -object, negative of (numeric complement of), "-")
$(ROW3 +object, the same value as (or, a copy of), "+")
$(ROW3 ~object, bitwise negation, "~")
$(ROW3 *object, access to what it points to, "*")
$(ROW3 ++object, increment, "++")
$(ROW3 --object, decrement, "--")
)
$(P
For example, the $(C ++) operator for $(C Duration) can be defined like this:
)
---
struct Duration {
int minute;
ref Duration opUnary(string op)()
if (op == "++") {
++minute;
return this;
}
}
---
$(P
Note that the return type of the operator is marked as $(C ref) here as well. This will be explained later below.
)
$(P
$(C Duration) objects can now be incremented by $(C ++):
)
---
auto duration = Duration(20);
++duration;
---
$(P
$(IX ++, post-increment) $(IX --, post-decrement) The post-increment and post-decrement operators cannot be overloaded. The $(C object++) and $(C object--) uses are handled by the compiler automatically by saving the previous value of the object. For example, the compiler applies the equivalent of the following code for post-increment:
)
---
/* The previous value is copied by the compiler
* automatically: */
Duration __previousValue__ = duration;
/* The ++ operator is called: */
++duration;
/* Then __previousValue__ is used as the value of the
* post-increment operation. */
---
$(P
Unlike some other languages, the copy inside post-increment has no cost in D if the value of the post-increment expression is not actually used. This is because the compiler replaces such post-increment expressions with their pre-increment counterparts:
)
---
/* The value of the expression is not used below. 这个
* only effect of the expression is incrementing 'i'. */
i++;
---
$(P
Because the $(I previous value) of $(C i) is not actually used above, the compiler replaces the expression with the following one:
)
---
/* The expression that is actually used by the compiler: */
++i;
---
$(P
Additionally, if an $(C opBinary) overload supports the $(C duration += 1) usage, then $(C opUnary) need not be overloaded for $(C ++duration) and $(C duration++). Instead, the compiler uses the $(C duration += 1) expression behind the scenes. Similarly, the $(C duration -= 1) overload covers the uses of $(C --duration) and $(C duration--) as well.
)
$(H6 $(IX binary operator) $(IX operator, binary) Binary operators)
$(P
An operator that takes two operands is called a binary operator:
)
---
totalWeight $(HILITE =) boxWeight $(HILITE +) chocolateWeight;
---
$(P
The line above has two separate binary operators: the $(C +) operator, which adds the values of the two operands that are on its two sides, and the $(C =) operator that assigns the value of its right-hand operand to its left-hand operand.
)
$(P
$(IX +, addition)
$(IX -, subtraction)
$(IX *, multiplication)
$(IX /)
$(IX %)
$(IX ^^)
$(IX &, bitwise and)
$(IX |)
$(IX ^, bitwise exclusive or)
$(IX <<)
$(IX >>)
$(IX >>>)
$(IX ~, concatenation)
$(IX in, operator)
$(IX ==)
$(IX !=)
$(IX <, less than)
$(IX <=)
$(IX >, greater than)
$(IX >=)
$(IX =)
$(IX +=)
$(IX -=)
$(IX *=)
$(IX /=)
$(IX %=)
$(IX ^^=)
$(IX &=)
$(IX |=)
$(IX ^=)
$(IX <<=)
$(IX >>=)
$(IX >>>=)
$(IX ~=)
$(IX opBinary)
$(IX opAssign)
$(IX opOpAssign)
$(IX opBinaryRight)
The rightmost column below describes the category of each operator. The ones marked as "=" assign to the left-hand side object.
)
$(TABLE full,
$(HEAD5 $(BR)Operator, $(BR)Description, $(BR)Function name, Function name$(BR)for right-hand side, $(BR)Category)
$(ROW5 +, add, opBinary, opBinaryRight, arithmetic)
$(ROW5 -, subtract, opBinary, opBinaryRight, arithmetic)
$(ROW5 *, multiply, opBinary, opBinaryRight, arithmetic)
$(ROW5 /, divide, opBinary, opBinaryRight, arithmetic)
$(ROW5 %, remainder of, opBinary, opBinaryRight, arithmetic)
$(ROW5 ^^, to the power of, opBinary, opBinaryRight, arithmetic)
$(ROW5 &, bitwise $(I and), opBinary, opBinaryRight, bitwise)
$(ROW5 |, bitwise $(I or), opBinary, opBinaryRight, bitwise)
$(ROW5 ^, bitwise $(I xor), opBinary, opBinaryRight, bitwise)
$(ROW5 <<, left-shift, opBinary, opBinaryRight, bitwise)
$(ROW5 >>, right-shift, opBinary, opBinaryRight, bitwise)
$(ROW5 >>>, unsigned right-shift, opBinary, opBinaryRight, bitwise)
$(ROW5 ~, concatenate, opBinary, opBinaryRight, )
$(ROW5 in, whether contained in, opBinary, opBinaryRight, )
$(ROW5 ==, whether equal to, opEquals, -, logical)
$(ROW5 !=, whether not equal to, opEquals, -, logical)
$(ROW5 <, whether before, opCmp, -, sorting)
$(ROW5 <=, whether not after, opCmp, -, sorting)
$(ROW5 >, whether after, opCmp, -, sorting)
$(ROW5 >=, whether not before, opCmp, -, sorting)
$(ROW5 =, assign, opAssign, -, =)
$(ROW5 +=, increment by, opOpAssign, -, =)
$(ROW5 -=, decrement by, opOpAssign, -, =)
$(ROW5 *=, multiply and assign, opOpAssign, -, =)
$(ROW5 /=, divide and assign, opOpAssign, -, =)
$(ROW5 %=, assign the remainder of, opOpAssign, -, =)
$(ROW5 ^^=, assign the power of, opOpAssign, -, =)
$(ROW5 &=, assign the result of &, opOpAssign, -, =)
$(ROW5 |=, assign the result of |, opOpAssign, -, =)
$(ROW5 ^=, assign the result of ^, opOpAssign, -, =)
$(ROW5 <<=, assign the result of <<, opOpAssign, -, =)
$(ROW5 >>=, assign the result of >>, opOpAssign, -, =)
$(ROW5 >>>=, assign the result of >>>, opOpAssign, -, =)
$(ROW5 ~=, append, opOpAssign, -, =)
)
$(P
$(C opBinaryRight) is for when the object can appear on the right-hand side of the operator. Let's assume a binary operator that we shall call $(I op) appears in the program:
)
---
x $(I op) y
---
$(P
In order to determine what member function to call, the compiler considers the following two options:
)
---
// the definition for x being on the left:
x.opBinary!"op"(y);
// the definition for y being on the right:
y.opBinaryRight!"op"(x);
---
$(P
The compiler picks the option that is a better match than the other.
)
$(P
$(C opBinaryRight) is useful when defining arithmetic types that would normally work on both sides of an operator like e.g. $(C int) does:
)
---
auto x = MyInt(42);
x + 1; // calls opBinary!"+"
1 + x; // calls opBinaryRight!"+"
---
$(P
Another common use of $(C opBinaryRight) is the $(C in) operator. It usually makes more sense to define $(C opBinaryRight) for the object that appears on the right-hand side of $(C in). We will see an example of this below.
)
$(P
The parameter name $(C rhs) that appears in the following definitions is short for $(I right-hand side). It denotes the operand that appears on the right-hand side of the operator:
)
---
x $(I op) y
---
$(P
For the expression above, the $(C rhs) parameter would represent the variable $(C y).
)
$(H5 Element indexing and slicing operators)
$(P
The following operators enable using a type as a collection of elements:
)
$(TABLE full,
$(HEAD3 Description, Function Name, Sample Usage)
$(ROW3 element access, opIndex, collection[i])
$(ROW3 assignment to element, opIndexAssign, collection[i] = 7)
$(ROW3 unary operation on element, opIndexUnary, ++collection[i])
$(ROW3 operation with assignment on element, opIndexOpAssign, collection[i] *= 2)
$(ROW3 number of elements, opDollar, collection[$ - 1])
$(ROW3 slice of all elements, opSlice, collection[])
$(ROW3 slice of some elements, opSlice(size_t, size_t), collection[i..j])
)
$(P
We will cover those operators later below.
)
$(P
The following operator functions are from the earlier versions of D. They are discouraged:
)
$(TABLE full,
$(HEAD3 Description, Function Name, Sample Usage)
$(ROW3 unary operation on all elements, opSliceUnary (discouraged), ++collection[])
$(ROW3 unary operation on some elements, opSliceUnary (discouraged), ++collection[i..j])
$(ROW3 assignment to all elements, opSliceAssign (discouraged), collection[] = 42)
$(ROW3 assignment to some elements, opSliceAssign (discouraged), collection[i..j] = 7)
$(ROW3 operation with assignment on all elements, opSliceOpAssign (discouraged), collection[] *= 2)
$(ROW3 operation with assignment on some elements, opSliceOpAssign (discouraged), collection[i..j] *= 2)
)
$(H6 Other operators)
$(P
The following operators can be overloaded as well:
)
$(TABLE full,
$(HEAD3 Description, Function Name, Sample Usage)
$(ROW3 function call, opCall, object(42))
$(ROW3 type conversion, opCast, to!int(object))
$(ROW3 dispatch for non-existent function, opDispatch, object.nonExistent())
)
$(P
These operators will be explained below under their own sections.
)
$(H5 Defining more than one operator at the same time)
$(P
To keep the code samples short, we have used only the $(C ++), $(C +), and $(C +=) operators above. It is conceivable that when one operator is overloaded for a type, many others would also need to be overloaded. For example, the $(C --) and $(C -=) operators are also defined for the following $(C Duration):
)
---
struct Duration {
int minute;
ref Duration opUnary(string op)()
if (op == "++") {
$(HILITE ++)minute;
return this;
}
ref Duration opUnary(string op)()
if (op == "--") {
$(HILITE --)minute;
return this;
}
ref Duration opOpAssign(string op)(in int amount)
if (op == "+") {
minute $(HILITE +)= amount;
return this;
}
ref Duration opOpAssign(string op)(in int amount)
if (op == "-") {
minute $(HILITE -)= amount;
return this;
}
}
unittest {
auto duration = Duration(10);
++duration;
assert(duration.minute == 11);
--duration;
assert(duration.minute == 10);
duration += 5;
assert(duration.minute == 15);
duration -= 3;
assert(duration.minute == 12);
}
void main() {
}
---
$(P
The operator overloads above have code duplications. The only differences between the similar functions are highlighted. Such code duplications can be reduced and sometimes avoided altogether by $(I string mixins). We will see the $(C mixin) keyword in a later chapter as well. I would like to show briefly how this keyword helps with operator overloading.
)
$(P
$(C mixin) inserts the specified string as source code right where the $(C mixin) statement appears in code. The following struct is the equivalent of the one above:
)
---
struct Duration {
int minute;
ref Duration opUnary(string op)()
if ((op == "++") || (op == "--")) {
$(HILITE mixin) (op ~ "minute;");
return this;
}
ref Duration opOpAssign(string op)(in int amount)
if ((op == "+") || (op == "-")) {
$(HILITE mixin) ("minute " ~ op ~ "= amount;");
return this;
}
}
---
$(P
If the $(C Duration) objects also need to be multiplied and divided by an amount, all that is needed is to add two more conditions to the template constraint:
)
---
struct Duration {
// ...
ref Duration opOpAssign(string op)(in int amount)
if ((op == "+") || (op == "-") ||
$(HILITE (op == "*") || (op == "/"))) {
mixin ("minute " ~ op ~ "= amount;");
return this;
}
}
unittest {
auto duration = Duration(12);
duration $(HILITE *=) 4;
assert(duration.minute == 48);
duration $(HILITE /=) 2;
assert(duration.minute == 24);
}
---
$(P
In fact, the template constraints are optional:
)
---
ref Duration opOpAssign(string op)(in int amount)
/* no constraint */ {
mixin ("minute " ~ op ~ "= amount;");
return this;
}
---
$(H5 $(IX return type, operator) Return types of operators)
$(P
When overloading an operator, it is advisable to observe the return type of the same operator on fundamental types. This would help with making sense of code and reducing confusions.
)
$(P
None of the operators on fundamental types return $(C void). This fact should be obvious for some operators. For example, the result of adding two $(C int) values as $(C a + b) is $(C int):
)
---
int a = 1;
int b = 2;
int c = a + b; // c gets initialized by the return value
// of the + operator
---
$(P
The return values of some other operators may not be so obvious. For example, even operators like $(C ++i) have values:
)
---
int i = 1;
writeln(++i); // prints 2
---
$(P
The $(C ++) operator not only increments $(C i), it also produces the new value of $(C i). Further, the value that is produced by $(C ++) is not just the new value of $(C i), rather $(I the variable $(C i) itself). We can see this fact by printing the address of the result of that expression:
)
---
int i = 1;
writeln("The address of i : ", &i);
writeln("The address of the result of ++i: ", &(++i));
---
$(P
The output contains identical addresses:
)
$(SHELL
The address of i : 7FFF39BFEE78
The address of the result of ++i: 7FFF39BFEE78
)
$(P
I recommend that you observe the following guidelines when overloading operators for your own types:
)
$(UL
$(LI $(B Operators that modify the object)
$(P
With the exception of $(C opAssign), it is recommended that the operators that modify the object return the object itself. This guideline has been observed above with the $(C TimeOfDay.opOpAssign!$(STRING "+")) and $(C Duration.opUnary!$(STRING "++")).
)
$(P
The following two steps achieve returning the object itself:
)
$(OL
$(LI The return type is the type of the struct, marked by the $(C ref) keyword to mean $(I reference).
)
$(LI The function is exited by $$(C return this) to mean $(I return this object).
)
)
$(P
The operators that modify the object are $(C opUnary!$(STRING "++")), $(C opUnary!$(STRING "--")), and all of the $(C opOpAssign) overloads.
)
)
$(LI $(B Logical operators)
$(P
$(C opEquals) that represents both $(C ==) and $(C !=) must return $(C bool). Although the $(C in) operator normally returns $(I the contained object), it can simply return $(C bool) as well.
)
)
$(LI $(B Sort operators)
$(P
$(C opCmp) that represents $(C <), $(C <=), $(C >), and $(C >=) must return $(C int).
)
)
$(LI $(B Operators that make a new object)
$(P
Some operators must make and return a new object:
)
$(UL
$(LI Unary operators $(C -), $(C +), and $(C ~); and the binary operator $(C ~).)
$(LI Arithmetic operators $(C +), $(C -), $(C *), $(C /), $(C %), and $(C ^^).)
$(LI Bitwise operators $(C &), $(C |), $(C ^), $(C <<), $(C >>), and $(C >>>).)
$(LI As has been seen in the previous chapter, $(C opAssign) returns a copy of this object by $(C return this).
$(P $(I $(B Note:) As an optimization, sometimes it makes more sense for $(C opAssign) to return $(C const ref) for large structs. I will not apply this optimization in this book.))
)
)
$(P
As an example of an operator that makes a new object, let's define the $(C opBinary!$(STRING "+")) overload for $(C Duration). This operator should add two $(C Duration) objects to make and return a new one:
)
---
struct Duration {
int minute;
Duration opBinary(string op)(in Duration rhs) const
if (op == "+") {
return Duration(minute + rhs.minute); // new object
}
}
---
$(P
That definition enables adding $(C Duration) objects by the $(C +) operator:
)
---
auto travelDuration = Duration(10);
auto returnDuration = Duration(11);
Duration totalDuration;
// ...
totalDuration = travelDuration $(HILITE +) returnDuration;
---
$(P
The compiler replaces that expression with the following member function call on the $(C travelDuration) object:
)
---
// the equivalent of the expression above:
totalDuration =
travelDuration.opBinary!"+"(returnDuration);
---
)
$(LI $(C opDollar)
$(P
Since it returns the number of elements of the container, the most suitable type for $(C opDollar) is $(C size_t). However, the return type can be other types as well (e.g. $(C int)).
)
)
$(LI $(B Unconstrained operators)
$(P
The return types of some of the operators depend entirely on the design of the user-defined type: The unary $(C *), $(C opCall), $(C opCast), $(C opDispatch), $(C opSlice), and all $(C opIndex) varieties.
)
)
)
$(H5 $(IX opEquals) $(C opEquals()) for equality comparisons)
$(P
This member function defines the behaviors of the $(C ==) and the $(C !=) operators.
)
$(P
The return type of $(C opEquals) is $(C bool).
)
$(P
For structs, the parameter of $(C opEquals) can be defined as $(C in). However, for speed efficiency $(C opEquals) can be defined as a template that takes $(C auto ref const) (also note the empty template parentheses below):
)
---
bool opEquals$(HILITE ())(auto ref const TimeOfDay rhs) const {
// ...
}
---
$(P
As we have seen in $(LINK2 /ders/d.en/lvalue_rvalue.html, the Lvalues and Rvalues chapter), $(C auto ref) allows lvalues to be passed by reference and rvalues by copy. However, since rvalues are not copied, rather moved, the signature above is efficient for both lvalues and rvalues.
)
$(P
To reduce confusion, $(C opEquals) and $(C opCmp) must work consistently. For two objects that $(C opEquals) returns $(C true), $(C opCmp) must return zero.
)
$(P
Once $(C opEquals()) is defined for equality, the compiler uses its opposite for inequality:
)
---
x == y;
// the equivalent of the previous expression:
x.opEquals(y);
x != y;
// the equivalent of the previous expression:
!(x.opEquals(y));
---
$(P
Normally, it is not necessary to define $(C opEquals()) for structs. The compiler generates it for structs automatically. The automatically-generated $(C opEquals) compares all of the members individually.
)
$(P
Sometimes the equality of two objects must be defined differently from this automatic behavior. For example, some of the members may not be significant in this comparison, or the equality may depend on a more complex logic.
)
$(P
Just as an example, let's define $(C opEquals()) in a way that disregards the minute information altogether:
)
---
struct TimeOfDay {
int hour;
int minute;
bool opEquals(in TimeOfDay rhs) const {
return hour == rhs.hour;
}
}
// ...
assert(TimeOfDay(20, 10) $(HILITE ==) TimeOfDay(20, 59));
---
$(P
Since the equality comparison considers the values of only the $(C hour) members, 20:10 and 20:59 end up being equal. (This is just an example; it should be clear that such an equality comparison would cause confusions.)
)
$(H5 $(IX opCmp) $(C opCmp()) for sorting)
$(P
Sort operators determine the sort orders of objects. All of the ordering operators $(C <), $(C <=), $(C >), and $(C >=) are covered by the $(C opCmp()) member function.
)
$(P
For structs, the parameter of $(C opCmp) can be defined as $(C in). However, as with $(C opEquals), it is more efficient to define $(C opCmp) as a template that takes $(C auto ref const):
)
---
int opCmp$(HILITE ())(auto ref const TimeOfDay rhs) const {
// ...
}
---
$(P
To reduce confusion, $(C opEquals) and $(C opCmp) must work consistently. For two objects that $(C opEquals) returns $(C true), $(C opCmp) must return zero.
)
$(P
Let's assume that one of these four operators is used as in the following code:
)
---
if (x $(I op) y) { $(CODE_NOTE $(I op) is one of <, <=, >, or >=)
---
$(P
The compiler converts that expression to the following logical expression and uses the result of the new logical expression:
)
---
if (x.opCmp(y) $(I op) 0) {
---
$(P
Let's consider the $(C <=) operator:
)
---
if (x $(HILITE <=) y) {
---
$(P
The compiler generates the following code behind the scenes:
)
---
if (x.opCmp(y) $(HILITE <=) 0) {
---
$(P
For the user-defined $(C opCmp()) to work correctly, this member function must return a result according to the following rules:
)
$(UL
$(LI $(I A negative value) if the left-hand object is considered to be before the right-hand object)
$(LI $(I A positive value) if the left-hand object is considered to be after the right-hand object)
$(LI $(I Zero) if the objects are considered to have the same sort order)
)
$(P
To be able to support those values, the return type of $(C opCmp()) must be $(C int), not $(C bool).
)
$(P
The following is a way of ordering $(C TimeOfDay) objects by first comparing the values of the $(C hour) members, and then comparing the values of the $(C minute) members (only if the $(C hour) members are equal):
)
---
int opCmp(in TimeOfDay rhs) const {
/* Note: Subtraction is a bug here if the result can
* overflow. (See the following warning in text.) */
return (hour == rhs.hour
? minute - rhs.minute
: hour - rhs.hour);
}
---
$(P
That definition returns the difference between the $(C minute) values when the $(C hour) members are the same, and the difference between the $(C hour) members otherwise. The return value would be a $(I negative value) when the $(I left-hand) object comes before in chronological order, a $(I positive value) if the $(I right-hand) object is before, and $(I zero) when they represent exactly the same time of day.
)
$(P
$(B Warning:) Using subtraction for the implementation of $(C opCmp) is a bug if valid values of a member can cause overflow. For example, the two objects below would be sorted incorrectly as the object with value $(C -2) is calculated to be $(I greater) than the one with value $(C int.max):
)
---
struct S {
int i;
int opCmp(in S rhs) const {
return i - rhs.i; $(CODE_NOTE_WRONG BUG)
}
}
void main() {
assert(S(-2) $(HILITE >) S(int.max)); $(CODE_NOTE_WRONG wrong sort order)
}
---
$(P
On the other hand, subtraction is acceptable for $(C TimeOfDay) because none of the valid values of the members of that $(C struct) can cause overflow in subtraction.
)
$(P
$(IX cmp, std.algorithm) $(IX lexicographical) You can use $(C std.algorithm.cmp) for comparing slices (including all string types and ranges). $(C cmp()) compares slices lexicographically and produces a negative value, zero, or positive value depending on their order. That result can directly be used as the return value of $(C opCmp):
)
---
import std.algorithm;
struct S {
string name;
int opCmp(in S rhs) const {
return $(HILITE cmp)(name, rhs.name);
}
}
---
$(P
Once $(C opCmp()) is defined, this type can be used with sorting algorithms like $(C std.algorithm.sort) as well. As $(C sort()) works on the elements, it is the $(C opCmp()) operator that gets called behind the scenes to determine their order. The following program constructs 10 objects with random values and sorts them with $(C sort()):
)
---
import std.random;
import std.stdio;
import std.string;
import std.algorithm;
struct TimeOfDay {
int hour;
int minute;
int opCmp(in TimeOfDay rhs) const {
return (hour == rhs.hour
? minute - rhs.minute
: hour - rhs.hour);
}
string toString() const {
return format("%02s:%02s", hour, minute);
}
}
void main() {
TimeOfDay[] times;
foreach (i; 0 .. 10) {
times ~= TimeOfDay(uniform(0, 24), uniform(0, 60));
}
sort(times);
writeln(times);
}
---
$(P
As expected, the elements are sorted from the earliest time to the latest time:
)
$(SHELL
[03:40, 04:10, 09:06, 10:03, 10:09, 11:04, 13:42, 16:40, 18:03, 21:08]
)
$(H5 $(IX opCall) $(IX ()) $(C opCall()) to call objects as functions)
$(P
The parentheses around the parameter list when calling functions are operators as well. We have already seen how $(C static opCall()) makes it possible to use the name of the $(I type) as a function. $(C static opCall()) allows creating objects with default values at run time.
)
$(P
Non-static $(C opCall()) on the other hand allows using the $(I objects) of user-defined types as functions:
)
---