D Programming Language 2.0

- Comments
- D
- Search
- Downloads
- Home

Last update Fri Oct 26 14:03:45 2007

# Functions

    FunctionBody:
    	BlockStatement
    	BodyStatement
    	InStatement BodyStatement
    	OutStatement BodyStatement
    	InStatement OutStatement BodyStatement
    	OutStatement InStatement BodyStatement

    InStatement:
    	in BlockStatement

    OutStatement:
    	out BlockStatement
    	out ( Identifier ) BlockStatement

    BodyStatement:
    	body BlockStatement

### Virtual Functions

Virtual functions are functions that are called indirectly through a function pointer table, called a vtbl[], rather than directly. All non-static non-private non-template member functions are virtual. This may sound inefficient, but since the D compiler knows all of the class hierarchy when generating code, all functions that are not overridden can be optimized to be non-virtual. In fact, since C++ programmers tend to "when in doubt, make it virtual", the D way of "make it virtual unless we can prove it can be made non-virtual" results, on average, in many more direct function calls. It also results in fewer bugs caused by not declaring a function virtual that gets overridden.

Functions with non-D linkage cannot be virtual, and hence cannot be overridden.

Member template functions cannot be virtual, and hence cannot be overridden.

Functions marked as `final` may not be overridden in a derived class, unless they are also `private`. For example:

    class A
    {
        int def() { ... }
        final int foo() { ... }
        final private int bar() { ... }
        private int abc() { ... }
    }

    class B : A
    {
        int def() { ... }	// ok, overrides A.def
        int foo() { ... }	// error, A.foo is final
        int bar() { ... }	// ok, A.bar is final private, but not virtual
        int abc() { ... }	// ok, A.abc is not virtual, B.abc is virtual
    }

    void test(A a)
    {
        a.def();	// calls B.def
        a.foo();	// calls A.foo
        a.bar();	// calls A.bar
        a.abc();	// calls A.abc
    }

    void func()
    {   B b = new B();
        test(b);
    }

Covariant return types are supported, which means that the overriding function in a derived class can return a type that is derived from the type returned by the overridden function:

    class A { }
    class B : A { }

    class Foo
    {
        A test() { return null; }
    }

    class Bar : Foo
    {
        B test() { return null; }	// overrides and is covariant with Foo.test()
    }

### Function Inheritance and Overriding

A functions in a derived class with the same name and parameter types as a function in a base class overrides that function:

    class A
    {
        int foo(int x) { ... }
    }

    class B : A
    {
        override int foo(int x) { ... }
    }

    void test()
    {
        B b = new B();
        bar(b);
    }

    void bar(A a)
    {
        a.foo(1);	// calls B.foo(int)
    }

However, when doing overload resolution, the functions in the base class are not considered:

    class A
    {
        int foo(int x) { ... }
        int foo(long y) { ... }
    }

    class B : A
    {
        override int foo(long x) { ... }
    }

    void test()
    {
        B b = new B();
        b.foo(1);	// calls B.foo(long), since A.foo(int) not considered
        A a = b;
        a.foo(1);	// issues runtime error (instead of calling A.foo(int))
    }

To consider the base class's functions in the overload resolution process, use an AliasDeclaration:

    class A
    {
        int foo(int x) { ... }
        int foo(long y) { ... }
    }

    class B : A
    {
        alias A.foo foo;
        override int foo(long x) { ... }
    }

    void test()
    {
        B b = new B();
        bar(b);
    }

    void bar(A a)
    {
        a.foo(1);		// calls A.foo(int)
        B b = new B();
        b.foo(1);		// calls A.foo(int)
    }

If such an AliasDeclaration is not used, the derived class's functions completely override all the functions of the same name in the base class, even if the types of the parameters in the base class functions are different. If, through implicit conversions to the base class, those other functions do get called, an `std.HiddenFuncError` exception is raised:

    import std.hiddenfunc;

    class A
    {
         void set(long i) { }
         void set(int i)  { }
    }
    class B : A
    {
         void set(long i) { }
    }

    void foo(A a)
    {   int i;
        try
        {
            a.set(3);   // error, throws runtime exception since
                        // A.set(int) should not be available from B
        }
        catch (HiddenFuncError o)
        {
    	i = 1;
        }
        assert(i == 1);
    }

    void main()
    {
        foo(new B);
    }

