Thread overview
generic toString() in a template class?
Jan 16, 2007
%u
Jan 16, 2007
Frits van Bommel
Jan 16, 2007
%u
Jan 17, 2007
Bill Baxter
January 16, 2007
I wonder how one could write generic toString() method in a template class?

Suppose, I have a template class S(T), and will pass in as type param: class
A, struct B, int C:

====================================
$ cat ts.d

class A{}

struct B{
  char[] toString()  {return "B";}
}

int c;

class S(T) {
  T obj;

char[] toString() {
  return obj.toString();
}

}

int main(char[][] args) {
  S!(A)   sa;
  S!(B*)  sb;
  S!(int) sc;

  printf("%.*s", sa.toString());
  printf("%.*s", sb.toString());
  printf("%.*s", sc.toString());

  return 0;
}

$ dmd.exe ts.d
ts.d(14): Error: no property 'toString' for type 'int'
ts.d(14): Error: function expected before (), not 1 of type int
ts.d(14): Error: cannot implicitly convert expression (1()) of type int to char[]
ts.d(22): template instance ts.S!(int) error instantiating
====================================

OK, no property 'toString' for type 'int'; let's use some trick to do function overloading:

====================================
$ cat ts.d

import std.string;

class A{}

struct B{
  char[] toString()  {return "B";}
}

int c;

char[] toStringFunc(Object obj) {return obj.toString();}
char[] toStringFunc(int i)      {return format(i);}

class S(T) {
  T obj;

char[] toString() {
  return toStringFunc(obj);
}

}

int main(char[][] args) {
  S!(A)   sa;
  S!(B*)  sb;
  S!(int) sc;

  printf("%.*s", sa.toString());
  printf("%.*s", sb.toString());
  printf("%.*s", sc.toString());

  return 0;
}

$ dmd.exe ts.d
ts.d(18): function ts.toStringFunc (Object) does not match parameter types (B *)
ts.d(18): Error: cannot implicitly convert expression (this.obj) of type B *
to int
ts.d(25): template instance ts.S!(B *) error instantiating
====================================

OK, struct is not Object, so let's just add char[] toStringFunc(void*  obj)
{return "null";}

====================================
$ cat ts.d
import std.string;

class A{}

struct B{
  char[] toString()  {return "B";}
}

int c;

char[] toStringFunc(Object obj) {return obj.toString();}
char[] toStringFunc(int i)      {return format(i);}
char[] toStringFunc(void*  obj) {return "null";}

class S(T) {
  T obj;

char[] toString() {
  return toStringFunc(obj);
}

}

int main(char[][] args) {
  S!(A)   sa;
  S!(B*)  sb;
  S!(int) sc;

  printf("%.*s", sa.toString());
  printf("%.*s", sb.toString());
  printf("%.*s", sc.toString());

  return 0;
}

$ dmd.exe ts.d
ts.d(19): function ts.toStringFunc called with argument types:
        (A)
matches both:
        ts.toStringFunc(Object)
and:
        ts.toStringFunc(void*)
ts.d(25): template instance ts.S!(A) error instantiating
====================================

Come on! class A matches both (Object) and (void*)?!

How could one write a generic toString() method in a template class then?

My suggestions:

-- if the compiler see a basic type, int.toString(), translate into a function
that does the Right Thing; just as if .toString() is also a property of basic
types like int.max, int.min.

-- class A, matches (Object) better than (void*), so just do the Right thing
to choose overloaded-function(Object obj).


