Thread overview
Generate toString() method at compile time
Mar 26, 2014
Ary Borenszweig
Mar 26, 2014
bearophile
Mar 26, 2014
Adam D. Ruppe
Mar 26, 2014
Ary Borenszweig
Mar 26, 2014
Adam D. Ruppe
Mar 26, 2014
Ary Borenszweig
Mar 26, 2014
ketmar
Mar 26, 2014
Adam D. Ruppe
March 26, 2014
Hi,

I'd like to generate a toString() method for my classes. For example:

---
import std.stdio;

class Foo {
  int x;
  int y;

  // mixin ...?
}

void main() {
  auto foo = new Foo;
  foo.x = 1;
  foo.y = 2;
  writeln(foo); // prints "Foo(x = 1, y = 2)"
}
---

I tried using getAllMembers, but that also gives me all methods of Foo. I just want the fields. I also tried FieldTypeTuple (http://dlang.org/phobos/std_traits.html#FieldTypeTuple) but I get that gives me the types. And then I can't find anything else in std.traits that could help me.

Is this possible?

Thanks,
Ary
March 26, 2014
Ary Borenszweig:

> I tried using getAllMembers, but that also gives me all methods of Foo. I just want the fields.

You can try to filter the results of getAllMembers.

Bye,
bearophile
March 26, 2014
On Wednesday, 26 March 2014 at 14:16:08 UTC, Ary Borenszweig wrote:
> I'd like to generate a toString() method for my classes. For example:

Try this:

  override string toString() {
        import std.conv;
        string s;
        s ~= this.classinfo.name ~ "(";
        foreach(idx, val; this.tupleof) {
                if(idx) s ~= ", ";
                s ~= this.tupleof[idx].stringof[5 .. $];
                s ~= " = ";
                s ~= to!string(val);
        }
        s ~= ")";
        return s;
  }

add that method to Foo.

$ ./test58
test58.Foo(x = 1, y = 2)
March 26, 2014
On 3/26/14, 11:47 AM, Adam D. Ruppe wrote:
> On Wednesday, 26 March 2014 at 14:16:08 UTC, Ary Borenszweig wrote:
>> I'd like to generate a toString() method for my classes. For example:
>
> Try this:
>
>    override string toString() {
>          import std.conv;
>          string s;
>          s ~= this.classinfo.name ~ "(";
>          foreach(idx, val; this.tupleof) {
>                  if(idx) s ~= ", ";
>                  s ~= this.tupleof[idx].stringof[5 .. $];
>                  s ~= " = ";
>                  s ~= to!string(val);
>          }
>          s ~= ")";
>          return s;
>    }
>
> add that method to Foo.
>
> $ ./test58
> test58.Foo(x = 1, y = 2)

Thanks! That's pretty neat. It seems I was missing "tupleof".
March 26, 2014
On Wednesday, 26 March 2014 at 14:53:55 UTC, Ary Borenszweig wrote:
> Thanks! That's pretty neat. It seems I was missing "tupleof".

Yea, there's other options too like __traits(derivedMembers) if(!is(__traits(getMember...)) == function) and stuff like that, filtering like bearophile said, I just think tupleof is the easiest option for this.
March 26, 2014
On 3/26/14, 11:47 AM, Adam D. Ruppe wrote:
> On Wednesday, 26 March 2014 at 14:16:08 UTC, Ary Borenszweig wrote:
>> I'd like to generate a toString() method for my classes. For example:
>
> Try this:
>
>    override string toString() {
>          import std.conv;
>          string s;
>          s ~= this.classinfo.name ~ "(";
>          foreach(idx, val; this.tupleof) {
>                  if(idx) s ~= ", ";
>                  s ~= this.tupleof[idx].stringof[5 .. $];
>                  s ~= " = ";
>                  s ~= to!string(val);
>          }
>          s ~= ")";
>          return s;
>    }
>
> add that method to Foo.
>
> $ ./test58
> test58.Foo(x = 1, y = 2)

A small question: tupleof seems to return a tuple of values. So this.tupleof[idx] would return a value. However when you do this.tupleof[idx].stringof that somehow gets the name of the field. Shouldn't it return the name of the value? I'm confused.
March 26, 2014
that's what i did in a hurry. it seems to work, but the code is very ugly.

[/code]
import std.conv, std.stdio, std.string, std.traits;


string gen_printer (alias cn) () {
  string res = "override string toString () {\nstring res = `" ~ cn.stringof ~ "(`;\n";
  bool need_comma = false;
  foreach (i, m; __traits(derivedMembers, cn)) {
    static if (!isCallable!(__traits(getMember, cn, m)) /*&& m != "Monitor"*/) {
      if (need_comma) res ~= `res ~= ", ";`; else need_comma = true;
      res ~= "res~=`" ~ m ~ " = `~to!string(" ~ m ~ ");";
    }
  }
  res ~= "res~=`)`;\n";
  res ~= "return res;\n}\n";
  return res;
}


class Foo {
  int x;
  int y;

  void test () {}

  mixin(gen_printer!Foo);
}


void main () {
  auto foo = new Foo;
  foo.x = 1;
  foo.y = 2;
  writefln("%s", foo); // prints "Foo(x = 1, y = 2)"
}
[/code]
March 26, 2014
On Wednesday, 26 March 2014 at 15:01:35 UTC, Ary Borenszweig wrote:
> A small question: tupleof seems to return a tuple of values.

Most accurately, it returns a tuple of variables (it is a bit magical). So

struct A { int a; string b; }

the tupleof there is int a; string b - variables with different types and different names. The stringof works as if you said A.a.stringof - it fetches the name of the variable.

The slice I did on it cuts off the "this." from the full name, leaving just the name.

Value to string is done with the to!string function, .stringof always works at compile time.