Struct default constructor - need some kind of solution for C++ interop

Ethan Watson via Digitalmars-d digitalmars-d at puremagic.com
Tue Sep 6 06:44:37 PDT 2016


Alright, so now I've definitely come up across something with 
Binderoo that has no easy solution.

For the sake of this example, I'm going to use the class I'm 
binary-matching with a C++ class and importing functionality with 
C++ function pointers to create a 100% functional match - our 
Mutex class. It doesn't have to be a mutex, it just needs to be 
any C++ class where a default constructor is non-trivial.

In C++, it looks much like what you'd expect:

class Mutex
{
public:
   Mutex();
   ~Mutex();
   void lock();
   bool tryLock();
   void unlock();

private:
   CRITICAL_SECTION  m_criticalSection;
};

Cool. Those functions call the exact library functions you'd 
expect, the constructor does an InitializeCriticalSection and the 
destructor does a DeleteCriticalSection.

Now, with Binderoo aiming to provide complete C++ matching to the 
point where it doesn't matter whether a class was allocated in 
C++ or D, this means I've chosen to make every C++-matching class 
a value type rather than a reference type. The reasoning is 
pretty simple:

class SomeOtherClass
{
private:
   SomeVitalObject m_object;
   Mutex           m_mutex;
};

This is a pretty common pattern. Other C++ classes will embed 
mutex instances inside them. A reference type for matching in 
this case is right out of the question. Which then leads to a 
major conundrum - default constructing this object in D.

D structs have initialisers, but you're only allowed constructors 
if you pass arguments. With a Binderoo matching struct 
declaration, it would basically look like this:

struct Mutex
{
   @BindConstructor void __ctor();
   @BindDestructor void __dtor();

   @BindMethod void lock();
   @BindMethod bool tryLock();
   @BindMethod void unlock();

   private CRITICAL_SECTION m_criticalSection;
}

After mixin expansion, it would look come out looking something 
like this:

struct Mutex
{
   pragma( inline ) this() { __methodTable.function0(); }
   pragma( inline ) ~this() { __methodTable.function1(); }

   pragma( inline ) void lock() { __methodTable.function2(); }
   pragma( inline ) bool tryLock() { return 
__methodTable.function3(); }
   pragma( inline ) void unlock() { __methodTable.function4(); }

   private CRITICAL_SECTION m_criticalSection;
}

(Imagine __methodTable is a gshared object with the relevant 
function pointers imported from C++.)

Of course, it won't compile. this() is not allowed for obvious 
reasons. But in this case, we need to call a corresponding 
non-trivial constructor in C++ code to get the functionality 
match.

Of course, given the simplicity of the class, I don't need to 
import C++ code to provide exact functionality at all. But I 
still need to call InitializeCriticalSection somehow whenever 
it's instantiated anywhere. This pattern of non-trivial default 
constructors is certainly not limited to mutexes, not in our 
codebase or wider C++ practices at all.

So now I'm in a bind. This is one struct I need to construct 
uniquely every time. And I also need to keep the usability up to 
not require calling some other function since this is matching a 
C++ class's functionality, including its ability to instantiate 
anywhere.

Suggestions?


More information about the Digitalmars-d mailing list