www.digitalmars.com

D Programming Language 2.0


Last update Sun Dec 30 20:22:42 2012

Programming in D for C++ Programmers

C++ 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.

See also: Programming in D for C Programmers


Defining constructors

The C++ Way

Constructors have the same name as the class:
class Foo
{
	Foo(int x); 
};

The D Way

Constructors are defined with the this keyword:
class Foo
{
	this(int x) { } 
}
which reflects how they are used in D.

Base class initialization

The C++ Way

Base constructors are called using the base initializer syntax.
class A { A() {... } };
class B : A
{
     B(int x)
	: A()		// call base constructor 
     {	...
     }
};

The D Way

The base class constructor is called with the super syntax:
class A { this() { ... } }
class B : A
{
     this(int x)
     {	...
	super();	// call base constructor 
	...
     }
}
It's superior to C++ in that the base constructor call can be flexibly placed anywhere in the derived constructor. D can also have one constructor call another one:
class A
{	int a;
	int b;
	this() { a = 7; b = foo(); } 
	this(int x)
	{
	    this();
	    a = x;
	}
}
Members can also be initialized to constants before the constructor is ever called, so the above example is equivalently written as:
class A
{	int a = 7;
	int b;
	this() { b = foo(); } 
	this(int x)
	{
	    this();
	    a = x;
	}
}

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;

inline bool operator==(const A& x, const A& y)
{
    return (memcmp(&x, &y, sizeof(struct A)) == 0); 
}
...
if (x == y)
    ...
Note that the operator overload must be done for every struct needing to be compared, and the implementation of that overloaded operator is free of any language help with type checking. The C++ way has an additional problem in that just inspecting the (x == y) does not give a clue what is actually happening, you have to go and find the particular overloaded operator==() that applies to verify what it really does.

There's a nasty bug lurking in the memcmp() implementation of operator==(). 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.

To address this, the operator==() can be implemented to do a memberwise compare. Unfortunately, this is unreliable because (1) if a member is added to the struct definition one may forget to add it to operator==(), and (2) floating point nan values compare unequal even if their bit patterns match.

There just is no robust solution in C++.

The D Way

D does it the obvious, straightforward way:
A x, y;
...
if (x == y) 
    ...

Creating a new typedef'd type

The C++ Way

Typedef's 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.
#define HANDLE_INIT	((Handle)(-1))
typedef void *Handle;
void foo(void *);
void bar(Handle);

Handle h = HANDLE_INIT;
foo(h);			// coding bug not caught 
bar(h);			// ok
The C++ solution is to create a dummy struct whose sole purpose is to get type checking and overloading on the new type.
#define HANDLE_INIT	((void *)(-1)) 
struct Handle
{   void *ptr;

    // default initializer
    Handle() { ptr = HANDLE_INIT; }

    Handle(int i) { ptr = (void *)i; }

    // conversion to underlying type 
    operator void*() { return ptr; }
};
void bar(Handle);

Handle h;
bar(h);
h = func();
if (h != HANDLE_INIT)
    ...

The D Way

No need for idiomatic constructions like the above. Just write:
typedef void* Handle = cast(void*)-1; 
void bar(Handle);

Handle h;
bar(h);
h = func();
if (h != Handle.init)
    ...
Note how a default initializer can be supplied for the typedef as a value of the underlying type.

Friends

The C++ Way

Sometimes two classes are tightly related but not by inheritance, but need to access each other's private members. This is done using friend declarations:
class A
{
    private:
	int a;

    public:
	int foo(B *j);
	friend class B;
	friend int abc(A *);
};

class B
{
    private:
	int b;

    public:
	int bar(A *j);
	friend class A;
};

int A::foo(B *j) { return j->b; }
int B::bar(A *j) { return j->a; } 

int abc(A *p) { return p->a; }

The D Way

In D, friend access is implicit in being a member of the same module. It makes sense that tightly related classes should be in the same module, so implicitly granting friend access to other module members solves the problem neatly:
module X;

class A
{
    private:
	static int a;

    public:
	int foo(B j) { return j.b; }
}

class B
{
    private:
	static int b;

    public:
	int bar(A j) { return j.a; } 
}

int abc(A p) { return p.a; }
The private attribute prevents other modules from accessing the members.

Operator overloading

The C++ Way

Given a struct that creates a new arithmetic data type, it's convenient to overload the comparison operators so it can be compared against integers:
struct A
{
	int operator <  (int i);
	int operator <= (int i);
	int operator >  (int i);
	int operator >= (int i);
};

