D Programming Language
Improve this page Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using local clone. Page wiki View or edit the community-maintained wiki page associated with this page.

# Expressions

C and C++ programmers will find the D expressions very familiar, with a few interesting additions.

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.

### Order Of Evaluation

The following binary expressions are evaluated in strictly left-to-right order:

OrExpression, XorExpression, AndExpression, CmpExpression, ShiftExpression, AddExpression, CatExpression, MulExpression, PowExpression, CommaExpression, OrOrExpression, AndAndExpression

The following binary expressions are evaluated in an implementation-defined order:

AssignExpression, function arguments

It is an error to depend on order of evaluation when it is not specified. For example, the following are illegal:

    i = i++;

If the compiler can determine that the result of an expression is illegally dependent on the order of evaluation, it can issue an error (but is not required to). The ability to detect these kinds of errors is a quality of implementation issue.

The evaluation order of function arguments is defined to be left to right. This is similar to Java but different to C/C++ where the evaluation order is unspecified. Thus, the following code is valid and well defined.

    import std.conv;
    int i = 0;
    assert(text(++i, ++i) == "12"); // left to right evaluation of arguments

But even though the order of evaluation is well defined writing code that depends on it is rarely recommended. Note that dmd currently does not comply with left to right evaluation of function arguments.

### Expressions

    Expression:
        CommaExpression

    CommaExpression:
        AssignExpression
        AssignExpression , CommaExpression

The left operand of the , is evaluated, then the right operand is evaluated. The type of the expression is the type of the right operand, and the result is the result of the right operand.

### Assign Expressions

    AssignExpression:
        ConditionalExpression
        ConditionalExpression = AssignExpression
        ConditionalExpression += AssignExpression
        ConditionalExpression -= AssignExpression
        ConditionalExpression *= AssignExpression
        ConditionalExpression /= AssignExpression
        ConditionalExpression %= AssignExpression
        ConditionalExpression &= AssignExpression
        ConditionalExpression |= AssignExpression
        ConditionalExpression ^= AssignExpression
        ConditionalExpression ~= AssignExpression
        ConditionalExpression <<= AssignExpression
        ConditionalExpression >>= AssignExpression
        ConditionalExpression >>>= AssignExpression
        ConditionalExpression ^^= AssignExpression

The right operand is implicitly converted to the type of the left operand, and assigned to it. The result type is the type of the lvalue, and the result value is the value of the lvalue after the assignment.

The left operand must be an lvalue.

#### Assignment Operator Expressions

Assignment operator expressions, such as:

    a op= b

are semantically equivalent to:

    a = cast(typeof(a))(a op b)

except that:

- operand a is only evaluated once
- overloading op uses a different function than overloading op= does
- the left operand of >>>= does not undergo integral promotions before shifting

### Conditional Expressions

    ConditionalExpression:
        OrOrExpression
        OrOrExpression ? Expression : ConditionalExpression

The first expression is converted to bool, and is evaluated.

If it is true, then the second expression is evaluated, and its result is the result of the conditional expression.

If it is false, 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 type void, then the resulting type is void. Otherwise, the second and third expressions are implicitly converted to a common type which becomes the result type of the conditional expression.

### OrOr Expressions

    OrOrExpression:
        AndAndExpression
        OrOrExpression || AndAndExpression

The result type of an OrOrExpression is bool, unless the right operand has type void, when the result is type void.

The OrOrExpression evaluates its left operand.

If the left operand, converted to type bool, evaluates to true, then the right operand is not evaluated. If the result type of the OrOrExpression is bool then the result of the expression is true.

If the left operand is false, then the right operand is evaluated. If the result type of the OrOrExpression is bool then the result of the expression is the right operand converted to type bool.

### AndAnd Expressions

    AndAndExpression:
        OrExpression
        AndAndExpression && OrExpression

The result type of an AndAndExpression is bool, unless the right operand has type void, when the result is type void.

The AndAndExpression evaluates its left operand.

If the left operand, converted to type bool, evaluates to false, then the right operand is not evaluated. If the result type of the AndAndExpression is bool then the result of the expression is false.

If the left operand is true, then the right operand is evaluated. If the result type of the AndAndExpression is bool then the result of the expression is the right operand converted to type bool.

### Bitwise Expressions

Bit wise expressions perform a bitwise operation on their operands. Their operands must be integral types. First, the default integral promotions are done. Then, the bitwise operation is done.

#### Or Expressions

    OrExpression:
        XorExpression
        OrExpression | XorExpression

The operands are OR'd together.

#### Xor Expressions

    XorExpression:
        AndExpression
        XorExpression ^ AndExpression

The operands are XOR'd together.

#### And Expressions

    AndExpression:
        CmpExpression
        AndExpression & CmpExpression

The operands are AND'd together.

### Compare Expressions

    CmpExpression:
        ShiftExpression
        EqualExpression
        IdentityExpression
        RelExpression
        InExpression

