This document specifies how to interface with C++ directly.
It is also possible to indirectly interface with C++ code, either through aC interface or a COM interface.
Being 100% compatible with C++ means more or less adding a fully functional C++ compiler front end to D. Anecdotal evidence suggests that writing such is a minimum of a 10 man-year project, essentially making a D compiler with such capability unimplementable. Other languages looking to hook up to C++ face the same problem, and the solutions have been:
D takes a pragmatic approach that assumes a couple modest accommodations can solve a significant chunk of the problem:
C++ global functions, including those in namespaces, can be declared and called in D, or defined in D and called in C++.
Given a C++ function in a C++ source file:
#include <iostream>using namespace std;int foo(int i, int j, int k){ cout << "i = " << i << endl; cout << "j = " << j << endl; cout << "k = " << k << endl; return 7;}In the corresponding D code,foo is declared as having C++ linkage and function calling conventions:
extern (C++)int foo(int i,int j,int k);
and then it can be called within the D code:
extern (C++)int foo(int i,int j,int k);void main(){ foo(1, 2, 3);}
Compiling the two files, the first with a C++ compiler, the second with a D compiler, linking them together, and then running it yields:
> g++ -c foo.cpp> dmd bar.d foo.o -L-lstdc++ && ./bari = 1j = 2k = 3
There are several things going on here:
To make a D function accessible from C++, give it C++ linkage:
import std.stdio;extern (C++)int foo(int i,int j,int k){ writefln("i = %s", i); writefln("j = %s", j); writefln("k = %s", k);return 1;}extern (C++)void bar();void main(){ bar();}
The C++ end looks like:
int foo(int i, int j, int k);void bar(){ foo(6, 7, 8);}Compiling, linking, and running produces the output:
> dmd -c foo.d> g++ bar.cpp foo.o -lphobos2 -pthread -o bar && ./bari = 6j = 7k = 8
C++ symbols that reside in namespaces can be accessed from D. Anamespace can be added to theextern (C++)LinkageAttribute:
extern (C++, N)int foo(int i,int j,int k);void main(){ N.foo(1, 2, 3);// foo is in C++ namespace 'N'}
C++ can open the same namespace in the same file and multiple files. In D, this can be done as follows:
module ns;extern (C++,`ns`){int foo() {return 1; }}
Any expression that resolves to either a tuple of strings or an empty tuple is accepted. When the expression resolves to an empty tuple, it is equivalent toextern (C++)
extern(C++, (expression)){int bar() {return 2; }}
or in multiple files, by organizing them in a package consisting of several modules:
ns/|-- a.d|-- b.d|--package.dFilens/a.d:
module a;extern (C++,`ns`) {int foo() {return 1; } }
Filens/b.d:
module b;extern (C++,`ns`) {int bar() {return 2; } }
Filens/package.d:
module ns;publicimport a, b;
Then import the package containing the extern C++ declarations as follows:
import ns;staticassert(foo() == 1 && bar() == 2);
Note that theextern (C++,ns) linkage attribute affects only the ABI (name mangling and calling convention) of these declarations. Importing them follows the usualD module import semantics.
Alternatively, the non-string form can be used to introduce a scope. Note that the enclosing module already provides a scope for the symbols declared in the namespace. This form does not allow closing and reopening the same namespace with in the same module. That is:
module a;extern (C++, ns1) {int foo() {return 1; } }
module b;extern (C++, ns1) {int bar() {return 2; } }
import a, b;staticassert(foo() == 1 && bar() == 2);
works, but:
extern (C++, ns1) {int foo() {return 1; } }extern (C++, ns1) {int bar() {return 2; } }
does not. Additionally, aliases can be used to avoid collision of symbols:
module a;extern (C++, ns) {int foo() {return 1; } }
module b;extern (C++, ns) {int bar() {return 2; } }
module ns;import a, b;alias foo = a.ns.foo;alias bar = b.ns.bar;
import ns;staticassert(foo() == 1 && bar() == 2);
C++ classes can be declared in D by using theextern (C++) attribute onclass,struct andinterface declarations.extern (C++) interfaces have the same restrictions as D interfaces, which means that Multiple Inheritance is supported to the extent that only one base class can have member fields.
extern (C++) structs do not support virtual functions but can be used to map C++ value types.
Unlike classes and interfaces with D linkage,extern (C++) classes and interfaces are not rooted inObject and cannot be used withtypeid.
D structs and classes have different semantics whereas C++ structs and classes are basically the same. The use of a D struct or class depends on the C++ implementation and not on the used C++ keyword.
When mapping a Dclass onto a C++struct, useextern(C++, struct) to avoid linking problems with C++ compilers (notably MSVC) that distinguish between C++'sclass andstruct when mangling. Conversely, useextern(C++, class) to map a Dstruct onto a C++class.
extern(C++, class) andextern(C++, struct) can be combined with C++ namespaces:
extern (C++,struct)extern (C++, foo)class Bar{}
The following example shows binding of a pure virtual function, its implementation in a derived class, a non-virtual member function, and a member field:
#include <iostream>using namespace std;class Base{ public: virtual void print3i(int a, int b, int c) = 0;};class Derived : public Base{ public: int field; Derived(int field) : field(field) {} void print3i(int a, int b, int c) { cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; } int mul(int factor);};int Derived::mul(int factor){ return field * factor;}Derived *createInstance(int i){ return new Derived(i);}void deleteInstance(Derived *&d){ delete d; d = 0;}We can use it in D code like:
extern(C++){abstractclass Base {void print3i(int a,int b,int c); }class Derived : Base {int field; @disablethis();overridevoid print3i(int a,int b,int c);finalint mul(int factor); } Derived createInstance(int i);void deleteInstance(ref Derived d);}void main(){import std.stdio;auto d1 = createInstance(5); writeln(d1.field); writeln(d1.mul(4)); Base b1 = d1; b1.print3i(1, 2, 3); deleteInstance(d1);assert(d1isnull);auto d2 = createInstance(42); writeln(d2.field); deleteInstance(d2);assert(d2isnull);}
Compiling, linking, and running produces the output:
> g++ base.cpp> dmd main.d base.o -L-lstdc++ && ./main520a = 1b = 2c = 342
Note how in the above example, the constructor is not bindable and isinstead disabled on the D side; an alternative would be to reimplement theconstructor in D. See thesection below on lifetime management for more information.
Given D code like:
extern (C++)int callE(E);extern (C++)interface E{int bar(int i,int j,int k);}class F : E{extern (C++)int bar(int i,int j,int k) {import std.stdio : writefln; writefln("i = %s", i); writefln("j = %s", j); writefln("k = %s", k);return 8; }}void main(){ F f =new F(); callE(f);}
The C++ code to access it looks like:
class E{ public: virtual int bar(int i, int j, int k);};int callE(E *e){ return e->bar(11, 12, 13);}> dmd -c base.d> g++ klass.cpp base.o -lphobos2 -pthread -o klass && ./klassi = 11j = 12k = 13
C++ allows a struct to inherit from a base struct. This is done in D usingalias this:
struct Base { ... members ... };struct Derived{ Base base;// make it the first fieldalias basethis; ... members ...}
In both C++ and D, if a struct has zero fields, the struct still has a size of 1 byte. But, in C++ if the struct with zero fields is used as a base struct, its size is zero (called theEmpty Base Optimization). There are two methods for emulating this behavior in D. The first forwards references to a function returning a faked reference to the base:
struct Base { ... members ... };struct DerivedStruct{staticif (Base.tupleof.length > 0) Base base;elserefinout(Base) base()inout {return *cast(inout(Base)*)&this; }alias basethis; ... members ...}
The second makes use of template mixins:
mixintemplate BaseMembers(){void memberFunction() { ... }}struct Base{mixin BaseMembers!();}struct Derived{mixin BaseMembers!(); ... members ...}
Note that the template mixin is evaluated in the context of its instantiation, not declaration. If this is a problem, the template mixin can use local imports, or have the member functions forward to the actual functions.
C++ function and type templates can be bound by using theextern (C++) attribute on a function or type template declaration.
Note that all instantiations used in D code must be provided by linking to C++ object code or shared libraries containing the instantiations.
For example:
#include <iostream>template<class T>struct Foo{ private: T field; public: Foo(T t) : field(t) {} T get(); void set(T t);};template<class T>T Foo<T>::get(){ return field;}template<class T>void Foo<T>::set(T t){ field = t;}Foo<int> makeIntFoo(int i){ return Foo<int>(i);}Foo<char> makeCharFoo(char c){ return Foo<char>(c);}template<class T>void increment(Foo<T> &foo){ foo.set(foo.get() + 1);}template<class T>void printThreeNext(Foo<T> foo){ for(size_t i = 0; i < 3; ++i) { std::cout << foo.get() << std::endl; increment(foo); }}// The following two functions ensure that the required instantiations of// printThreeNext are provided by this code modulevoid printThreeNexti(Foo<int> foo){ printThreeNext(foo);}void printThreeNextc(Foo<char> foo){ printThreeNext(foo);}extern(C++):struct Foo(T){private: T field;public: @disablethis(); T get();void set(T t);}Foo!int makeIntFoo(int i);Foo!char makeCharFoo(char c);void increment(T)(ref Foo!T foo);void printThreeNext(T)(Foo!T foo);extern(D)void main(){auto i = makeIntFoo(42);assert(i.get() == 42); i.set(1); increment(i);assert(i.get() == 2);auto c = makeCharFoo('a'); increment(c);assert(c.get() == 'b'); c.set('A'); printThreeNext(c);}
Compiling, linking, and running produces the output:
> g++ -c template.cpp> dmd main.d template.o -L-lstdc++ && ./mainABC
C++ and D follow different rules for function overloading. D source code, even when callingextern (C++) functions, will still follow D overloading rules.
C++ code explicitly manages memory with calls to::operator new() and::operator delete(). D'snew operator allocates memory using the D garbage collector, so no explicit delete is necessary. D'snew operator is not compatible with C++'s::operator new and::operator delete. Attempting to allocate memory with D'snew and deallocate with C++::operator delete will result in miserable failure.
D can explicitly manage memory using a variety of library tools, such as withstd.experimental.allocator. Additionally,core.stdc.stdlib.malloc andcore.stdc.stdlib.free can be used directly for connecting to C++ functions that expectmalloc'd buffers.
If pointers to memory allocated on the D garbage collector heap are passed to C++ functions, it's critical to ensure that the referenced memory will not be collected by the D garbage collector before the C++ function is done with it. This is accomplished by:
An interior pointer to the allocated memory block is sufficient to let the GC know the object is in use; i.e. it is not necessary to maintain a pointer to thebeginning of the allocated memory.
The garbage collector does not scan the stacks of threads not registered with the D runtime, nor does it scan the data segments of shared libraries that aren't registered with the D runtime.
| D type | C++ type |
|---|---|
| void | void |
| byte | signed char |
| ubyte | unsigned char |
| char | char (chars are unsigned in D) |
| core.stdc.stddef.wchar_t | wchar_t |
| short | short |
| ushort | unsigned short |
| int | int |
| uint | unsigned |
| long | long if it is 64 bits wide, otherwiselong long |
| ulong | unsigned long if it is 64 bits wide, otherwiseunsigned long long |
| core.stdc.config.cpp_long | long |
| core.stdc.config.cpp_ulong | unsigned long |
| float | float |
| double | double |
| real | long double |
| extern (C++)struct | struct orclass without virtual member functions |
| extern (C++)class | struct orclass with virtual member functions |
| extern (C++)interface | struct orclass with no member fields |
| union | union |
| enum | enum |
| type* | type* |
| reftype (in parameter lists only) | type& |
| type[dim] | type[dim] for a variable/field declaration, oruseref for function parameter |
| type[dim]* | type(*)[dim] |
| type[] | noextern (C++) equivalent,see below |
| type[type] | no equivalent |
| typefunction(parameters) | type(*)(parameters) |
| typedelegate(parameters) | no equivalent |
These equivalents hold when the D and C++ compilers used are companions on the host platform.
These are not supported forextern (C++). Forextern (C), they are equivalent to a struct template. For example:
extern (C)const(char)[] slice;
dmd -HC generates the following C++ declaration:
extern"C" _d_dynamicArray<constchar > slice;
_d_dynamicArray is generated as follows:
/// Represents a D [] arraytemplate<typename T>struct _d_dynamicArrayfinal{ size_t length; T *ptr; _d_dynamicArray() : length(0), ptr(NULL) { } _d_dynamicArray(size_t length_in, T *ptr_in) : length(length_in), ptr(ptr_in) { } T& operator[](const size_t idx) {assert(idx < length);return ptr[idx]; }const T& operator[](const size_t idx)const {assert(idx < length);return ptr[idx]; }};
D structs and unions are analogous to C's.
C code often adjusts the alignment and packing of struct members with a command line switch or with various implementation specific #pragmas. D supports explicit alignment attributes that correspond to the C compiler's rules. Check what alignment the C code is using, and explicitly set it for the D struct declaration.
D supports bitfields in the standard library: seestd.bitmanip.bitfields.
C++ constructors, copy constructors, move constructors and destructors cannot be called directly in D code, and D constructors, postblit operators and destructors cannot be directly exported to C++ code. Interoperation of types with these special operators is possible by either 1) disabling the operator in the client language and only using it in the host language, or 2) faithfully reimplementing the operator in the client language. With the latter approach, care needs to be taken to ensure observable semantics remain the same with both implementations, which can be difficult, or in some edge cases impossible, due to differences in how the operators work in the two languages. For example, in D all objects are movable and there is no move constructor.
D's operator overloading is primarily involved with overloading its unary and binary operators including the increment, index, slice, cast, function call, comparison operator etc. Overloads of these operators that exist in C++ can be directly called from D provided the D operator and its C++ counterpart semantically agree. Below is a simple use case involving the assignment, index, and function call operator overloads interoperating between D and C++.
In a foo.cpp file:
#include <iostream>class A{public: int a; int b; int buffer[3]; A() { a = 0; b = 0; } A(int a, int b); A& operator=(const A& obj); //overloading the assignment operator void operator()(int a, int b, int c); // overloading the call operator int& operator[](int value); // overloading the index operator void printbuffer();};A::A(int a, int b){ this->a = a; this->b = b;}A& A::operator=(const A& obj){ a = obj.a; b = obj.b; return *this;}void A::operator()(int a, int b, int c){ std::cout << "value of a is "<< a << std::endl; std::cout << "value of b is "<< b << std::endl; std::cout << "value of c is "<< c << std::endl;}int& A::operator[](int value){ return buffer[value];}void A::printbuffer(){ for (int i = 0; i < 3; i++) { std::cout << "the buffer numbers of index "<< i << " is "<< buffer[i] << std:: endl; }}In a bar.d file:
import std.stdio;extern(C++)struct A{int a;int b;int[3] buffer;this(int a,int b);ref A opAssign(refconst A);// links with A::operator=void opCall(int a,int b,int c);// links with A::operator()refint opIndex(int value);// links with A::operator[]void printbuffer();}void main(){auto obj1 = A(1, 3);auto obj2 = A(13, 43); obj2 = obj1;// opAssign called here writeln("obj2's a is ", obj2.a); writeln("obj2's b is ", obj2.b); obj2(20, 40, 60); obj2[0] = 30; obj2[1] = 60; obj2[2] = 90; obj2.printbuffer();}
Compiling, linking, and running produces the output:
> g++ -c foo.cpp> dmd bar.d foo.o -L-lstdc++ && ./barobj2's a is 1obj2's b is 3value of a is 20value of b is 40value of c is 60the buffer numbers of index 0 is 30the buffer numbers of index 1 is 60the buffer numbers of index 2 is 90
D runtime type identification uses completely different techniques than C++. The two are incompatible.
Exception interoperability is a work in progress.
At present, C++ exceptions cannot be caught in or thrown from D, and D exceptions cannot be caught in or thrown from C++. Additionally, objects in C++ stack frames are not guaranteed to be destroyed when unwinding the stack due to a D exception, and vice versa.
The plan is to support all of the above except throwing D exceptions directly in C++ code (but they will be throwable indirectly by calling into a D function with C++ linkage).
| Feature | D | C++98 |
|---|---|---|
| const keyword | Yes | Yes |
| immutable keyword | Yes | No |
| const notation | // Functional://ptr to const ptr to const intconst(int*)* p; | // Postfix://ptr to const ptr to const intconst int *const *p; |
| transitive const | // Yes://const ptr to const ptr to const intconstint** p;**p = 3;// error | // No:// const ptr to ptr to intint** const p;**p = 3; // ok |
| cast away const | // Yes:// ptr to const intconst(int)* p;int* q =cast(int*)p;// ok | // Yes:// ptr to const intconst int* p;int* q = const_cast<int*>p; //ok |
| cast+mutate | // No:// ptr to const intconst(int)* p;int* q =cast(int*)p;*q = 3;// undefined behavior | // Yes:// ptr to const intconst int* p;int* q = const_cast<int*>p;*q = 3; // ok |
| overloading | // Yes:void foo(int x);void foo(constint x);//ok | // No:void foo(int x);void foo(const int x); //error |
| const/mutable aliasing | // Yes:void foo(constint* x,int* y){ bar(*x);// bar(3) *y = 4; bar(*x);// bar(4)}...int i = 3;foo(&i, &i); | // Yes:void foo(const int* x, int* y){ bar(*x); // bar(3) *y = 4; bar(*x); // bar(4)}...int i = 3;foo(&i, &i); |
| immutable/mutable aliasing | // No:void foo(immutableint* x,int* y){ bar(*x);// bar(3) *y = 4;// undefined behavior bar(*x);// bar(??)}...int i = 3;foo(cast(immutable)&i, &i); | No immutables |
| type of string literal | immutable(char)[] | const char* |
| string literal to non-const | not allowed | allowed, but deprecated |