int operator <  (int i, A &a) { return a >  i; }
int operator <= (int i, A &a) { return a >= i; }
int operator >  (int i, A &a) { return a <  i; }
int operator >= (int i, A &a) { return a <= i; } 
A total of 8 functions are necessary.

The D Way

D recognizes that the comparison operators are all fundamentally related to each other. So only one function is necessary:
struct A
{
	int opCmp(int i); 
}
The compiler automatically interprets all the <, <=, > and >= operators in terms of the cmp function, as well as handling the cases where the left operand is not an object reference.

Similar sensible rules hold for other operator overloads, making using operator overloading in D much less tedious and less error prone. Far less code needs to be written to accomplish the same effect.


Namespace using declarations

The C++ Way

A using-declaration in C++ is used to bring a name from a namespace scope into the current scope:
namespace foo
{
    int x;
}
using foo::x;

The D Way

D uses modules instead of namespaces and #include files, and alias declarations take the place of using declarations:
/** Module foo.d **/
module foo;
int x;

/** Another module **/ 
import foo;
alias foo.x x;
Alias is a much more flexible than the single purpose using declaration. Alias can be used to rename symbols, refer to template members, refer to nested class types, etc.

RAII (Resource Acquisition Is Initialization)

The C++ Way

In C++, resources like memory, etc., all need to be handled explicitly. Since destructors automatically get called when leaving a scope, RAII is implemented by putting the resource release code into the destructor:
class File
{   Handle *h;

    ~File()
    {
	h->release(); 
    }
};

The D Way

The bulk of resource release problems are simply keeping track of and freeing memory. This is handled automatically in D by the garbage collector. The second common resources used are semaphores and locks, handled automatically with D's synchronized declarations and statements.

The few RAII issues left are handled by scope classes. Scope classes get their destructors run when they go out of scope.

scope class File
{   Handle h;

    ~this()
    {
	h.release();
    }
}

void test()
{
    if (...)
    {   scope f = new File();
	...
    } // f.~this() gets run at closing brace, even if 
      // scope was exited via a thrown exception
}

Properties

The C++ Way

It is common practice to define a field, along with object-oriented get and set functions for it:
class Abc
{
  public:
    void setProperty(int newproperty) { property = newproperty; } 
    int getProperty() { return property; }

  private:
    int property;
};

Abc a;
a.setProperty(3);
int x = a.getProperty();
All this is quite a bit of typing, and it tends to make code unreadable by filling it with getProperty() and setProperty() calls.

The D Way

Properties can be get and set using the normal field syntax, yet the get and set will invoke methods instead.
class Abc
{
    // set 
    void property(int newproperty) { myprop = newproperty; }

    // get
    int property() { return myprop; }

  private:
    int myprop;
}
which is used as:
Abc a;
a.property = 3;		// equivalent to a.property(3)
int x = a.property;	// equivalent to int x = a.property() 
Thus, in D a property can be treated like it was a simple field name. A property can start out actually being a simple field name, but if later if becomes necessary to make getting and setting it function calls, no code needs to be modified other than the class definition. It obviates the wordy practice of defining get and set properties ‘just in case’ a derived class should need to override them. It's also a way to have interface classes, which do not have data fields, behave syntactically as if they did.

Recursive Templates

The C++ Way

An advanced use of templates is to recursively expand them, relying on specialization to end it. A template to compute a factorial would be:
template<int n> class factorial
{
    public:
	enum { result = n * factorial<n - 1>::result }; 
};

template<> class factorial<1>
{
    public:
	enum { result = 1 };
};

void test()
{
    printf("%d\n", factorial<4>::result); // prints 24
}

The D Way

The D version is analogous, though a little simpler, taking advantage of promotion of single template members to the enclosing name space:
template factorial(int n)
{
    enum { factorial = n * .factorial!(n-1) }
}

template factorial(int n : 1)
{
    enum { factorial = 1 }
}

void test()
{
    writefln("%d", factorial!(4));	// prints 24 
}

Meta Templates

The problem: create a typedef for a signed integral type that is at least nbits in size.

The C++ Way

This example is simplified and adapted from one written by Dr. Carlo Pescio in Template Metaprogramming: Make parameterized integers portable with this novel technique.

