www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Bug or Feature? compile error: to!string(const Object)

reply "francesco cattoglio" <francesco.cattoglio gmail.com> writes:
I found out today that the following code won't compile:

import std.conv;

class MyClass {}

void doStuffKo(const MyClass instance)
{
	string temp = to!(string)(instance);	
}

Everything compiles fine if I remove the const from the function 
signature.
I found out this issue named in earlier threads but I could not 
find any bug about it on the issue tracker. Is this a bug, a 
missing feature, or is this something that is almost impossible 
to achieve and therefore not implemented on purpose?
Jun 30 2014
next sibling parent reply Jonathan M Davis via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Monday, June 30, 2014 22:22:50 francesco cattoglio via Digitalmars-d wrote:
 I found out today that the following code won't compile:

 import std.conv;

 class MyClass {}

 void doStuffKo(const MyClass instance)
 {
   string temp = to!(string)(instance);
 }

 Everything compiles fine if I remove the const from the function
 signature.
 I found out this issue named in earlier threads but I could not
 find any bug about it on the issue tracker. Is this a bug, a
 missing feature, or is this something that is almost impossible
 to achieve and therefore not implemented on purpose?

It's a consequence of Object's toString not being const, but making it const would cause other problems. As it is, comparing const Objects only works because of a hack (the runtime actually casts aways const to do the comparison, which risks serious bugs if opEquals actually mutates something - e.g. for caching). The long term plan is to remove toString, opEquals, toHash, and opCmp from Object so that the derived classes can decide whether to make them const or not. Unlike Java and C#, we have proper templates, so we can templatize all of the stuff in the runtime which uses those functions so that they don't have to be on Object. https://issues.dlang.org/show_bug.cgi?id=9769 https://issues.dlang.org/show_bug.cgi?id=9770 https://issues.dlang.org/show_bug.cgi?id=9771 https://issues.dlang.org/show_bug.cgi?id=9772 Unfortunately, there hasn't been a lot of progress on what needs to be done to remove those functions from Object thanks in part to some compiler bugs as well as how thorny the AA implementation is. We intend to get there eventually though, which would solve your problem (assuming that you're derived class declared an appropriate toString of course). - Jonathan M Davis
Jun 30 2014
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 07/03/2014 05:16 AM, Wanderer wrote:
 On Wednesday, 2 July 2014 at 17:21:36 UTC, Jonathan M Davis via
 Digitalmars-d wrote:
 By not putting these functions on Object, it allows them to have whatever
 attributes they need when declared in derived types. Without that,
 we're stuck

That's not the problem of the Object class, that's the problem of D syntax that doesn't allow to override a "normal" method with "const" method of the same signature. Whenever the body of the method touches argument's internals or not,

Yes, int opCmp(Object) -> int opCmp(const Object)const wouldn't be unsound (but there are workarounds.) But what one actually wants is closer to int opCmp(const typeof(this))const.
 whenever the result type is immutable or not,

Example?
 is the implementation detail, not part of the signature and should
 not prevent the method from being overridden.

 Removing basic operations

Because, indeed, what is more basic than throwing an exception on attempted comparison of arbitrary unrelated class instances. The other methods being defined on arbitrary objects does not actually buy a whole lot either.
 from the root of the class hierarchy can cripple the whole OO model of D.
 ...

If 'cripple the whole OO model' means 'remove the methods of the root class', then yes. (Seriously, what is it actually supposed to mean?)
 with whatever attributes are on the one in Object, which is unacceptably
 restrictive. The _only_ thing we lose here is the ability to call any
 of these
 functions on Object directly, which arguably is no real loss.

 - Jonathan M Davis

Look at this from this point: a programmer new to D, writes his first class. Then he tries to sort a collection/array of such newborn objects, and - apparently - gets a compiler error because "your class doesn't define opCmp".

