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?