There is no way in C++ to do conditional compilation based on the result of an expression based on template parameters, so all control flow follows from pattern matching of the template argument against various explicit template specializations. Even worse, there is no way to do template specializations based on relationships like "less than or equal to", so the example uses a clever technique where the template is recursively expanded, incrementing the template value argument by one each time, until a specialization matches. If there is no match, the result is an unhelpful recursive compiler stack overflow or internal error, or at best a strange syntax error.

A preprocessor macro is also needed to make up for the lack of template typedefs.

#include <limits.h>

template< int nbits > struct Integer
{
    typedef Integer< nbits + 1 > :: int_type int_type ;
} ;

struct Integer< 8 >
{
    typedef signed char int_type ;
} ;

struct Integer< 16 > 
{
    typedef short int_type ;
} ;

struct Integer< 32 > 
{
    typedef int int_type ;
} ;

struct Integer< 64 >
{
    typedef long long int_type ;
} ;

// If the required size is not supported, the metaprogram
// will increase the counter until an internal error is
// signaled, or INT_MAX is reached. The INT_MAX 
// specialization does not define a int_type, so a 
// compiling error is always generated
struct Integer< INT_MAX >
{
} ;

// A bit of syntactic sugar
#define Integer( nbits ) Integer< nbits > :: int_type 

#include <stdio.h>

int main()
{
    Integer( 8 ) i ;
    Integer( 16 ) j ;
    Integer( 29 ) k ;
    Integer( 64 ) l ;
    printf("%d %d %d %d\n",
	sizeof(i), sizeof(j), sizeof(k), sizeof(l)); 
    return 0 ;
}

The C++ Boost Way

This version uses the C++ Boost library. It was provided by David Abrahams.
#include <boost/mpl/if.hpp>
#include <boost/mpl/assert.hpp>

template <int nbits> struct Integer
    : mpl::if_c<(nbits <= 8), signed char
    , mpl::if_c<(nbits <= 16), short
    , mpl::if_c<(nbits <= 32), long
    , long long>::type >::type >
{
    BOOST_MPL_ASSERT_RELATION(nbits, <=, 64);
}

#include <stdio.h>

int main()
{
    Integer< 8 > i ;
    Integer< 16 > j ;
    Integer< 29 > k ;
    Integer< 64 > l ;
    printf("%d %d %d %d\n",
	sizeof(i), sizeof(j), sizeof(k), sizeof(l)); 
    return 0 ;
}

The D Way

The D version could also be written with recursive templates, but there's a better way. Unlike the C++ example, this one is fairly easy to figure out what is going on. It compiles quickly, and gives a sensible compile time message if it fails.
import std.stdio;

template Integer(int nbits)
{
    static if (nbits <= 8)
	alias byte Integer;
    else static if (nbits <= 16)
	alias short Integer;
    else static if (nbits <= 32)
	alias int Integer;
    else static if (nbits <= 64)
	alias long Integer;
    else
	static assert(0);
}

int main()
{
    Integer!(8) i ;
    Integer!(16) j ;
    Integer!(29) k ;
    Integer!(64) l ;
    writefln("%d %d %d %d",
	i.sizeof, j.sizeof, k.sizeof, l.sizeof); 
    return 0;
}

Type Traits

Type traits are another term for being able to find out properties of a type at compile time.

The C++ Way

The following template comes from C++ Templates: The Complete Guide, David Vandevoorde, Nicolai M. Josuttis pg. 353 which determines if the template's argument type is a function:
template<typename T> class IsFunctionT
{
    private:
	typedef char One;
	typedef struct { char a[2]; } Two;
	template<typename U> static One test(...);
	template<typename U> static Two test(U (*)[1]);
    public:
	enum { Yes = sizeof(IsFunctionT<T>::test<T>(0)) == 1 };
};

void test()
{
    typedef int (fp)(int);

    assert(IsFunctionT<fp>::Yes == 1);
}
This template relies on the SFINAE (Substitution Failure Is Not An Error) principle. Why it works is a fairly advanced template topic.

The D Way

SFINAE (Substitution Failure Is Not An Error) can be done in D without resorting to template argument pattern matching:
template IsFunctionT(T)
{
    static if ( is(T[]) )
	const int IsFunctionT = 0;
    else
	const int IsFunctionT = 1;
}

void test()
{
    typedef int fp(int);

    assert(IsFunctionT!(fp) == 1);
}
The task of discovering if a type is a function doesn't need a template at all, nor does it need the subterfuge of attempting to create the invalid array of functions type. The IsExpression expression can test it directly:
void test()
{
    alias int fp(int);

    assert( is(fp == function) );
}




Forums | Comments |  D  | Search | Downloads | Home