$(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)
)
)