### Equality Expressions

    EqualExpression:
        ShiftExpression == ShiftExpression
        ShiftExpression != ShiftExpression

Equality expressions compare the two operands for equality (==) or inequality (!=). The type of the result is bool. The operands go through the usual conversions to bring them to a common type before comparison.

If they are integral values or pointers, equality is defined as the bit pattern of the type matches exactly.

Equality for floating point types is more complicated. -0 and +0 compare as equal. If either or both operands are NAN, then both the == returns false and != returns true. Otherwise, the bit patterns are compared for equality.

For complex numbers, equality is defined as equivalent to:

    x.re == y.re && x.im == y.im

and inequality is defined as equivalent to:

    x.re != y.re || x.im != y.im

Equality for struct objects means the logical product of all equality results of the corresponding object fields. If all struct fields use bitwise equality, the whole struct equality could be optimized to one memory comparison operation (the existence of alignment holes in the objects is accounted for, usually by setting them all to 0 upon initialization).

For class and struct objects, the expression (a == b) is rewritten as a.opEquals(b), and (a != b) is rewritten as !a.opEquals(b).

For class objects, the == and != operators compare the contents of the objects. Therefore, comparing against null is invalid, as null has no contents. Use the is and !is operators instead.

    class C;
    C c;
    if (c == null)  // error
        ...
    if (c is null)  // ok
        ...

For static and dynamic arrays, equality is defined as the lengths of the arrays matching, and all the elements are equal.

#### Identity Expressions

    IdentityExpression:
        ShiftExpression is ShiftExpression
        ShiftExpression !is ShiftExpression

The is compares for identity. To compare for not identity, use e1 !is e2. The type of the result is bool. The operands go through the usual conversions to bring them to a common type before comparison.

For class objects, identity is defined as the object references are for the same object. Null class objects can be compared with is.

For struct objects, identity is defined as the bits in the struct being identical.

For static and dynamic arrays, identity is defined as referring to the same array elements and the same number of elements.

For other operand types, identity is defined as being the same as equality.

The identity operator is cannot be overloaded.

### Relational Expressions

    RelExpression:
        ShiftExpression < ShiftExpression
        ShiftExpression <= ShiftExpression
        ShiftExpression > ShiftExpression
        ShiftExpression >= ShiftExpression
        ShiftExpression !<>= ShiftExpression
        ShiftExpression !<> ShiftExpression
        ShiftExpression <> ShiftExpression
        ShiftExpression <>= ShiftExpression
        ShiftExpression !> ShiftExpression
        ShiftExpression !>= ShiftExpression
        ShiftExpression !< ShiftExpression
        ShiftExpression !<= ShiftExpression

First, the integral promotions are done on the operands. The result type of a relational expression is bool.

For class objects, the result of Object.opCmp() forms the left operand, and 0 forms the right operand. The result of the relational expression (o1 op o2) is:

    (o1.opCmp(o2) op 0)

It is an error to compare objects if one is null.

For static and dynamic arrays, the result of the relational op 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

Integer comparisons happen when both operands are integral types.

Integer comparison operators
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. Use casts to make both operands signed or both operands unsigned.

#### Floating point comparisons

If one or both operands are floating point, then a floating point comparison is performed.

Useful floating point operations must take into account NAN values. In particular, a relational operator can have NAN operands. The result of a relational operation on float values is less, greater, equal, or unordered (unordered means either or both of the operands is a NAN). That means there are 14 possible comparison conditions to test for:

Floating point comparison operators
Operator | Greater | Less | Equal | Unordered | Exception | Relation
--- | --- | --- | --- | --- | --- | ---
== | F | F | T | F | no | equal
!= | T | T | F | T | no | unordered, less, or greater
> | T | F | F | F | yes | greater
>= | T | F | T | F | yes | greater or equal
< | F | T | F | F | yes | less
<= | F | T | T | F | yes | less or equal
!<>= | F | F | F | T | no | unordered
<> | T | T | F | F | yes | less or greater
<>= | T | T | T | F | yes | less, equal, or greater
!<= | T | F | F | T | no | unordered or greater
!< | T | F | T | T | no | unordered, greater, or equal
!>= | F | T | F | T | no | unordered or less
!> | F | T | T | T | no | unordered, less, or equal
!<> | F | F | T | T | no | unordered or equal

##### Notes:

1. For floating point comparison operators, (a !op b) is not the same as !(a op b).
2. "Unordered" means one or both of the operands is a NAN.
3. "Exception" means the Invalid Exception is raised if one of the operands is a NAN. It does not mean an exception is thrown. The Invalid Exception can be checked using the functions in std.c.fenv.

#### Class comparisons

For class objects, the relational operators compare the contents of the objects. Therefore, comparing against null is invalid, as null has no contents.

    class C;
    C c;
    if (c < null)  // error
        ...

### In Expressions

    InExpression:
        ShiftExpression in ShiftExpression
        ShiftExpression !in ShiftExpression

