Every experienced C++ programmer accumulates a series of idioms and techniqueswhich become second nature. Sometimes, when learning a new language, thoseidioms can be so comfortable it's hard to see how to do the equivalent in thenew language. So here's a collection of common C++ techniques, and how to do thecorresponding task in D.
See also:Programming in D for C Programmersclass Foo{ Foo(int x);};class Foo{this(int x) { }}which reflects how they are used in D.
class A { A() {... } };class B : A{ B(int x) : A() // call base constructor { ... }};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; }}
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++.A x, y;...if (x == y) ...#define HANDLE_INIT ((Handle)(-1))typedef void *Handle;void foo(void *);void bar(Handle);Handle h = HANDLE_INIT;foo(h); // coding bug not caughtbar(h); // okThe 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) ...alias Handle = Typedef!(void*,cast(void*)-1);void bar(Handle);Handle h;bar(h);h = func();if (h != Handle.init) ...Unlike a barealias, usingstd.typecons.Typedef ensures the two types are not considered as equals. Note how a default initializer can be supplied tostd.typecons.Typedef as a value of the underlying type.
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; }module X;class A{private:staticint a;public:int foo(B j) {return j.b; }}class B{private:staticint b;public:int bar(A j) {return j.a; }}int abc(A p) {return p.a; }Theprivate attribute prevents other modules from accessing the members.
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.struct A{int opCmp(int i);}
The compiler automatically interprets all the <, <=, > and >= operators in terms of thecmp 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 foo{ int x;}using foo::x;/** Module foo.d **/module foo;int x;/** Another module **/import foo;alias x = foo.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.
class File{ Handle *h; ~File() { h->release(); }};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'ssynchronized declarations and statements.
The few RAII issues left are handled bystructs. Astruct gets its destructor run when it goes out of scope.
struct File{ Handle h; ~this() { h.release(); }}void test(){if (...) {auto f = File(); ... }// f.~this() gets run at closing brace, even if// scope was exited via a thrown exception}
classes are typically managed by the garbage collector which doesn'tlend itself to RAII. If you need deterministic destruction withclassesyou can usestd.typecons.scoped (which will also allocate theclass on the stack instead of the garbage collector managed heap).
See alsoScopeGuardStatement for a more generalizedmechanism that lets you run arbitrary statements whenever leaving the currentscope.
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.class Abc{// set @propertyvoid property(int newproperty) { myprop = newproperty; }// get @propertyint property() {return myprop; }private:int myprop;}Abc a =new Abc;a.property = 3;int x = a.property;assert(x == 3);
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}template factorial(int n){enum factorial = n * .factorial!(n-1);}template factorial(int n : 1){enum factorial = 1;}unittest{import std.stdio; writeln(factorial!(4));// prints 24}
enum factorial(int n) = n * .factorial!(n-1);enum factorial(int n : 1) = 1;staticassert(factorial!(4) == 24);
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 long 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 generatedstruct 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 ;}#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 ;}import std.stdio;template Integer(int nbits){staticif (nbits <= 8)alias Integer =byte;elsestaticif (nbits <= 16)alias Integer =short;elsestaticif (nbits <= 32)alias Integer =int;elsestaticif (nbits <= 64)alias Integer =long;elsestaticassert(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;}
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 theSFINAE (Substitution Failure Is Not An Error) principle. Why it works is a fairly advanced template topic.template IsFunctionT(T){staticif (is(T[]) )constint IsFunctionT = 0;elseconstint IsFunctionT = 1;}unittest{aliasint fp(int);staticassert(IsFunctionT!(fp) == 1);}
unittest{aliasint fp(int);staticassert(is(fp ==function) );}
class Interface { public: virtual int method() = 0;};class FirstVariant : public Interface { public: int method() { ... }};class SecondVariant : public Interface { public: int method() { ... }};The interface is used as follows:FirstVariant FirstVariant;SecondVariant SecondVariant;FirstVariant.method();SecondVariant.method();
interface Interface {int method();}class FirstVariant : Interface {int method() { ... }}class SecondVariant : Interface {int method() { ... }}The interface is used as follows:
FirstVariant FirstVariant =new FirstVariant();SecondVariant SecondVariant =new SecondVariant();FirstVariant.method();SecondVariant.method();
module test;void main(){struct Data {int id; }import std.container.array : Array; Array!Data gallery; Data d1; gallery.insertBack(d1);auto d2 = gallery[0]; d2.id = 1;assert(d2.id != gallery[0].id);// d2 is neither ref nor pointer}
auto d2 = &gallery[0]; d2.id = 1;assert(d2.id == gallery[0].id);