Coming originally from Smalltalk, I sorely missed a lot of the expressiveness of that language in C++ (e.g. closures with non-local returns, symbols, class-side inheritance, reflection), some features of which however I managed to emulate or substitute. I want to share one particular simple solution with you, in the hope it will be useful:
Class-side Inheritance
C++ doesn’t natively support virtual static methods, that is, inheritance with overrides on the class side. Inheritance requires instances (vtables) and therefore classes would need to be instances of a meta class, which is not how C++ is implemented (the C++20 metaclasses proposal doesn’t solve this either, unfortunately).
It is quite possible that you don’t miss it yet, if you never used it.
Polymorphic class-side code is useful for all sorts of representing knowledge (constants, properties, behaviour, defaults) alongside the class hierarchy. Basically all code that can or must run without having an instance available goes there (e.g. policies, factories, instance registries, creating objects read from a stream, etc). Another typical use is a class-side ‘registry’ that manages instances of that class, e.g. for lookup or caching. Regular static methods can only do so much, as they can’t make use of inheritance. Once you get used to it, it greatly streamlines the organization of your code, in that everything relevant for a class is coded immediately there.
Implementation
The example shows two classes Cluster and Chord, plus there meta classes Cluster::Class and Chord::Class. Essentially we get a meta class hierarchy running in parallel to the class hierarchy. The meta classes implement their counterpart’s class-side methods. A singleton of each is held to run them and provide inheritance.
class Cluster
{
public:
struct Class
{
virtual ~Class() {}
static Class& methods()
{ static Class meta; return meta; }
// All virtual class methods of Cluster go here
virtual const String xmlTag()
{ return "cluster-structure"; }
virtual Cluster readFromFile (InputStream s)
{ ... }
};
virtual Class& class_methods()
{ return Class::methods(); }
}
class Chord : public Cluster
{
public:
struct Class : public Cluster::Class
{
virtual ~Class() {}
static Class& methods()
{ static Class meta; return meta; }
// All virtual class methods of Chord go here
const String xmlTag() override
{ return "chord-structure"; }
Chord readFromFile override (InputStream s)
{
auto c = Cluster::Class::methods().readFromFile(s);
return whatever(c);
}
};
Class& class_methods() override
{ return Class::methods(); }
}
The pattern is very repetitive and simple. It can be much condensed and made more legible with macros: (they should be trivial, but I can post them if needed)
class Cluster
{
public:
CLASS_METHODS_ROOT
virtual const String xmlTag()
{ return "cluster-structure"; }
virtual Cluster readFromFile (InputStream s)
{ ... }
CLASS_METHODS_ROOT_END
}
class Chord : public Cluster
{
public:
CLASS_METHODS_WITH_SUPERCLASS (Cluster)
const String xmlTag() override
{ return "chord-structure"; }
Chord readFromFile override (InputStream s)
{
auto c = Cluster::Class::methods().readFromFile(s);
return whatever(c);
}
CLASS_METHODS_END
}
That’s basically all it takes. In your cpp file, you would implement (meta) class methods by adding ::Class to the class name, which makes those pretty self-explanatory:
Array<Chord*> Chord::Class::allInstances () {...}
Note that instance methods in Cluster that call class_methods().xmlTag() properly deliver 'chord-structure'
if the instance currently running the code of Cluster is in fact a Chord (virtual class methods).
The primitive singleton ‘meta’ is probably not thread-safe. Juce singletons work too, albeit more verbose and not header-only.
Possibly this can be done with templates better than with macros? If so, I’d love to hear about it.