An associative array can be tested to see if an element is in the array:

    int foo[char[]];
    ...
    if ("hello" in foo)
        ...

The in expression has the same precedence as the relational expressions <, <=, etc. The return value of the InExpression is null if the element is not in the array; if it is in the array it is a pointer to the element.

The !in expression is the logical negation of the in operation.

### Shift Expressions

    ShiftExpression:
        AddExpression
        ShiftExpression << AddExpression
        ShiftExpression >> AddExpression
        ShiftExpression >>> AddExpression

The operands must be integral types, and undergo the usual integral 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.

<< is a left shift. >> is a signed right shift. >>> is an unsigned right shift.

It's illegal to shift by the same or more bits than the size of the quantity being shifted:

    int c;
    auto x = c << 33;        // error

### Add Expressions

    AddExpression:
        MulExpression
        AddExpression + MulExpression
        AddExpression - MulExpression
        CatExpression

If the operands are of integral types, they undergo integral promotions, and then are brought to a common type using the usual arithmetic conversions.

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 the usual arithmetic conversions.

If the operator is + or -, and 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.

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.

If both operands are pointers, and the operator is +, then it is illegal. For -, the pointers are subtracted and the result is divided by the size of the type pointed to by the operands. It is an error if the pointers point to different types.

If both operands are of integral types and an overflow or underflow occurs in the computation, wrapping will happen. That is, uint.max + 1 == uint.min and uint.min - 1 == uint.max.

Add expressions for floating point operands are not associative.

### Cat Expressions

    CatExpression:
        AddExpression ~ MulExpression

A CatExpression concatenates arrays, producing a dynmaic array with the result. The arrays must be arrays of the same element type. If one operand is an array and the other is of that array's element type, that element is converted to an array of length 1 of that element, and then the concatenation is performed.

### Mul Expressions

    MulExpression:
        UnaryExpression
        MulExpression * UnaryExpression
        MulExpression / UnaryExpression
        MulExpression % UnaryExpression

The operands must be arithmetic types. They undergo integral promotions, and then are brought to a common type using the usual 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. If the divisor is zero, an Exception is thrown.

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.

### Unary Expressions

    UnaryExpression:
        & UnaryExpression
        ++ UnaryExpression
        -- UnaryExpression
        * UnaryExpression
        - UnaryExpression
        + UnaryExpression
        ! UnaryExpression
        ComplementExpression
        ( Type ) . Identifier
        ( Type ) . TemplateInstance
        DeleteExpression
        CastExpression
        PowExpression

#### Complement Expressions

    ComplementExpression:
        ~ UnaryExpression

ComplementExpressions work on integral types (except bool). All the bits in the value are complemented.

Note: unlike in C and C++, the usual integral promotions are not performed prior to the complement operation.

#### New Expressions

    NewExpression:
        new AllocatorArgumentsopt Type
        NewExpressionWithArgs

    NewExpressionWithArgs:
        new AllocatorArgumentsopt Type [ AssignExpression ]
        new AllocatorArgumentsopt Type ( ArgumentListopt )
        NewAnonClassExpression

    AllocatorArguments:
        ( ArgumentListopt )

    ArgumentList:
        AssignExpression
        AssignExpression ,
        AssignExpression , ArgumentList

NewExpressions are used to allocate memory on the garbage collected heap (default) or using a class or struct specific allocator.

To allocate multidimensional arrays, the declaration reads in the same order as the prefix array declaration order.

    char[][] foo;   // dynamic array of strings
    ...
    foo = new char[][30]; // allocate array of 30 strings

The above allocation can also be written as:

    foo = new char[][](30); // allocate array of 30 strings

To allocate the nested arrays, multiple arguments can be used:

    int[][][] bar;
    ...
    bar = new int[][][](5, 20, 30);

Which is equivalent to:

    bar = new int[][][5];
    foreach (ref a; bar)
    {
        a = new int[][20];
        foreach (ref b; a)
        {
            b = new int[30];
        }
    }

If there is a new ( ArgumentList ), then those arguments are passed to the class or struct specific allocator function after the size argument.

If a NewExpression is used as an initializer for a function local variable with scope storage class, and the ArgumentList to new is empty, then the instance is allocated on the stack rather than the heap or using the class specific allocator.

#### Delete Expressions

    DeleteExpression:
        delete UnaryExpression

If the UnaryExpression is a class object reference, and there is a destructor for that class, the destructor is called for that object instance.

Next, if the UnaryExpression 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 the garbage collector was not used to allocate the memory for the instance, undefined behavior will result.

If the UnaryExpression is a pointer or a dynamic array, the garbage collector is called to immediately release the memory. If the garbage collector was not used to allocate the memory for the instance, undefined behavior will result.

The pointer, dynamic array, or reference is set to null after the delete is performed. Any attempt to reference the data after the deletion via another reference to it will result in undefined behavior.

