Skip to content
V. Khmelevskiy edited this page Oct 23, 2018 · 1 revision

This document describes potential issues in code that you might encounter and how to deal with them.

Variable initializers

Suppose that we have following C++ code

void someFun()
{
    MyStruct a(42);
    //...
}

Now according to D Language specification page for structs the following equivalent is

void someFun()
{
    MyStruct a = MyStruct(42);
    //...
}

The same is true for classes.

Variable lists

void someFun()
{
    float a = 42.F, b = random();
    //...
}

This is not yet handled properly so it will look like

void someFun()
{
    float a = 42fb = random();
    //...
}

How to fix: just add the comma in between.

Implicit construction

C++ allows calling constructor implicitly unless they are marked with explicit attribute

Vec2 getVector()
{
    return 42, 42;
}

Or

Vec2 getVector()
{
    return {42, 42};
}

Now depending on the case it might end up looking like

Vec2 getVector()
{
    return Vec2((42, 42));
}

Notice the double parenthesis, get rid of the inner one set. Or

Vec2 getVector()
{
    return Vec2(Vec2(42, 42));
}

Now there is extra construction takes the place, the fix is same, get rid of inner Vec2 and parenthesis set.

Temporary variable return

This looks trivial in C++

Vec2 getVector()
{
    Vec2 vec;
    // ... do some vec calculation ...
    return vec;
}

But you can see something like this in generated code

Vec2 getVector()
{
    Vec2 vec;
    // ... do some vec calculation ...
    return Vec2(vec);
}

Even though this code is perfectly valid it does change the meaning, so the proper fix is to get rid of copy construction

Vec2 getVector()
{
    Vec2 vec;
    // ... do some vec calculation ...
    return vec;
}

Missing template params

This one is very rare, and I will try to fix this ASAP, but here is the example

template<typename T> class MyData
{
    void setNew(T*);
    T getVal();
    // ...
}

Might end up looking like normal class

class MyData
{
    void setNew(T*);
    T getVal();
    // ...
}

Notice that T parameter is gone, let's bring it back where it should be.

class MyData(T)
{
    void setNew(T*);
    T getVal();
    // ...
}

SFINAE

Ok, this time something serious. Suppose that we want to select the type depending on some compile-time information, such as the size of the type.

template<int> struct typesel; 
template<> struct typesel<4> { typedef int myint; };
template<> struct typesel<8> { typedef long long myint; };

// now use it to get properly sized type
typedef typesel<sizeof(OtherIntType)>::myint myint;

For those who are unfamiliar - this is the trick(or if you prefer - technique) called "Substitution Failure Is Not An Error", the compiler will try to select all variants and if they all failed issue an error, for that case there is generic version without implementation (line 1). Now there is 2 more real variants that does pretty obvious job. If we pass in any type with size of either 4 or 8 compiler will instantiate the relevant template.

But the thing is, D doesn't have (nor need) such tricks. However SFINAE leftovers will end up in the generated code...

struct typesel<1>;
struct typesel<1>;

alias myint = typesel<sizeof(OtherIntType)>::myint;

It has 3 problems:

  • it has those leftovers for each variant
  • it is not yet handled properly and the code pasted as is
  • template variants information is gone and you have to look up the code yourself (in this simple case this isn't the problem because the code is generated for this particular situation)

The proper fix is to use CTFE or simply static if's

static if (OtherIntType.sizeof == 4)
alias myint = int;
else if (OtherIntType.sizeof == 8)
alias myint = long;
else static assert(0, "unknown type");

You can notice that this is totally different from original code