comments?
January 16, 2007
%u wrote:
> I wonder how one could write generic toString() method in a template class?
> 
> Suppose, I have a template class S(T), and will pass in as type param: class
> A, struct B, int C:
> 
> ====================================
> $ cat ts.d
> 
> class A{}
> 
> struct B{
>   char[] toString()  {return "B";}
> }
> 
> int c;
> 
> class S(T) {
>   T obj;
> 
> char[] toString() {
>   return obj.toString();
> }
> 
> }
> 
> int main(char[][] args) {
>   S!(A)   sa;
>   S!(B*)  sb;
>   S!(int) sc;
> 
>   printf("%.*s", sa.toString());
>   printf("%.*s", sb.toString());
>   printf("%.*s", sc.toString());
> 
>   return 0;
> }
> 
> $ dmd.exe ts.d
> ts.d(14): Error: no property 'toString' for type 'int'
> ts.d(14): Error: function expected before (), not 1 of type int
> ts.d(14): Error: cannot implicitly convert expression (1()) of type int to char[]
> ts.d(22): template instance ts.S!(int) error instantiating
> ====================================
> 
> OK, no property 'toString' for type 'int'; let's use some trick to do function
> overloading:
> 
> ====================================
> $ cat ts.d
> 
> import std.string;
> 
> class A{}
> 
> struct B{
>   char[] toString()  {return "B";}
> }
> 
> int c;
> 
> char[] toStringFunc(Object obj) {return obj.toString();}
> char[] toStringFunc(int i)      {return format(i);}
> 
> class S(T) {
>   T obj;
> 
> char[] toString() {
>   return toStringFunc(obj);
> }
> 
> }
> 
> int main(char[][] args) {
>   S!(A)   sa;
>   S!(B*)  sb;
>   S!(int) sc;
> 
>   printf("%.*s", sa.toString());
>   printf("%.*s", sb.toString());
>   printf("%.*s", sc.toString());
> 
>   return 0;
> }
> 
> $ dmd.exe ts.d
> ts.d(18): function ts.toStringFunc (Object) does not match parameter types (B *)
> ts.d(18): Error: cannot implicitly convert expression (this.obj) of type B *
> to int
> ts.d(25): template instance ts.S!(B *) error instantiating
> ====================================
> 
> OK, struct is not Object, so let's just add char[] toStringFunc(void*  obj)
> {return "null";}
> 
> ====================================
> $ cat ts.d
> import std.string;
> 
> class A{}
> 
> struct B{
>   char[] toString()  {return "B";}
> }
> 
> int c;
> 
> char[] toStringFunc(Object obj) {return obj.toString();}
> char[] toStringFunc(int i)      {return format(i);}
> char[] toStringFunc(void*  obj) {return "null";}
> 
> class S(T) {
>   T obj;
> 
> char[] toString() {
>   return toStringFunc(obj);
> }
> 
> }
> 
> int main(char[][] args) {
>   S!(A)   sa;
>   S!(B*)  sb;
>   S!(int) sc;
> 
>   printf("%.*s", sa.toString());
>   printf("%.*s", sb.toString());
>   printf("%.*s", sc.toString());
> 
>   return 0;
> }
> 
> $ dmd.exe ts.d
> ts.d(19): function ts.toStringFunc called with argument types:
>         (A)
> matches both:
>         ts.toStringFunc(Object)
> and:
>         ts.toStringFunc(void*)
> ts.d(25): template instance ts.S!(A) error instantiating
> ====================================
> 
> Come on! class A matches both (Object) and (void*)?!
> 
> How could one write a generic toString() method in a template class then?
> 
> My suggestions:
> 
> -- if the compiler see a basic type, int.toString(), translate into a function
> that does the Right Thing; just as if .toString() is also a property of basic
> types like int.max, int.min.
> 
> -- class A, matches (Object) better than (void*), so just do the Right thing
> to choose overloaded-function(Object obj).
> 
> 
> comments?

Current D, off the top of my head:

# class S (T) {
#   T obj ;
#
#   char[] toString () {
#     static if (is(T == class)) {
#       return obj.toString;
#     }
#     else static if (is(T == struct)) {
#       static if (is(typeof(T.toString() == char[])))
#         return obj.toString;
#       }
#       else {
#         static assert (false, "class S!(T): struct T must expose function char[] toString()");
#       }
#     }
#     else {
#       std.string.toString(obj);
#     }
#   }
# }

-- Chris Nicholson-Sauls
January 16, 2007
%u wrote:
> I wonder how one could write generic toString() method in a template class?
> 
> Suppose, I have a template class S(T), and will pass in as type param: class
> A, struct B, int C:
[snip]

How about this one:
-----
  char[] toString() {
    return std.string.format("%s", obj);
  }
-----

Works for most types you're likely to want formatted, as long as you don't mind how it formats them.
It doesn't support function pointers and delegates, nor will it probably like structs without toString() defined, but other than that I think it supports everything.