If UnaryExpression is a variable allocated on the stack, the class destructor (if any) is called for that instance. Neither the garbage collector nor any class deallocator is called.

#### Cast Expressions

    CastExpression:
        cast ( Type ) UnaryExpression
        cast ( TypeCtorsopt ) UnaryExpression

A CastExpression converts the UnaryExpression to Type.

    cast(foo) -p; // cast (-p) to type foo
    (foo) - p;      // subtract p from foo

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.

Note: This is equivalent to the behavior of the dynamic_cast operator in C++.

    class A { ... }
    class B : A { ... }

    void test(A a, B b)
    {
        B bx = a;         // error, need cast
        B bx = cast(B) a; // bx is null if a is not a B
        A ax = b;         // no cast needed
        A ax = cast(A) b; // no runtime check needed for upcast
    }

In order to determine if an object o is an instance of a class B 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 dynamic array to another dynamic array is done only if the array lengths multiplied by the element sizes match. The cast is done as a type paint, with the array length adjusted to match any change in element size. If there's not a match, a runtime error is generated.

    import std.stdio;

    int main()
    {
        byte[] a = [1,2,3];
        auto b = cast(int[])a; // runtime array cast misalignment

        int[] 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);
        return 0;
    }

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 precision
        assert(a == b);
        assert(a == 3.40483);
        assert(a == 3.40483L);
        assert(a == 3.40483F);
        double d = 3.40483; // truncate literal when assigned to variable
        assert(d != a);     // so it is no longer the same
        const double x = 3.40483; // assignment to const is not
        assert(x == a);     // truncated if the initializer is visible
    }

Casting a value v to a struct S, when value is not a struct of the same type, is equivalent to:

    S(v)

Casting to a CastQual replaces the qualifiers to the type of the UnaryExpression.

    shared int x;
    assert(is(typeof(cast(const)x) == const int));

Casting with no Type or CastQual removes any top level const, immutable, shared or inout type modifiers from the type of the UnaryExpression.

    shared int x;
    assert(is(typeof(cast()x) == int));

Casting an expression to void type is allowed to mark that the result is unused. On ExpressionStatement, it could be used properly to avoid "has no effect" error.

    void foo(lazy void exp) {}
    void main()
    {
        foo(10);            // NG - has no effect in expression '10'
        foo(cast(void)10);  // OK
    }

### Pow Expressions

    PowExpression:
        PostfixExpression
        PostfixExpression ^^ UnaryExpression

PowExpression raises its left operand to the power of its right operand.

### Postfix Expressions

    PostfixExpression:
        PrimaryExpression
        PostfixExpression . Identifier
        PostfixExpression . TemplateInstance
        PostfixExpression . NewExpression
        PostfixExpression ++
        PostfixExpression --
        PostfixExpression ( ArgumentListopt )
        TypeCtorsopt BasicType ( ArgumentListopt )
        IndexExpression
        SliceExpression

### Index Expressions

    IndexExpression:
        PostfixExpression [ ArgumentList ]

PostfixExpression is evaluated. If PostfixExpression is an expression of type static array or dynamic array, the symbol $ is set to be the the number of elements in the array. If PostfixExpression is an ExpressionTuple, the symbol $ is set to be the the number of elements in the tuple. A new declaration scope is created for the evaluation of the ArgumentList and $ appears in that scope only.

If PostfixExpression is an ExpressionTuple, then the ArgumentList must consist of only one argument, and that must be statically evaluatable to an integral constant. That integral constant n then selects the nth expression in the ExpressionTuple, which is the result of the IndexExpression. It is an error if n is out of bounds of the ExpressionTuple.

### Slice Expressions

    SliceExpression:
        PostfixExpression [ ]
        PostfixExpression [ AssignExpression .. AssignExpression ]

PostfixExpression is evaluated. if PostfixExpression is an expression of type static array or dynamic array, the special variable $ is declared and set to be the length of the array. A new declaration scope is created for the evaluation of the AssignExpression..AssignExpression and $ appears in that scope only.

The first AssignExpression is taken to be the inclusive lower bound of the slice, and the second AssignExpression is the exclusive upper bound. The result of the expression is a slice of the PostfixExpression array.

If the [ ] form is used, the slice is of the entire array.

The type of the slice is a dynamic array of the element type of the PostfixExpression.

A SliceExpression is not a modifiable lvalue.

If the slice bounds can be known at compile time, the slice expression is implicitly convertible to an lvalue of static array. For example:

    arr[a .. b]     // typed T[]

If both a and b are integers (may be constant-folded), the slice expression can be convered to a static array type T[b - a].

    void foo(int[2] a)
    {
        assert(a == [2, 3]);
    }
    void bar(ref int[2] a)
    {
        assert(a == [2, 3]);
        a[0] = 4;
        a[1] = 5;
        assert(a == [4, 5]);
    }
    void baz(int[3] a) {}

    void main()
    {
        int[] arr = [1, 2, 3];

        foo(arr[1 .. 3]);
        assert(arr == [1, 2, 3]);

        bar(arr[1 .. 3]);
        assert(arr == [1, 4, 5]);

      //baz(arr[1 .. 3]); // cannot match length
    }

