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.