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?

Reply via email to