- Getting the Size of a Type
- Get the max and min values of a type
- Primitive Types
- Special Floating Point Values
- Remainder after division of floating point numbers
- Dealing with NANs in floating point compares
- Asserts are a necessary part of any good defensive coding strategy
- Initializing all elements of an array
- Looping through an array
- Creating an array of variable size
- String Concatenation
- Formatted printing
- Forward referencing functions
- Functions that have no arguments
- Labeled break and continue statements
- Goto Statements
- Struct tag name space
- Looking up strings
- Setting struct member alignment
- Anonymous Structs and Unions
- Declaring struct types and variables
- Getting the offset of a struct member
- Union Initializations
- Struct Initializations
- Array Initializations
- Escaped String Literals
- ASCII versus Wide Characters
- Arrays that parallel an enum
- Creating a new type with typedef
- Comparing structs
- Comparing strings
- Sorting arrays
- String literals
- Data Structure Traversal
- Unsigned Right Shift
- Dynamic Closures
- Variadic Function Parameters
Programming in D for C Programmers
Every experienced C programmer accumulates a series of idioms and techniques which become second nature. Sometimes, when learning a new language, those idioms can be so comfortable it's hard to see how to do the equivalent in the new language. So here's a collection of common C techniques, and how to do the corresponding task in D.
Since C does not have object-oriented features, there's a separate section for object-oriented issues Programming in D for C++ Programmers.
The C preprocessor is covered in The C Preprocessor vs D.
Getting the Size of a Type
The C Way
sizeof(int) sizeof(char *) sizeof(double) sizeof(struct Foo)
The D Way
Use the sizeof property:
int.sizeof (char *).sizeof double.sizeof Foo.sizeof
Get the max and min values of a type
The C Way
#include <limits.h> #include <math.h> CHAR_MAX CHAR_MIN ULONG_MAX DBL_MIN
The D Way
char.max char.min ulong.max double.min
Primitive Types
C to D types
bool => bool char => char signed char => byte unsigned char => ubyte short => short unsigned short => ushort wchar_t => core.stdc.stddef.wchar_t int => int unsigned => uint long => core.stdc.config.c_long unsigned long => core.stdc.config.c_ulong long long => long unsigned long long => ulong float => float double => double long double => real _Imaginary long double => ireal _Complex long double => creal
Although char is an unsigned 8-bit type, and wchar is an unsigned 16-bit type, they have their own separate types in order to aid overloading and type safety.
Ints and unsigneds in C are of varying size; not so in D.
Special Floating Point Values
The C Way
#include <fp.h> NAN INFINITY #include <float.h> DBL_DIG DBL_EPSILON DBL_MANT_DIG DBL_MAX_10_EXP DBL_MAX_EXP DBL_MIN_10_EXP DBL_MIN_EXP
The D Way
double.nan double.infinity double.dig double.epsilon double.mant_dig double.max_10_exp double.max_exp double.min_10_exp double.min_exp
Remainder after division of floating point numbers
The C Way
#include <math.h> float f = fmodf(x,y); double d = fmod(x,y); long double r = fmodl(x,y);
The D Way
D supports the remainder (%) operator on floating point operands:float f = x % y; double d = x % y; real r = x % y;
Dealing with NANs in floating point compares
The C Way
C doesn't define what happens if an operand to a compare is NAN, and few C compilers check for it (the Digital Mars C compiler is an exception, DM's compilers do check for NAN operands).#include <math.h> if (isnan(x) || isnan(y)) result = false; else result = (x < y);
The D Way
D offers a full complement of comparisons and operators that work with NAN arguments.result = (x < y); // false if x or y is nan
Asserts are a necessary part of any good defensive coding strategy
The C Way
C doesn't directly support assert in the language, but does define a macro in the standard library header assert.h. That macro writes a diagnostic message on stderr when the condition given as parameter is not true. The message will use __FILE__, __LINE__ and __func__ (C99) to localize the failing assertion.
#include <assert.h> assert(e == 0);
The D Way
D simply builds assert into the language:assert(e == 0);
Initializing all elements of an array
The C Way
#define ARRAY_LENGTH 17 int array[ARRAY_LENGTH]; for (i = 0; i < ARRAY_LENGTH; i++) array[i] = value;
The D Way
int[17] array;
array[] = value;
Looping through an array
The C Way
The array length is defined separately, or a clumsy and error prone sizeof() expression is used to get the length.
#define ARRAY_LENGTH 17 int array[ARRAY_LENGTH]; for (size_t i = 0; i < ARRAY_LENGTH; i++) func(array[i]);or:
int array[17]; for (size_t i = 0; i < sizeof(array) / sizeof(array[0]); i++) func(array[i]);
The D Way
The length of an array is accessible through the length property.int[17] array; foreach (i; 0 .. array.length) func(array[i]);or even better (for value access):
int[17] array; foreach (int value; array) func(value);In D, the element type can be inferred even without using an explicit type qualifier:
int[17] array; foreach (value; array) func(value);or through the ref keyword (for reference access):
int[17] array; foreach (ref value; array) value += 42;
Creating an array of variable size
The C Way
C cannot do this with arrays. It is necessary to create a separate variable for the length, and then explicitly manage the size of the array:#include <stdlib.h> int array_length; int *array; int *newarray; newarray = realloc(array, (array_length + 1) * sizeof(int)); if (!newarray) error("out of memory"); array = newarray; array[array_length++] = x;
The D Way
D supports dynamic arrays, which can be easily resized. D supports all the requisite memory management.int[] array; int x; array.length = array.length + 1; array[array.length - 1] = x;
String Concatenation
The C Way
There are several difficulties to be resolved, like when can storage be freed, dealing with null pointers, finding the length of the strings, and memory allocation:#include <string.h> char *s1; char *s2; char *s; // Concatenate s1 and s2, and put result in s s = malloc((s1 ? strlen(s1) : 0) + (s2 ? strlen(s2) : 0) + 1); if (!s) error("out of memory"); if (s1) strcpy(s, s1); else *s = 0; if (s2) strcpy(s + strlen(s), s2); // Append "hello" to s char hello[] = "hello"; size_t lens = s ? strlen(s) : 0; char *news = realloc(s, lens + sizeof(hello) + 1); if (!news) error("out of memory"); s = news; memcpy(s + lens, hello, sizeof(hello));
The D Way
D overloads the operators ~ and ~= for char and wchar arrays to mean concatenate and append, respectively:string s1;
string s2;
string s;
s = s1 ~ s2;
s ~= "hello";
Formatted printing
The C Way
printf() is the general purpose formatted print routine:#include <stdio.h> printf("Calling all cars %d times!\n", ntimes);
The D Way
What can we say? printf() rules:printf("Calling all cars %d times!\n", ntimes);
writefln() improves on printf() by being type-aware and type-safe:
import std.stdio; writefln("Calling all cars %s times!", ntimes);
Forward referencing functions
The C Way
Functions cannot be forward referenced. Hence, to call a function not yet encountered in the source file, it is necessary to insert a function declaration lexically preceding the call.void forwardfunc(); void myfunc() { forwardfunc(); } void forwardfunc() { ... }
The D Way
The program is looked at as a whole, and so not only is it not necessary to code forward declarations, it is not even allowed! D avoids the tedium and errors associated with writing forward referenced function declarations twice. Functions can be defined in any order.void myfunc() { forwardfunc(); } void forwardfunc() { ... }
Functions that have no arguments
The C Way
void foo(void);
The D Way
D is a strongly typed language, so there is no need to explicitly say a function takes no arguments, just don't declare it as having arguments.void foo()
{
...
}
Labeled break and continue statements
The C Way
Break and continue statements only apply to the innermost nested loop or switch, so a multilevel break must use a goto:for (i = 0; i < 10; i++) { for (j = 0; j < 10; j++) { if (j == 3) goto Louter; if (j == 4) goto L2; } L2: ; } Louter: ;
The D Way
Break and continue statements can be followed by a label. The label is the label for an enclosing loop or switch, and the break applies to that loop.Louter: for (i = 0; i < 10; i++) { for (j = 0; j < 10; j++) { if (j == 3) break Louter; if (j == 4) continue Louter; } } // break Louter goes here
Goto Statements
The C Way
The much maligned goto statement is a staple for professional C coders. It's necessary to make up for sometimes inadequate control flow statements.The D Way
Many C-way goto statements can be eliminated with the D feature of labeled break and continue statements. But D is a practical language for practical programmers who know when the rules need to be broken. So of course D supports goto statements.Struct tag name space
The C Way
It's annoying to have to use the struct keyword every time a type is specified, so a common idiom is to use:typedef struct ABC { ... } ABC;
The D Way
Struct tag names are not in a separate name space, they are in the same name space as ordinary names. Hence:struct ABC { ... }
Looking up strings
The C Way
Given a string, compare the string against a list of possible values and take action based on which one it is. A typical use for this might be command line argument processing.#include <string.h> void dostring(char *s) { enum Strings { Hello, Goodbye, Maybe, Max }; static char *table[] = { [Hello] ="hello", [Goodbye]="goodbye", [Maybe] ="maybe" }; int i; for (i = 0; i < Max; i++) { if (strcmp(s, table[i]) == 0) break; } switch (i) { case Hello: ... case Goodbye: ... case Maybe: ... default: ... } }The problem with this is trying to maintain 3 parallel data structures, the enum, the table, and the switch cases. If there are a lot of values, the connection between the 3 may not be so obvious when doing maintenance, and so the situation is ripe for bugs. Designated initializers as were introduced with C99 allow to link correctly 2 of the 3 data structures, but at the cost of a lot of typing. Additionally, if the number of values becomes large, a binary or hash lookup will yield a considerable performance increase over a simple linear search. But coding these can be time consuming, and they need to be debugged. It's typical that such just never gets done.
The D Way
D extends the concept of switch statements to be able to handle strings as well as numbers. Then, the way to code the string lookup becomes straightforward:void dostring(string s) { switch (s) { case "hello": ... case "goodbye": ... case "maybe": ... default: ... } }Adding new cases becomes easy. The compiler can be relied on to generate a fast lookup scheme for it, eliminating the bugs and time required in hand-coding one.
Setting struct member alignment
The C Way
It's done through a command line switch which affects the entire program, and woe results if any modules or libraries didn't get recompiled. To address this, #pragmas are used:#pragma pack(1) struct ABC { ... }; #pragma pack()But #pragmas are nonportable both in theory and in practice from compiler to compiler.
The D Way
D has a syntax for setting the alignment that is common to all D compilers. The actual alignment done is compatible with the companion C compiler's alignment, for ABI compatibility. To match a particular layout across architectures, use align(1) and manually specify it.
struct ABC { int z; // z is aligned to the default align (1) int x; // x is byte aligned align (4) { ... // declarations in {} are 32-bit aligned } align (2): // switch to 16-bit alignment from here on int y; // y is 16-bit aligned }
Anonymous Structs and Unions
Sometimes, it's nice to control the layout of a struct with nested structs and unions.The C Way
Before C11 C didn't allow for anonymous structs or unions, which meant that dummy member names were necessary:struct Foo { int i; union { struct { int x; long y; } abc; char *p; } bar; }; #define x bar.abc.x #define y bar.abc.y #define p bar.p struct Foo f; f.i; f.x; f.y; f.p;Not only is it clumsy, but using macros means a symbolic debugger won't understand what is being done, and the macros have global scope instead of struct scope.
The D Way
Anonymous structs and unions are used to control the layout in a more natural manner:struct Foo { int i; union { struct { int x; long y; } char* p; } } Foo f; f.i; f.x; f.y; f.p;
Declaring struct types and variables
The C Way
Is to do it in one statement ending with a semicolon:
struct Foo { int x; int y; } foo;
Or to separate the two:
struct Foo { int x; int y; }; // note terminating ; struct Foo foo;
The D Way
Struct definitions and declarations can't be done in the same statement:
struct Foo { int x; int y; } // note there is no terminating ; Foo foo;
which means that the terminating ; can be dispensed with, eliminating the confusing difference between struct {} and function block {} in how semicolons are used.
Getting the offset of a struct member
The C Way
Naturally, another macro is used:#include <stddef> struct Foo { int x; int y; }; off = offsetof(Foo, y);
The D Way
An offset is just another property:struct Foo { int x; int y; } off = Foo.y.offsetof;
Union Initializations
The C Way
Unions are initialized using the "first member" rule:union U { int a; long b; }; union U x = { 5 }; // initialize member 'a' to 5 union U y = { .b = 42l }; // initialize member 'b' to 42 (C99)Adding union members or rearranging them can have disastrous consequences for any initializers. Designated initializers in C99 fix that issue.
The D Way
In D, which member is being initialized is mentioned explicitly:union U { int a; long b; } U x = { a:5 };avoiding the confusion and maintenance problems.
Struct Initializations
The C Way
Members are initialized by their position within the { }s:struct S { int a; int b; }; struct S x = { 5, 3 }; struct S y = { .b=3, .a=5 }; /* C99 */This isn't much of a problem with small structs, but when there are numerous members, it becomes tedious to get the initializers carefully lined up with the field declarations. Then, if members are added or rearranged, all the initializations have to be found and modified appropriately. This is a minefield for bugs. Designated initializers in C99 fix that issue.
The D Way
Member initialization can be done explicitly:struct S { int a; int b; } S x = { b:3, a:5 };The meaning is clear, and there no longer is a positional dependence.
Array Initializations
The C Way
C initializes arrays by positional dependence. C99 fixes the issue:int a[3] = { 3,2,1 }; int a[3] = { [2]=1, [0]=3, [1]=2 }; /* C99 designated initializer */ int a[3] = { [2]=1, [0]=3, 2 }; /* C99 designated initializer */Nested arrays may or may not have the { }:
int b[3][2] = { 2,3, {6,5}, 3,4 };
The D Way
D does it by positional dependence too, but an index can be used as well. The D syntax is lighter than C99 designated initializers. The following all produce the same result:int[3] a = [ 3, 2, 0 ]; int[3] a = [ 3, 2 ]; // unsupplied initializers are 0, just like in C int[3] a = [ 2:0, 0:3, 1:2 ]; int[3] a = [ 2:0, 0:3, 2 ]; // if not supplied, the index is the // previous one plus one.This can be handy if the array will be indexed by an enum, and the order of enums may be changed or added to:
enum color { black, red, green } int[3] c = [ black:3, green:2, red:5 ];Nested array initializations must be explicit and consistent with the array types:
int[2][3] b = [ [2,3], [6,5], [3,4] ]; int[2][3] b = [[2,6,3],[3,5,4]]; // error
Escaped String Literals
The C Way
C has problems with the DOS file system because a \ is an escape in a string. To specifiy the file c:\root\file.c you would use:char file[] = "c:\\root\\file.c";This gets even more unpleasant with regular expressions. Consider the escape sequence to match a quoted string:
/"[^\\]*(\\.[^\\]*)*"/
In C, this horror is expressed as:
char quoteString[] = "\"[^\\\\]*(\\\\.[^\\\\]*)*\"";
The D Way
D has both C-style string literals which can use escaping, and WYSIWYG (what you see is what you get) raw strings, usable with the `foo` and r"bar" syntax:string file = r"c:\root\file.c"; // c:\root\file.c string quotedString = `"[^\\]*(\\.[^\\]*)*"`; // "[^\\]*(\\.[^\\]*)*"The famous hello world string becomes:
string hello = "hello world\n";
ASCII versus Wide Characters
Modern programming requires that wchar strings be supported in an easy way, for internationalization of the programs.
The C Way
C uses the wchar_t and the L prefix on strings:#include <wchar.h> char foo_ascii[] = "hello"; wchar_t foo_wchar[] = L"hello";Things get worse if code is written to be both ascii and wchar compatible. A macro is used to switch strings from ascii to wchar:
#include <tchar.h> tchar string[] = TEXT("hello");Furthermore, in practice wchar_t is not usable in portable code as its size is implementation dependent. On POSIX conforming machines it generally represents an UTF-32 codeunit, on Windows an UTF-16 code unit. C11 introduced C++11 types char16_t and char32_t to overcome this issue.
The D Way
The type of a string is determined by semantic analysis, so there is no need to wrap strings in a macro call. Alternatively, if type inference is used, the string can have a c, w or d suffix, representing UTF-8, UTF-16 and UTF-32 encoding, respectively. If no suffix is used the type is inferred to be a UTF-8 string:string utf8 = "hello"; // UTF-8 string wstring utf16 = "hello"; // UTF-16 string dstring utf32 = "hello"; // UTF-32 string auto str = "hello"; // UTF-8 string auto _utf8 = "hello"c; // UTF-8 string auto _utf16 = "hello"w; // UTF-16 string auto _utf32 = "hello"d; // UTF-32 string
Arrays that parallel an enum
The C Way
Consider:enum COLORS { red, blue, green, max }; char *cstring[max] = {"red", "blue", "green" }; char *cstring[max] = {[red]="red", [blue]="blue", [green]="green" }; /* C99 */This is fairly easy to get right because the number of entries is small. But suppose it gets to be fairly large. Then it can get difficult to maintain correctly when new entries are added. C99 added designated initializers to solve that problem.
The D Way
enum COLORS { red, blue, green } string[COLORS.max + 1] cstring = [ COLORS.red : "red", COLORS.blue : "blue", COLORS.green : "green", ];Not perfect, but better.
Creating a new type with typedef
The C Way
Typedefs in C are weak, that is, they really do not introduce a new type. The compiler doesn't distinguish between a typedef and its underlying type.typedef void *Handle; void foo(void *); void bar(Handle); Handle h; foo(h); // coding bug not caught bar(h); // okThe C solution is to create a dummy struct whose sole purpose is to get type checking and overloading on the new type.
struct Handle__ { void *value; } typedef struct Handle__ *Handle; void foo(void *); void bar(Handle); Handle h; foo(h); // syntax error bar(h); // okHaving a default value for the type involves defining a macro, a naming convention, and then pedantically following that convention:
#define HANDLE_INIT ((Handle)-1) Handle h = HANDLE_INIT; h = func(); if (h != HANDLE_INIT) ...For the struct solution, things get even more complex:
struct Handle__ HANDLE_INIT; void init_handle(void) // call this function upon startup { HANDLE_INIT.value = (void *)-1; } Handle h = HANDLE_INIT; h = func(); if (memcmp(&h,&HANDLE_INIT,sizeof(Handle)) != 0) ...There are 4 names to remember: Handle, HANDLE_INIT, struct Handle__, value.
The D Way
D has powerful metaprogramming abilities which allow it to implement typedef as a library feature. Simply import std.typecons and use the Typedef template:import std.typecons; alias Handle = Typedef!(void*); void foo(void*); void bar(Handle); Handle h; foo(h); // syntax error bar(h); // okTo handle a default value, pass the initializer to the Typedef template as the second argument and refer to it with the .init property:
alias Handle = Typedef!(void*, cast(void*)-1); Handle h; h = func(); if (h != Handle.init) ...Now there's only one name to remember: Handle.
Comparing structs
The C Way
While C defines struct assignment in a simple, convenient manner:struct A x, y; ... x = y;it does not for struct comparisons. Hence, to compare two struct instances for equality:
#include <string.h> struct A x, y; ... if (memcmp(&x, &y, sizeof(struct A)) == 0) ...
Note the obtuseness of this, coupled with the lack of any kind of help from the language with type checking.
There's a nasty bug lurking in the memcmp(). The layout of a struct, due to alignment, can have 'holes' in it. C does not guarantee those holes are assigned any values, and so two different struct instances can have the same value for each member, but compare different because the holes contain different garbage.
The D Way
D does it the obvious, straightforward way:A x, y;
...
if (x == y)
...
Comparing strings
The C Way
The library function strcmp() is used:char str[] = "hello"; if (strcmp(str, "betty") == 0) // do strings match? ...C uses 0 terminated strings, so the C way has an inherent inefficiency in constantly scanning for the terminating 0.
The D Way
Why not use the == operator?string str = "hello"; if (str == "betty") ...
D strings have the length stored separately from the string. Thus, the implementation of string compares can be much faster than in C (the difference being equivalent to the difference in speed between the C memcmp() and strcmp()).
D supports comparison operators on strings, too:
string str = "hello"; if (str < "betty") ...which is useful for sorting/searching.
Sorting arrays
The C Way
Although many C programmers tend to reimplement bubble sorts over and over, the right way to sort in C is to use qsort():int compare(const void *p1, const void *p2) { type *t1 = (type *)p1; type *t2 = (type *)p2; return *t1 - *t2; } type array[10]; ... qsort(array, sizeof(array)/sizeof(array[0]), sizeof(array[0]), compare);A compare() must be written for each type, and much careful typo-prone code needs to be written to make it work. The indirect function call required for each comparison limits the achievable performance of the qsort() routine.
The D Way
D has a powerful std.algorithm module with optimized sorting routines, which work for any built-in or user-defined type which can be compared:import std.algorithm; type[] array; ... sort(array); // sort array in-place
String literals
The C Way
String literals in C cannot span multiple lines, so to have a block of text it is necessary to use \ line splicing:"This text spans\n\ multiple\n\ lines\n"C's string literal concatenation doesn't really solve the problem:
"This text spans\n" "multiple\n" "lines\n"If there is a lot of text, this can wind up being tedious.
The D Way
String literals can span multiple lines, as in:"This text spans
multiple
lines
"
So blocks of text can just be cut and pasted into the D
source.
Data Structure Traversal
The C Way
Consider a function to traverse a recursive data structure. In this example, there's a simple symbol table of strings. The data structure is an array of binary trees. The code needs to do an exhaustive search of it to find a particular string in it, and determine if it is a unique instance.
To make this work, a helper function membersearchx is needed to recursively walk the trees. The helper function needs to read and write some context outside of the trees, so a custom struct Paramblock is created and a pointer to it is used to maximize efficiency.
struct Symbol { char *id; struct Symbol *left; struct Symbol *right; }; struct Paramblock { char *id; struct Symbol *sm; }; static void membersearchx(struct Paramblock *p, struct Symbol *s) { while (s) { if (strcmp(p->id,s->id) == 0) { if (p->sm) error("ambiguous member %s\n",p->id); p->sm = s; } if (s->left) membersearchx(p,s->left); s = s->right; } } struct Symbol *symbol_membersearch(Symbol *table[], int tablemax, char *id) { struct Paramblock pb; int i; pb.id = id; pb.sm = NULL; for (i = 0; i < tablemax; i++) { membersearchx(pb, table[i]); } return pb.sm; }
The D Way
This is the same algorithm in D, and it shrinks dramatically. Since nested functions have access to the lexically enclosing function's variables, there's no need for a Paramblock or to deal with its bookkeeping details. The nested helper function is contained wholly within the function that needs it, improving locality and maintainability.
The performance of the two versions is indistinguishable.
class Symbol { string id; Symbol left; Symbol right; } Symbol symbol_membersearch(Symbol[] table, string id) { Symbol sm; void membersearchx(Symbol s) { while (s) { if (id == s.id) { if (sm) error("ambiguous member %s\n", id); sm = s; } if (s.left) membersearchx(s.left); s = s.right; } } for (int i = 0; i < table.length; i++) { membersearchx(table[i]); } return sm; }
Unsigned Right Shift
The C Way
The right shift operators >> and >>= are signed shifts if the left operand is a signed integral type, and are unsigned right shifts if the left operand is an unsigned integral type. To produce an unsigned right shift on an int, a cast is necessary:int i, j; ... j = (unsigned)i >> 3;If i is an int, this works fine. But if i is of a type created with typedef,
myint i, j; ... j = (unsigned)i >> 3;and myint happens to be a long int, then the cast to unsigned will silently throw away the most significant bits, corrupting the answer.
The D Way
D has the right shift operators >> and >>= which behave as they do in C. But D also has explicitly unsigned right shift operators >>> and >>>= which will do an unsigned right shift regardless of the sign of the left operand. Hence,myint i, j; ... j = i >>> 3;avoids the unsafe cast and will work as expected with any integral type.
Dynamic Closures
The C Way
Consider a reusable container type. In order to be reusable, it must support a way to apply arbitrary code to each element of the container. This is done by creating an apply function that accepts a function pointer to which is passed each element of the container contents.
A generic context pointer is also needed, represented here by void *p. The example here is of a trivial container class that holds an array of ints, and a user of that container that computes the maximum of those ints.
void apply(void *p, int *array, int dim, void (*fp)(void *, int)) { for (int i = 0; i < dim; i++) fp(p, array[i]); } struct Collection { int array[10]; }; void comp_max(void *p, int i) { int *pmax = (int *)p; if (i > *pmax) *pmax = i; } void func(struct Collection *c) { int max = INT_MIN; apply(&max, c->array, sizeof(c->array)/sizeof(c->array[0]), comp_max); }
While this works, it isn't very flexible.
The D Way
The D version makes use of delegates to transmit context information for the apply function, and nested functions both to capture context information and to improve locality.class Collection { int[10] array; void apply(void delegate(int) fp) { for (int i = 0; i < array.length; i++) fp(array[i]); } } void func(Collection c) { int max = int.min; void comp_max(int i) { if (i > max) max = i; } c.apply(&comp_max); }Pointers are eliminated, as well as casting and generic pointers. The D version is fully type safe. An alternate method in D makes use of function literals:
void func(Collection c) { int max = int.min; c.apply(delegate(int i) { if (i > max) max = i; } ); }eliminating the need to create irrelevant function names.
Variadic Function Parameters
The task is to write a function that takes a varying number of arguments, such as a function that sums its arguments.The C Way
#include <stdio.h> #include <stdarg.h> int sum(int dim, ...) { int i; int s = 0; va_list ap; va_start(ap, dim); for (i = 0; i < dim; i++) s += va_arg(ap, int); va_end(ap); return s; } int main() { int i; i = sum(3, 8,7,6); printf("sum = %d\n", i); return 0; }There are two problems with this. The first is that the sum function needs to know how many arguments were supplied. It has to be explicitly written, and it can get out of sync with respect to the actual number of arguments written. The second is that there's no way to check that the types of the arguments provided really were ints, and not doubles, strings, structs, etc.
The D Way
The ... following an array parameter declaration means that the trailing arguments are collected together to form an array. The arguments are type checked against the array type, and the number of arguments becomes a property of the array:import std.stdio; int sum(int[] values ...) { int s = 0; foreach (int x; values) s += x; return s; } int main() { int i; i = sum(8,7,6); writefln("sum = %d", i); return 0; }