Functions with non-D linkage 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() }
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(); // 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(); bar(b); } void bar(A a) { a.foo(1); // calls A.foo(int) B b = new B(); b.foo(1); // calls B.foo(long), since A.foo(int) not considered }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) }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 }
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.
Function Parameters
Parameters are in, out, or inout.
in is the default; out and inout work like
storage classes. For example:
int foo(int x, out int y, inout int z, int q);x is in, y is out, z is inout, and q is in.
out is rare enough, and inout even rarer, to attach the keywords to them and leave in as the default. The reasons to have them are:
void foo(out int bar) { } int bar = 3; foo(bar); // bar is now 0
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 argumentVariadic functions with non-D linkage must have at least one non-variadic parameter declared.
int abc(...); // ok, D linkage extern (C) def(...); // error, must have at least one parameterVariadic 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 }For variadic functions with D linkage, 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.
class FOO { } void foo(int x, ...) { printf("%d arguments\n", _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; printf("\t%d\n", j); } else if (_arguments[i] == typeid(long)) { long j = *cast(long *)_argptr; _argptr += long.sizeof; printf("\t%lld\n", j); } else if (_arguments[i] == typeid(double)) { double d = *cast(double *)_argptr; _argptr += double.sizeof; printf("\t%g\n", d); } else if (_arguments[i] == typeid(FOO)) { FOO f = *cast(FOO*)_argptr; _argptr += FOO.sizeof; printf("\t%p\n", f); } else assert(0); } } void main() { FOO f = new FOO(); printf("%p\n", f); foo(1, 2, 3L, 4.5, f); }which prints:
00870FD0 4 arguments int 2 long 3 double 4.5 FOO 00870FD0To protect against the vagaries of stack layouts on different CPU architectures, use std.stdarg to access the variadic arguments:
import std.stdarg; void foo(int x, ...) { printf("%d arguments\n", _arguments.length); for (int i = 0; i < _arguments.length; i++) { _arguments[i].print(); if (_arguments[i] == typeid(int)) { int j = va_arg!(int)(_argptr); printf("\t%d\n", j); } else if (_arguments[i] == typeid(long)) { long j = va_arg!(long)(_argptr); printf("\t%lld\n", j); } else if (_arguments[i] == typeid(double)) { double d = va_arg!(double)(_argptr); printf("\t%g\n", d); } else if (_arguments[i] == typeid(FOO)) { FOO f = va_arg!(FOO)(_argptr); printf("\t%p\n", f); } else assert(0); } }
It is an error to declare a local variable that is never referred to. Dead variables, like anachronistic dead code, is 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 only be accessed by the most nested lexically enclosing function, or by another nested function at the same nesting depth:
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; } } }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() } } } }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.
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, however, are not valid once the function declaring them has exited, in the same manner that pointers to stack variables are not valid upon exit from a function:
int* bar() { int b; test(); int i = dg(); // error, test.a no longer 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.
Future directions: Function pointers and delegates may merge into a common syntax and be interchangeable with each other.