Wonderful. The current behaviour is a runtime crash. (And there was a time when your objects would silently end up sorted by the signed low 32 bits of their in-memory address (Usually. Implementing opCmp as exp(lhs)-exp(rhs) is not actually valid due to the possibility of overflow https://github.com/D-Programming-Language/druntime/blob/master/src/object_.d#L100).)
 He wants to fix that by adding the method. Question:
 where should he read about what opCmp is, what arguments it takes, what
 value returns, and the most important, what basic rules it should comply
 with? If these things aren't defined in Object anymore, where they
 should be defined instead? If there is no easy access to opCmp's
 contract, what the chances are that the method will be written correctly?

http://lmgtfy.com/?q=dlang+opCmp&l=1
Jul 03 2014
prev sibling next sibling parent "Wanderer" <no-reply no-reply.org> writes:
On Tuesday, 1 July 2014 at 01:13:25 UTC, Jonathan M Davis via 
Digitalmars-d wrote:
 The long term plan is to remove toString, opEquals, toHash, and 
 opCmp from
 Object so that the derived classes can decide whether to make 
 them const or
 not. Unlike Java and C#, we have proper templates, so we can 
 templatize all of
 the stuff in the runtime which uses those functions so that 
 they don't have to
 be on Object.

Remove toString from the root of the object hierarchy?? How do you plan to implement ~ operator for constructing strings? It should work with any types, even unbeknown to each other.
Jul 02 2014
prev sibling next sibling parent "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Wednesday, 2 July 2014 at 07:07:18 UTC, Wanderer wrote:
 On Tuesday, 1 July 2014 at 01:13:25 UTC, Jonathan M Davis via 
 Digitalmars-d wrote:
 The long term plan is to remove toString, opEquals, toHash, 
 and opCmp from
 Object so that the derived classes can decide whether to make 
 them const or
 not. Unlike Java and C#, we have proper templates, so we can 
 templatize all of
 the stuff in the runtime which uses those functions so that 
 they don't have to
 be on Object.

Remove toString from the root of the object hierarchy?? How do you plan to implement ~ operator for constructing strings?

What does the ~ operator have to do with it?
 It should work with any types, even unbeknown to each other.

I suppose the current `Object.toString` can be made a free-standing function that's automatically used (via UFCS) when there's no proper toString member on the class.
Jul 02 2014
prev sibling next sibling parent Jonathan M Davis via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Wed, 02 Jul 2014 07:07:17 +0000
Wanderer via Digitalmars-d <digitalmars-d puremagic.com> wrote:

 On Tuesday, 1 July 2014 at 01:13:25 UTC, Jonathan M Davis via
 Digitalmars-d wrote:
 The long term plan is to remove toString, opEquals, toHash, and
 opCmp from
 Object so that the derived classes can decide whether to make
 them const or
 not. Unlike Java and C#, we have proper templates, so we can
 templatize all of
 the stuff in the runtime which uses those functions so that
 they don't have to
 be on Object.

Remove toString from the root of the object hierarchy?? How do you plan to implement ~ operator for constructing strings? It should work with any types, even unbeknown to each other.

The ~ operator has nothing to do with toString. Strings are arrays, and ~ works with arrays already. ~ doesn't work with Object and will only work with user-defined types which define opBinary!"~". The only thing that removing toString from Object will affect would be the ability to call toString on Object directly instead of on a reference of a derived class type, which really doesn't lose much, since it's arguably a horrible idea to be passing Object around anyway. All of the code in druntime and Phobos which deals with toString is already templated, so it doesn't need to operate on Object directly and doesn't need toString to be on Object, just on the derived types that a program declares. Having toString, opEquals, opCmp, and toHash on Object is fundamentally broken in the face of const, safe, nothrow, etc. because we're forced to either put the attributes on them or not. If we put them on them, then those functions are restricted by those attributes, which is unacceptable in many cases (e.g. a class which lazily loads its members would not work with a const opEquals), and if we don't put those attributes on them (as is currently the case), then you can't do things like have call toString on a const object. Given the power of D's templates, we do not need to put opEquals, toString, opCmp, or toHash on Object. Languages like Java and C# are forced to, because they do not have proper templates and thus have to use Object directly in many cases. We do not have that problem, and putting those functions on Object is a mistake from early in D's design that we need to correct in order to be able to use safe, const, nothrow, etc. with classes correctly. - Jonathan M Davis
Jul 02 2014
prev sibling next sibling parent "Wanderer" <no-reply no-reply.org> writes:
On Wednesday, 2 July 2014 at 09:24:39 UTC, Jonathan M Davis via 
Digitalmars-d wrote:
 The ~ operator has nothing to do with toString. Strings are 
 arrays, and ~
 works with arrays already. ~ doesn't work with Object and will 
 only work with
 user-defined types which define opBinary!"~". The only thing 
 that removing
 toString from Object will affect would be the ability to call 
 toString on
 Object directly instead of on a reference of a derived class 
 type, which
 really doesn't lose much, since it's arguably a horrible idea 
 to be passing
 Object around anyway. All of the code in druntime and Phobos 
 which deals with
 toString is already templated, so it doesn't need to operate on 
 Object
 directly and doesn't need toString to be on Object, just on the 
 derived types
 that a program declares.

 Having toString, opEquals, opCmp, and toHash on Object is 
 fundamentally broken
 in the face of const,  safe, nothrow, etc. because we're forced 
 to either put
 the attributes on them or not. If we put them on them, then 
 those functions
 are restricted by those attributes, which is unacceptable in 
 many cases (e.g.
 a class which lazily loads its members would not work with a 
 const opEquals),
 and if we don't put those attributes on them (as is currently 
 the case), then
 you can't do things like have call toString on a const object.

 Given the power of D's templates, we do not need to put 
 opEquals, toString,
 opCmp, or toHash on Object. Languages like Java and C# are 
 forced to, because
 they do not have proper templates and thus have to use Object 
 directly in many
 cases. We do not have that problem, and putting those functions 
 on Object is a
 mistake from early in D's design that we need to correct in 
 order to be able
 to use  safe, const, nothrow, etc. with classes correctly.

 - Jonathan M Davis

"~" operator has to do with toString(), because it performs string concatenation (at least documentation says so) and toString() is the common way to convert arbitrary object into a string. Having ~ operator implemented the way you explained - via opBinary!"~" in each class and not in Object class - looks like a bad idea to me, because 1) the same concatenation code will be duplicated across various classes, including user-defined ones, and 2) user-defined classes can define "~" in some uncommon way which would confuse everyone who uses these classes. Java's approach (one common implementation of the "+" operator for strings concatenation) only requires user classes to define toString() method without redefining string operations, which is convenient. "Languages like Java and C# are forced to, because they do not have proper templates and thus have to use Object directly in many cases." That's not exactly true. The main reason why Object class has these methods, is the ability to define general contracts for these methods. For example, Object defines once and for all that toString() returns String representing object's contents, or that equals() and hashCode() results comply with each other. If you remove these methods from Object, you will dispense these contracts as well, forcing programmers to implement these methods in least predictable way. That will cause chaos.
Jul 02 2014
prev sibling next sibling parent "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Wednesday, 2 July 2014 at 11:33:56 UTC, Wanderer wrote:
 On Wednesday, 2 July 2014 at 09:24:39 UTC, Jonathan M Davis via 
 Digitalmars-d wrote:
 The ~ operator has nothing to do with toString. Strings are 
 arrays, and ~
 works with arrays already. ~ doesn't work with Object and will 
 only work with
 user-defined types which define opBinary!"~". The only thing 
 that removing
 toString from Object will affect would be the ability to call 
 toString on
 Object directly instead of on a reference of a derived class 
 type, which
 really doesn't lose much, since it's arguably a horrible idea 
 to be passing
 Object around anyway. All of the code in druntime and Phobos 
 which deals with
 toString is already templated, so it doesn't need to operate 
 on Object
 directly and doesn't need toString to be on Object, just on 
 the derived types
 that a program declares.

 Having toString, opEquals, opCmp, and toHash on Object is 
 fundamentally broken
 in the face of const,  safe, nothrow, etc. because we're 
 forced to either put
 the attributes on them or not. If we put them on them, then 
 those functions
 are restricted by those attributes, which is unacceptable in 
 many cases (e.g.
 a class which lazily loads its members would not work with a 
 const opEquals),
 and if we don't put those attributes on them (as is currently 
 the case), then
 you can't do things like have call toString on a const object.

 Given the power of D's templates, we do not need to put 
 opEquals, toString,
 opCmp, or toHash on Object. Languages like Java and C# are 
 forced to, because
 they do not have proper templates and thus have to use Object 
 directly in many
 cases. We do not have that problem, and putting those 
 functions on Object is a
 mistake from early in D's design that we need to correct in 
 order to be able
 to use  safe, const, nothrow, etc. with classes correctly.

 - Jonathan M Davis