If an `HiddenFuncError` exception is thrown in your program, the use of overloads and overrides needs to be reexamined in the relevant classes.

A function parameter's default value is not inherited:

    class A
    {
        void foo(int x = 5) { ... }
    }

    class B : A
    {
        void foo(int x = 7) { ... }
    }

    class C : B
    {
        void foo(int x) { ... }
    }

    void test()
    {
        A a = new A();
        a.foo();		// calls A.foo(5)

        B b = new B();
        b.foo();		// calls B.foo(7)

        C c = new C();
        c.foo();		// error, need an argument for C.foo
    }

### Inline Functions

There is no inline keyword. The compiler makes the decision whether to inline a function or not, analogously to the register keyword no longer being relevant to a compiler's decisions on enregistering variables. (There is no register keyword either.)

## Function Overloading

In C++, there are many complex levels of function overloading, with some defined as "better" matches than others. If the code designer takes advantage of the more subtle behaviors of overload function selection, the code can become difficult to maintain. Not only will it take a C++ expert to understand why one function is selected over another, but different C++ compilers can implement this tricky feature differently, producing subtly disastrous results.

In D, function overloading is simple. It matches exactly, it matches with implicit conversions, or it does not match. If there is more than one match, it is an error.

Functions defined with non-D linkage cannot be overloaded.

## Overload Sets

Functions declared at the same scope overload against each other, and are called an Overload Set. A typical example of an overload set are functions defined at module level:

    module A;
    void foo() { }
    void foo(long i) { }

`A.foo()` and `A.foo(long)` form an overload set. A different module can also define functions with the same name:

    module B;
    class C { }
    void foo(C) { }
    void foo(int i) { }

and A and B can be imported by a third module, C. Both overload sets, the `A.foo` overload set and the `B.foo` overload set, are found. An instance of `foo` is selected based on it matching in exactly one overload set:

    import A;
    import B;

    void bar(C c)
    {
        foo();    // calls A.foo()
        foo(1L);  // calls A.foo(long)
        foo(c);   // calls B.foo(C)
        foo(1,2); // error, does not match any foo
        foo(1);   // error, matches A.foo(long) and B.foo(int)
        A.foo(1); // calls A.foo(long)
    }

Even though `B.foo(int)` is a better match than `A.foo(long)` for `foo(1)`, it is an error because the two matches are in different overload sets.

Overload sets can be merged with an alias declaration:

    import A;
    import B;

    alias A.foo foo;
    alias B.foo foo;

    void bar(C c)
    {
        foo();    // calls A.foo()
        foo(1L);  // calls A.foo(long)
        foo(c);   // calls B.foo(C)
        foo(1,2); // error, does not match any foo
        foo(1);   // calls B.foo(int)
        A.foo(1); // calls A.foo(long)
    }

### Function Parameters

Parameter storage classes are in, out, ref, lazy, final, const, invariant, or scope. For example:

    int foo(in int x, out int y, ref int z, int q);

x is in, y is out, z is ref, and q is none.

The in storage class is equivalent to final const scope.

If no storage class is specified, the parameter becomes a mutable copy of its argument.

- The function declaration makes it clear what the inputs and outputs to the function are.
- It eliminates the need for IDL as a separate language.
- It provides more information to the compiler, enabling more error checking and possibly better code generation.

out parameters are set to the default initializer for the type of it. For example:

    void foo(out int x)
    {
        // x is set to 0 at start of foo()
    }

    int a = 3;
    foo(a);
    // a is now 0

    void abc(out int x)
    {
        x = 2;
    }

    int y = 3;
    abc(y);
    // y is now 2

    void def(ref int x)
    {
        x += 1;
    }

    int z = 3;
    def(z);
    // z is now 4

For dynamic array and object parameters, which are passed by reference, in/out/ref apply only to the reference and not the contents.