Following forms of slice expression can be convertible to a static array type:

e
  An expression that contains no side effects.
a, b
  Integers (that may be constant-folded).

Form | The length calculated at compile time
--- | ---
arr[] | The compile time length of arr if it's known.
arr[a .. b] | b - a
arr[e-a .. e] | a
arr[e .. e+b] | b
arr[e-a .. e+b] | a + b
arr[e+a .. e+b] | b - a if a <= b
arr[e-a .. e-b] | a - b if a >= b

If PostfixExpression is an ExpressionTuple, then the result of the slice is a new ExpressionTuple formed from the upper and lower bounds, which must statically evaluate to integral constants. It is an error if those bounds are out of range.

### Primary Expressions

    PrimaryExpression:
        Identifier
        . Identifier
        TemplateInstance
        . TemplateInstance
        this
        super
        null
        true
        false
        $
        IntegerLiteral
        FloatLiteral
        CharacterLiteral
        StringLiterals
        ArrayLiteral
        AssocArrayLiteral
        FunctionLiteral
        AssertExpression
        MixinExpression
        ImportExpression
        NewExpressionWithArgs
        BasicTypeX . Identifier
        BasicTypeX ( ArgumentListopt )
        TypeCtor ( Type ) . Identifier
        TypeCtor ( Type ) ( ArgumentListopt )
        Typeof
        TypeidExpression
        IsExpression
        ( Expression )
        TraitsExpression
        SpecialKeyword

#### .Identifier

Identifier is looked up at module scope, rather than the current lexically nested scope.

#### this

Within a non-static member function, this resolves to a reference to the object for which the function was called. If the object is an instance of a struct, this will be a pointer to that instance. If a member function is called with an explicit reference to typeof(this), a non-virtual call is made:

    class A
    {
        char get() { return 'A'; }

        char foo() { return typeof(this).get(); }
        char bar() { return this.get(); }
    }

    class B : A
    {
        override char get() { return 'B'; }
    }

    void main()
    {
        B b = new B();

        assert(b.foo() == 'A');
        assert(b.bar() == 'B');
    }

Assignment to this is not allowed.

#### super

super is identical to this, except that it is cast to this's base class. It is an error if there is no base class. It is an error to use super within a struct member function. (Only class Object has no base class.) If a member function is called with an explicit reference to super, a non-virtual call is made.

Assignment to super is not allowed.