"~" operator has to do with toString(), because it performs string concatenation (at least documentation says so) and toString() is the common way to convert arbitrary object into a string. Having ~ operator implemented the way you explained - via opBinary!"~" in each class and not in Object class - looks like a bad idea to me

This is not what's going to happen. opBinary!"~" doesn't need to be implemented in each class then any more than it needs to be now. It's only strictly about `toString` and the other methods Jonathan mentions, nothing else will be affected.
Jul 02 2014
prev sibling next sibling parent "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Wednesday, 2 July 2014 at 11:33:56 UTC, Wanderer wrote:
 On Wednesday, 2 July 2014 at 09:24:39 UTC, Jonathan M Davis via 
 Digitalmars-d wrote:
 "Languages like Java and C# are forced to, because they do not 
 have proper templates and thus have to use Object directly in 
 many cases."

 That's not exactly true. The main reason why Object class has 
 these methods, is the ability to define general contracts for 
 these methods. For example, Object defines once and for all 
 that toString() returns String representing object's contents, 
 or that equals() and hashCode() results comply with each other. 
 If you remove these methods from Object, you will dispense 
 these contracts as well, forcing programmers to implement these 
 methods in least predictable way. That will cause chaos.

No, it won't. The standard way to convert an object to a string is calling `std.conv.to`. This is a templated function that's capable of inspecting the type it's passed. If the type has a `toString` method (as is currently the case for Object and descendants), it will be used. If not (e.g. for structs that don't define `toString`), it has a default implementation that lists the fields: struct StructWithToString { int x, y; string toString() const { return "I'm a struct."; } } struct StructWithoutToString { int x, y; } void main() { import std.stdio; import std.conv : to; StructWithToString s1; StructWithoutToString s2; writeln(s1); writeln(s2); } Outputs: I'm a struct. StructWithoutToString(0, 0) This default formatting can easily be extended to objects (if it doesn't work already). And for opEquals/toHash, there can be a default implementation too, if this is considered useful (I believe it is already provided for structs).
Jul 02 2014
prev sibling next sibling parent Jonathan M Davis via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Wed, 02 Jul 2014 11:33:54 +0000
Wanderer via Digitalmars-d <digitalmars-d puremagic.com> wrote:

 "~" operator has to do with toString(), because it performs
 string concatenation (at least documentation says so) and
 toString() is the common way to convert arbitrary object into a
 string.