Lazy arguments are evaluated not when the function is called, but when the parameter is evaluated within the function. Hence, a lazy argument can be executed 0 or more times. A lazy parameter cannot be an lvalue.

    void dotimes(int n, lazy void exp)
    {
        while (n--)
    	exp();
    }

    void test()
    {   int x;
        dotimes(3, writefln(x++));
    }

prints to the console:

    0
    1
    2

A lazy parameter of type `void` can accept an argument of any type.

## Variadic Functions

Functions taking a variable number of arguments are called variadic functions. A variadic function can take one of three forms:

1. C-style variadic functions
2. Variadic functions with type info
3. Typesafe variadic functions

### C-style Variadic Functions

A C-style variadic function is declared as taking a parameter of ... after the required function parameters. It has non-D linkage, such as `extern (C)`:

    extern (C) int foo(int x, int y, ...);

    foo(3, 4);	// ok
    foo(3, 4, 6.8);	// ok, one variadic argument
    foo(2);		// error, y is a required argument

There must be at least one non-variadic parameter declared.

    extern (C) int def(...); // error, must have at least one parameter

C-style variadic functions match the C calling convention for variadic functions, and is most useful for calling C library functions like `printf`. The implementiations of these variadic functions have a special local variable declared for them, _argptr, which is a `void*` pointer to the first of the variadic arguments. To access the arguments, _argptr must be cast to a pointer to the expected argument type:

    foo(3, 4, 5);	// first variadic argument is 5

    int foo(int x, int y, ...)
    {   int z;

        z = *cast(int*)_argptr;	// z is set to 5
    }

To protect against the vagaries of stack layouts on different CPU architectures, use std.c.stdarg to access the variadic arguments:

    import std.c.stdarg;

### D-style Variadic Functions

Variadic functions with argument and type info are declared as taking a parameter of ... after the required function parameters. It has D linkage, and need not have any non-variadic parameters declared:

    int abc(char c, ...);	// one required parameter: c
    int def(...);		// ok

These variadic functions have a special local variable declared for them, _argptr, which is a `void*` pointer to the first of the variadic arguments. To access the arguments, _argptr must be cast to a pointer to the expected argument type:

    foo(3, 4, 5);	// first variadic argument is 5

    int foo(int x, int y, ...)
    {   int z;

        z = *cast(int*)_argptr;	// z is set to 5
    }

An additional hidden argument with the name _arguments and type `TypeInfo[]` is passed to the function. _arguments gives the number of arguments and the type of each, enabling the creation of typesafe variadic functions.

    import std.stdio;

    class Foo { int x = 3; }
    class Bar { long y = 4; }

    void printargs(int x, ...)
    {
        writefln("%d arguments", _arguments.length);
        for (int i = 0; i < _arguments.length; i++)
        {   _arguments[i].print();

    	if (_arguments[i] == typeid(int))
    	{
    	    int j = *cast(int *)_argptr;
    	    _argptr += int.sizeof;
    	    writefln("\t%d", j);
    	}
    	else if (_arguments[i] == typeid(long))
    	{
    	    long j = *cast(long *)_argptr;
    	    _argptr += long.sizeof;
    	    writefln("\t%d", j);
    	}
    	else if (_arguments[i] == typeid(double))
    	{
    	    double d = *cast(double *)_argptr;
    	    _argptr += double.sizeof;
    	    writefln("\t%g", d);
    	}
    	else if (_arguments[i] == typeid(Foo))
    	{
    	    Foo f = *cast(Foo*)_argptr;
    	    _argptr += Foo.sizeof;
    	    writefln("\t%X", f);
    	}
    	else if (_arguments[i] == typeid(Bar))
    	{
    	    Bar b = *cast(Bar*)_argptr;
    	    _argptr += Bar.sizeof;
    	    writefln("\t%X", b);
    	}
    	else
    	    assert(0);
        }
    }

    void main()
    {
        Foo f = new Foo();
        Bar b = new Bar();

        writefln("%X", f);
        printargs(1, 2, 3L, 4.5, f, b);
    }

which prints:

    00870FE0
    5 arguments
    int
            2
    long
            3
    double
            4.5
    Foo
            00870FE0
    Bar
            00870FD0