#### null

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 type typeof(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.

#### true, false

These are of type bool and when cast to another integral type become the values 1 and 0, respectively.

#### Character Literals

Character literals are single characters and resolve to one of type char, wchar, or dchar. If the literal is a \u escape sequence, it resolves to type wchar. If the literal is a \U escape sequence, it resolves to type dchar. Otherwise, it resolves to the type with the smallest size it will fit into.

#### String Literals

    StringLiterals:
        StringLiteral
        StringLiterals StringLiteral

String literals can implicitly convert to any of the following types, they 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 convered to static array types.

    void foo(char[2] a)
    {
        assert(a == "bc");
    }
    void bar(ref const char[2] a)
    {
        assert(a == "bc");
    }
    void baz(const char[3] a) {}

    void main()
    {
        string str = "abc";
        foo(str[1 .. 3]);
        bar(str[1 .. 3]);
      //baz(str[1 .. 3]); // cannot match length
    }

String literals have a 0 appended to them, which makes them easy to pass to C or C++ functions expecting a const char* string. The 0 is not included in the .length property of the string literal.

#### Array Literals

    ArrayLiteral:
        [ ArgumentListopt ]

Array literals are a comma-separated list of AssignExpressions between square brackets [ and ]. The AssignExpressions form the elements of a dynamic array, the length of the array is the number of elements. The common type of the all elements is taken to be the type of the array element, and all elements are implicitly converted to that type.

    auto a1 = [1,2,3];  // type is int[], with elements 1, 2 and 3
    auto a2 = [1u,2,3]; // type is uint[], with elements 1u, 2u, and 3u

By default, an array literal is typed as a dynamic array, but the element count is known at compile time. So all array literals can be implicitly convered to static array types.

    void foo(long[2] a)
    {
        assert(a == [2, 3]);
    }
    void bar(ref long[2] a)
    {
        assert(a == [2, 3]);
        a[0] = 4;
        a[1] = 5;
        assert(a == [4, 5]);
    }
    void baz(const char[3] a) {}

    void main()
    {
        long[] arr = [1, 2, 3];

        foo(arr[1 .. 3]);
        assert(arr == [1, 2, 3]);

        bar(arr[1 .. 3]);
        assert(arr == [1, 4, 5]);

      //baz(arr[1 .. 3]); // cannot match length
    }

If any of the arguments in the ArgumentList are an ExpressionTuple, then the elements of the ExpressionTuple are inserted as arguments in place of the tuple.

Array literals are allocated on the memory managed heap. Thus, they can be returned safely from functions:

    int[] foo()
    {
        return [1, 2, 3];
    }

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 literals are cast, the array is reinterpreted as the new type, and the length is recomputed:

    import std.stdio;

    void main()
    {
        // cast array literal
        const short[] ct = cast(short[]) [cast(byte)1, 1];
        // this is equivalent with:
        // const short[] ct = [cast(short)1, cast(short)1];
        writeln(ct);  // writes [1, 1]

        // cast other array expression
        // --> normal behavior of CastExpression
        byte[] arr = [cast(byte)1, cast(byte)1];
        short[] rt = cast(short[]) arr;
        writeln(rt);  // writes [257]
    }

In other words, casting literal expression will change the literal type.

#### Associative Array Literals

    AssocArrayLiteral:
        [ KeyValuePairs ]

    KeyValuePairs:
        KeyValuePair
        KeyValuePair , KeyValuePairs

    KeyValuePair:
        KeyExpression : ValueExpression

    KeyExpression:
        AssignExpression

    ValueExpression:
        AssignExpression

Associative array literals are a comma-separated list of key: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. An AssocArrayLiteral 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 the KeyValuePairs are an ExpressionTuple, then the elements of the ExpressionTuple are inserted as arguments in place of the tuple.

#### Function Literals

    FunctionLiteral:
        function Typeopt ParameterAttributes opt FunctionLiteralBody
        delegate Typeopt ParameterAttributes opt FunctionLiteralBody
        ParameterAttributes FunctionLiteralBody
        FunctionLiteralBody
        Lambda

    ParameterAttributes:
        Parameters
        Parameters FunctionAttributes

    FunctionLiteralBody:
        BlockStatement
        FunctionContractsopt BodyStatement

FunctionLiterals enable embedding anonymous functions and anonymous delegates directly into expressions. Type is the return type of the function or delegate, if omitted it is inferred from any ReturnStatements in the FunctionLiteralBody. ( ArgumentList ) forms the arguments to the function. If omitted it defaults to the empty argument list ( ). The type of a function literal is pointer to function or pointer to delegate. If the keywords function or delegate are omitted, it is inferred from whether FunctionLiteralBody is actually accessing to the outer context.

For example:

    int function(char c) fp; // declare pointer to a function

    void test()
    {
        static int foo(char c) { return 6; }

        fp = &foo;
    }

is exactly equivalent to:

    int function(char c) fp;

    void test()
    {
        fp = function int(char c) { return 6;} ;
    }

And:

    int abc(int delegate(int i));

    void test()
    {
        int b = 3;
        int foo(int c) { return 6 + b; }

        abc(&foo);
    }

is exactly equivalent to:

    int abc(int delegate(int i));

    void test()
    {
        int b = 3;

        abc( delegate int(int c) { return 6 + b; } );
    }

and the following where the return type int and function/delegate are inferred:

    int abc(int delegate(int i));
    int def(int function(int s));

    void test()
    {
        int b = 3;

        abc( (int c) { return 6 + b; } );  // inferred to delegate
        def( (int c) { return c * 2; } );  // inferred to function
      //def( (int c) { return c * b; } );  // error!
        // Because the FunctionLiteralBody accesses b, then the function literal type
        // is inferred to delegate. But def cannot receive delegate.
    }

If the type of a function literal can be uniquely determined from its context, the parameter type inference is possible.

    void foo(int function(int) fp);

    void test()
    {
        int function(int) fp = (n) { return n * 2; };
        // The type of parameter n is inferred to int.

        foo((n) { return n * 2; });
        // The type of parameter n is inferred to int.
    }

Anonymous delegates can behave like arbitrary statement literals. For example, here an arbitrary statement is executed by a loop:

    double test()
    {
        double d = 7.6;
        float f = 2.3;

        void loop(int k, int j, void delegate() statement)
        {
            for (int i = k; i < j; i++)
            {
                statement();
            }
        }

        loop(5, 100, { d += 1; });
        loop(3, 10,  { f += 3; });

        return d + f;
    }

When comparing with nested functions, the function form is analogous to static or non-nested functions, and the delegate form is analogous to non-static nested functions. In other words, a delegate literal can access stack variables in its enclosing function, a function literal cannot.

#### Lambdas

    Lambda:
        function Typeopt ParameterAttributes => AssignExpression
        delegate Typeopt ParameterAttributes => AssignExpression
        ParameterAttributes => AssignExpression
        Identifier => AssignExpression

Lambdas are a shorthand syntax for FunctionLiterals.

1. Just one Identifier is rewritten to Parameters:

   ( Identifier )

2. The following part => AssignExpression is rewritten to FunctionLiteralBody:

   { return AssignExpression ; }

Example usage:

    import std.stdio;

    void main()
    {
        auto i = 3;
        auto twice  = function (int x) => x * 2;
        auto square = delegate (int x) => x * x;

        auto n = 5;
        auto mul_n = (int x) => x * n;

        writeln(twice(i));   // prints 6
        writeln(square(i));  // prints 9
        writeln(mul_n(i));   // prints 15
    }

#### Uniform construction syntax for built-in scalar types

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 short
    auto b = double(a); // implicitly convert a short variable 'a' to double
    auto 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.init
    auto b = wchar();   // same as: wchar.init
    auto c = creal();   // same as: creal.init

#### Assert Expressions

    AssertExpression:
        assert ( AssignExpression )
        assert ( AssignExpression , AssignExpression )

The assert expression is used to declare conditions that the programmer asserts must hold at that point in the program if the program logic has been correctly implemented. It can be used both as a debugging tool and as a way of communicating to the compiler facts about the code that it may employ to produce more efficient code.

Programs for which AssignExpression is false are invalid. Subsequent to such a false result, the program is in an invalid, non-recoverable state.

As a debugging tool, the compiler may insert checks to verify that the condition indeed holds by evaluating AssignExpression at runtime. If it evaluates to a non-null class reference, the class invariant is run. Otherwise, if it evaluates to a non-null pointer to a struct, the struct invariant is run. Otherwise, if the result is false, an AssertError is thrown. If the result is true, then no exception is thrown. In this way, if a bug in the code causes the assertion to fail, execution is aborted, prompting the programmer to fix the problem.

It is implementation defined whether the AssignExpression is evaluated at run time or not. Programs that rely on side effects of AssignExpression are invalid.

The result type of an assert expression is void. Asserts are a fundamental part of the Contract Programming support in D.

The expression assert(0) is a special case; it signifies that it is unreachable code. Either AssertError is thrown at runtime if it is reachable, or the execution is halted (on the x86 processor, a HLT instruction can be used to halt execution). The optimization and code generation phases of compilation may assume that it is unreachable code.

The second AssignExpression, if present, must be implicitly convertible to type const(char)[]. It is evaluated if the result is false, and the string result is appended to the AssertError's message.

    void main()
    {
        assert(0, "an" ~ " error message");
    }

When compiled and run, it will produce the message:

    Error: AssertError Failure test.d(3) an error message

#### Mixin Expressions

    MixinExpression:
        mixin ( AssignExpression )

The AssignExpression must evaluate at compile time to a constant string. The text contents of the string must be compilable as a valid Expression, and is compiled as such.

    int foo(int x)
    {
        return mixin("x + 1") * 7;  // same as ((x + 1) * 7)
    }

#### Import Expressions

    ImportExpression:
        import ( AssignExpression )

The AssignExpression 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 a 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 you pass one or more paths 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"));
    }