The common way to convert anything to string in D, is to use std.conv.to - i.e. to!string(foo). It's generic and works not only with anything that defines toString but with built-in types as well. Most code should not be calling toString directly.
 Having ~ operator implemented the way you explained - via
 opBinary!"~" in each class and not in Object class - looks like a
 bad idea to me, because 1) the same concatenation code will be
 duplicated across various classes, including user-defined ones,
 and 2) user-defined classes can define "~" in some uncommon way
 which would confuse everyone who uses these classes. Java's
 approach (one common implementation of the "+" operator for
 strings concatenation) only requires user classes to define
 toString() method without redefining string operations, which is
 convenient.

If you want to be able to concatenate two objects, they must define opBinary!"~". That's the case now. If you want to convert two objects to strings and concatenate those, then you convert them to strings and concatenate them, e.g. to!string(foo) ~ to!string(bar) or format("%s%s", foo, bar). Implementing toString does not automatically make anything concatenatable except for the strings that you get when converting those objects to strings. The objects themselves are not concatenatable.
 "Languages like Java and C# are forced to, because they do not
 have proper templates and thus have to use Object directly in
 many cases."

 That's not exactly true. The main reason why Object class has
 these methods, is the ability to define general contracts for
 these methods. For example, Object defines once and for all that
 toString() returns String representing object's contents, or that
 equals() and hashCode() results comply with each other. If you
 remove these methods from Object, you will dispense these
 contracts as well, forcing programmers to implement these methods
 in least predictable way. That will cause chaos.

There's nothing chaotic about it. It's how templates work, and a lot of D uses them already - e.g. that's how the range API is defined. Template constraints do a wonderful job of ensuring that templated functions are called on types which implement the functions and properties with the right API, and all four of these functions which are on Object are already being defined by programmers without any kind of base class for structs. In addition to that, _none_ of these functions should be called by most programs. toString should be called by to!string, toHash is normally only needed by the built-in AAs or other hash table implementations. opEquals is called by using == or !=, and opCmp is used by using any of the other comparison operators. _Some_ code may have to use one or more of those four functions directly, but it should be extremely rare. The average program shouldn't need to care whether Object has any of these functions. By not putting these functions on Object, it allows them to have whatever attributes they need when declared in derived types. Without that, we're stuck with whatever attributes are on the one in Object, which is unacceptably restrictive. The _only_ thing we lose here is the ability to call any of these functions on Object directly, which arguably is no real loss. - Jonathan M Davis
Jul 02 2014
prev sibling parent "Wanderer" <no-reply no-reply.org> writes:
On Wednesday, 2 July 2014 at 17:21:36 UTC, Jonathan M Davis via 
Digitalmars-d wrote:
 By not putting these functions on Object, it allows them to 
 have whatever
 attributes they need when declared in derived types. Without 
 that, we're stuck

That's not the problem of the Object class, that's the problem of D syntax that doesn't allow to override a "normal" method with "const" method of the same signature. Whenever the body of the method touches argument's internals or not, whenever the result type is immutable or not, is the implementation detail, not part of the signature and should not prevent the method from being overridden. Removing basic operations from the root of the class hierarchy can cripple the whole OO model of D.
 with whatever attributes are on the one in Object, which is 
 unacceptably
 restrictive. The _only_ thing we lose here is the ability to 
 call any of these
 functions on Object directly, which arguably is no real loss.

 - Jonathan M Davis

Look at this from this point: a programmer new to D, writes his first class. Then he tries to sort a collection/array of such newborn objects, and - apparently - gets a compiler error because "your class doesn't define opCmp". He wants to fix that by adding the method. Question: where should he read about what opCmp is, what arguments it takes, what value returns, and the most important, what basic rules it should comply with? If these things aren't defined in Object anymore, where they should be defined instead? If there is no easy access to opCmp's contract, what the chances are that the method will be written correctly?
Jul 02 2014