templated toString and class inheritance

cc cc at nevernet.com
Sun Dec 25 19:12:54 UTC 2022


On Sunday, 25 December 2022 at 19:10:13 UTC, cc wrote:
> I have come up with one solution, which I will attach in the 
> next post.

Here is what I am playing with currently.  I don't know if it's 
the best way to handle the situation.  Redundant checks end up 
being made.  Given that there are two levels of indirection that 
need to be considered (What OutputRange template is being passed 
around? and What is the runtime type of the object being 
written?), I had to get a little creative, I think.  Since we 
can't cleanly anticipate which matches of 
toString(W)..isOutputRange will be used (and could be a voldemort 
type) I have incorporated the registering of classes directly 
into the toString handler, but this can still potentially fail if 
derived classes are spread into different files and not called 
first.

```d
module util.tostringmaster;
import std.string;
import std.traits;
import std.format;
import std.range.primitives;

// TODO: Make sure toString template is actually compatible with 
the desired W
//       Proper error handling/fallback if matching toString 
doesn't exist

abstract final class ToStringMaster {
	private:
	static abstract class ToStringBase {}
	static abstract class ToStringWriter(W) : ToStringBase {
		void writeWith(ref W writer, Object obj);
	}
	static final class ToStringHolder(W, T) : ToStringWriter!W {
		override void writeWith(ref W writer, Object obj) {
			T t = cast(T) obj;
			t.toString(writer);
		}
	}
	static ToStringBase[string][string] table;
	static void[0][string] writerTable;
	static void registerAll(W, string MOD)() {
		enum WNAME = fullyQualifiedName!W;
		enum MNAME = WNAME~":"~MOD;
		//pragma(msg, "REGISTERALL "~MNAME);
		if (MNAME in writerTable)
			return;
		writerTable.require(MNAME);
		{
			mixin(`import `~MOD~`;`);
			mixin(`alias ALL = __traits(allMembers, `~MOD~`);`);
			static foreach (sym; ALL) {{
				mixin(`alias SYM = __traits(getMember, `~MOD~`, "`~sym~`");`);
				static if (is(SYM == class)) {
					alias CLASS = SYM;
					alias TOSTRING = __traits(getMember, CLASS, "toString");
					static if (__traits(isTemplate, TOSTRING)) {
						register!(W, CLASS);
					}
				}
			}}
			// Explicit class registration
			//register!(W, Animal);
			//register!(W, Dog);
			//register!(W, Bulldog);
		}
	}
	static void register(W, T)() {
		enum WNAME = fullyQualifiedName!W;
		enum TNAME = fullyQualifiedName!T;
		table.require(WNAME);
		table[WNAME][TNAME] = new ToStringHolder!(W, T);
	}
	static void redirect(W, T)(ref W writer, T obj) {
		enum WNAME = fullyQualifiedName!W;
		static if (!(WNAME.indexOf("__lambda") >= 0)) { // Don't 
register hasToString, etc
			registerAll!(W, moduleName!(T));

			scope auto tname = typeid(obj).name;
			if (auto wp = WNAME in table) {
				if (auto p = tname in *wp) {
					auto tsh = cast(ToStringWriter!W) *p;
					assert(tsh, "Invalid ToStringWriter: "~WNAME);
					tsh.writeWith(writer, obj);
					return;
				}
			}
			writer.formattedWrite("<Unknown:%s>", tname);
		}
	}
}

// Mixin alternative instead of delegates
/* enum string ToStringReal = q{
	static if (!(fullyQualifiedName!W.indexOf("__lambda") >= 0)) { 
// hasToString
		static assert(__FUNCTION__.endsWith(".toString"), "Only include 
this mixin in toString");
		if (typeid(this) !is typeof(this).classinfo) {
			ToStringMaster.redirect(writer, this);
			return;
		}
	}
}; */

void realToStringOr(T,W)(T obj, ref W writer, scope void 
delegate() origDG = null) if (isOutputRange!(W, char)) {
	if (typeid(obj) !is T.classinfo) {
		ToStringMaster.redirect(writer, obj);
		return;
	}
	if (origDG !is null) origDG();
}
```

```d
class Animal {
	void toString(W)(ref W writer) if (isOutputRange!(W, char)) {
		realToStringOr(this, writer, {
			writer.formattedWrite("Animal()");
		});
	}
}
class Dog : Animal {
	void toString(W)(ref W writer) if (isOutputRange!(W, char)) {
		realToStringOr(this, writer, {
			writer.formattedWrite("Dog()");
		});
	}
}
final class Bulldog : Dog {
	bool sunglasses;
	void toString(W)(ref W writer) if (isOutputRange!(W, char)) {
		// We don't need realToStringOr if we know for certain this 
class will never be derived, but...
		writer.formattedWrite("Bulldog(cool: %s)", sunglasses);
	}
}
void main() {
	auto animals = [new Animal, new Dog, new Bulldog];
	foreach (animal; animals) {
		animal.writeln;
	}
}
```

```
Animal()
Dog()
Bulldog(cool: false)
```



More information about the Digitalmars-d mailing list