To protect against the vagaries of stack layouts on different CPU architectures, use std.stdarg to access the variadic arguments:

    import std.stdio;
    import std.stdarg;

    void foo(int x, ...)
    {
        writefln("%d arguments", _arguments.length);
        for (int i = 0; i < _arguments.length; i++)
        {   _arguments[i].print();

    	if (_arguments[i] == typeid(int))
    	{
    	    int j = va_arg!(int)(_argptr);
    	    writefln("\t%d", j);
    	}
    	else if (_arguments[i] == typeid(long))
    	{
    	    long j = va_arg!(long)(_argptr);
    	    writefln("\t%d", j);
    	}
    	else if (_arguments[i] == typeid(double))
    	{
    	    double d = va_arg!(double)(_argptr);
    	    writefln("\t%g", d);
    	}
    	else if (_arguments[i] == typeid(FOO))
    	{
    	    FOO f = va_arg!(FOO)(_argptr);
    	    writefln("\t%X", f);
    	}
    	else
    	    assert(0);
        }
    }

### Typesafe Variadic Functions

Typesafe variadic functions are used when the variable argument portion of the arguments are used to construct an array or class object.

For arrays:

    int test()
    {
        return sum(1, 2, 3) + sum(); // returns 6+0
    }

    int func()
    {
        int[3] ii = [4, 5, 6];
        return sum(ii);		// returns 15
    }

    int sum(int[] ar ...)
    {
        int s;
        foreach (int x; ar)
    	s += x;
        return s;
    }

For static arrays:

    int test()
    {
        return sum(2, 3);	// error, need 3 values for array
        return sum(1, 2, 3); // returns 6
    }

    int func()
    {
        int[3] ii = [4, 5, 6];
        int[] jj = ii;
        return sum(ii);		// returns 15
        return sum(jj);		// error, type mismatch
    }

    int sum(int[3] ar ...)
    {
        int s;
        foreach (int x; ar)
    	s += x;
        return s;
    }

For class objects:

    class Foo
    {
        int x;
        char[] s;

        this(int x, char[] s)
        {
    	this.x = x;
    	this.s = s;
        }
    }

    void test(int x, Foo f ...);

    ...

    Foo g = new Foo(3, "abc");
    test(1, g);		// ok, since g is an instance of Foo
    test(1, 4, "def");	// ok
    test(1, 5);		// error, no matching constructor for Foo

An implementation may construct the object or array instance on the stack. Therefore, it is an error to refer to that instance after the variadic function has returned:

    Foo test(Foo f ...)
    {
        return f;	// error, f instance contents invalid after return
    }

    int[] test(int[] a ...)
    {
        return a;		// error, array contents invalid after return
        return a[0..1];	// error, array contents invalid after return
        return a.dup;	// ok, since copy is made
    }

For other types, the argument is built with itself, as in:

    int test(int i ...)
    {
        return i;
    }

    ...
    test(3);	// returns 3
    test(3, 4);	// error, too many arguments
    int[] x;
    test(x);	// error, type mismatch

### Lazy Variadic Functions

If the variadic parameter is an array of delegates with no parameters:

    void foo(int delegate()[] dgs ...);

Then each of the arguments whose type does not match that of the delegate is converted to a delegate.

    int delegate() dg;
    foo(1, 3+x, dg, cast(int delegate())null);

is the same as:

    foo( { return 1; }, { return 3+x; }, dg, null );

## Local Variables

It is an error to use a local variable without first assigning it a value. The implementation may not always be able to detect these cases. Other language compilers sometimes issue a warning for this, but since it is always a bug, it should be an error.

It is an error to declare a local variable that is never referred to. Dead variables, like anachronistic dead code, are just a source of confusion for maintenance programmers.

It is an error to declare a local variable that hides another local variable in the same function:

    void func(int x)
    {   int x;		error, hides previous definition of x
         double y;
         ...
         {   char y;	error, hides previous definition of y
    	  int z;
         }
         {   wchar z;	legal, previous z is out of scope
         }
    }

