Template with template?

Chris wendlec at tcd.ie
Fri Mar 21 02:56:49 PDT 2014


On Friday, 21 March 2014 at 09:54:24 UTC, Chris wrote:
> 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 ...

Btw, I was initially inspired by Objective-C's NSSet that can 
hold arbitrary objects.


More information about the Digitalmars-d-learn mailing list