If you want something a bit more customizable, try something like this: (Chris beat me to posting the general idea though)
-----
import std.string;	// for .toString and format
import std.utf;		// for toUTF8

struct S(T) {
  T obj;

  char[] toString() {
    static if(is(typeof(obj.toString()) : char[]))	// structs with toString & objects
    {
        return obj ? obj.toString() : "null-obj";
    }
    else static if(is(typeof(obj.toUTF8()) : char[]))	// char[], wchar[] & dchar[]
    {
        return obj.toUTF8();
    }
    else static if (is(T : void*))	// pointers
    {
        return obj ? format(obj) : "null-ptr";
    }
    else static if (is(typeof(std.string.toString(obj)) : char[])) // anything supported by std.string.toString
    {
	return std.string.toString(obj);
    }
    else
    {
	version(ReportDefaultFormatting) pragma(msg, "Default formatting for " ~ T.mangleof);
	return format("%s", obj);
    }
  }

}
-----
January 16, 2007
== Quote from Frits van Bommel (fvbommel@REMwOVExCAPSs.nl)'s article
> How about this one:
> -----
>    char[] toString() {
>      return std.string.format("%s", obj);
>    }
> -----
> Works for most types you're likely to want formatted, as long as you don't mind how it formats them.

Thank you.  I'd prefer this simple solution; static check on T's type looks too messy to me.  finally:

===================================
$ cat ts.d
import std.string;

class  A  {char[] toString()  {return "A";}}
struct B  {char[] toString()  {return "B";}}

class S(T) {
  T obj;
  this(T o) {obj = o;}
  char[] toString() {return std.string.format("S!%s", obj);}
}

int main(char[][] args) {
  A a = new A();
  B b;
  int c = 911;

  S!(A)      sa = new S!(   A )( a);
  S!(S!(A)) ssa = new S!(S!(A))(sa);

  S!(B*) sbn = new S!(B*)(null);
  S!(B*)  sb = new S!(B*)(&b);
  S!(B ) ssb = new S!(B )( b);

  S!(int) sc = new S!(int)(c);

  printf("%.*s\n",  sa.toString());
  printf("%.*s\n", ssa.toString());

  printf("%.*s\n", sbn.toString());
  printf("%.*s\n",  sb.toString());
  printf("%.*s\n", ssb.toString());

  printf("%.*s\n", sc.toString());

  return 0;
}

===================================
$ dmd.exe ts.d
g:\project\dmd\bin\..\..\dm\bin\link.exe ts,,,user32+kernel32/noi;

$ ./ts.exe
S!A
S!S!A
S!0000
S!12FF18
S!B
S!911
===================================

> It doesn't support function pointers and delegates, nor will it probably
> like structs without toString() defined, but other than that I think it
> supports everything.
> If you want something a bit more customizable, try something like this:
> (Chris beat me to posting the general idea though)
> -----
> import std.string;	// for .toString and format
> import std.utf;		// for toUTF8
> struct S(T) {
>    T obj;
>    char[] toString() {
>      static if(is(typeof(obj.toString()) : char[]))	// structs with
> toString & objects
>      {
>          return obj ? obj.toString() : "null-obj";
>      }
>      else static if(is(typeof(obj.toUTF8()) : char[]))	// char[],
> wchar[] & dchar[]
>      {
>          return obj.toUTF8();
>      }
>      else static if (is(T : void*))	// pointers
>      {
>          return obj ? format(obj) : "null-ptr";
>      }
>      else static if (is(typeof(std.string.toString(obj)) : char[])) //
> anything supported by std.string.toString
>      {
> 	return std.string.toString(obj);
>      }
>      else
>      {
> 	version(ReportDefaultFormatting) pragma(msg, "Default formatting for "
> ~ T.mangleof);
> 	return format("%s", obj);
>      }
>    }
> }
> -----

January 17, 2007
%u wrote:
> I wonder how one could write generic toString() method in a template class?
> 
>[...]
> 
> My suggestions:
> 
> -- if the compiler see a basic type, int.toString(), translate into a function
> that does the Right Thing; just as if .toString() is also a property of basic
> types like int.max, int.min.

I'm all for the Smalltalk-ish idea of making built-in types act more like full-fledged objects.

--bb