toString
is a vitally important (IMO) method for relaying text information about an object to the user, at least during the development and debugging stages of an application but quite often in production as well. The basic version usable by the standard library potentially allocates (apologies to this thread, which got me thinking about this again, for borrowing the class names):
class Animal {
override string toString() {
return "Animal()";
}
}
class Dog : Animal {
override string toString() {
return "Dog()";
}
}
class Bulldog : Dog {
bool sunglasses;
override string toString() {
return format("Bulldog(cool: %s)", sunglasses);
}
}
Now, even the most diehard GC enthusiast should agree that situations can arise where you might potentially be calling toString hundreds of times per frame in a high-intensity application loop, and allocating every single time is highly undesirable. I won't bother discussing issues of caching values as the specific use cases and complexity aren't relevant here. Fortunately, std.format
provides a non-[necessarily-]allocating alternative:
import std.format;
import std.range.primitives; // Mandatory, see aside
class Animal {
void toString(W)(ref W writer) if (isOutputRange!(W, char)) {
writer.formattedWrite("Animal()");
}
}
class Dog : Animal {
void toString(W)(ref W writer) if (isOutputRange!(W, char)) {
writer.formattedWrite("Dog()");
}
}
class Bulldog : Dog {
bool sunglasses;
void toString(W)(ref W writer) if (isOutputRange!(W, char)) {
writer.formattedWrite("Bulldog(cool: %s)", sunglasses);
}
}
However, we have a problem:
void main() {
auto animals = [new Animal, new Dog, new Bulldog];
foreach (animal; animals) {
animal.writeln;
}
}
Animal()
Animal()
Animal()
As this function is templated, it cannot be virtual as I understand it, and thus we have this problem of determining the correct toString to call when the type is known only at runtime.
Current solutions feel somewhat clumsy, and involve for example manually registering all such relevant classes and proxying toString with a handler function that identifies an object by its runtime typeid and looking up the relevant correct function to call. Automatically determining which classes to register can be difficult, especially if classes inherit each other across multiple files or are added at a later date, and increase complexity and programmer responsibility to maintain.
Given how important toString
is, it would be great if there were some kind of special case or path the compiler could use to facilitate this (though a more general-purpose solution would also be interesting). I briefly thought "Wouldn't it be nice if TypeInfo_Class could store a reference to the class's matching toString function that could be called at runtime?", but given that it's a template, that's a no go. Are there better ways to handle this? Have ideas/best practices for working around templates and virtual functions or things like that been discussed before? It feels like something that should be so simple, conceptually, but despite bordering on a number of D's great features there doesn't seem to a be a simple fire-and-forget solution.
I have come up with one solution, which I will attach in the next post. Hopefully I haven't missed something completely obvious with all this.