$(DDOC $(DDOC_BLANKLINE ) $(DDOC_BLANKLINE ) $(SPEC_S Operator Overloading, $(DDOC_BLANKLINE ) $(HEADERNAV_TOC $(HEADERNAV_SUBITEMS unary, Unary Operator Overloading, $(HEADERNAV_ITEM postincrement_postdecrement_operators, Postincrement $(I e)$(D ++) and Postdecrement $(I e)$(D --) Operators) $(HEADERNAV_ITEM index_unary_operators, Overloading Index Unary Operators) $(HEADERNAV_ITEM slice_unary_operators, Overloading Slice Unary Operators) ) $(HEADERNAV_SUBITEMS cast, Cast Operator Overloading, $(HEADERNAV_ITEM boolean_operators, Boolean Operations) ) $(HEADERNAV_ITEM binary, Binary Operator Overloading) $(HEADERNAV_SUBITEMS eqcmp, Overloading the Comparison Operators, $(HEADERNAV_ITEM equals, Overloading $(D ==) and $(D !=)) $(HEADERNAV_ITEM compare, Overloading $(D <), $(D <)$(D =), $(D >), and $(D >)$(D =)) ) $(HEADERNAV_SUBITEMS function-call, Function Call Operator Overloading, $(HEADERNAV_ITEM static-opcall, Static opCall) ) $(HEADERNAV_SUBITEMS assignment, Assignment Operator Overloading, $(HEADERNAV_ITEM index_assignment_operator, Index Assignment Operator Overloading) $(HEADERNAV_ITEM slice_assignment_operator, Slice Assignment Operator Overloading) ) $(HEADERNAV_SUBITEMS op-assign, Op Assignment Operator Overloading, $(HEADERNAV_ITEM index_op_assignment, Index Op Assignment Operator Overloading) $(HEADERNAV_ITEM slice_op_assignment, Slice Op Assignment Operator Overloading) ) $(HEADERNAV_SUBITEMS array-ops, Array Indexing and Slicing Operators Overloading, $(HEADERNAV_ITEM array, Index Operator Overloading) $(HEADERNAV_ITEM slice, Slice Operator Overloading) $(HEADERNAV_ITEM dollar, Dollar Operator Overloading) $(HEADERNAV_ITEM index-slicing-example, Complete Example) ) $(HEADERNAV_ITEM dispatch, Forwarding) $(HEADERNAV_ITEM old-style, D1 style operator overloading) ) $(DDOC_BLANKLINE ) $(P Operator overloading is accomplished by rewriting operators whose operands are class or struct objects into calls to specially named members. No additional syntax is used. ) $(DDOC_BLANKLINE )

$(LEGACY_LNAME2 Unary, unary, Unary Operator Overloading)

$(DDOC_BLANKLINE ) $(TABLE2 Overloadable Unary Operators, $(I op), $(I rewrite) $(TROW $(ARGS $(D -)$(I e)), $(ARGS $(I e)$(D .opUnary!("-")())) ) $(TROW $(ARGS $(D +)$(I e)), $(ARGS $(I e)$(D .opUnary!("+")())) ) $(TROW $(ARGS $(D ~)$(I e)), $(ARGS $(I e)$(D .opUnary!("~")())) ) $(DDOC_BLANKLINE ) $(TROW $(ARGS $(D *)$(I e)), $(ARGS $(I e)$(D .opUnary!("*")())) ) $(DDOC_BLANKLINE ) $(TROW $(ARGS $(D ++)$(I e)), $(ARGS $(I e)$(D .opUnary!("++")())) ) $(DDOC_BLANKLINE ) $(TROW $(ARGS $(D --)$(I e)), $(ARGS $(I e)$(D .opUnary!("--")())) ) ) $(DDOC_BLANKLINE ) $(P For example, in order to overload the $(D -) (negation) operator for struct S, and no other operator:) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD struct) S { $(D_KEYWORD int) m; $(D_KEYWORD int) opUnary(string s)() $(D_KEYWORD if) (s == $(D_STRING "-")) { $(D_KEYWORD return) -m; } } $(D_KEYWORD void) main() { S s = {2}; $(D_KEYWORD assert)(-s == -2); } ) ) $(DDOC_BLANKLINE ) $(NOTE opUnary above can also be declared using a template parameter specialization:) $(D_CODE $(D_KEYWORD int) opUnary(string s : $(D_STRING "-"))() ) $(DDOC_BLANKLINE )

$(LNAME2 postincrement_postdecrement_operators, Postincrement $(I e)$(D ++) and Postdecrement $(I e)$(D --) Operators)

$(DDOC_BLANKLINE ) $(P These are not directly overloadable, but instead are rewritten in terms of the ++$(I e) and --$(I e) prefix operators: ) $(DDOC_BLANKLINE ) $(TABLE2 Postfix Operator Rewrites, $(I op), $(I rewrite) $(TROW $(ARGS $(I e)$(D --)), $(ARGS $(D $(LPAREN)auto t =) $(I e)$(D , )$(I e).opUnary!"--"$(D , t$(RPAREN )))) $(TROW $(ARGS $(I e)$(D ++)), $(ARGS $(D $(LPAREN)auto t =) $(I e)$(D , )$(I e).opUnary!"++"$(D , t$(RPAREN )))) ) $(DDOC_BLANKLINE )

$(LNAME2 index_unary_operators, Overloading Index Unary Operators)

$(DDOC_BLANKLINE ) $(P Indexing can be $(RELATIVE_LINK2 array, overloaded). A unary operation on an index expression can also be overloaded independently. This works for multidimensional indexing.) $(DDOC_BLANKLINE ) $(TABLE2 Overloadable Index Unary Operators, $(I op), $(I rewrite) $(TROW $(D -)$(I a)[$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)], $(ARGS $(I a).opIndexUnary!("-")$(LPAREN)$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)$(RPAREN ))) $(TROW $(D +)$(I a)[$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)], $(I a)$(D .opIndexUnary!("+")$(LPAREN))$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)$(RPAREN ) ) $(TROW ~$(I a)[$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)], $(I a)$(D .opIndexUnary!("~")$(LPAREN))$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)$(RPAREN ) ) $(TROW *$(I a)[$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)], $(I a)$(D .opIndexUnary!("*")$(LPAREN))$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)$(RPAREN ) ) $(TROW ++$(I a)[$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)], $(I a).opIndexUnary!("++")$(LPAREN)$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)$(RPAREN ) ) $(TROW --$(I a)[$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)], $(I a).opIndexUnary!("--")$(LPAREN)$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)$(RPAREN ) ) ) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD struct) S { $(D_KEYWORD private) $(D_KEYWORD int)[] a; $(D_KEYWORD void) opIndexUnary(string s: $(D_STRING "++"))(size_t i) { ++a[i]; } } S s = {[4]}; ++s[0]; $(D_KEYWORD assert)(s.a[0] == 5); ) ) $(DDOC_BLANKLINE )

$(LNAME2 slice_unary_operators, Overloading Slice Unary Operators)