While this might look unreasonable, in practice whenever this is done it either is a bug or at least looks like a bug.

It is an error to return the address of or a reference to a local variable.

It is an error to have a local variable and a label with the same name.

## Nested Functions

Functions may be nested within other functions:

    int bar(int a)
    {
        int foo(int b)
        {
    	int abc() { return 1; }

    	return b + abc();
        }
        return foo(a);
    }

    void test()
    {
        int i = bar(3);	// i is assigned 4
    }

Nested functions can be accessed only if the name is in scope.

    void foo()
    {
       void A()
       {
         B();   // error, B() is forward referenced
         C();   // error, C undefined
       }
       void B()
       {
           A();	// ok, in scope
           void C()
           {
               void D()
               {
                   A();      // ok
                   B();      // ok
                   C();      // ok
                   D();      // ok
               }
           }
       }
       A(); // ok
       B(); // ok
       C(); // error, C undefined
    }

and:

    int bar(int a)
    {
        int foo(int b) { return b + 1; }
        int abc(int b) { return foo(b); }	// ok
        return foo(a);
    }

    void test()
    {
        int i = bar(3);	// ok
        int j = bar.foo(3);	// error, bar.foo not visible
    }

Nested functions have access to the variables and other symbols defined by the lexically enclosing function. This access includes both the ability to read and write them.

    int bar(int a)
    {   int c = 3;

        int foo(int b)
        {
    	b += c;		// 4 is added to b
    	c++;		// bar.c is now 5
    	return b + c;	// 12 is returned
        }
        c = 4;
        int i = foo(a);	// i is set to 12
        return i + c;	// returns 17
    }

    void test()
    {
        int i = bar(3);	// i is assigned 17
    }

This access can span multiple nesting levels:

    int bar(int a)
    {   int c = 3;

        int foo(int b)
        {
    	int abc()
    	{
    	    return c;	// access bar.c
    	}
    	return b + c + abc();
        }
        return foo(3);
    }

Static nested functions cannot access any stack variables of any lexically enclosing function, but can access static variables. This is analogous to how static member functions behave.

    int bar(int a)
    {   int c;
        static int d;

        static int foo(int b)
        {
    	b = d;		// ok
    	b = c;		// error, foo() cannot access frame of bar()
    	return b + 1;
        }
        return foo(a);
    }

Functions can be nested within member functions:

    struct Foo
    {   int a;

        int bar()
        {   int c;

    	int foo()
    	{
    	    return c + a;
    	}
    	return 0;
        }
    }

Member functions of nested classes and structs do not have access to the stack variables of the enclosing function, but do have access to the other symbols:

    void test()
    {   int j;
        static int s;

        struct Foo
        {   int a;

    	int bar()
    	{   int c = s;		// ok, s is static
    	    int d = j;		// error, no access to frame of test()

    	    int foo()
    	    {
    		int e = s;	// ok, s is static
    		int f = j;	// error, no access to frame of test()
    		return c + a;	// ok, frame of bar() is accessible,
    				// so are members of Foo accessible via
    				// the 'this' pointer to Foo.bar()
    	    }

    	    return 0;
    	}
        }
    }

Nested functions always have the D function linkage type.

Unlike module level declarations, declarations within function scope are processed in order. This means that two nested functions cannot mutually call each other:

    void test()
    {
        void foo() { bar(); }	// error, bar not defined
        void bar() { foo(); }	// ok
    }

The solution is to use a delegate:

    void test()
    {
        void delegate() fp;
        void foo() { fp(); }
        void bar() { foo(); }
        fp = &bar;
    }

Future directions: This restriction may be removed.

### Delegates, Function Pointers, and Closures

A function pointer can point to a static nested function:

    int function() fp;

    void test()
    {   static int a = 7;
        static int foo() { return a + 3; }

        fp = &foo;
    }

    void bar()
    {
        test();
        int i = fp();	// i is set to 10
    }

A delegate can be set to a non-static nested function:

    int delegate() dg;

    void test()
    {   int a = 7;
        int foo() { return a + 3; }

        dg = &foo;
        int i = dg();	// i is set to 10
    }

