Template with template?

Chris wendlec at tcd.ie
Fri Mar 21 02:54:22 PDT 2014


On Thursday, 20 March 2014 at 20:20:28 UTC, John Colvin wrote:
> On Thursday, 20 March 2014 at 19:38:25 UTC, Chris wrote:
>> On Thursday, 20 March 2014 at 18:54:30 UTC, John Colvin wrote:
>>> On Thursday, 20 March 2014 at 18:39:32 UTC, Chris wrote:
>>>> On Thursday, 20 March 2014 at 17:49:52 UTC, John Colvin 
>>>> wrote:
>>>>> On Thursday, 20 March 2014 at 16:40:50 UTC, Chris wrote:
>>>>>> On Thursday, 20 March 2014 at 16:32:34 UTC, Vladimir 
>>>>>> Panteleev wrote:
>>>>>>> On Thursday, 20 March 2014 at 16:28:46 UTC, Chris wrote:
>>>>>>>> How can I instantiate Person with Trait, i.e. a template 
>>>>>>>> with a template?
>>>>>>>>
>>>>>>>> struct Trait(T0, T1) {
>>>>>>>> T0 name;
>>>>>>>> T1 value;
>>>>>>>> T1[T0] map;
>>>>>>>>
>>>>>>>> this(T0 name, T1 value) {
>>>>>>>> this.name = name;
>>>>>>>> this.value = value;
>>>>>>>> map[name] = value;
>>>>>>>> }
>>>>>>>> }
>>>>>>>>
>>>>>>>> class Person(T) {
>>>>>>>> T traits[];
>>>>>>>>
>>>>>>>> void addTrait(T trait) {
>>>>>>>> traits ~= trait;
>>>>>>>> }
>>>>>>>> }
>>>>>>>>
>>>>>>>>
>>>>>>>> void main()
>>>>>>>> {
>>>>>>>> auto trait1 = Trait!(string, string)("Name", "John");
>>>>>>>> auto trait2 = Trait!(string, int)("Age", 42);
>>>>>>>> writefln(%s", trait1.map);
>>>>>>>> writefln(%s", trait2.map);
>>>>>>>> // above code compiles and works
>>>>>>>> }
>>>>>>>
>>>>>>> Person!(Trait!(string, string)) person;
>>>>>>>
>>>>>>> -- or --
>>>>>>>
>>>>>>> alias MyTrait = Trait!(string, string);
>>>>>>> Person!MyTrait person;
>>>>>>>
>>>>>>> Note that this approach won't let you have traits with 
>>>>>>> different parameters within the same Person type.
>>>>>>
>>>>>> Yep, I've already tried this (sorry I should've mentioned 
>>>>>> it!). But I don't want this restriction.
>>>>>
>>>>> Arrays are homogeneous. All the elements must be of the 
>>>>> same type. Different instantiations of templates are 
>>>>> different types.
>>>>>
>>>>> You could use an array of std.variant.Variant
>>>>
>>>> The elements are all of type Trait. However, Type itself 
>>>> might be
>>>> of different types. That's why it is not possible? I've come
>>>> across this restriction before when using templates, which 
>>>> is a
>>>> big disappointment because it restricts the "templatization" 
>>>> /
>>>> generalization of data structures somewhat.
>>>
>>> Trait is not a type. Trait is a template. An instantiation of 
>>> the Trait template is a type.
>>>
>>>
>>> Arrays are contiguous, homogeneous data. This is fundamental 
>>> to their design and their performance characteristics.
>>> Workarounds use at least one of the following: indirection, 
>>> tagging* and padding. Variant uses tagging and padding. 
>>> Interface/base-class arrays use indirection (and tagging, 
>>> ultimately).
>>>
>>> *inline or external, or even compile-time.
>>>
>>> This is true in *every* programming language, just with 
>>> different names.
>>
>> I thought the array T[] traits could hold any _type_ the 
>> template Trait is instantiated into. That's where I got it 
>> wrong. I understand the practical aspects of this restriction 
>> (homogeneous data, performance and the work around involved 
>> etc.). However, this makes templates less universal and rather 
>> cumbersome to work with in certain circumstances. Take for 
>> example the Person class. If I want to do numerical operations 
>> with the age of the person, I will have to convert the age to 
>> an int (or whatever) later on instead of just doing it once at 
>> the beginning (when loading data). So everytime I access 
>> Trait.map["age"] I will have to convert it to a number before 
>> I can calculate anything. This, or I store it in a field of 
>> its own when instantiating Trait. Whatever workaround I choose 
>> it will make it less elegant and less simple.
>>
>> Maybe I expect(ed) to much of templates. Mea culpa.
>
> Try this:
>
> import std.stdio;
> import std.variant;
>
> enum maxTraitSize = 64;
>
> struct Trait(T0, T1)
> {
> 	T0 name;
> 	T1 value;
> 	T1[T0] map;
>
> 	static assert(T0.sizeof + T1.sizeof + (T1[T0]).sizeof <= 
> maxTraitSize);
> 	
> 	this(T0 name, T1 value)
> 	{
> 		this.name = name;
> 		this.value = value;
> 		map[name] = value;
> 	}
> }
>
> class Person
> {
> 	alias ElT = VariantN!maxTraitSize;
> 	ElT[] traits;
>
> 	void addTrait(T)(T trait)
> 		if(is(T == Trait!Q, Q...))
> 	{
> 		traits ~= ElT(trait);
> 	}
> }
>
> void main()
> {
> 	auto trait1 = Trait!(string, string)("Name", "John");
> 	auto trait2 = Trait!(string, int)("Age", 42);
> 	writefln("%s", trait1.map);
> 	writefln("%s", trait2.map);
> 	
> 	auto p = new Person;
> 	p.addTrait(trait1);
> 	p.addTrait(trait2);
> 	writeln(p.traits);
> }

Thanks John, this does what I had in mind. I don't know, though, 
if I will use it for a real world application. I would have to 
test the behavior thoroughly first. The background is that I will 
have variable user input, i.e. users (non-programmers) define 
traits and rules. There is no way to foresee what names users 
will choose. That's why Trait stores arbitrary keys and values 
(and Meta's solution is not an option). I only thought it would 
be nice to have [string:int] straight away for numerical 
operations further down the road. I was also thinking about using 
std.variant. But I'm just starting out with this ...


More information about the Digitalmars-d-learn mailing list