$(DDOC_BLANKLINE ) $(P Slicing can be $(RELATIVE_LINK2 slice, overloaded). A unary operation on a slice can also be overloaded independently. opIndexUnary is defined either with no function arguments for a full slice, or with two arguments for the start and end indices of the slice.) $(DDOC_BLANKLINE ) $(TABLE2 Overloadable Slice Unary Operators, $(I op), $(I rewrite) $(TROW $(D -)$(I a)$(D [)$(I i)..$(I j)$(D ]), $(I a)$(D .opIndexUnary!("-")$(LPAREN))$(I a)$(D .opSlice$(LPAREN))$(I i), $(I j)$(D $(RPAREN )$(RPAREN )) ) $(DDOC_BLANKLINE ) $(TROW $(D +)$(I a)$(D [)$(I i)..$(I j)$(D ]), $(I a)$(D .opIndexUnary!("+")$(LPAREN))$(I a)$(D .opSlice$(LPAREN))$(I i), $(I j)$(D $(RPAREN )$(RPAREN )) ) $(DDOC_BLANKLINE ) $(TROW $(D ~)$(I a)$(D [)$(I i)..$(I j)$(D ]), $(I a)$(D .opIndexUnary!("~")$(LPAREN))$(I a)$(D .opSlice$(LPAREN))$(I i), $(I j)$(D $(RPAREN )$(RPAREN )) ) $(DDOC_BLANKLINE ) $(TROW $(D *)$(I a)$(D [)$(I i)..$(I j)$(D ]), $(I a)$(D .opIndexUnary!("*")$(LPAREN))$(I a)$(D .opSlice$(LPAREN))$(I i), $(I j)$(D $(RPAREN )$(RPAREN )) ) $(DDOC_BLANKLINE ) $(TROW $(D ++)$(I a)$(D [)$(I i)..$(I j)$(D ]), $(I a)$(D .opIndexUnary!("++")$(LPAREN))$(I a)$(D .opSlice$(LPAREN))$(I i), $(I j)$(D $(RPAREN )$(RPAREN )) ) $(DDOC_BLANKLINE ) $(TROW $(D --)$(I a)$(D [)$(I i)..$(I j)$(D ]), $(I a)$(D .opIndexUnary!("--")$(LPAREN))$(I a)$(D .opSlice$(LPAREN))$(I i), $(I j)$(D $(RPAREN )$(RPAREN )) ) $(DDOC_BLANKLINE ) $(TROW $(D -)$(I a)$(D [ ]), $(I a)$(D .opIndexUnary!("-")()) ) $(DDOC_BLANKLINE ) $(TROW $(D +)$(I a)$(D [ ]), $(I a)$(D .opIndexUnary!("+")()) ) $(DDOC_BLANKLINE ) $(TROW $(D ~)$(I a)$(D [ ]), $(I a)$(D .opIndexUnary!("~")()) ) $(DDOC_BLANKLINE ) $(TROW $(D *)$(I a)$(D [ ]), $(I a)$(D .opIndexUnary!("*")()) ) $(DDOC_BLANKLINE ) $(TROW $(D ++)$(I a)$(D [ ]), $(I a)$(D .opIndexUnary!("++")()) ) $(DDOC_BLANKLINE ) $(TROW $(D --)$(I a)$(D [ ]), $(I a)$(D .opIndexUnary!("--")()) ) ) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD struct) S { $(D_KEYWORD private) $(D_KEYWORD int)[] a; $(D_KEYWORD void) opIndexUnary(string s: $(D_STRING "--"))() { --a[]; } } S s = {[1, 2]}; --s[]; $(D_KEYWORD assert)(s.a == [0, 1]); ) ) $(DDOC_BLANKLINE ) $(NOTE For backward compatibility, if the above rewrites fail to compile and $(D opSliceUnary) is defined, then the rewrites $(D a.opSliceUnary!(op)(i, j)) and $(D a.opSliceUnary!(op)) are tried instead, respectively.) $(DDOC_BLANKLINE )

$(LEGACY_LNAME2 Cast, cast, Cast Operator Overloading)

$(DDOC_BLANKLINE ) $(P To define how one type can be cast to another, define the opCast template method, which is used as follows:) $(TABLE2 Cast Operators, $(I op), $(I rewrite) $(TROW cast$(LPAREN)$(I type)$(RPAREN ) $(I e), $(I e).opCast!$(LPAREN)$(I type)$(RPAREN )() ) ) $(DDOC_BLANKLINE ) $(P Note that opCast is only ever used with an explicit cast expression, except in the case of boolean operations (see next section).) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD struct) S { $(D_KEYWORD void)* mem; $(D_KEYWORD bool) opCast(T)() $(D_KEYWORD if) ($(D_KEYWORD is)(T == $(D_KEYWORD bool))) => mem !$(D_KEYWORD is) $(D_KEYWORD null); } S s = S($(D_KEYWORD new) $(D_KEYWORD int)); $(D_KEYWORD auto) b = $(D_KEYWORD cast)($(D_KEYWORD bool)) s; $(D_KEYWORD assert)(b); $(D_COMMENT //b = s; // error )) ) $(P If the return type of opCast differs from the type parameter of the cast, then the result is implicitly converted to type.) $(DDOC_BLANKLINE )

$(LNAME2 boolean_operators, Boolean Operations)

$(DDOC_BLANKLINE ) $(P Notably absent from the list of overloaded unary operators is the ! logical negation operator. More obscurely absent is a unary operator to convert to a bool result. Instead, for structs these are covered by a rewrite to: ) $(D_CODE opCast!($(D_KEYWORD bool))(e) ) $(DDOC_BLANKLINE ) $(P So,) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD if) (e) => $(D_KEYWORD if) (e.opCast!($(D_KEYWORD bool))) $(D_KEYWORD if) (!e) => $(D_KEYWORD if) (!e.opCast!($(D_KEYWORD bool))) ) $(DDOC_BLANKLINE ) $(P and similarly for other boolean conditional expressions and $(DDSUBLINK spec/expression, logical_expressions, logical operators) used on the struct instance.) $(DDOC_BLANKLINE ) $(P This only happens, however, for instances of structs. Class references are converted to bool by checking to see if the class reference is null or not. ) $(DDOC_BLANKLINE ) $(DDOC_BLANKLINE )

$(LEGACY_LNAME2 Binary, binary, Binary Operator Overloading)