The stack variables referenced by a nested function are still valid even after the function exits (this is different from D 1.0). This is called a closure. Returning addresses of stack variables, however, is not a closure and is an error.

    int* bar()
    {   int b;
        test();
        int i = dg();	// ok, test.a is in a closure and still exists
        return &b;		// error, bar.b not valid after bar() exits
    }

Delegates to non-static nested functions contain two pieces of data: the pointer to the stack frame of the lexically enclosing function (called the frame pointer) and the address of the function. This is analogous to struct/class non-static member function delegates consisting of a this pointer and the address of the member function. Both forms of delegates are interchangeable, and are actually the same type:

    struct Foo
    {   int a = 7;
        int bar() { return a; }
    }

    int foo(int delegate() dg)
    {
        return dg() + 1;
    }

    void test()
    {
        int x = 27;
        int abc() { return x; }
        Foo f;
        int i;

        i = foo(&abc);	// i is set to 28
        i = foo(&f.bar);	// i is set to 8
    }

This combining of the environment and the function is called a dynamic closure.

The .ptr property of a delegate will return the frame pointer value as a `void*`.

The .funcptr property of a delegate will return the function pointer value as a function type.

Future directions: Function pointers and delegates may merge into a common syntax and be interchangeable with each other.

### Anonymous Functions and Anonymous Delegates

See Function Literals.

## main() Function

For console programs, `main()` serves as the entry point. It gets called after all the module initializers are run, and after any unittests are run. After it returns, all the module destructors are run. `main()` must be declared using one of the following forms:

    void main() { ... }
    void main(char[][] args) { ... }
    int main() { ... }
    int main(char[][] args) { ... }

## Compile Time Function Execution

A subset of functions can be executed at compile time. This is useful when constant folding algorithms need to include recursion and looping. In order to be executed at compile time, a function must meet the following criteria:

1. function arguments must all be:
   - integer literals
   - floating point literals
   - character literals
   - string literals
   - array literals where the members are all items in this list
   - associative array literals where the members are all items in this list
   - struct literals where the members are all items in this list
   - const variables initialized with a member of this list
2. function parameters may not be variadic, or lazy
3. the function may not be nested or synchronized
4. the function may not be a non-static member, i.e. it may not have a `this` pointer
5. expressions in the function may not:
   - throw exceptions
   - use pointers, delegates, non-const arrays, or classes
   - reference any global state or variables
   - reference any local static variables
   - new or delete
   - call any function that is not executable at compile time
6. the following statement types are not allowed:
   - synchronized statements
   - throw statements
   - with statements
   - scope statements
   - try-catch-finally statements
   - labelled break and continue statements
7. as a special case, the following properties can be executed at compile time:

   `.dup`
   `.length`
   `.keys`
   `.values`

In order to be executed at compile time, the function must appear in a context where it must be so executed, for example:

- initialization of a static variable
- dimension of a static array
- argument for a template value parameter

    template eval( A... )
    {
        const typeof(A[0]) eval = A[0];
    }

    int square(int i) { return i * i; }

    void foo()
    {
      static j = square(3);     // compile time
      writefln(j);
      writefln(square(4));      // run time
      writefln(eval!(square(5))); // compile time
    }

Executing functions at compile time can take considerably longer than executing it at run time. If the function goes into an infinite loop, it will hang at compile time (rather than hanging at run time).

Functions executed at compile time can give different results from run time in the following scenarios:

- floating point computations may be done at a higher precision than run time
- dependency on implementation defined order of evaluation
- use of uninitialized variables

These are the same kinds of scenarios where different optimization settings affect the results.

### String Mixins and Compile Time Function Execution

Any functions that execute at compile time must also be executable at run time. The compile time evaluation of a function does the equivalent of running the function at run time. This means that the semantics of a function cannot depend on compile time values of the function. For example:

    int foo(char[] s)
    {
        return mixin(s);
    }

    const int x = foo("1");

is illegal, because the runtime code for foo() cannot be generated. A function template would be the appropriate method to implement this sort of thing.

Copyright © 1999-2007 by Digital Mars, All Rights Reserved | Page generated by Ddoc. | Comments