digitalmars.D - Bug or Feature? compile error: to!string(const Object)
- francesco cattoglio (13/13) Jun 30 2014 I found out today that the following code won't compile:
- Jonathan M Davis via Digitalmars-d (21/34) Jun 30 2014 It's a consequence of Object's toString not being const, but making it c...
- Wanderer (5/14) Jul 02 2014 Remove toString from the root of the object hierarchy?? How do
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (5/19) Jul 02 2014 I suppose the current `Object.toString` can be made a
- Jonathan M Davis via Digitalmars-d (26/40) Jul 02 2014 The ~ operator has nothing to do with toString. Strings are arrays, and ...
- Wanderer (27/71) Jul 02 2014 "~" operator has to do with toString(), because it performs
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (5/61) Jul 02 2014 This is not what's going to happen. opBinary!"~" doesn't need to
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (32/45) Jul 02 2014 No, it won't. The standard way to convert an object to a string
- Jonathan M Davis via Digitalmars-d (32/57) Jul 02 2014 The common way to convert anything to string in D, is to use std.conv.to...
- Wanderer (20/30) Jul 02 2014 That's not the problem of the Object class, that's the problem of
- Timon Gehr (18/49) Jul 03 2014 Yes, int opCmp(Object) -> int opCmp(const Object)const wouldn't be
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
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 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
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 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
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:What does the ~ operator have to do with it?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 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.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
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 ~ 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, 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 DavisThe 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 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
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, 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. 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
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: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.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, 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
Jul 02 2014
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: 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
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.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
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 stuckThat'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 DavisLook 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
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: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.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 stuckThat'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,Example?is the implementation detail, not part of the signature and should not prevent the method from being overridden.Removing basic operationsBecause, 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?)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).)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 DavisLook 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?http://lmgtfy.com/?q=dlang+opCmp&l=1
Jul 03 2014