Can a D library have some types determined by the client program?

cc cc at nevernet.com
Fri Mar 8 16:54:48 UTC 2024


On Friday, 8 March 2024 at 06:03:51 UTC, Liam McGillivray wrote:
> A problem I have is that the 3 classes Map, Tile, and Unit 
> reference each-other. If I turn Map into a template, than it 
> complains about the member variable of Unit declared as `Map 
> map;` without arguments. I change this line to `Map!TileType 
> map;` but this requires that Unit is also turned into a 
> template.

If you don't want Unit to be a template, you can just have Map 
derive from a basic interface or abstract class.  You can also 
have every relevant class share similar templates, you just need 
to remember to supply the template arguments everywhere.  You'll 
need to think about how much interoperation you want between 
these classes.  Does Unit really need to know what TileType map 
is using, or can it just trust that when it asks Map to move, Map 
will handle everything related to tile types?  Generally it's 
best practice to have as much of the inner workings isolated as 
possible and just provide methods to access functionality.  To 
ease some of the template argument!spaghetti, you could insert 
aliases into the classes via fully qualified symbol names.
Another alternative, if everything is defined on one file, you 
could wrap everything in a single template, but I don't usually 
favor this strategy.

```d
//version=Interfaced;
version=AllTemplated;
//version=AllTemplatedAliases;
//version=OneTemplate;

version(Interfaced) {
	interface IMap {
		Unit addNewUnit();
	}
	class Map(TileType) : IMap {
		Unit[] units;
		Unit addNewUnit() {
			auto unit = new Unit(this);
			units ~= unit;
			return unit;
		}
	}
	class Unit {
		IMap map;
		private this(IMap map) {
			this.map = map;
		}
	}
	void main() {
		auto map = new Map!uint;
		auto unit = map.addNewUnit;
	}

} else version(AllTemplated) {
	class Map(TileType) {
		Unit!TileType[] units;
		auto addNewUnit() {
			auto unit = new Unit!TileType(this);
			units ~= unit;
			return unit;
		}
	}
	class Unit(TileType) {
		Map!TileType map;
		private this(Map!TileType map) {
			this.map = map;
		}
	}
	void main() {
		auto map = new Map!uint;
		auto unit = map.addNewUnit;
	}

} else version(AllTemplatedAliases) {
	class Map(TileType) {
		alias Unit = mymodule.Unit!TileType;
		Unit[] units;
		auto addNewUnit() {
			auto unit = new Unit(this);
			units ~= unit;
			return unit;
		}
	}
	class Unit(TileType) {
		alias Map = mymodule.Map!TileType;
		Map map;
		private this(Map map) {
			this.map = map;
		}
	}
	void main() {
		auto map = new Map!uint;
		auto unit = map.addNewUnit;
	}

} else version(OneTemplate) {
	template Map(TileType) {
		class Map {
			Unit[] units;
			auto addNewUnit() {
				auto unit = new Unit(this);
				units ~= unit;
				return unit;
			}
		}
		class Unit {
			Map map;
			private this(Map map) {
				this.map = map;
			}
		}
	}
	void main() {
		auto map = new Map!uint;
		auto unit = map.addNewUnit;
	}
}
```

If a given class doesn't really need to know what the template 
parameters are to the other class it's interacting with, I would 
avoid defining too many template types everywhere and just use 
interfaces or abstract parent classes.

> After changing `class Unit` to `class Unit (TileType), it 
> complains about the line `Unit* occupant;` in Tile.

Are you sure you need a pointer here?  Class objects in D are 
already reference-type by default.


More information about the Digitalmars-d-learn mailing list