Maya API Wrapping with CRTP

So I came across this: https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

The amazing python ‘classmethod’.

A way to inherit static members and define static interfaces.

In python there exists the decorator “classmethod”, which is like a staticmethod that receives the type on which it is called, including inherited types.
Additionally it can be overriden and the base class function can be called separately like any ordinary member function.

Now imagine this construction:

class BaseClass(object):
	def __init__(self, name):
		self.__name = name
		print name

	@classmethod
	def getName(cls):
		raise NotImplementedError()

	@classmethod
	def creator(cls):
		return cls.creator(cls.getName())

This base class can instantiate through the creator method, that includes instantiating any subclasses because the cls argument is the owning type.
This then calls upon another classmethod, which is not implemented, effectively demanding that subclasses implement this method.
What this creates is the ability to define static interfaces, as well as share codes between those interfaces while implementation is still kept abstract.

class SubClass1(BaseClass):
	@classmethod
	def getName(cls):
		return 'SubClass1'

class SubClass2(BaseClass):
	@classmethod
	def getName(cls):
		return 'SubClass2'

instance1 = SubClass1.creator() # prints SubClass1
instance2 = SubClass2.creator() # prints SubClass2

So you see the class inherited the creator method, the creator method was aware of the sub-type and called the right getName method.

The C++ ‘Curiously recurring template pattern’ (or CRTP).

This is a terribly confusing trick that allows us to mimic the above python idiom.

So let’s take a similar scenario!

template <class T> class TemplateBase 
{
protected:
	const char* name;
	TemplateBase(const char* inName)
	{
		name = inName;
	}

public:
	static T* sCreator()
	{
		return new T(T::sName()); 
	}
}

This is functionally similar to the python example. It creates a given type and won’t compile
unless the specified type has a static sName() member returning a const char*.

Sidetracking here, that would allow us to do this:

class SubClass1
{
	const char* name;
public:
	MyName(const char* inName) { name = inName; }
	static const char* sName() { return "SubClass1"; }
}
SubClass1* instance = TemplateBase<SubClass1>::sCreator();

But (no longer sidetracking) here comes the recurring part:

class SubClass2 : public TemplateBase<SubClass2>
{
protected:
	using TemplateBase::TemplateBase;
public:
	static const char* sName() 
	{
		return "SubClass2"; 
	}
}
SubClass2* instance = SubClass2::sCreator();

This generates a template implementation for it’s own subclass, which curiously doesn’t cause
any problems, even though a base class is accessing a subclass’s (static) functions.

This also hides the (inherited) constructor so really only the sCreator function can be used
for instantiation of our class. And because the subclass doesn’t define sCreator we can very intuitively
call it on our subclass itself.

When giving arguments to a template, and then using it, the C++ compiler generates new
code for us specific to the type given to the template. So multiple subclasses currently do not
share a common base class.

Maya API utility using CRTP

The Maya API is oftem implemented in a way that demands things for the programmer, without raising
errors when the programmer does something wrong. With modern C++ we can enforce much more rules and
with the complexity of software we definately should cut the contributer some slack!

Here is a little setup for creating an MPxNode wrapper that allows the compiler to communicate what is needed.

template  class MPxNodeCRTP : public MPxNode
{
public:
	static void* sCreator() { return new T(); }
	static MStatus sInitializeWrapper() { MStatus status = T::sInitialize(); CHECK_MSTATUS_AND_RETURN_IT(status); }
}
// utility macro:
#define INHERIT_MPXNODE(CLASS) class CLASS : public MPxNodeCRTP

I’ve been taking this further to handle registration of input and output attributes,
so attributeAffects gets handled by the base class, as well as MFn::kUnknownParameter exceptions in compute().

Leave a Reply

Your email address will not be published. Required fields are marked *