Mixin programming foreach

Ali Çehreli acehreli at yahoo.com
Mon Sep 27 17:14:11 UTC 2021


On 9/27/21 9:23 AM, eXodiquas wrote:

 > I found this forum very helpful

Same here. :)

 > for my (maybe) stupid questions

Often repeated but there are no stupid questions.

 > First of all, I'm not exactly sure what this code here, from the
 > documentation at https://dlang.org/articles/mixin.html, does:
 >
 > ```d
 > template GenStruct(string Name, string M1)
 > {
 >      const char[] GenStruct = "struct " ~ Name ~ "{ int " ~ M1 ~ "; }";
 > }
 >
 > mixin(GenStruct!("Foo", "bar"));
 > ```
 >
 > In my understanding we create a template called `GenStruct` with compile
 > time arguments `Name` and `M1`. We then create the `char[] GenStruct`
 > which holds the source code for the `struct`. Afterwards we do the
 > `mixin`call and create the source code during compile time to have it
 > ready to go.

All correct.

 > But I don't understand why the `char[] GenStruct` is there.

That example is cofusing because it includes elements that has nothing 
to do with a string mixin. The following could be used without raising 
your question:

string GenStruct(string Name, string M1) {
   return "struct " ~ Name ~ "{ int " ~ M1 ~ "; }";
}

unittest {
   mixin(GenStruct("Foo", "bar"));

   auto foo = Foo(42);
   assert(foo.bar == 42);
}

void main() {
}

 > template is an
 > Is this the name of the `mixin` or is the `template GenStruct` the name
 > of what we pass to the `mixin`?

The original code uses an eponymous template, which can be defined as 
"given a template definition that includes a symbol same as the 
template's name, that symbol will mean the same thing as instances of 
that template." So, in this case, the template instance 
GenStruct!("Foo", "bar") happens to be the same thing as the GenStruct 
member of that template.

 > Can we create more than one const char[]
 > in one `template` scope

Yes.

 > and what would it do if we could?

1) The template would take advantage of those other symbols in its 
implementation:

template GenStruct(string Name, string M1)
{
   // Note: I would define both of these as 'enum's;
   // just staying with the same code style.
   const char[] memberDef = "int " ~ M1 ~ ";";
   const char[] GenStruct = "struct " ~ Name ~ "{ " ~ memberDef ~ " }";
}

2) Members of a template can be used directly:

// Defines two symbols
template Foo(string s) {
   import std.format : format;

   enum integerVarDef = format!"int %s_i;"(s);
   enum doubleVarDef = format!"double %s_d;"(s);
}

// Mixing in one or the other symbol
void main() {
   mixin(Foo!"hello".integerVarDef);
   hello_i = 42;

   mixin(Foo!"world".doubleVarDef);
   world_d = 1.5;
}

3) All symbols can be mixed in as a whole. (Note it's a template mixin 
in this case as opposed to a string mixin.)

template MemberListFeature(T)  {
   T[] theList;

   void addMember(T value) {
     theList ~= value;
   }

   T getMember(size_t index) {
     return theList[index];
   }
}

struct S {
   mixin MemberListFeature!string;
   // Now this struct has one member array and two member functions.
}

unittest {
   auto s = S();
   s.addMember("hello");
   s.getMember(0) == "hello";
}

void main() {
}

etc. :)

 > But nevertheless, I copied the code and changed it a bit to something
 > like this:
 >
 > ```d
 > template Vector(int dimension) {
 >    string cps = "";
 >    foreach(d; 0..dimension) {
 >      cps ~= "x" ~ d ~ " = 0;\n";
 >    }
 >    const char[] Vector = "struct Vector {\n" ~ dimension.to!string ~ cps
 > ~ "}";
 > }
 > ```
 > In my head a call to `mixin(Vector!5)` should result in something like
 > this:
 >
 > ```d
 > struct Vector5 {
 >    int x0;
 >    int x1;
 >    int x2;
 >    int x3;
 >    int x4;
 > }
 > ```
 > But it does not compile because `Error: declaration expected, not
 > foreach` which I understand as "foreach is not possible during compile
 > time",

Not true. foreach *is* available at compile time but not in the template 
scope. (On the other hand, 'static foreach' is available inside a 
template scope or module scope, etc.) Here is an almost direct 
implementation of your idea:

template Vector(int dimension) {
   import std.conv : to;

   string makeMembers() {
     // Note: No need to initialize with ""
     string cps;
     foreach(d; 0..dimension) {
       // Note: Initial values are not needed in D.
       // (Already initialized.)
       cps ~= "int x" ~ d.to!string ~ ";\n";
     }
     return cps;
   }

   // Note: The name Vector would be confusing; changing to
   // VectorStruct
   const char[] Vector = "struct VectorStruct {\n"
                         ~ makeMembers()
                         ~ "}";
}

// Note: Visualizing the result:
pragma(msg, Vector!5);
// Also see the compiler switch -mixin

mixin(Vector!3);

void main() {
   auto v = VectorStruct();
   v.x0 = 0;
   v.x1 = 1;
   v.x2 = 2;
}

However, Vector vs. VectorStruct points at a problem there. Instead, 
here is another example that uses 'static foreach' and uses not a 
'template' that defines a string but a 'struct template' directly:

struct Vector(size_t dimension) {
   static foreach (d; 0..dimension) {
     import std.format : format;
     mixin (format!"  int x%s;"(d));
   }
}

void main() {
   auto v = Vector!3();
   v.x0 = 0;
   v.x1 = 1;
   v.x2 = 2;
}

 > And as a final question. Let's say the above problems are all solved and
 > I construct such a template. How could I build functions that take any
 > of those `Vector`s as an argument?
 > For example I want to add two of those, the function would look
 > something like this, or am I lost again?
 >
 > ```d
 > Vector!T add(Vector!T lhs, Vector!T rhs) {
 >    return ?;
 > }
 > ```

T is a compile-time parameter there, which must be defined as such. I 
will stay with my size_t template parameter:

struct Vector(size_t dimension) {
   static foreach (d; 0..dimension) {
     import std.format : format;
     mixin (format!"  int x%s;"(d));
   }
}

import std.stdio;

void main() {
   auto a = Vector!2(1, 2);
   auto b = Vector!2(3, 4);
   auto c = add(a, b);

   writeln(c);
}

Vector!dim add(size_t dim)(Vector!dim lhs, Vector!dim rhs) {
   // As there are many different ways of implementing both Vector
   // and addition, I will just cheat and assume there is only
   // the x0 member:
   return Vector!dim(lhs.x0 + rhs.x0);
}

 > How could I iterate over all components if such a `Vector`?

It is possible with .tupleof but unlike your design, I would use an 
int[dimension] static array in the implementation like I do with the 
Polygon struct template here:

   http://ddili.org/ders/d.en/templates_more.html

Still, assuming the 'c' variable from the last example above exists, 
here is how .tupleof might be used:

   foreach (i, member; c.tupleof) {
     writefln!"Member %s is %s"(i, member);
   }

Ali



More information about the Digitalmars-d-learn mailing list