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.See also: Programming in D for C Programmers
- Defining Constructors
- Base class initialization
- Comparing structs
- Creating a new typedef'd type
- Friends
- Operator overloading
- Namespace using declarations
- RAII (Resource Acquisition Is Initialization)
- Properties
- Recursive Templates
- Meta Templates
- Type Traits
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) );
}