$(DDOC_BLANKLINE ) $(P The following binary operators are overloadable:) $(DDOC_BLANKLINE ) $(TABLE2 Overloadable Binary Operators, $(TROW $(D +), $(D -), $(D *), $(D /), $(CODE_PERCENT ), $(D ^^), $(CODE_AMP )) $(TROW $(CODE_PIPE ), $(D ^), $(D <)$(D <), $(D >)$(D >), $(D >)$(D >)$(D >), $(D ~), $(DDSUBLINK spec/expression, InExpression, $(D in))) ) $(DDOC_BLANKLINE ) $(P The expression:) $(D_CODE a $(METACODE op) b ) $(P is rewritten as one of:) $(D_CODE a.opBinary!($(METACODE $(D_STRING "op")))(b) b.opBinaryRight!($(METACODE $(D_STRING "op")))(a) ) $(DDOC_BLANKLINE ) $(P and the one with the $(SINGLEQUOTE better) match is selected. It is an error for both to equally match. Example: ) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD struct) S { $(D_KEYWORD int)[] data; $(D_COMMENT // this ~ rhs ) $(D_KEYWORD int)[] opBinary(string op : $(D_STRING "~"))($(D_KEYWORD int) rhs) { $(D_KEYWORD return) data ~ rhs; } $(D_COMMENT // lhs ~ this ) $(D_KEYWORD int)[] opBinaryRight(string op : $(D_STRING "~"))($(D_KEYWORD int) lhs) { $(D_KEYWORD return) lhs ~ data; } } $(D_KEYWORD void) main() { $(D_KEYWORD auto) s = S([2,3]); $(D_KEYWORD assert)(s ~ 4 == [2,3,4]); $(D_COMMENT // opBinary ) $(D_KEYWORD assert)(1 ~ s == [1,2,3]); $(D_COMMENT // opBinaryRight )} ) ) $(DDOC_BLANKLINE ) $(P Operator overloading for a number of operators can be done at the same time. For example, if only the + or - operators are supported:) $(DDOC_BLANKLINE ) $(D_CODE T opBinary(string op)(T rhs) { $(D_KEYWORD static) $(D_KEYWORD if) (op == $(D_STRING "+")) $(D_KEYWORD return) data + rhs.data; $(D_KEYWORD else) $(D_KEYWORD static) $(D_KEYWORD if) (op == $(D_STRING "-")) $(D_KEYWORD return) data - rhs.data; $(D_KEYWORD else) $(D_KEYWORD static) $(D_KEYWORD assert)(0, $(D_STRING "Operator ")~op~$(D_STRING " not implemented")); } ) $(DDOC_BLANKLINE ) $(P To do them all en masse:) $(DDOC_BLANKLINE ) $(D_CODE T opBinary(string op)(T rhs) { $(D_KEYWORD return) $(D_KEYWORD mixin)($(D_STRING "data ")~op~$(D_STRING " rhs.data")); } ) $(DDOC_BLANKLINE ) $(P Note that opIn and opIn_r have been deprecated in favor of opBinary!"in" and opBinaryRight!"in" respectively.) $(DDOC_BLANKLINE )

$(LNAME2 eqcmp, Overloading the Comparison Operators)

$(DDOC_BLANKLINE ) $(P D allows overloading of the comparison operators $(D ==), $(D !=), $(D <), $(D <=), $(D >=), $(D >) via two functions, $(D opEquals) and $(D opCmp).) $(DDOC_BLANKLINE ) $(P The equality and inequality operators are treated separately from comparison operators because while practically all user-defined types can be compared for equality, only a subset of types have a meaningful ordering. For example, while it makes sense to determine if two RGB color vectors are equal, it is not meaningful to say that one color is greater than another, because colors do not have an ordering. Thus, one would define $(D opEquals) for a $(D Color) type, but not $(D opCmp).) $(DDOC_BLANKLINE ) $(P Furthermore, even with orderable types, the order relation may not be linear. For example, one may define an ordering on sets via the subset relation, such that $(D x < y) is true if $(D x) is a (strict) subset of $(D y). If $(D x) and $(D y) are disjoint sets, then neither $(D x < y) nor $(D y < x) holds, but that does not imply that $(D x == y). Thus, it is insufficient to determine equality purely based on $(D opCmp) alone. For this reason, $(D opCmp) is only used for the inequality operators $(D <), $(D <=), $(D >=), and $(D >). The equality operators $(D ==) and $(D !=) always employ $(D opEquals) instead.) $(DDOC_BLANKLINE ) $(P Therefore, it is the programmer's responsibility to ensure that opCmp and $(D opEquals) are consistent with each other. If opEquals is not specified, the compiler provides a default version that does member-wise comparison. If this suffices, one may define only $(D opCmp) to customize the behaviour of the inequality operators. But if not, then a custom version of $(D opEquals) should be defined as well, in order to preserve consistent semantics between the two kinds of comparison operators.) $(DDOC_BLANKLINE ) $(P Finally, if the user-defined type is to be used as a key in the built-in associative arrays, then the programmer must ensure that the semantics of $(D opEquals) and $(D toHash) are consistent. If not, the associative array may not work in the expected manner.) $(DDOC_BLANKLINE )

$(LNAME2 equals, Overloading $(D ==) and $(D !=))

$(DDOC_BLANKLINE ) $(P Expressions of the form $(CODE a != b) are rewritten as $(CODE !(a == b)).) $(DDOC_BLANKLINE ) $(P Given $(CODE a == b) :) $(DDOC_BLANKLINE ) $(OL $(LI If a and b are both class objects, then the expression is rewritten as: $(D_CODE .object.opEquals(a, b) ) $(P and that function is implemented as:) $(D_CODE $(D_KEYWORD bool) opEquals(Object a, Object b) { $(D_KEYWORD if) (a $(D_KEYWORD is) b) $(D_KEYWORD return) $(D_KEYWORD true); $(D_KEYWORD if) (a $(D_KEYWORD is) $(D_KEYWORD null) || b $(D_KEYWORD is) $(D_KEYWORD null)) $(D_KEYWORD return) $(D_KEYWORD false); $(D_KEYWORD if) ($(D_KEYWORD typeid)(a) == $(D_KEYWORD typeid)(b)) $(D_KEYWORD return) a.opEquals(b); $(D_KEYWORD return) a.opEquals(b) && b.opEquals(a); } ) ) $(LI Otherwise the expressions a.opEquals(b) and b.opEquals(a) are tried. If both resolve to the same opEquals function, then the expression is rewritten to be a.opEquals(b). ) $(LI If one is a better match than the other, or one compiles and the other does not, the first is selected.) $(LI Otherwise, an error results.) ) $(DDOC_BLANKLINE ) $(P If overriding $(D Object.opEquals()) for classes, the class member function signature should look like:) $(D_CODE $(D_KEYWORD class) C { $(D_KEYWORD override) $(D_KEYWORD bool) opEquals(Object o) { ... } } ) $(DDOC_BLANKLINE ) $(P If structs declare an $(D opEquals) member function for the identity comparison, it could have several forms, such as:) $(D_CODE $(D_KEYWORD struct) S { $(D_COMMENT // lhs should be mutable object ) $(D_KEYWORD bool) opEquals($(D_KEYWORD const) S s) { ... } $(D_COMMENT // for r-values $(LPAREN)e.g. temporaries$(RPAREN ) ) $(D_KEYWORD bool) opEquals($(D_KEYWORD ref) $(D_KEYWORD const) S s) { ... } $(D_COMMENT // for l-values $(LPAREN)e.g. variables$(RPAREN ) ) $(D_COMMENT // both hand side can be const object ) $(D_KEYWORD bool) opEquals($(D_KEYWORD const) S s) $(D_KEYWORD const) { ... } $(D_COMMENT // for r-values $(LPAREN)e.g. temporaries$(RPAREN ) )} ) $(DDOC_BLANKLINE ) $(P Alternatively, declare a single templated $(D opEquals) function with an $(DDSUBLINK spec/template, auto-ref-parameters, auto ref) parameter:) $(D_CODE $(D_KEYWORD struct) S { $(D_COMMENT // for l-values and r-values, ) $(D_COMMENT // with converting both hand side implicitly to const ) $(D_KEYWORD bool) opEquals()($(D_KEYWORD auto) $(D_KEYWORD ref) $(D_KEYWORD const) S s) $(D_KEYWORD const) { ... } } ) $(DDOC_BLANKLINE ) $(DDOC_BLANKLINE )

$(LNAME2 compare, Overloading $(D <), $(D <)$(D =), $(D >), and $(D >)$(D =))

$(DDOC_BLANKLINE ) $(P Comparison operations are rewritten as follows:) $(DDOC_BLANKLINE ) $(TABLE2 Rewriting of comparison operations, comparison, rewrite 1, rewrite 2 $(TROW $(D a) $(D <) $(D b), $(D a.opCmp(b)) $(D <) $(D 0), $(ARGS $(D b.opCmp(a)) $(D >) $(D 0))) $(TROW $(D a) $(D <)$(D = b), $(ARGS $(D a.opCmp(b)) $(D <)$(D = 0)), $(ARGS $(D b.opCmp(a)) $(D >)$(D = 0))) $(TROW $(D a) $(D >) $(D b), $(ARGS $(D a.opCmp(b)) $(D >) $(D 0)), $(ARGS $(D b.opCmp(a)) $(D <) $(D 0))) $(TROW $(D a) $(D >)$(D = b), $(ARGS $(D a.opCmp(b)) $(D >)$(D = 0)), $(ARGS $(D b.opCmp(a)) $(D <)$(D = 0))) ) $(DDOC_BLANKLINE ) $(P Both rewrites are tried. If only one compiles, that one is taken. If they both resolve to the same function, the first rewrite is done. If they resolve to different functions, the best matching one is used. If they both match the same, but are different functions, an ambiguity error results.) $(D_CODE $(D_KEYWORD struct) B { $(D_KEYWORD int) opCmp($(D_KEYWORD int)) { $(D_KEYWORD return) -1; } $(D_KEYWORD int) opCmp($(D_KEYWORD ref) $(D_KEYWORD const) S) { $(D_KEYWORD return) -1; } $(D_KEYWORD int) opCmp($(D_KEYWORD ref) $(D_KEYWORD const) C) { $(D_KEYWORD return) -1; } } $(D_KEYWORD struct) S { $(D_KEYWORD int) opCmp($(D_KEYWORD ref) $(D_KEYWORD const) S) { $(D_KEYWORD return) 1; } $(D_KEYWORD int) opCmp($(D_KEYWORD ref) B) { $(D_KEYWORD return) 0; } } $(D_KEYWORD struct) C { $(D_KEYWORD int) opCmp($(D_KEYWORD ref) $(D_KEYWORD const) B) { $(D_KEYWORD return) 0; } } $(D_KEYWORD void) main() { S s; $(D_KEYWORD const) S cs; B b; C c; $(D_KEYWORD assert)(s > s); $(D_COMMENT // s.opCmp$(LPAREN)s$(RPAREN ) > 0 ) $(D_KEYWORD assert)(!(s < b)); $(D_COMMENT // s.opCmp$(LPAREN)b$(RPAREN ) > 0 - S.opCmp$(LPAREN)ref B$(RPAREN ) is exact match ) $(D_KEYWORD assert)(!(b < s)); $(D_COMMENT // s.opCmp$(LPAREN)b$(RPAREN ) < 0 - S.opCmp$(LPAREN)ref B$(RPAREN ) is exact match ) $(D_KEYWORD assert)(b < cs); $(D_COMMENT // b.opCmp$(LPAREN)s$(RPAREN ) < 0 - B.opCmp$(LPAREN)ref const S$(RPAREN ) is exact match ) $(D_KEYWORD static) $(D_KEYWORD assert)(!$(D_KEYWORD __traits)(compiles, b < c)); $(D_COMMENT // both C.opCmp and B.opcmp match exactly )} ) $(P If overriding $(D Object.opCmp()) for classes, the class member function signature should look like:) $(D_CODE $(D_KEYWORD class) C { $(D_KEYWORD override) $(D_KEYWORD int) opCmp(Object o) { ... } } ) $(DDOC_BLANKLINE ) $(P If structs declare an $(D opCmp) member function, it should have the following form:) $(D_CODE $(D_KEYWORD struct) S { $(D_KEYWORD int) opCmp($(D_KEYWORD ref) $(D_KEYWORD const) S s) $(D_KEYWORD const) { ... } } ) $(P Note that $(D opCmp) is only used for the inequality operators; expressions like $(D a == b) always uses $(D opEquals). If $(D opCmp) is defined but $(D opEquals) isn't, the compiler will supply a default version of $(D opEquals) that performs member-wise comparison. If this member-wise comparison is not consistent with the user-defined opCmp, then it is up to the programmer to supply an appropriate version of $(D opEquals). Otherwise, inequalities like $(D a <= b) will behave inconsistently with equalities like $(D a == b).) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD struct) S { $(D_KEYWORD int) i, j; $(D_KEYWORD int) opCmp($(D_KEYWORD ref) $(D_KEYWORD const) S s) $(D_KEYWORD const) { $(D_KEYWORD return) (i > s.i) - (i < s.i); } $(D_COMMENT // ignore j )} S a = {2, 3}; S b = {2, 1}; S c = {3, 0}; $(D_KEYWORD assert)(a < c); $(D_KEYWORD assert)(a <= b); $(D_KEYWORD assert)(!(a < b)); $(D_COMMENT // opCmp ignores j )$(D_KEYWORD assert)(a != b); $(D_COMMENT // generated opEquals tests both i and j members )) ) $(DDOC_BLANKLINE ) $(BEST_PRACTICE Using (i > s.i) - (i < s.i) instead of i - s.i to compare integers avoids overflow.) $(DDOC_BLANKLINE ) $(DDOC_BLANKLINE )

$(LEGACY_LNAME2 FunctionCall, function-call, Function Call Operator Overloading)

$(DDOC_BLANKLINE ) $(P The function call operator, $(D ()), can be overloaded by declaring a function named $(CODE opCall): ) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD struct) F { $(D_KEYWORD int) $(CODE_HIGHLIGHT opCall)(); $(D_KEYWORD int) $(CODE_HIGHLIGHT opCall)($(D_KEYWORD int) x, $(D_KEYWORD int) y, $(D_KEYWORD int) z); } $(D_KEYWORD void) test() { F f; $(D_KEYWORD int) i; i = f(); $(D_COMMENT // same as i = f.opCall$(LPAREN)$(RPAREN ); ) i = f(3,4,5); $(D_COMMENT // same as i = f.opCall$(LPAREN)3,4,5$(RPAREN ); )} ) $(DDOC_BLANKLINE ) $(P In this way a struct or class object can behave as if it were a function. ) $(DDOC_BLANKLINE ) $(P Note that merely declaring $(D opCall) automatically disables $(DDSUBLINK spec/struct, StructLiteral, struct literal) syntax. To avoid the limitation, declare a $(DDSUBLINK spec/struct, Struct-Constructor, constructor) so that it takes priority over $(D opCall) in $(D Type(...)) syntax. ) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD struct) Multiplier { $(D_KEYWORD int) factor; $(D_KEYWORD this)($(D_KEYWORD int) num) { factor = num; } $(D_KEYWORD int) opCall($(D_KEYWORD int) value) { $(D_KEYWORD return) value * factor; } } $(D_KEYWORD void) main() { Multiplier m = Multiplier(10); $(D_COMMENT // invoke constructor ) $(D_KEYWORD assert)(m.factor == 10); $(D_KEYWORD int) result = m(5); $(D_COMMENT // invoke opCall ) $(D_KEYWORD assert)(result == 50); } ) ) $(DDOC_BLANKLINE )

$(LNAME2 static-opcall, Static opCall)

$(DDOC_BLANKLINE ) $(P $(CODE static opCall) also works as expected for a function call operator with type names. ) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD struct) Double { $(CODE_HIGHLIGHT $(D_KEYWORD static)) $(D_KEYWORD int) $(CODE_HIGHLIGHT opCall)($(D_KEYWORD int) x) { $(D_KEYWORD return) x * 2; } } $(D_KEYWORD void) test() { $(D_KEYWORD int) i = Double(2); $(D_KEYWORD assert)(i == 4); } ) $(DDOC_BLANKLINE ) $(P Mixing struct constructors and $(D static opCall) is not allowed.) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD struct) S { $(D_KEYWORD this)($(D_KEYWORD int) i) {} $(D_KEYWORD static) S opCall() $(D_COMMENT // disallowed due to constructor ) { $(D_KEYWORD return) S.init; } } ) $(DDOC_BLANKLINE ) $(P Note: $(D static opCall) can be used to simulate struct constructors with no arguments, but this is not recommended practice. Instead, the preferred solution is to use a factory function to create struct instances. ) $(DDOC_BLANKLINE )

$(LEGACY_LNAME2 Assignment, assignment, Assignment Operator Overloading)

$(DDOC_BLANKLINE ) $(P The assignment operator $(CODE =) can be overloaded if the left hand side is a struct aggregate, and $(CODE opAssign) is a member function of that aggregate.) $(DDOC_BLANKLINE ) For struct types, operator overloading for the identity assignment is allowed. $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD struct) S { $(D_COMMENT // identity assignment, allowed. ) $(D_KEYWORD void) $(CODE_HIGHLIGHT opAssign)(S rhs); $(D_COMMENT // not identity assignment, also allowed. ) $(D_KEYWORD void) $(CODE_HIGHLIGHT opAssign)($(D_KEYWORD int)); } S s; s = S(); $(D_COMMENT // Rewritten to s.opAssign$(LPAREN)S$(LPAREN)$(RPAREN )$(RPAREN ); )s = 1; $(D_COMMENT // Rewritten to s.opAssign$(LPAREN)1$(RPAREN ); )) $(DDOC_BLANKLINE ) However for class types, identity assignment is not allowed. All class types have reference semantics, so identity assignment by default rebinds the left-hand-side to the argument at the right, and this is not overridable. $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD class) C { $(D_COMMENT // If X is the same type as C or the type which is ) $(D_COMMENT // implicitly convertible to C, then opAssign would ) $(D_COMMENT // accept identity assignment, which is disallowed. ) $(D_COMMENT // C opAssign$(LPAREN)...$(RPAREN ); ) $(D_COMMENT // C opAssign$(LPAREN)X$(RPAREN ); ) $(D_COMMENT // C opAssign$(LPAREN)X, ...$(RPAREN ); ) $(D_COMMENT // C opAssign$(LPAREN)X ...$(RPAREN ); ) $(D_COMMENT // C opAssign$(LPAREN)X, U = defaultValue, etc.$(RPAREN ); ) $(D_COMMENT // not an identity assignment - allowed ) $(D_KEYWORD void) $(CODE_HIGHLIGHT opAssign)($(D_KEYWORD int)); } C c = $(D_KEYWORD new) C(); c = $(D_KEYWORD new) C(); $(D_COMMENT // Rebinding referencee )c = 1; $(D_COMMENT // Rewritten to c.opAssign$(LPAREN)1$(RPAREN ); )) $(DDOC_BLANKLINE )

$(LNAME2 index_assignment_operator, Index Assignment Operator Overloading)

$(DDOC_BLANKLINE ) $(P If the left hand side of an assignment is an index operation on a struct or class instance, it can be overloaded by providing an $(D opIndexAssign) member function. Expressions of the form a[$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)] = c are rewritten as a.opIndexAssign$(LPAREN)c, $(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)$(RPAREN ). ) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD struct) A { $(D_KEYWORD int) $(CODE_HIGHLIGHT opIndexAssign)($(D_KEYWORD int) value, size_t i1, size_t i2); } $(D_KEYWORD void) test() { A a; a$(CODE_HIGHLIGHT [)i,3$(CODE_HIGHLIGHT ]) = 7; $(D_COMMENT // same as a.opIndexAssign$(LPAREN)7,i,3$(RPAREN ); )} ) $(DDOC_BLANKLINE )

$(LNAME2 slice_assignment_operator, Slice Assignment Operator Overloading)

$(DDOC_BLANKLINE ) $(P If the left hand side of an assignment is a slice operation on a struct or class instance, it can be overloaded by implementing an opIndexAssign member function that takes the return value of the opSlice function as parameter(s). Expressions of the form $(CODE a[)$(I i)..$(I j)$(D ] = c) are rewritten as $(CODE a.opIndexAssign$(LPAREN)c,) $(D a.opSlice!0$(LPAREN))$(I i), $(I j)$(D $(RPAREN )$(RPAREN )), and $(CODE a[] = c) as $(CODE a.opIndexAssign(c)). ) $(DDOC_BLANKLINE ) $(P See $(RELATIVE_LINK2 array-ops, Array Indexing and Slicing Operators Overloading) for more details. ) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD struct) A { $(D_KEYWORD int) opIndexAssign($(D_KEYWORD int) v); $(D_COMMENT // overloads a[] = v ) $(D_KEYWORD int) opIndexAssign($(D_KEYWORD int) v, size_t[2] slice); $(D_COMMENT // overloads a[i .. j] = v ) size_t[2] opSlice(size_t dim)(size_t i, size_t j); $(D_COMMENT // overloads i .. j )} $(D_KEYWORD void) test() { A a; $(D_KEYWORD int) v; a[] = v; $(D_COMMENT // same as a.opIndexAssign$(LPAREN)v$(RPAREN ); ) a[3..4] = v; $(D_COMMENT // same as a.opIndexAssign$(LPAREN)v, a.opSlice!0$(LPAREN)3,4$(RPAREN )$(RPAREN ); )} ) $(DDOC_BLANKLINE ) $(P For backward compatibility, if rewriting $(D a[)$(I i)..$(I j)$(D ]) as $(D a.opIndexAssign$(LPAREN)a.opSlice!0$(LPAREN))$(I i), $(I j)$(D $(RPAREN )$(RPAREN )) fails to compile, the legacy rewrite $(D opSliceAssign$(LPAREN)c,) $(I i), $(I j)$(D $(RPAREN )) is used instead. ) $(DDOC_BLANKLINE )

$(LEGACY_LNAME2 OpAssign, op-assign, Op Assignment Operator Overloading)

$(DDOC_BLANKLINE ) $(P The following op assignment operators are overloadable:) $(DDOC_BLANKLINE ) $(TABLE2 Overloadable Op Assignment Operators, $(TROW $(D +=), $(D -=), $(D *=), $(D /=), $(CODE_PERCENT )$(D =), $(D ^^=), $(CODE_AMP )$(D =)) $(TROW $(CODE_PIPE )$(D =), $(D ^=), $(D <)$(D <)$(D =), $(D >)$(D >)$(D =), $(D >)$(D >)$(D >)$(D =), $(D ~=), $(NBSP )) ) $(DDOC_BLANKLINE ) $(P The expression:) $(D_CODE a $(METACODE op)= b ) $(DDOC_BLANKLINE ) $(P is rewritten as:) $(DDOC_BLANKLINE ) $(D_CODE a.opOpAssign!($(METACODE $(D_STRING "op")))(b) ) $(DDOC_BLANKLINE ) Example: $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD struct) S { $(D_KEYWORD int) i; $(D_KEYWORD void) opOpAssign(string op: $(D_STRING "+"))($(D_KEYWORD int) rhs) { i += rhs; } } S s = {2}; s += 3; $(D_KEYWORD assert)(s.i == 5); ) ) $(DDOC_BLANKLINE )

$(LNAME2 index_op_assignment, Index Op Assignment Operator Overloading)

$(DDOC_BLANKLINE ) $(P If the left hand side of an $(I op)= is an index expression on a struct or class instance and $(D opIndexOpAssign) is a member:) $(DDOC_BLANKLINE ) $(D_CODE a[$(METACODE $(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n))] $(METACODE op)= c ) $(DDOC_BLANKLINE ) $(P it is rewritten as:) $(DDOC_BLANKLINE ) $(D_CODE a.opIndexOpAssign!($(METACODE $(D_STRING "op")))(c, $(METACODE $(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n))) ) $(DDOC_BLANKLINE )

$(LNAME2 slice_op_assignment, Slice Op Assignment Operator Overloading)

$(DDOC_BLANKLINE ) $(P If the left hand side of an $(I op)= is a slice expression on a struct or class instance and $(D opIndexOpAssign) is a member:) $(DDOC_BLANKLINE ) $(D_CODE a[$(METACODE $(I i)..$(I j))] $(METACODE op)= c ) $(DDOC_BLANKLINE ) $(P it is rewritten as:) $(DDOC_BLANKLINE ) $(D_CODE a.opIndexOpAssign!($(METACODE $(D_STRING "op")))(c, a.opSlice($(METACODE $(I i), $(I j)))) ) $(DDOC_BLANKLINE ) $(P and) $(DDOC_BLANKLINE ) $(D_CODE a[] $(METACODE op)= c ) $(DDOC_BLANKLINE ) $(P it is rewritten as:) $(DDOC_BLANKLINE ) $(D_CODE a.opIndexOpAssign!($(METACODE $(D_STRING "op")))(c) ) $(DDOC_BLANKLINE ) $(P For backward compatibility, if the above rewrites fail and opSliceOpAssign is defined, then the rewrites $(D a.opSliceOpAssign(c, i, j)) and $(D a.opSliceOpAssign(c)) are tried, respectively. ) $(DDOC_BLANKLINE ) $(DDOC_BLANKLINE )

$(LEGACY_LNAME2 ArrayOps, array-ops, Array Indexing and Slicing Operators Overloading)

$(DDOC_BLANKLINE ) $(P The array indexing and slicing operators are overloaded by implementing the $(D opIndex), $(D opSlice), and $(D opDollar) methods. These may be combined to implement multidimensional arrays. ) $(DDOC_BLANKLINE )

$(LEGACY_LNAME2 Array, array, Index Operator Overloading)

$(DDOC_BLANKLINE ) $(P Expressions of the form $(D arr[)$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)$(D ]) are translated into $(D arr.opIndex$(LPAREN))$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)$(D $(RPAREN )). For example: ) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD struct) A { $(D_KEYWORD int) $(CODE_HIGHLIGHT opIndex)(size_t i1, size_t i2, size_t i3); } $(D_KEYWORD void) test() { A a; $(D_KEYWORD int) i; i = a[5,6,7]; $(D_COMMENT // same as i = a.opIndex$(LPAREN)5,6,7$(RPAREN ); )} ) $(DDOC_BLANKLINE ) $(P In this way a struct or class object can behave as if it were an array. ) $(DDOC_BLANKLINE ) $(P If an index expression can be rewritten using $(D opIndexAssign) or $(D opIndexOpAssign), those are preferred over $(D opIndex). ) $(DDOC_BLANKLINE )

$(LEGACY_LNAME2 Slice, slice, Slice Operator Overloading)

$(DDOC_BLANKLINE ) $(P Overloading the slicing operator means overloading expressions like $(D a[]) or $(D a[)$(I i)..$(I j)$(D ]), where the expressions inside the square brackets contain slice expressions of the form $(I i)..$(I j). ) $(DDOC_BLANKLINE ) $(P To overload $(D a[]), simply define $(D opIndex) with no parameters: ) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD struct) S { $(D_KEYWORD int)[] impl; $(D_KEYWORD int)[] opIndex() { $(D_KEYWORD return) impl[]; } } $(D_KEYWORD void) main() { $(D_KEYWORD auto) s = S([1,2,3]); $(D_KEYWORD int)[] t = s[]; $(D_COMMENT // calls s.opIndex$(LPAREN)$(RPAREN ) ) $(D_KEYWORD assert)(t == [1,2,3]); } ) ) $(DDOC_BLANKLINE ) $(P To overload array slicing of the form $(D a[)$(I i)..$(I j)$(D ]), two steps are needed. First, the expressions of the form $(I i)..$(I j) are translated via $(D opSlice!0) into objects that encapsulate the endpoints $(I i) and $(I j). Then these objects are passed to $(D opIndex) to perform the actual slicing.) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD struct) S { $(D_KEYWORD int)[] impl; $(D_KEYWORD int)[] opSlice(size_t dim: 0)(size_t i, size_t j) { $(D_KEYWORD return) impl[i..j]; } $(D_KEYWORD int)[] opIndex()($(D_KEYWORD int)[] slice) { $(D_KEYWORD return) slice; } } $(D_KEYWORD void) main() { $(D_KEYWORD auto) s = S([1, 2, 3]); $(D_KEYWORD int)[] t = s[0..2]; $(D_COMMENT // calls s.opIndex$(LPAREN)s.opSlice$(LPAREN)0, 2$(RPAREN )$(RPAREN ) ) $(D_KEYWORD assert)(t == [1, 2]); } ) ) $(DDOC_BLANKLINE ) $(P This design was chosen in order to support mixed indexing and slicing in multidimensional arrays; for example, in translating expressions like $(D arr[1, 2..3, 4]). More precisely, an expression of the form $(D arr[)$(I b)$(SUBSCRIPT 1), $(I b)$(SUBSCRIPT 2), ... $(I b)$(SUBSCRIPT n)$(D ]) is translated into $(D arr.opIndex$(LPAREN))$(I c)$(SUBSCRIPT 1), $(I c)$(SUBSCRIPT 2), ... $(I c)$(SUBSCRIPT n)$(D $(RPAREN )). Each argument $(I b)$(SUBSCRIPT i) can be either a single expression, in which case it is passed directly as the corresponding argument $(I c)$(SUBSCRIPT i) to $(D opIndex); or it can be a slice expression of the form $(I x)$(SUBSCRIPT i)$(D ..)$(I y)$(SUBSCRIPT i), in which case the corresponding argument $(I c)$(SUBSCRIPT i) to $(D opIndex) is $(D arr.opSlice!i$(LPAREN))$(I x)$(SUBSCRIPT i)$(D , )$(I y)$(SUBSCRIPT i)$(D $(RPAREN )). Namely: ) $(DDOC_BLANKLINE ) $(TABLE2 , $(I op), $(I rewrite) $(TROW $(D arr[1, 2, 3]), $(D arr.opIndex(1, 2, 3)) ) $(TROW $(D arr[1..2, 3..4, 5..6]), $(D arr.opIndex(arr.opSlice!0(1,2), arr.opSlice!1(3,4), arr.opSlice!2(5,6))) ) $(TROW $(D arr[1, 2..3, 4]), $(D arr.opIndex(1, arr.opSlice!1(2,3), 4)) ) ) $(DDOC_BLANKLINE ) $(P Similar translations are done for assignment operators involving slicing, for example: ) $(DDOC_BLANKLINE ) $(TABLE2 , $(I op), $(I rewrite) $(TROW $(D arr[1, 2..3, 4] = c), $(D arr.opIndexAssign(c, 1, arr.opSlice!1(2, 3), 4)) ) $(TROW $(D arr[2, 3..4] += c), $(D arr.opIndexOpAssign!"+"(c, 2, arr.opSlice!1(2, 3))) ) ) $(DDOC_BLANKLINE ) $(P The intention is that $(D opSlice!i) should return a user-defined object that represents an interval of indices along the $(D i)'th dimension of the array. This object is then passed to $(D opIndex) to perform the actual slicing operation. If only one-dimensional slicing is desired, $(D opSlice) may be declared without the compile-time parameter $(D i). ) $(DDOC_BLANKLINE ) $(P Note that in all cases, $(D arr) is only evaluated once. Thus, an expression like $(D getArray()[1, 2..3, $-1]=c) has the effect of:) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD auto) __tmp = getArray(); __tmp.opIndexAssign(c, 1, __tmp.opSlice!1(2,3), __tmp.opDollar!2 - 1); ) $(P where the initial function call to $(D getArray) is only executed once. ) $(DDOC_BLANKLINE ) $(NOTE For backward compatibility, $(D a[]) and $(D a[)$(I i)..$(I j)$(D ]) can also be overloaded by implementing $(D opSlice()) with no arguments and $(D opSlice$(LPAREN))$(I i), $(I j)$(D $(RPAREN )) with two arguments, respectively. This only applies for one-dimensional slicing, and dates from when D did not have full support for multidimensional arrays. This usage of $(D opSlice) is discouraged. ) $(DDOC_BLANKLINE )

$(LEGACY_LNAME2 Dollar, dollar, Dollar Operator Overloading)

$(DDOC_BLANKLINE ) $(P Within the arguments to array index and slicing operators, $(D $) gets translated to $(D opDollar!i), where $(D i) is the position of the expression $(D $) appears in. For example: ) $(DDOC_BLANKLINE ) $(TABLE2 , $(I op), $(I rewrite) $(TROW $(D arr[$-1, $-2, 3]), $(D arr.opIndex(arr.opDollar!0 - 1, arr.opDollar!1 - 2, 3)) ) $(TROW $(D arr[1, 2, 3..$]), $(D arr.opIndex(1, 2, arr.opSlice!2(3, arr.opDollar!2))) ) ) $(DDOC_BLANKLINE ) $(P The intention is that $(D opDollar!i) should return the length of the array along its $(D i)'th dimension, or a user-defined object representing the end of the array along that dimension, that is understood by $(D opSlice) and $(D opIndex). ) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD struct) Rectangle { $(D_KEYWORD int) width, height; $(D_KEYWORD int)[][] impl; $(D_KEYWORD this)($(D_KEYWORD int) w, $(D_KEYWORD int) h) { width = w; height = h; impl = $(D_KEYWORD new) $(D_KEYWORD int)[w][h]; } $(D_KEYWORD int) opIndex(size_t i1, size_t i2) { $(D_KEYWORD return) impl[i1][i2]; } $(D_KEYWORD int) opDollar(size_t pos)() { $(D_KEYWORD static) $(D_KEYWORD if) (pos==0) $(D_KEYWORD return) width; $(D_KEYWORD else) $(D_KEYWORD return) height; } } $(D_KEYWORD void) test() { $(D_KEYWORD auto) r = Rectangle(10,20); $(D_KEYWORD int) i = r[$-1, 0]; $(D_COMMENT // same as: r.opIndex$(LPAREN)r.opDollar!0, 0$(RPAREN ), ) $(D_COMMENT // which is r.opIndex$(LPAREN)r.width-1, 0$(RPAREN ) ) $(D_KEYWORD int) j = r[0, $-1]; $(D_COMMENT // same as: r.opIndex$(LPAREN)0, r.opDollar!1$(RPAREN ) ) $(D_COMMENT // which is r.opIndex$(LPAREN)0, r.height-1$(RPAREN ) )} ) $(DDOC_BLANKLINE ) $(P As the above example shows, a different compile-time argument is passed to $(D opDollar) depending on which argument it appears in. A $(D $) appearing in the first argument gets translated to opDollar!0, a $(D $) appearing in the second argument gets translated to $(D opDollar!1), and so on. Thus, the appropriate value for $(D $) can be returned to implement multidimensional arrays. ) $(DDOC_BLANKLINE ) $(P Note that $(D opDollar!i) is only evaluated once for each $(D i) where $(D $) occurs in the corresponding position in the indexing operation. Thus, an expression like $(D arr[$-sqrt($), 0, $-1]) has the effect of: ) $(D_CODE $(D_KEYWORD auto) __tmp1 = arr.opDollar!0; $(D_KEYWORD auto) __tmp2 = arr.opDollar!2; arr.opIndex(__tmp1 - sqrt(__tmp1), 0, __tmp2 - 1); ) $(DDOC_BLANKLINE ) $(P If $(D opIndex) is declared with only one argument, the compile-time argument to $(D opDollar) may be omitted. In this case, it is illegal to use $(D $) inside an array indexing expression with more than one argument. ) $(DDOC_BLANKLINE )

$(LNAME2 index-slicing-example, Complete Example)

$(DDOC_BLANKLINE ) $(P The code example below shows a simple implementation of a 2-dimensional array with overloaded indexing and slicing operators. The explanations of the various constructs employed are given in the sections following.) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD struct) Array2D(E) { E[] impl; $(D_KEYWORD int) stride; $(D_KEYWORD int) width, height; $(D_KEYWORD this)($(D_KEYWORD int) width, $(D_KEYWORD int) height, E[] initialData = []) { impl = initialData; $(D_KEYWORD this).stride = $(D_KEYWORD this).width = width; $(D_KEYWORD this).height = height; impl.length = width * height; } $(D_COMMENT // Index a single element, e.g., arr[0, 1] ) $(D_KEYWORD ref) E opIndex($(D_KEYWORD int) i, $(D_KEYWORD int) j) { $(D_KEYWORD return) impl[i + stride*j]; } $(D_COMMENT // Array slicing, e.g., arr[1..2, 1..2], arr[2, 0..$], arr[0..$, 1]. ) Array2D opIndex($(D_KEYWORD int)[2] r1, $(D_KEYWORD int)[2] r2) { Array2D result; $(D_KEYWORD auto) startOffset = r1[0] + r2[0]*stride; $(D_KEYWORD auto) endOffset = r1[1] + (r2[1] - 1)*stride; result.impl = $(D_KEYWORD this).impl[startOffset .. endOffset]; result.stride = $(D_KEYWORD this).stride; result.width = r1[1] - r1[0]; result.height = r2[1] - r2[0]; $(D_KEYWORD return) result; } $(D_KEYWORD auto) opIndex($(D_KEYWORD int)[2] r1, $(D_KEYWORD int) j) { $(D_KEYWORD return) opIndex(r1, [j, j+1]); } $(D_KEYWORD auto) opIndex($(D_KEYWORD int) i, $(D_KEYWORD int)[2] r2) { $(D_KEYWORD return) opIndex([i, i+1], r2); } $(D_COMMENT // Support for `x..y` notation in slicing operator for the given dimension. ) $(D_KEYWORD int)[2] opSlice(size_t dim)($(D_KEYWORD int) start, $(D_KEYWORD int) end) $(D_KEYWORD if) (dim >= 0 && dim < 2) $(D_KEYWORD in) { $(D_KEYWORD assert)(start >= 0 && end <= $(D_KEYWORD this).opDollar!dim); } $(D_KEYWORD do) { $(D_KEYWORD return) [start, end]; } $(D_COMMENT // Support `$` in slicing notation, e.g., arr[1..$, 0..$-1]. ) @property $(D_KEYWORD int) opDollar(size_t dim : 0)() { $(D_KEYWORD return) width; } @property $(D_KEYWORD int) opDollar(size_t dim : 1)() { $(D_KEYWORD return) height; } } $(D_KEYWORD void) main() { $(D_KEYWORD auto) arr = Array2D!$(D_KEYWORD int)(4, 3, [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]); $(D_COMMENT // Basic indexing ) $(D_KEYWORD assert)(arr[0, 0] == 0); $(D_KEYWORD assert)(arr[1, 0] == 1); $(D_KEYWORD assert)(arr[0, 1] == 4); $(D_COMMENT // Use of opDollar ) $(D_KEYWORD assert)(arr[$-1, 0] == 3); $(D_KEYWORD assert)(arr[0, $-1] == 8); $(D_COMMENT // Note the value of $ differs by dimension ) $(D_KEYWORD assert)(arr[$-1, $-1] == 11); $(D_COMMENT // Slicing ) $(D_KEYWORD auto) slice1 = arr[1..$, 0..$]; $(D_KEYWORD assert)(slice1[0, 0] == 1 && slice1[1, 0] == 2 && slice1[2, 0] == 3 && slice1[0, 1] == 5 && slice1[1, 1] == 6 && slice1[2, 1] == 7 && slice1[0, 2] == 9 && slice1[1, 2] == 10 && slice1[2, 2] == 11); $(D_KEYWORD auto) slice2 = slice1[0..2, 1..$]; $(D_KEYWORD assert)(slice2[0, 0] == 5 && slice2[1, 0] == 6 && slice2[0, 1] == 9 && slice2[1, 1] == 10); $(D_COMMENT // Thin slices ) $(D_KEYWORD auto) slice3 = arr[2, 0..$]; $(D_KEYWORD assert)(slice3[0, 0] == 2 && slice3[0, 1] == 6 && slice3[0, 2] == 10); $(D_KEYWORD auto) slice4 = arr[0..3, 2]; $(D_KEYWORD assert)(slice4[0, 0] == 8 && slice4[1, 0] == 9 && slice4[2, 0] == 10); } ) ) $(DDOC_BLANKLINE ) $(DDOC_BLANKLINE )

$(LEGACY_LNAME2 Dispatch, dispatch, Forwarding)

$(DDOC_BLANKLINE ) $(P Member names not found in a class or struct can be forwarded to a template function named $(CODE opDispatch) for resolution. ) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD import) std.stdio; $(D_KEYWORD struct) S { $(D_KEYWORD void) opDispatch(string s, T)(T i) { writefln($(D_STRING "S.opDispatch$(LPAREN)'%s', %s$(RPAREN )"), s, i); } } $(D_KEYWORD class) C { $(D_KEYWORD void) opDispatch(string s)($(D_KEYWORD int) i) { writefln($(D_STRING "C.opDispatch$(LPAREN)'%s', %s$(RPAREN )"), s, i); } } $(D_KEYWORD struct) D { $(D_KEYWORD template) opDispatch(string s) { $(D_KEYWORD enum) $(D_KEYWORD int) opDispatch = 8; } } $(D_KEYWORD void) main() { S s; s.opDispatch!($(D_STRING "hello"))(7); s.foo(7); $(D_KEYWORD auto) c = $(D_KEYWORD new) C(); c.foo(8); D d; writefln($(D_STRING "d.foo = %s"), d.foo); $(D_KEYWORD assert)(d.foo == 8); } ) ) $(DDOC_BLANKLINE )

$(LEGACY_LNAME2 Old-Style, old-style, D1 style operator overloading)

$(DDOC_BLANKLINE ) $(P The $(LINK2 http://digitalmars.com/d/1.0/operatoroverloading.html, D1 operator overload mechanisms) are deprecated. ) $(SPEC_SUBNAV_PREV_NEXT function, Functions, template, Templates) ) )