Expression:CommaExpression
An expression is a sequence of operators and operands that specifies an evaluation.The syntax, order of evaluation, and semantics of expressions are as follows.
Expressions are used to compute values with a resulting type. These values can then be assigned, tested, or ignored. Expressions can also have side effects.
For any expressionexpr, the full expression ofexpr is defined as follows. Ifexpr parses as asubexpression of another expressionexpr1, then the full expression ofexpr is thefull expression ofexpr1. Otherwise,expr is its own full expression.
Each expression has a unique full expression. Example:
return f() + g() * 2;The full expression ofg() * 2 above isf() + g() * 2, but not thefull expression off() + g() because the latter is not parsed as a subexpression.
Note: Although the definition is straightforward, a few subtleties exist related to function literals:
return (() => x + f())() * g();The full expression off() above isx + f(), not the expression passedtoreturn. This is because the parent ofx + f() has function literal type, not expression type.
The following expressions, and no others, are calledlvalue expressions orlvalues:
Expressions that are not lvalues arervalues. Rvalues include all literals, special value keywords such as__FILE__ and__LINE__,enum values, and the result of expressions not defined as lvalues above.
The built-in address-of operator (unary&) may only be applied to lvalues.
Given an expressionexpr that is a subexpression of a fullexpressionfullexpr, thesmallest short-circuit expression, if any, is the shortestsubexpressionscexpr offullexpr that is anAndAndExpression (&&) or anOrOrExpression (||), such thatexpr is a subexpression ofscexpr. Example:
((f() * 2 && g()) + 1) || h()
The smallest short-circuit expressionof the subexpressionf() * 2 above isf() * 2 && g(). Example:
(f() && g()) + h()
The subexpressionh() above has no smallest short-circuit expression.
Built-in prefix unary expressions++ and-- are evaluated as if lowered (rewritten) toassignments as follows:
| Expression | Equivalent |
|---|---|
| ++expr | ((expr) += 1) |
| --expr | ((expr) -= 1) |
Therefore, the result of prefix++ and-- is the lvalue after the side effect has beeneffected.
Built-in postfix unary expressions++ and-- are evaluated as if lowered (rewritten) tolambdainvocations as follows:
| Expression | Equivalent |
|---|---|
| expr++ | (ref x){auto t = x; ++x; return t;}(expr) |
| expr-- | (ref x){auto t = x; --x; return t;}(expr) |
Therefore, the result of postfix++ and-- is an rvalue just before the side effect has been effected.
int i = 0;assert(++i == 1);assert(i++ == 1);assert(i == 2);int* p = [1, 2].ptr;assert(*p++ == 1);assert(*p == 2);
Binary expressions except forAssignExpression,OrOrExpression, andAndAndExpression are evaluated in lexical order (left-to-right). Example:
int i = 2;i = ++i * i++ + i;assert(i == 3 * 3 + 4);
OrOrExpression andAndAndExpression evaluate their left-hand side argumentfirst. Then,OrOrExpression evaluates its right-hand side if and only if its left-handside does not evaluate to nonzero.AndAndExpression evaluates its right-hand side if andonly if its left-hand side evaluates to nonzero.
ConditionalExpression evaluates its left-hand side argumentfirst. Then, if the result is nonzero, the second operand is evaluated. Otherwise, the third operandis evaluated.
Calls to functions withextern(D)linkage (which isthe default linkage) are evaluated in the following order:
Example calling afunction pointer:
voidfunction(int a,int b,int c) fun(){ writeln("fun() called");staticvoid r(int a,int b,int c) { writeln("callee called"); }return &r;}int f1() { writeln("f1() called");return 1; }int f2() { writeln("f2() called");return 2; }int f3(int x) { writeln("f3() called");return x + 3; }int f4() { writeln("f4() called");return 4; }// evaluates fun() then f1() then f2() then f3() then f4()// after which control is transferred to the calleefun()(f1(), f3(f2()), f4());
Expressions and statements may create and/or consume rvalues. Such values are calledtemporaries and do not have a name or a visible scope. Their lifetime is managed automaticallyas defined in this section.
For each evaluation that yields a temporary value, the lifetime of that temporary begins at theevaluation point, similarly to creation of a usual named value initialized with an expression.
Termination of lifetime of temporaries does not obey the customary scoping rules and is definedas follows:
If a subexpression of an expression throws an exception, all temporaries created up to theevaluation of that subexpression will be destroyed per the rules above. No destructor calls willbe issued for temporaries not yet constructed.
Note: An intuition behind these rules is that destructors of temporaries are deferred to the end of fullexpression and in reverse order of construction, with the exception that the right-hand side of&& and|| are considered their own full expressions even when part of larger expressions.
Note: TheConditionalExpressione1 ? e2 : e3 is nota special case although it evaluates expressions conditionally:e1 and one ofe2 ande3 may create temporaries. Their destructors are insertedto the end of the full expression in the reverse order of creation.
Example:
import std.stdio;struct S{int x;this(int n) { x = n; writefln("S(%s)", x); } ~this() { writefln("~S(%s)", x); }}void main(){bool b = (S(1) == S(2) || S(3) != S(4)) && S(5) == S(6);}
S(1)S(2)S(3)S(4)~S(4)~S(3)S(5)S(6)~S(6)~S(5)~S(2)~S(1)First,S(1) andS(2) are evaluated in lexical order. Per the rules, they will be destroyed atthe end of the full expression and in reverse order. The comparisonS(1) == S(2) yieldsfalse, so the right-hand side of the|| is evaluated causingS(3) andS(4) to be evaluated,also in lexical order. However, their destruction is not deferred to the end of the fullexpression. Instead,S(4) and thenS(3) are destroyed at the end of the|| expression.Following their destruction,S(5) andS(6) are constructed in lexical order. Again they are notdestroyed at the end of the full expression, but right at the end of the&& expression.Consequently, the destruction ofS(6) andS(5) is carried before that ofS(2) andS(1).
CommaExpression:AssignExpressionCommaExpression,AssignExpression
The left operand of the, is evaluated, then the right operand is evaluated. In C, the result of a comma expression is the result of the right operand. In D, using the result of a comma expression isn't allowed. Consequently a comma expression is only useful when each operand has a side effect.
int x, y;// expression statementx = 1, y = 1;// evaluate a comma expression at the end of each loop iterationfor (; y < 10; x++, y *= 2) writefln("%s, %s", x, y);
AssignExpression:ConditionalExpressionConditionalExpression=AssignExpressionConditionalExpression+=AssignExpressionConditionalExpression-=AssignExpressionConditionalExpression*=AssignExpressionConditionalExpression/=AssignExpressionConditionalExpression%=AssignExpressionConditionalExpression&=AssignExpressionConditionalExpression|=AssignExpressionConditionalExpression^=AssignExpressionConditionalExpression~=AssignExpressionConditionalExpression<<=AssignExpressionConditionalExpression>>=AssignExpressionConditionalExpression>>>=AssignExpressionConditionalExpression^^=AssignExpression
For all assign expressions, the left operand must be a modifiable lvalue. The type of the assign expression is the type of the left operand, and the result is the value of the left operand after assignment occurs. The resulting expression is a modifiable lvalue.
If the operator is= then it is simple assignment.
Otherwise, the right operand is implicitly converted to the type of the left operand, and assigned to it.
For arguments of built-in types, assignment operator expressions such as
a op= bare semantically equivalent to:
a =cast(typeof(a))(a op b)except that:
Narrowing conversions are allowed. Truncating conversions will be an error.
void f(short s){byte b; b += s;// OK, though it may overflow//b += 1.5F; // Deprecated, truncation}
For user-defined types, assignment operator expressions areoverloaded separately from the binary operators. Still the left operand must be an lvalue.
ConditionalExpression:OrOrExpressionOrOrExpression?Expression:ConditionalExpression
The first expression is converted tobool, and is evaluated.
If it istrue, then the second expression is evaluated, and its result is the result of the conditional expression.
If it isfalse, then the third expression is evaluated, and its result is the result of the conditional expression.
If either the second or third expressions are of typevoid, then the resulting type isvoid. Otherwise, the second and third expressions are implicitly converted to a common type which becomes the result type of the conditional expression.
bool test;int a, b, c;...test ? a = b : c = 2;// error(test ? a = b : c) = 2;// OK
This makes the intent clearer, because the first statement can easily be misread as the following code:
test ? a = b : (c = 2);
OrOrExpression:AndAndExpressionOrOrExpression||AndAndExpression
The result type of anOrOrExpression isbool, unless the right operand has typevoid, when the result is typevoid.
TheOrOrExpression evaluates its left operand.
If the left operand, converted to typebool, evaluates totrue, then the right operand is not evaluated. If the result type of theOrOrExpression isbool then the result of the expression istrue.
If the left operand isfalse, then the right operand is evaluated. If the result type of theOrOrExpression isbool then the result of the expression is the right operand converted to typebool.
AndAndExpression:OrExpressionAndAndExpression&&OrExpression
The result type of anAndAndExpression isbool, unless the right operand has typevoid, when the result is typevoid.
TheAndAndExpression evaluates its left operand.
If the left operand, converted to typebool, evaluates tofalse, then the right operand is not evaluated. If the result type of theAndAndExpression isbool then the result of the expression isfalse.
If the left operand istrue, then the right operand is evaluated. If the result type of theAndAndExpression isbool then the result of the expression is the right operand converted to typebool.
Bit wise expressions perform abitwise operation on their operands. Their operands must be integral types. First, theUsual Arithmetic Conversions are done. Then, the bitwise operation is done.
int x, a, b;x = a & 5 == b;// errorx = a & 5is b;// errorx = a & 5 <= b;// errorx = (a & 5) == b;// OKx = a & (5 == b);// OK
OrExpression:XorExpressionOrExpression|XorExpression
The operands are OR'd together.
XorExpression:AndExpressionXorExpression^AndExpression
The operands are XOR'd together.
AndExpression:CmpExpressionAndExpression&CmpExpression
The operands are AND'd together.
CmpExpression:EqualExpressionIdentityExpressionRelExpressionInExpressionShiftExpression
EqualExpression:ShiftExpression==ShiftExpressionShiftExpression!=ShiftExpression
Equality expressions compare the two operands for equality (==) or inequality (!=). The type of the result isbool.
Inequality is defined as the logical negation of equality.
assert(5 == 5L);assert(byte(4) == 4F);int i = 1, j = 1;assert(&i != &j);assert(&i !=null);// elements of different types are comparable, even when different sizesint[] ia = ['A', 'B', 'C'];assert(ia =="ABC");byte[] ba = [1, 2];assert(ba == [1F, 2F]);
x.re == y.re && x.im == y.im
For class references,a == b is rewritten to.object.opEquals(a, b), which handlesnull. This is intended to compare the contents of two objects, however an appropriateopEquals method override must be defined for this to work. The defaultopEquals provided by the rootObject class is equivalent to theis operator.
For struct objects, the expression(a == b) is rewritten asa.opEquals(b), or failing that,b.opEquals(a).
For both class references and struct objects,(a != b) is rewritten as!(a == b).
SeeopEquals for details.
For struct objects, equality means the result of theopEquals() member function. If anopEquals() is not provided, one will be generated. Equality is defined as the logical product of all equality results of the corresponding object fields.
struct S{int i = 4; string s ="four";}S s;assert(s == S());s.s ="foul";assert(s != S());
If there are overlapping fields, which happens with unions, the default equality will compare each of the overlapping fields.
IdentityExpression:ShiftExpressionisShiftExpressionShiftExpression! isShiftExpression
Theis operator compares for identity of expression values. To compare for nonidentity, usee1 !is e2. The type of the result isbool. The operands undergo theUsual Arithmetic Conversions to bring them to a common type before comparison.
For class / interface objects, identity is defined as the object references being identical. Class references can be efficiently compared againstnull usingis. Note that interface objects need not have the same reference of the class they were cast from. To test whether aninterface shares a class instance with anotherinterface /class value, cast both operands toObject before comparing withis.
interface I {void g(); }interface I1 : I {void g1(); }interface I2 : I {void g2(); }interface J : I1, I2 {void h(); }class C : J{overridevoid g() { }overridevoid g1() { }overridevoid g2() { }overridevoid h() { }}void main() @safe{ C c =new C; I i1 =cast(I1) c; I i2 =cast(I2) c;assert(i1 !is i2);// not identicalassert(c !is i2);// not identicalassert(cast(Object) i1iscast(Object) i2);// identical}
For struct objects and floating point values, identity is defined as the bits in the operands being identical.
For static and dynamic arrays, identity of two arrays is given when both arrays refer to the same memory location and contain the same number of elements.
Object o;assert(oisnull);auto a = [1, 2];assert(ais a[0..$]);assert(a !is a[0..1]);auto b = [1, 2];assert(a !is b);
For other operand types, identity is defined as being the same as equality.
The identity operatoris cannot be overloaded.
RelExpression:ShiftExpression<ShiftExpressionShiftExpression<=ShiftExpressionShiftExpression>ShiftExpressionShiftExpression>=ShiftExpression
First, theUsual Arithmetic Conversions are done on the operands. The result type of a relational expression isbool.
For static and dynamic arrays, the result of aCmpExpression is the result of the operator applied to the first non-equal element of the array. If two arrays compare equal, but are of different lengths, the shorter array compares as "less" than the longer array.
Integer comparisons happen when both operands are integral types.
| Operator | Relation |
|---|---|
| < | less |
| > | greater |
| <= | less or equal |
| >= | greater or equal |
| == | equal |
| != | not equal |
It is an error to have one operand be signed and the other unsigned for a<,<=,> or>= expression. Usecasts to make both operands signed or both operands unsigned.
If one or both operands are floating point, then a floating point comparison is performed.
ACmpExpression can haveNaN operands. If either or both operands isNaN, the floating point comparison operation returns as follows:
| Operator | Relation | Returns |
|---|---|---|
| < | less | false |
| > | greater | false |
| <= | less or equal | false |
| >= | greater or equal | false |
| == | equal | false |
| != | unordered, less, or greater | true |
For struct objects, aRelExpression performs a comparison which first evaluatesa matchingopCmp method call.
For class references, aRelExpression performs a comparison which first evaluates to anint which is either:
class C{overrideint opCmp(Object o) {assert(0); }}void main(){ C c;//if (c < null) {} // compile-time errorassert(cisnull);assert(c <new C);// C.opCmp is not called}
Secondly, for class and struct objects, the evaluatedint is compared against zero using the given operator, which forms the result of theRelExpression. For more information, seeopCmp.
InExpression:ShiftExpressioninShiftExpressionShiftExpression! inShiftExpression
A container such as an associative arraycan be tested to see if it contains a certain key:
int[string] foo;...if ("hello"in foo){// the string was found}
The result of anInExpression is a pointer for associative arrays. The pointer isnull if the container has no matching key. If there is a match, the pointer points to a value associated with the key.
The!in expression is the logical negation of thein operation.
Thein expression has the same precedence as the relational expressions<,<=, etc.
ShiftExpression:AddExpressionShiftExpression<<AddExpressionShiftExpression>>AddExpressionShiftExpression>>>AddExpression
The operands must be integral types, and undergo theInteger Promotions. The result type is the type of the left operand after the promotions. The result value is the result of shifting the bits by the right operand's value.
int c;int s = -3;auto y = c << s;// implementation defined valueauto x = c << 33;// error, max shift count allowed is 31
AddExpression:MulExpressionAddExpression+MulExpressionAddExpression-MulExpressionAddExpression~MulExpression
In the cases of the Additive operations+ and-:
If the operands are of integral types, they undergo theUsual Arithmetic Conversions, and then are brought to a common type using theUsual Arithmetic Conversions.
If both operands are of integral types and an overflow or underflow occurs in the computation, wrapping will happen. For example:
If either operand is a floating point type, the other is implicitly converted to floating point and they are brought to a common type via theUsual Arithmetic Conversions.
Add expressions for floating point operands are not associative.
If the first operand is a pointer, and the second is an integral type, the resulting type is the type of the first operand, and the resulting value is the pointer plus (or minus) the second operand multiplied by the size of the type pointed to by the first operand.
int[] a = [1,2,3];int* p = a.ptr;assert(*p == 1);*(p + 2) = 4;// same as `p[2] = 4`assert(a[2] == 4);
IndexOperation can also be used with a pointer and has the same behaviour as adding an integer, then dereferencing the result.
If the second operand is a pointer, and the first is an integral type, and the operator is+, the operands are reversed and the pointer arithmetic just described is applied.
Producing a pointer through pointer arithmetic is not allowed in@safe code.
If both operands are pointers, and the operator is+, then it is illegal.
If both operands are pointers, and the operator is-, the pointers are subtracted and the result is divided by the size of the type pointed to by the operands. In this calculation the assumed size ofvoid is one byte. It is an error if the pointers point to different types. The type of the result isptrdiff_t.
int[] a = [1,2,3];ptrdiff_t d = &a[2] - a.ptr;assert(d == 2);
In the case of the Additive operation~:
ACatExpression concatenates a container's data with other data, producing a new container.
For a dynamic array, the other operand must either be another array or a single value that implicitly converts to the element type of the array. SeeArray Concatenation.
MulExpression:UnaryExpressionMulExpression*UnaryExpressionMulExpression/UnaryExpressionMulExpression%UnaryExpression
The operands must be arithmetic types. They undergo theUsual Arithmetic Conversions.
For integral operands, the*,/, and% correspond to multiply, divide, and modulus operations. For multiply, overflows are ignored and simply chopped to fit into the integral type.
For integral operands of the/ and% operators, the quotient rounds towards zero and the remainder has the same sign as the dividend.
The following divide or modulus integral operands:
are illegal if encountered during Compile Time Execution.
For floating point operands, the* and/ operations correspond to the IEEE 754 floating point equivalents.% is not the same as the IEEE 754 remainder. For example,15.0 % 10.0 == 5.0, whereas for IEEE 754,remainder(15.0,10.0) == -5.0.
Mul expressions for floating point operands are not associative.
UnaryExpression:&UnaryExpression++UnaryExpression--UnaryExpression*UnaryExpression-UnaryExpression+UnaryExpression!UnaryExpressionComplementExpressionDeleteExpressionCastExpressionThrowExpressionPowExpression
| Operator | Description |
|---|---|
| & | Take memory address of anlvalue - seepointers |
| ++ | Increment before use - seeorder of evaluation |
| -- | Decrement before use |
| * | Dereference/indirection - typically for pointers |
| - | Negative |
| + | Positive |
| ! | Logical NOT |
The usualInteger Promotions are performed prior to unary- and+ operations.
ComplementExpression:~UnaryExpression
ComplementExpressions work on integral types (exceptbool). All the bits in the value are complemented. The usualInteger Promotions are performed prior to the complement operation.
DeleteExpression:deleteUnaryExpression
If theUnaryExpression is a class object reference, and there is a destructor for that class, the destructor is called for that object instance.
Next, if theUnaryExpression is a class object reference, or a pointer to a struct instance, and the class or struct has overloaded operator delete, then that operator delete is called for that class object instance or struct instance.
Otherwise, the garbage collector is called to immediately free the memory allocated for the class instance or struct instance.
If theUnaryExpression is a pointer or a dynamic array, the garbage collector is called to immediately release the memory.
The pointer, dynamic array, or reference is set tonull after the delete is performed. Any attempt to reference the data after the deletion via another reference to it will result in undefined behavior.
IfUnaryExpression is a variable allocated on the stack, the class destructor (if any) is called for that instance. The garbage collector is not called.
CastExpression:cast (Type)UnaryExpressionCastQual
ACastExpression converts theUnaryExpression toType.
cast(foo) -p;// cast (-p) to type foo(foo) - p;// subtract p from foo
For situations whereimplicit conversions on basic types cannot be performed, the type system may be forced to accept the reinterpretation of a memory region by using a cast.
An example of such a scenario is represented by trying to store a wider type into a narrower one:
int a;byte b = a;// cannot implicitly convert expression a of type int to byte
When casting a source type that is wider than the destination type, the value is truncated to the destination size.
int a = 64389;// 00000000 00000000 11111011 10000101byte b =cast(byte) a;// 10000101ubyte c =cast(ubyte) a;// 10000101short d =cast(short) a;// 11111011 10000101ushort e =cast(ushort) a;// 11111011 10000101writeln(b);writeln(c);writeln(d);writeln(e);
For integral types casting from a narrower type to a wider type is done by performing sign extension.
ubyte a = 133;// 10000101byte b = a;// 10000101writeln(a);writeln(b);ushort c = a;// 00000000 10000101short d = b;// 11111111 10000101writeln(c);writeln(d);
See also:Casting Integers.
Any casting of a class reference to a derived class reference is done with a runtime check to make sure it really is a downcast.null is the result if it isn't.
class A {}class B : A {}void main(){ A a =new A;//B b = a; // error, need cast B b =cast(B) a;// b is null if a is not a Bassert(bisnull); a = b;// no cast needed a =cast(A) b;// no runtime check needed for upcastassert(ais b);}
In order to determine if an objecto is an instance of a classB use a cast:
if (cast(B) o){// o is an instance of B}else{// o is not an instance of B}
Casting a pointer type to and from a class type is done as a type paint (i.e. a reinterpret cast).
Casting a pointer variable to another pointer type modifies the value that will be obtained as a result of dereferencing, along with the number of bytes on which pointer arithmetic is performed.
int val = 25185;// 00000000 00000000 01100010 01100001char *ch =cast(char*)(&val);writeln(*ch);// awriteln(cast(int)(*ch));// 97writeln(*(ch + 1));// bwriteln(cast(int)(*(ch + 1)));// 98
Similarly, when casting a dynamically allocated array to a type of smaller size, the bytes of the initial array will be divided and regrouped according to the new dimension.
import core.stdc.stdlib;int *p =cast(int*) malloc(5 *int.sizeof);for (int i = 0; i < 5; i++) { p[i] = i + 'a';}// p = [97, 98, 99, 100, 101]char* c =cast(char*) p;// c = [97, 0, 0, 0, 98, 0, 0, 0, 99 ...]for (int i = 0; i < 5 *int.sizeof; i++) { writeln(c[i]);}
When casting a pointer of type A to a pointer of type B and type B is wider than type A, attempts at accessing the memory exceeding the size of A will result in undefined behaviour.
char c = 'a';int *p =cast(int*) (&c);writeln(*p);
It is also possible to cast pointers to basic data types. A common practice could be to cast the pointer to an int value and then print its address:
import core.stdc.stdlib;int *p =cast(int*) malloc(int.sizeof);int a =cast(int) p;writeln(a);
T[] a;...cast(U[]) aCasting a non-literal dynamic arraya to another dynamic array typeU[] is allowed only when the result will contain every byte of data that was referenced bya. This is enforced with a runtime check that the byte length ofa's elements is divisible byU.sizeof. If there is a remainder, a runtime error is generated. The cast is done as a type paint, and the resulting array's length is set to(a.length * T.sizeof) / U.sizeof.
byte[] a = [1,2,3];//auto b = cast(int[])a; // runtime error: array cast misalignmentint[] c = [1, 2, 3];auto d =cast(byte[])c;// ok// prints:// [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]writeln(d);
See also:Casting array literals.
A slice of statically known length can be cast to a static array type when the byte counts of their respective data match.
void f(int[] b){char[4] a;staticassert(!__traits(compiles, a =cast(char[4]) b));// unknown lengthstaticassert(!__traits(compiles, a =cast(char[4]) b[0..2]));// too many bytes a =cast(char[4]) b[0..1];// OKconst i = 1; a =cast(char[4]) b[i..2];// OK}
See also:Slice conversion to static array.
Casting a static array to another static array is done only if the array lengths multiplied by the element sizes match; a mismatch is illegal. The cast is done as a type paint (aka a reinterpret cast). The contents of the array are not changed.
byte[16] b = 3;// set each element to 3assert(b[0] == 0x03);int[4] ia =cast(int[4]) b;// print elements as hexforeach (i; ia) writefln("%x", i);/* prints: 3030303 3030303 3030303 3030303 */
Casting an integer to a smaller integral will truncate the value towards the least significant bits. If the target type is signed and the most significant bit is set after truncation, that bit will be lost from the value and the sign bit will be set.
uint a = 260;auto b =cast(ubyte) a;assert(b == 4);// truncated like 260 & 0xffint c = 128;assert(cast(byte)c == -128);// reinterpreted
Converting between signed and unsigned types will reinterpret the value if the destination type cannot represent the source value.
short c = -1;ushort d = c;assert(d ==ushort.max);assert(uint(c) ==uint.max);ubyte e = 255;byte f = e;assert(f == -1);// reinterpretedassert(short(e) == 255);// no change
Casting a floating point literal from one type to another changes its type, but internally it is retained at full precision for the purposes of constant folding.
void test(){real a = 3.40483L;real b; b = 3.40483;// literal is not truncated to double precisionassert(a == b);assert(a == 3.40483);assert(a == 3.40483L);assert(a == 3.40483F);double d = 3.40483;// truncate literal when assigned to variableassert(d != a);// so it is no longer the sameconstdouble x = 3.40483;// assignment to const is notassert(x == a);// truncated if the initializer is visible}
Casting a floating point value to an integral type is the equivalent of converting to an integer using truncation. If the floating point value is outside the range of the integral type, the cast will produce an invalid result (this is also the case in C, C++).
void main(){int a =cast(int) 0.8f;assert(a == 0);long b =cast(long) 1.5;assert(b == 1L);long c =cast(long) -1.5;assert(c == -1);// if the float overflows, the cast returns the integer value of// 80000000_00000000H (64-bit operand) or 80000000H (32-bit operand)long d =cast(long)float.max;assert(d ==long.min);int e =cast(int) (1234.5 +int.max);assert(e ==int.min);// for types represented on 16 or 8 bits, the result is the same as// 32-bit types, but the most significant bits are ignoredshort f =cast(short)float.max;assert(f == 0);}
An expressione can be cast to a struct typeS:
struct S{int i;}struct R{short[2] a;}S s =cast(S) 5;// same as S(5)assert(s.i == 5);staticassert(!__traits(compiles,cast(S)long.max));// S(long.max) is invalidR r = R([1, 2]);s =cast(S) r;// reinterpret rassert(s.i == 0x00020001);byte[4] a = [1, 0, 2, 0];assert(r ==cast(R) a);// reinterpret a
A struct instance can be cast to a static array type when their.sizeof properties each give the same result.
struct S {short a, b, c; }S s = S(1, 2, 3);staticassert(!__traits(compiles,cast(short[2]) s));// size mismatchshort[3] x =cast(short[3]) s;assert(x.tupleof == s.tupleof);auto y =cast(byte[6]) s;assert(y == [1, 0, 2, 0, 3, 0]);
CastQual:cast (TypeCtorsopt)UnaryExpression
ACastQual replaces the qualifiers in the type of theUnaryExpression:
sharedint x;staticassert(is(typeof(cast(const)x) ==constint));
Casting with no type or qualifiers removes any top levelconst,immutable,shared orinout type modifiers from the type of theUnaryExpression.
sharedint x;staticassert(is(typeof(cast()x) ==int));
Casting an expression tovoid type is allowed to mark that the result is unused. OnExpressionStatement, it could be used properly to avoid a "has no effect" error.
void foo(lazyvoid exp) {}void main(){ foo(10);// NG - expression '10' has no effect foo(cast(void)10);// OK}
ThrowExpression:throwAssignExpression
AssignExpression is evaluated and must yield a reference to aThrowable or a class derived fromThrowable. The reference is thrown as an exception, interrupting the current control flow to continue at a suitablecatch clause of atry-statement. This process will execute any applicablescope (exit) /scope (failure) passed since entering the correspondingtry block.
thrownew Exception("message");
TheThrowable must not be a qualified asimmutable,const,inout orshared. The runtime may modify a thrown object (e.g. to contain a stack trace) which would violateconst orimmutable objects.
AThrowExpression may be nested in another expression:
void foo(intfunction() f) {}void main() { foo(() =>thrownew Exception());}
The type of aThrowExpression isnoreturn.
PowExpression:PostfixExpressionPostfixExpression^^UnaryExpression
PowExpression raises its left operand to the power of its right operand.
PostfixExpression:PrimaryExpressionPostfixExpression.IdentifierPostfixExpression.TemplateInstancePostfixExpression.NewExpressionPostfixExpression++PostfixExpression--PostfixExpression(NamedArgumentListopt)TypeCtorsoptBasicType(NamedArgumentListopt)PostfixExpressionIndexOperationPostfixExpressionSliceOperation
| Operation | Description |
|---|---|
| .Identifier | Either:
|
| .NewExpression | Instantiate a nested class |
| ++ | Increment after use - seeorder of evaluation |
| -- | Decrement after use |
| (args) | Either:
|
| IndexOperation | Select a single element |
| SliceOperation | Select a series of elements |
ArgumentList:AssignExpressionAssignExpression,AssignExpression,ArgumentListNamedArgumentList:NamedArgumentNamedArgument,NamedArgument,NamedArgumentListNamedArgument:Identifier:AssignExpressionAssignExpression
A callable expression can precede a list of named arguments in parentheses. The following expressions can be called:
void f(int,int);void g(){ f(5, 6); (&f)(5, 6);}
Arguments in aNamedArgumentList are matched to function parameters as follows:
A type can precede a list of arguments. See:
IndexOperation:[ArgumentList]
The basePostfixExpression is evaluated. The special variable$ is declared and set to be the number of elements in the basePostfixExpression (when available). A new declaration scope is created for the evaluation of theArgumentList and$ appears in that scope only.
The index operator can beoverloaded. Using multiple indices inArgumentList is only supported for operator overloading.
SliceOperation:[ ][Slice][Slice,]Slice:AssignExpressionAssignExpression,SliceAssignExpression..AssignExpressionAssignExpression..AssignExpression,Slice
The basePostfixExpression is evaluated. The special variable$ is declared and set to be the number of elements in thePostfixExpression (when available). A new declaration scope is created for the evaluation of theAssignExpression ..AssignExpression and$ appears in that scope only.
The firstAssignExpression is taken to be the inclusive lower bound of the slice, and the secondAssignExpression is the exclusive upper bound. The result of the expression is a slice of the elements inPostfixExpression.
If the[ ] form is used, the slice is of all the elements in the basePostfixExpression. The base expression cannot be a pointer.
The slice operator can beoverloaded. Using more than oneSlice is only supported for operator overloading.
ASliceOperation is not a modifiable lvalue.
If the slice bounds can be known at compile time, the slice expression may be implicitly convertible to a static array lvalue. For example:
arr[a .. b]// typed T[]If botha andb are integers (which may be constant-folded), the slice expression can be converted to a static array of typeT[b - a].
void f(int[2] sa) {}int[] arr = [1, 2, 3];void test(){//f(arr); // error, can't convert f(arr[1 .. 3]);// OK//f(arr[0 .. 3]); // errorint[2] g() {return arr[0 .. 2]; }}
void bar(refint[2] a){assert(a == [2, 3]); a = [4, 5];}void main(){int[] arr = [1, 2, 3];// slicing an lvalue gives an lvalue bar(arr[1 .. 3]);assert(arr == [1, 4, 5]);}
PrimaryExpression:Identifier.IdentifierTemplateInstance.TemplateInstance$LiteralExpressionAssertExpressionMixinExpressionImportExpressionNewExpressionFundamentalType.IdentifierTypeCtoropt(Type) .Identifier(Type) .TemplateInstanceFundamentalType(NamedArgumentListopt)TypeCtoropt(Type)(NamedArgumentListopt)TypeofTypeidExpressionIsExpression(Expression)SpecialKeywordRvalueExpressionTraitsExpressionLiteralExpression:thissupernulltruefalseIntegerLiteralFloatLiteralCharacterLiteralStringLiteralInterpolationExpressionSequenceArrayLiteralAssocArrayLiteralFunctionLiteral
| Expression | Description |
|---|---|
| .Identifier | Module Scope Operator |
| $ | Number of elements in an object being indexed/sliced. |
| (Type).Identifier | Access atype property or astatic member of a type. |
| FundamentalType(arg) | Uniform construction of scalar type with optional argument. |
| (Type)(args) | Construct a type with optional arguments. |
| (Expression) | Evaluate an expression - useful as asubexpression. |
Within a constructor or non-static member function,this resolves to a reference to the object for which the function was called.
typeof(this) is valid anywhere inside an aggregate type definition. If a class member function is called with an explicit reference totypeof(this), a non-virtual call is made:
class A{char get() {return 'A'; }char foo() {returntypeof(this).get(); }// calls `A.get`char bar() {returnthis.get(); }// dynamic, same as just `get()`}class B : A{overridechar get() {return 'B'; }}void main(){ B b =new B();assert(b.foo() == 'A');assert(b.bar() == 'B');}
Assignment tothis is not allowed for classes.
See also:
super is identical tothis, except that it is cast tothis's base class. It is an error if there is no base class. (The onlyextern(D) class without a base class isObject, however, note thatextern(C++) classes have no base class unless specified.) If a member function is called with an explicit reference tosuper, a non-virtual call is made.
Assignment tosuper is not allowed.
See also:Base Class Construction.
null represents the null value for pointers, pointers to functions, delegates, dynamic arrays, associative arrays, and class objects. If it has not already been cast to a type, it is given the singular typetypeof(null) and it is an exact conversion to convert it to the null value for pointers, pointers to functions, delegates, etc. After it is cast to a type, such conversions are implicit, but no longer exact.
SeeStringLiteral grammar.
String literals are read-only. A string literal without aStringPostfix can implicitly convert to any of the following types, which have equal weight:
| immutable(char)* |
| immutable(wchar)* |
| immutable(dchar)* |
| immutable(char)[] |
| immutable(wchar)[] |
| immutable(dchar)[] |
By default, a string literal is typed as a dynamic array, but the element count is known at compile time. So all string literals can be implicitly converted to an immutable static array:
void foo(char[2] a){assert(a[0] == 'b');}void bar(refconstchar[2] a){assert(a =="bc");}void main(){ foo("bc"); foo("b");// OK//foo("bcd"); // error, too many chars bar("bc");// OK, same length//bar("b"); // error, lengths must match}
A string literal converts to a static array rvalue of the same or longer length. Any extra elements are padded with zeros. A string literal can also convert to a static array lvalue of the same length.
String literals have a'\0' appended to them, which makes them easy to pass to C or C++ functions expecting a null-terminatedconst char* string. The'\0' is not included in the.length property of the string literal.
Concatenation of string literals requires the use ofthe~ operator, and is resolved at compile time. C style implicit concatenation without an intervening operator is error prone and not supported in D.
Because hex string literals contain binary data not limited to textual data, they allow additional conversions over other string literals.
A hex string literal implicitly converts to a constantbyte[] orubyte[].
immutableubyte[] b = x"3F 80 00 00";constbyte[] c = x"3F 80 00 00";
A hex string literal can be explicitly cast to an array of integers with a larger size than 1. A big endian byte order in the hex string will be assumed.
staticimmutableuint[] data =cast(immutableuint[]) x"AABBCCDD";staticassert(data[0] == 0xAABBCCDD);
This requires the length of the hex string to be a multiple of the array element's size in bytes.
static e =cast(immutableushort[]) x"AA BB CC";// Error, length of 3 bytes is not a multiple of 2, the size of a `ushort`
When a hex string literal gets constant folded, the result is no longer considered a hex string literal
staticimmutablebyte[] b = x"AA" ~"G";// Error: cannot convert `string` to `immutable byte[]`
ArrayLiteral:[ArgumentListopt]
An array literal is a comma-separated list of expressions between square brackets[ and]. The expressions form the elements of a dynamic array. The length of the array is the number of elements.
The element type of the array is inferred as the common type of all the elements, and each expression is implicitly converted to that type. When there is an expected array type, the elements of the literal will be implicitly converted to the expected element type.
auto a1 = [1, 2, 3];// type is int[], with elements 1, 2 and 3auto a2 = [1u, 2, 3];// type is uint[], with elements 1u, 2u, and 3ubyte[] a3 = [1, 2, 3];// OKbyte[] a4 = [128];// error
int[2] sa = [1, 2];// OKint[2] sb = [1];// error
If anyArrayMemberInitialization is aValueSeq, then the elements of theValueSeq are inserted as expressions in place of the sequence.
Escaping array literals are always allocated on the memory managed heap. Thus, they can be returned safely from functions:
int[] foo(){return [1, 2, 3];}
An array literal is not GC allocated if:
void f(scopeint[] a,int[2] sa) @nogc{ sa = [7, 8];}void g(int[] b) @nogc;// `b` is not scope, so may escapevoid main() @nogc{int[3] sa = [1, 2, 3]; f([1, 2], [3, 4]);//scope int[] a = [5, 6]; // requires `-preview=dip1000`//g([1, 2]); // error, array literal heap allocatedassert([1, 2] < [3, 2]);assert([1, 2][1] == 2);foreach (e; [4, 2, 9])assert(e > 0);}
When array literals are cast to another array type, each element of the array is cast to the new element type. When arrays that are not literalsare cast, the array is reinterpreted as the new type, and the length is recomputed:
// cast array literalconstubyte[] ct =cast(ubyte[]) [257, 257];// this is equivalent to:// const ubyte[] ct = [cast(ubyte) 257, cast(ubyte) 257];writeln(ct);// writes [1, 1]// cast other array expression// --> normal behavior of CastExpressionbyte[] arr = [1, 1];short[] rt =cast(short[]) arr;writeln(rt);// writes [257]
AssocArrayLiteral:[KeyValuePairs]KeyValuePairs:KeyValuePairKeyValuePair,KeyValuePairsKeyValuePair:KeyExpression:ValueExpressionKeyExpression:AssignExpressionValueExpression:AssignExpression
Associative array literals are a comma-separated list ofkey:value pairs between square brackets[ and]. The list cannot be empty. The common type of the all keys is taken to be the key type of the associative array, and all keys are implicitly converted to that type. The common type of the all values is taken to be the value type of the associative array, and all values are implicitly converted to that type. AnAssocArrayLiteral cannot be used to statically initialize anything.
[21u:"he", 38:"ho", 2:"hi"];// type is string[uint],// with keys 21u, 38u and 2u// and values "he", "ho", and "hi"
If any of the keys or values in theKeyValuePairs are aValueSeq, then the elements of theValueSeq are inserted as arguments in place of the sequence.
Associative array initializers may contain duplicate keys, however, in that case, the lastKeyValuePair lexicographically encountered is stored.
auto aa = [21:"he", 38:"ho", 2:"hi", 2:"bye"];assert(aa[2] =="bye")
FunctionLiteral:functionRefOrAutoRefoptBasicTypeWithSuffixesoptParameterWithAttributesoptFunctionLiteralBodydelegateRefOrAutoRefoptBasicTypeWithSuffixesoptParameterWithMemberAttributesoptFunctionLiteralBodyRefOrAutoRefoptParameterWithMemberAttributesFunctionLiteralBodyBlockStatementIdentifier=>AssignExpressionBasicTypeWithSuffixes:BasicTypeTypeSuffixesoptParameterWithAttributes:ParametersFunctionAttributesoptParameterWithMemberAttributes:ParametersMemberFunctionAttributesoptFunctionLiteralBody:=>AssignExpressionSpecifiedFunctionBodyRefOrAutoRef:refauto ref
FunctionLiterals enable embedding anonymous functions and anonymous delegates directly into expressions. Short function literals are known aslambdas.
For example:
intfunction(char c) fp;// declare pointer to a functionvoid test(){staticint foo(char c) {return 6; } fp = &foo;}is exactly equivalent to:
intfunction(char c) fp;void test(){ fp =functionint(char c) {return 6; };}
A delegate is necessary if theFunctionLiteralBody accesses any non-static local variables in enclosing functions.
int abc(intdelegate(int i));void test(){int b = 3;int foo(int c) {return 6 + b; } abc(&foo);}is exactly equivalent to:
int abc(intdelegate(int i));void test(){int b = 3; abc(delegateint(int c) {return 6 + b; } );}
The use ofref declares that the return value is returned by reference:
void main(){int x;auto dg =delegaterefint() {return x; }; dg() = 3;assert(x == 3);}
If a literal omitsfunction ordelegate and there's no expected type from the context, then it is inferred to be a delegate if it accesses a variable in an enclosing function, otherwise it is a function pointer.
void test(){int b = 3;auto fp = (uint c) {return c * 2; };// inferred as function pointerauto dg = (int c) {return 6 + b; };// inferred as delegatestaticassert(!is(typeof(fp) ==delegate));staticassert(is(typeof(dg) ==delegate));}
If a delegate is expected, the literal will be inferred as a delegate even if it accesses no variables from an enclosing function:
void abc(intdelegate(int i)) {}void def(uintfunction(uint s)) {}void test(){int b = 3; abc( (int c) {return 6 + b; } );// inferred as delegate abc( (int c) {return c * 2; } );// inferred as delegate def( (uint c) {return c * 2; } );// inferred as function//def( (uint c) { return c * b; } ); // error!// Because the FunctionLiteral accesses b, its type// is inferred as delegate. But def cannot accept a delegate argument.}
If the type of a function literal can be uniquely determined from its context, parameter type inference is possible.
void foo(intfunction(int) fp);void test(){intfunction(int) fp = (n) {return n * 2; };// The type of parameter n is inferred as int. foo((n) {return n * 2; });// The type of parameter n is inferred as int.}
auto fp = (i) {return 1; };// error, cannot infer type of `i`
Function literals can bealiased. Aliasing a function literal with unspecified parameter types produces afunction template with type parameters for each unspecified parameter type of the literal. Type inference for the literal is then done when the template is instantiated.
alias fpt = (i) {return i; };// ok, infer type of `i` when used//auto fpt(T)(T i) { return i; } // equivalentauto v = fpt(4);// `i` is inferred as intauto d = fpt(10.3);// `i` is inferred as doublealias fp = fpt!float;auto f = fp(0);// f is a float
The return type of theFunctionLiteral can be inferred from either theAssignExpression, or anyReturnStatements in theBlockStatement. If there is a different expected type from the context, and the initial inferred return type implicitly converts to the expected type, then the return type is inferred as the expected type.
auto fi = (int i) {return i; };staticassert(is(typeof(fi(5)) ==int));longfunction(int) fl = (int i) {return i; };staticassert(is(typeof(fl(5)) ==long));
Parameters can be omitted completely for a function literal when there is aBlockStatement function body.
auto f = { writeln("hi"); };// OK, f has type `void function()`f();{ writeln("hi"); }();// error() { writeln("hi"); }();// OK
void loop(int n,voiddelegate() statement){foreach (_; 0 .. n) { statement(); }}void main(){int n = 0; loop(5, { n += 1; });assert(n == 5);}
The syntax=> AssignExpression is equivalent to{ return AssignExpression; }.
void main(){auto i = 3;auto twice =function (int x) => x * 2;assert(twice(i) == 6);auto square =delegate () => i * i;assert(square() == 9);auto n = 5;auto mul_n = (int x) => x * n;assert(mul_n(i) == 15);}
The syntaxIdentifier => AssignExpression is equivalent to(Identifier) { return AssignExpression; }.
// the following two declarations are equivalentalias fp = i => 1;alias fp = (i) {return 1; };
int motor(alias fp)(int i){return fp(i) + 1;}int engine(){return motor!(i => i * 2)(6);// returns 13}
The implicit conversions of built-in scalar types can be explicitly represented by using function call syntax. For example:
auto a =short(1);// implicitly convert an integer literal '1' to shortauto b =double(a);// implicitly convert a short variable 'a' to doubleauto c =byte(128);// error, 128 cannot be represented in a byte
If the argument is omitted, it means default construction of the scalar type:
auto a =ushort();// same as: ushort.initauto b =wchar();// same as: wchar.init
The argument may not be given a name:
auto a =short(x: 1);// Error
See also:Usual Arithmetic Conversions.
AssertExpression:assert (AssertArguments)AssertArguments:AssignExpressionAssignExpression,AssignExpression,AssignExpressionAssignExpression,AssignExpression,
The firstAssignExpression is evaluated andconverted to a boolean value. If the value is nottrue, anAssert Failure has occurred and the program enters anInvalid State.
int i = fun();assert(i > 0);
AssertExpression has different semantics if it is in aunittest orin contract.
If the firstAssignExpression is a reference to a class instance for which aclassInvariant exists, the classInvariant must hold.
If the firstAssignExpression is a pointer to a struct instance for which astructInvariant exists, the structInvariant must hold.
The type of anAssertExpression isvoid.
auto x = 4;assert(x < 3);When in use, the above will throw anAssertError with a message4 >= 3.
If the firstAssignExpression consists entirely of compile time constants, and evaluates tofalse, it is a special case - it signifies that subsequent statements are unreachable code. Compile Time Function Execution (CTFE) calls are not attempted for the evaluation. Such anAssertExpression has typenoreturn.
This allows the compiler to suppress an error when there is a missing return statement:
int f(int x){if (x > 0) {return 5 / x; }assert(0);// no need to use a dummy return statement here}
The implementation may handle the case of the firstAssignExpression evaluating tofalse at compile time differently - even when otherasserts are ignored, it may still generate aHLT instruction or equivalent.
See also:static assert.
The secondAssignExpression, if present, must be implicitly convertible to typeconst(char)[]. When present, the implementation may evaluate it and print the resulting message upon assert failure:
void main(){assert(0,"an" ~" error message");}
When compiled and run, typically it will produce the message:
core.exception.AssertError@test.d(3) an error message
MixinExpression:mixin (ArgumentList)
EachAssignExpression in theArgumentList is evaluated at compile time, and the result must be representable as a string. The resulting strings are concatenated to form a string. The text contents of the string must be compilable as a validExpression, and is compiled as such.
int foo(int x){returnmixin("x +", 1) * 7;// same as ((x + 1) * 7)}
ImportExpression:import (AssignExpression)
TheAssignExpression must evaluate at compile time to a constant string. The text contents of the string are interpreted as a file name. The file is read, and the exact contents of the file become ahex string literal.
Implementations may restrict the file name in order to avoid directory traversal security vulnerabilities. A possible restriction might be to disallow any path components in the file name.
Note that by default an import expression will not compile unless one or more paths are passed via the-J switch. This tells the compiler where it should look for the files to import. This is a security feature.
void foo(){// Prints contents of file foo.txt writeln(import("foo.txt"));}
NewExpression:newTypenewType[AssignExpression]newType(NamedArgumentListopt)NewAnonClassExpression
NewExpressions allocate memory on thegarbage collected heap by default.
new T constructs an instance of typeT and default-initializes it. The result's type is:
int* i =newint;assert(*i == 0);// int.initObject o =new Object;//int[] a = new int[]; // error, need length argument
TheType(NamedArgumentList) form allows passing either a single initializer of the same type, or multiple arguments for more complex types:
int* i =newint(5);assert(*i == 5);Exception e =new Exception("info");assert(e.msg =="info");int[] a =newint[](2);assert(a.length == 2);a =newint[2];// same, see below
TheType[AssignExpression] form allocates a dynamic array with length equal toAssignExpression. It is preferred to use theType(NamedArgumentList) form when allocating dynamic arrays instead, as it is more general.
The result is aunique expression which can implicitly convert to other qualifiers:
immutable o =new Object;
If aNewExpression is used with a class type as an initializer for a function local variable withscope storage class, then the instance isallocated on the stack.
new can also be used to allocate anested class.
To allocate multidimensional arrays, the declaration reads in the same order as the prefix array declaration order.
char[][] foo;// dynamic array of strings...foo =newchar[][30];// allocate array of 30 strings
The above allocation can also be written as:
foo =newchar[][](30);// allocate array of 30 strings
To allocate the nested arrays, multiple arguments can be used:
int[][][] bar;bar =newint[][][](5, 20, 30);assert(bar.length == 5);assert(bar[0].length == 20);assert(bar[0][0].length == 30);
bar =newint[][][5];foreach (ref a; bar){ a =newint[][20];foreach (ref b; a) { b =newint[30]; }}
TypeidExpression:typeid (Type)typeid (Expression)
IfType, returns an instance of classTypeInfo corresponding toType.
IfExpression, returns an instance of classTypeInfo corresponding to the type of theExpression. If the type is a class, it returns theTypeInfo of the dynamic type (i.e. the most derived type). TheExpression is always executed.
class A { }class B : A { }void main(){import std.stdio; writeln(typeid(int));// intuint i; writeln(typeid(i++));// uint writeln(i);// 1 A a =new B(); writeln(typeid(a));// B writeln(typeid(typeof(a)));// A}
IsExpression:is (Type)is (Type:TypeSpecialization)is (Type==TypeSpecialization)is (Type:TypeSpecialization,TemplateParameterList)is (Type==TypeSpecialization,TemplateParameterList)is (TypeIdentifier)is (TypeIdentifier:TypeSpecialization)is (TypeIdentifier==TypeSpecialization)is (TypeIdentifier:TypeSpecialization,TemplateParameterList)is (TypeIdentifier==TypeSpecialization,TemplateParameterList)TypeSpecialization:TypeTypeCtorstructunionclassinterfaceenum__vectorfunctiondelegatesuperreturn__parametersmodulepackage
AnIsExpression is evaluated at compile time and is used to check if an expression is a valid type. In addition, there are forms which can also:
The result of anIsExpression is a boolean which istrue if the condition is satisfied andfalse if not.
Type is the type being tested. It must be syntactically correct, but it need not be semantically correct. If it is not semantically correct, the condition is not satisfied.
TypeSpecialization is the type thatType is being pattern matched against.
IsExpressions may be used in conjunction withtypeof to check whether an expression type checks correctly. For example,is(typeof(foo)) will returntrue iffoo has a valid type.
The condition is satisfied ifType is semantically correct.Type must be syntactically correct regardless.
pragma(msg,is(5));// errorpragma(msg,is([][]));// error
int i;staticassert(is(int));staticassert(is(typeof(i)));// samestaticassert(!is(Undefined));staticassert(!is(typeof(int)));// int is not an expressionstaticassert(!is(i));// i is a valuealias Func =int(int);// function typestaticassert(is(Func));staticassert(!is(Func[]));// fails as an array of functions is not allowed
The condition is satisfied ifType is semantically correct and it is the same as or can be implicitly converted toTypeSpecialization.TypeSpecialization is only allowed to be aType.
alias Bar =short;staticassert(is(Bar :int));// short implicitly converts to intstaticassert(!is(Bar : string));
IfTypeSpecialization is a type, the condition is satisfied ifType is semantically correct and is the same type asTypeSpecialization.
alias Bar =short;staticassert(is(Bar ==short));staticassert(!is(Bar ==int));
IfTypeSpecialization is aTypeCtor then the condition is satisfied ifType is of thatTypeCtor:
staticassert(is(constint ==const));staticassert(is(constint[] ==const));staticassert(!is(const(int)[] ==const));// head is mutablestaticassert(!is(immutableint ==const));
IfTypeSpecialization is one ofstructunionclassinterfaceenum__vectorfunctiondelegatemodulepackage then the condition is satisfied ifType is one of those.
Object o;staticassert(!is(o ==class));// `o` is not a typestaticassert(is(Object ==class));staticassert(is(ModuleInfo ==struct));staticassert(!is(int ==class));void f();staticassert(!is(f ==function));// `f` is not a typestaticassert(is(typeof(f) ==function));staticassert(!is(typeof(&f) ==function));// function pointer is not a function
Themodule andpackage forms are satisfied whenType is a symbol, not atype, unlike the other forms. TheisModule andisPackage__traits should be used instead.Package modules are considered to be both packages and modules.
TypeSpecialization can also be one of these keywords:
| keyword | condition |
|---|---|
| super | true ifType is a class or interface |
| return | true ifType is a function, delegate or function pointer |
| __parameters | true ifType is a function, delegate or function pointer |
class C {}staticassert(is(C ==super));void foo(int i);staticassert(!is(foo ==return));staticassert(is(typeof(foo) ==return));staticassert(is(typeof(foo) ==__parameters));
See also:Traits.
Identifier is declared to be an alias of the resulting type if the condition is satisfied. TheIdentifier forms can only be used if theIsExpression appears in aStaticIfCondition or the first argument of aStaticAssert.
The condition is satisfied ifType is semantically correct. If so,Identifier is declared to be an alias ofType.
struct S{int i, j;}staticassert(is(typeof(S.i) T) && T.sizeof == 4);
alias Bar =short;void foo(){staticif (is(Bar T))alias S = T;elsealias S =long;pragma(msg, S);// short// if T was defined, it remains in scopeif (is(T))pragma(msg, T);// short//if (is(Bar U)) {} // error, cannot declare U here}
IfTypeSpecialization is a type, the condition is satisfied ifType is semantically correct and it is the same as or can be implicitly converted toTypeSpecialization.Identifier is declared to be an alias of theTypeSpecialization.
alias Bar =int;staticif (is(Bar T :int))alias S = T;elsealias S =long;staticassert(is(S ==int));
IfTypeSpecialization is a type pattern involvingIdentifier, type deduction ofIdentifier is attempted based on eitherType or a type that it implicitly converts to. The condition is only satisfied if the type pattern is matched.
struct S{long* i;alias ithis;// S converts to long*}staticif (is(S U : U*))// S is matched against the pattern U*{ U u;}staticassert(is(U ==long));
The way the type ofIdentifier is determined is analogous to the way template parameter types are determined byTemplateTypeParameterSpecialization.
IfTypeSpecialization is a type, the condition is satisfied ifType is semantically correct and is the same type asTypeSpecialization.Identifier is declared to be an alias of theTypeSpecialization.
const x = 5;staticif (is(typeof(x) T ==constint))// satisfied, T is now definedalias S = T;staticassert(is(T));// T is in scopepragma(msg, T);// const int
IfTypeSpecialization is a type pattern involvingIdentifier, type deduction ofIdentifier is attempted based onType. The condition is only satisfied if the type pattern is matched.
alias Foo =long*;staticif (is(Foo U == U*))// Foo is matched against the pattern U*{ U u;}staticassert(is(U ==long));
IfTypeSpecialization is a valid keyword for theis(Type == Keyword) form, the condition is satisfied in the same manner.Identifier is set as follows:
| keyword | alias type forIdentifier |
|---|---|
| struct | Type |
| union | Type |
| class | Type |
| interface | Type |
| super | TypeSeq of base classes and interfaces |
| enum | the base type of the enum |
| __vector | the static array type of the vector |
| function | TypeSeq of the function parameter types. For C- and D-style variadic functions, only the non-variadic parameters are included. For typesafe variadic functions, the... is ignored. |
| delegate | the function type of the delegate |
| return | the return type of the function, delegate, or function pointer |
| __parameters | the parameter sequence of a function, delegate, or function pointer. This includes the parameter types, names, and default values. |
| const | Type |
| immutable | Type |
| inout | Type |
| shared | Type |
| module | the module |
| package | the package |
enum E :byte { Emember }staticif (is(E V ==enum))// satisfied, E is an enum V v;// v is declared to be a bytestaticassert(is(V ==byte));
is (Type:TypeSpecialization,TemplateParameterList)is (Type==TypeSpecialization,TemplateParameterList)is (TypeIdentifier:TypeSpecialization,TemplateParameterList)is (TypeIdentifier==TypeSpecialization,TemplateParameterList)
More complex types can be pattern matched. TheTemplateParameterList declares symbols based on the parts of the pattern that are matched, analogously to the way implied template parameters are matched.
Example: Matching a Template Instantiation
struct Tuple(T...){// ...}alias Tup2 = Tuple!(int, string);staticif (is(Tup2 : Template!Args,alias Template, Args...)){staticassert(__traits(isSame, Template, Tuple));staticassert(is(Template!(int, string) == Tup2));// same struct}staticassert(is(Args[0] ==int));staticassert(is(Args[1] == string));
Type cannot be matched whenTypeSpecialization is analias template instance:
struct S(T) {}alias A(T) = S!T;staticassert(is(A!int : S!T, T));//static assert(!is(A!int : A!T, T));
Example: Matching an Associative Array
alias AA =long[string];staticif (is(AA T : T[U], U : string))// T[U] is the pattern{pragma(msg, T);// longpragma(msg, U);// string}// no match, B is not an intstaticassert(!is(AA A : A[B], B :int));
Example: Matching a Static Array
staticif (is(int[10] W : W[len],int len))// W[len] is the pattern{staticassert(len == 10);}staticassert(is(W ==int));// no match, len should be 10staticassert(!is(int[10] X : X[len],int len : 5));
RvalueExpression:__rvalue (AssignExpression)
TheRvalueExpression causes the embeddedAssignExpression to be treated as an rvalue whether it is an rvalue or an lvalue.
If both ref and non-ref parameter overloads are present, an rvalue is preferably matched to the non-ref parameters, and an lvalue is preferably matched to the ref parameter. AnRvalueExpression will preferably match with the non-ref parameter.
An rvalue argument is owned by the function called. Hence, if an lvalue is matched to the rvalue argument, a copy is made of the lvalue to be passed to the function. The function will then call the destructor (if any) on the parameter at the conclusion of the function. An rvalue argument is not copied, as it is assumed to already be unique, and is also destroyed at the conclusion of the function.
The called function's semantics are the same whether a parameter originated as an rvalue or is a copy of an lvalue. This means that anRvalueExpression argument destroys the expression upon function return. Attempts to continue to use the lvalue expression are invalid. The compiler won't always be able to detect a use after being passed to the function, which means that the destructor for the object must reset the object's contents to its initial value, or at least a benign value that can be destructed more than once.
struct S{ubyte* p; ~this() { free(p);// add `p = null;` here to prevent double free }}void aggh(S s){}void oops(){ S s; s.p =cast(ubyte*)malloc(10); aggh(__rvalue(s));// destructor of s called at end of scope, double-freeing s.p}
RvalueExpressions enable the use ofmove constructors andmove assignments.
It is also allowed to use the__rvalue keyword as a function attribute. The presence of this attribute will infer theRvalueExpression upon the function call expression at the call-site, as if applied to the function's return value. This is only accepted on functions that return by reference.
ref S fun(returnref S s)__rvalue{return s;}S s;S t = fun(s);// call inferred as: __rvalue(fun(s))
SpecialKeyword:__FILE____FILE_FULL_PATH____MODULE____LINE____FUNCTION____PRETTY_FUNCTION__
__FILE__ and__LINE__ expand to the source file name and line number at the point of instantiation. The path of the source file is left up to the compiler.
__FILE_FULL_PATH__ expands to the absolute source file name at the point of instantiation.
__MODULE__ expands to the module name at the point of instantiation.
__FUNCTION__ expands to the fully qualified name of the function at the point of instantiation.
__PRETTY_FUNCTION__ is similar to__FUNCTION__, but also expands the function return type, its parameter types, and its attributes.
Example:
module test;import std.stdio;void test(string file =__FILE__, size_t line =__LINE__, string mod =__MODULE__, string func =__FUNCTION__, string pretty =__PRETTY_FUNCTION__, string fileFullPath =__FILE_FULL_PATH__){ writefln("file: '%s', line: '%s', module: '%s',\nfunction: '%s', " ~"pretty function: '%s',\nfile full path: '%s'", file, line, mod, func, pretty, fileFullPath);}int main(string[] args){ test();return 0;}
Assuming the file was at /example/test.d, this will output:
file: 'test.d', line: '13', module: 'test',function: 'test.main', pretty function: 'int test.main(string[] args)',file full path: '/example/test.d'
Warning: Do not usemixin(__FUNCTION__) to get the symbol for the current function. This seems to be a common thing for programmers to attempt when trying to get the symbol for the current function in order to do introspection on it, since D does not currently have a direct way to get that symbol. However, usingmixin(__FUNCTION__) means that the symbol for the function will be looked up by name, which means that it's subject to the various rules that go with symbol lookup, which can cause various problems. One such problem would that if a function is overloaded, the result will be the first overload whether that's the current function or not.
Given that D doesn't currently have a way to directly get the symbol for the current function, the best way to do it is to get the parent symbol of a symbol within the function, since that avoids any issues surrounding symbol lookup rules. An example of that which doesn't rely on any other symbols is__traits(parent {}). It declares an anonymous, nested function, whose parent is then the current function. So, getting its parent gets the symbol for the current function.
An implementation may rearrange the evaluation of expressions according to arithmetic associativity and commutativity rules as long as, within that thread of execution, no observable difference is possible.
This rule precludes any associative or commutative reordering of floating point expressions.