www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - generic toString() in a template class?

reply %u <no where.com> writes:
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?
Jan 16 2007
next sibling parent Chris Nicholson-Sauls <ibisbasenji gmail.com> writes:
%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
Jan 16 2007
prev sibling next sibling parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
%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:

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); } } } -----
Jan 16 2007
parent %u <no where.com> writes:
== 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);
      }
    }
 }
 -----

Jan 16 2007
prev sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
%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
Jan 16 2007