#### Typeid Expressions

    TypeidExpression:
        typeid ( Type )
        typeid ( Expression )

If Type, returns an instance of class TypeInfo corresponding to Type.

If Expression, returns an instance of class TypeInfo corresponding to the type of the Expression. If the type is a class, it returns the TypeInfo of the dynamic type (i.e. the most derived type). The Expression is always executed.

    class A { }
    class B : A { }

    void main()
    {
        writeln(typeid(int));        // int
        uint i;
        writeln(typeid(i++));        // uint
        writeln(i);                  // 1
        A a = new B();
        writeln(typeid(a));          // B
        writeln(typeid(typeof(a)));  // A
    }

#### IsExpression

    IsExpression:
        is ( Type )
        is ( Type : TypeSpecialization )
        is ( Type == TypeSpecialization )
        is ( Type : TypeSpecialization , TemplateParameterList )
        is ( Type == TypeSpecialization , TemplateParameterList )
        is ( Type Identifier )
        is ( Type Identifier : TypeSpecialization )
        is ( Type Identifier == TypeSpecialization )
        is ( Type Identifier : TypeSpecialization , TemplateParameterList )
        is ( Type Identifier == TypeSpecialization , TemplateParameterList )

    TypeSpecialization:
        Type
        struct
        union
        class
        interface
        enum
        function
        delegate
        super
        const
        immutable
        inout
        shared
        return
        __parameters

IsExpressions are evaluated at compile time and are used for checking for valid types, comparing types for equivalence, determining if one type can be implicitly converted to another, and deducing the subtypes of a type. The result of an IsExpression is an int of type 0 if the condition is not satisified, 1 if it is.

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.

Identifier is declared to be an alias of the resulting type if the condition is satisfied. The Identifier forms can only be used if the IsExpression appears in a StaticIfCondition.

TypeSpecialization is the type that Type is being compared against.

The forms of the IsExpression are:

1. is ( Type )
   The condition is satisfied if Type is semantically correct (it must be syntactically correct regardless).

       alias int func(int);    // func is a alias to a function type
       void foo()
       {
           if (is(func[]))     // not satisfied because arrays of
                               // functions are not allowed
               writeln("satisfied");
           else
               writeln("not satisfied");

           if (is([][]))       // error, [][] is not a syntactically valid type
               ...
       }

2. is ( Type : TypeSpecialization )
   The condition is satisfied if Type is semantically correct and it is the same as or can be implicitly converted to TypeSpecialization. TypeSpecialization is only allowed to be a Type.

       alias bar = short;
       void foo(bar x)
       {
           if (is(bar : int))   // satisfied because short can be
                                // implicitly converted to int
               writeln("satisfied");
           else
               writeln("not satisfied");
       }

3. is ( Type == TypeSpecialization )

   The condition is satisfied if Type is semantically correct and is the same type as TypeSpecialization.

   If TypeSpecialization is one of struct union class interface enum function delegate const immutable shared then the condition is satisfied if Type is one of those.

       alias bar = short;

       void test(bar x)
       {
           if (is(bar == int))   // not satisfied because short is not
                                 // the same type as int
               writeln("satisfied");
           else
               writeln("not satisfied");
       }

4. is ( Type Identifier )
   The condition is satisfied if Type is semantically correct. If so, Identifier is declared to be an alias of Type.

       alias bar = short;
       void foo(bar x)
       {
           static if (is(bar T))
               alias S = T;
           else
               alias S = long;

           writeln(typeid(S)); // prints "short"
           if (is(bar T))      // error, Identifier T form can
                               // only be in StaticIfConditions
               ...
       }

5. is ( Type Identifier : TypeSpecialization )

   The condition is satisfied if Type is the same as TypeSpecialization, or if Type is a class and TypeSpecialization is a base class or base interface of it. The Identifier is declared to be either an alias of the TypeSpecialization or, if TypeSpecialization is dependent on Identifier, the deduced type.

       alias bar = int;
       alias abc = long*;
       void foo(bar x, abc a)
       {
           static if (is(bar T : int))
               alias S = T;
           else
               alias S = long;

           writeln(typeid(S));  // prints "int"

           static if (is(abc U : U*))
           {
               U u;
               writeln(typeid(typeof(u)));  // prints "long"
           }
       }

   The way the type of Identifier is determined is analogous to the way template parameter types are determined by TemplateTypeParameterSpecialization.

6. is ( Type Identifier == TypeSpecialization )

   The condition is satisfied if Type is semantically correct and is the same as TypeSpecialization. The Identifier is declared to be either an alias of the TypeSpecialization or, if TypeSpecialization is dependent on Identifier, the deduced type.

   If TypeSpecialization is one of struct union class interface enum function delegate const immutable shared then the condition is satisfied if Type is one of those. Furthermore, Identifier is set to be an alias of the type:

   keyword | alias type for Identifier
   --- | ---
   struct | Type
   union | Type
   class | Type
   interface | Type
   super | TypeTuple of base classes and interfaces
   enum | the base type of the enum
   function | TypeTuple 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 tuple of a function, delegate, or function pointer. This includes the parameter types, names, and default values.
   const | Type
   immutable | Type
   shared | Type

       alias bar = short;
       enum E : byte { Emember }
       void foo(bar x)
       {
           static if (is(bar T == int))   // not satisfied, short is not int
               alias S = T;
           alias U = T;                   // error, T is not defined

           static if (is(E V == enum))    // satisified, E is an enum
               V v;                       // v is declared to be a byte
       }

7. is ( Type : TypeSpecialization , TemplateParameterList )
   is ( Type == TypeSpecialization , TemplateParameterList )
   is ( Type Identifier : TypeSpecialization , TemplateParameterList )
   is ( Type Identifier == TypeSpecialization , TemplateParameterList )

   More complex types can be pattern matched; the TemplateParameterList declares symbols based on the parts of the pattern that are matched, analogously to the way implied template parameters are matched.

       import std.stdio, std.typecons;

       void main()
       {
           alias Tup = Tuple!(int, string);
           alias AA = long[char[]];

           static if (is(Tup : TX!TL, alias TX, TL...))
           {
               writeln(is(TX!(int, long) == Tuple!(int, long)));  // true
               writeln(typeid(TL[0]));  // int
               writeln(typeid(TL[1]));  // immutable(char)[]
           }

           static if (is(AA T : T[U], U : const char[]))
           {
               writeln(typeid(T));  // long
               writeln(typeid(U));  // const char[]
           }

           static if (is(AA A : A[B], B : int))
           {
               assert(0);  // should not match, as B is not an int
           }

           static if (is(int[10] W : W[V], int V))
           {
               writeln(typeid(W));  // int
               writeln(V);          // 10
           }

           static if (is(int[10] X : X[Y], int Y : 5))
           {
               assert(0);  // should not match, Y should be 10
           }
       }

### Associativity and Commutativity

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.

Copyright © 1999-2015 by Digital Mars ®, All Rights Reserved | Page generated by Ddoc on Sat Apr 25 12:22:46 2015