www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Proposal: Implicit type conversion for structs

reply Jaak <jpdt telkomsa.net> writes:
Hello

I propose the following extension to structs:

One can define methods for struct T of the type:

   T opImplicitConvert(T2 t)

which allows the implicit conversion of variables of
type T2 to type T.
(the name itself is not important, others possibilities:
  opImpConvFrom, opICast_r, etc.)

A conversion does no make sense if the types T2 and T
are the same, as that can cause an infinite chain
of conversions.

i.e.   T opImplicitConvert(T t)  is illegal


We now get opAssign semantics without breaking
or changing the way assignments currently work.

Example code:

struct Vector {
   float[] elems;

   ...

   Vector opImplicitConvert(float[] a)
   {
     Vector v;
     v.elems = a.dup;
     return v;
   }
}

...

Vector u, v;

// Current system
u = v;  // Currently OK
u = [1f 2f 3f]; // ERROR

// Proposed system
u = v;  // Still OK
v = [1f 2f 3f]; // OK, as Vector.opImplicitConvert(float[]) exists




Some possible enhancements:

1. A smarter compiler may be able to figure out conversions chains if
a direct conversion is not possible.
I.e.

MyVector a;
YourVector b;
HisVector c;


// uses Vector.opImpConv(YourVector.opImpConv(HisVector)) if
// Vector.opImpConv(HisVector) does not exist
a = c;



2. If property syntax can be used to add methods to structs, one can do 
the following:

Vector opImplicitConvert(Vector, OtherVector w) {
   Vector t;
   t.elems = w.elems;
   return t;
}

// Then, variables of type OtherVector can be implicitly
// converted to type Vector. This is especially useful if
// the source implementing the struct Vector is not available.

OtherVector w;
v = w; // expands to v = v.opImplicitConvert(w);




Any comments?

-
Jaak
Oct 25 2006
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Maybe you can try to explain this once more in a format of
1. Here's a problem currently faced by developers
2. here's a suggestion for how to fix it
3. heres how my suggestion resolves that problem
4. here's why I think this is the best/only way to resolve the problem.

Reading your proposal, it's not clear what problem you're trying to 
solve to begin with, so it's hard to get excited about the solution. 
It's also difficult for others to offer you alternative solutions to the 
problem, not knowing what the problem is to begin with.

That said, the proposal sounds reminiscent of C++'s overloadable 
(implicit) conversion operators, which have caused me more trouble than 
benefit.  E.g.
     operator float() const { return aFloat; }

Item 5 from Scott Meyer's Effective C++:
"Be wary of user-defined conversion functions
...you usually don't want to provide type conversion functions of *any* 
ilk.  The fundamental problem is that such functions often end up being 
called when you neither want nor expect them to be.  The result can be 
incorrect and unintuitive program behavior that is maddeningly difficult 
to diagnose.  ... In general, the more experience C++ programmers have, 
the more likely they are to eschew type conversion operators."

In C++ you can now label these conversion functions as 'explicit', 
meaning the above would require and explicit cast (i.e. (float)anObject) 
to invoke it, but at that point you might as well avoid the magic syntax 
and just provide a toFloat() method that will be both more obvious to 
readers of the code and easier to find for users of your API trying to 
find that functionality.

--bb

Jaak wrote:
 Hello
 
 I propose the following extension to structs:
 
 One can define methods for struct T of the type:
 
   T opImplicitConvert(T2 t)
 
 which allows the implicit conversion of variables of
 type T2 to type T.
 (the name itself is not important, others possibilities:
  opImpConvFrom, opICast_r, etc.)
 
 A conversion does no make sense if the types T2 and T
 are the same, as that can cause an infinite chain
 of conversions.
 
 i.e.   T opImplicitConvert(T t)  is illegal
 
 
 We now get opAssign semantics without breaking
 or changing the way assignments currently work.
 
 Example code:
 
 struct Vector {
   float[] elems;
 
   ...
 
   Vector opImplicitConvert(float[] a)
   {
     Vector v;
     v.elems = a.dup;
     return v;
   }
 }
 
 ...
 
 Vector u, v;
 
 // Current system
 u = v;  // Currently OK
 u = [1f 2f 3f]; // ERROR
 
 // Proposed system
 u = v;  // Still OK
 v = [1f 2f 3f]; // OK, as Vector.opImplicitConvert(float[]) exists
 
 
 
 
 Some possible enhancements:
 
 1. A smarter compiler may be able to figure out conversions chains if
 a direct conversion is not possible.
 I.e.
 
 MyVector a;
 YourVector b;
 HisVector c;
 
 
 // uses Vector.opImpConv(YourVector.opImpConv(HisVector)) if
 // Vector.opImpConv(HisVector) does not exist
 a = c;
 
 
 
 2. If property syntax can be used to add methods to structs, one can do 
 the following:
 
 Vector opImplicitConvert(Vector, OtherVector w) {
   Vector t;
   t.elems = w.elems;
   return t;
 }
 
 // Then, variables of type OtherVector can be implicitly
 // converted to type Vector. This is especially useful if
 // the source implementing the struct Vector is not available.
 
 OtherVector w;
 v = w; // expands to v = v.opImplicitConvert(w);
 
 
 
 
 Any comments?
 
 -
 Jaak
Oct 25 2006
parent reply Jaak <jpdt telkomsa.net> writes:
Bill Baxter wrote:
 Maybe you can try to explain this once more in a format of
 1. Here's a problem currently faced by developers
The main idea is achieving transparent interoperability of types. When two types are compatible (defined by the existence of suitable conversion functions) one can use them as if they are the same type and all the magic happens in the background. For example: VectorTypeA a; VectorTypeB b1, b2; a = b1 + b2; And as a side effect one gets effects similar to having an overloadable opAssign operators without changing assignments that currently compile. i.e. Vector v = [1f, 2f, 3f]
 2. here's a suggestion for how to fix it
 3. heres how my suggestion resolves that problem
Its, basically a way of specifying implicit conversions between types. Structs (or arrays using property notation) define a function which converts from one type to another: TO opIConvert(FROM what){...} If a suitable function exist, it gets called in cases when type TO is expected and type FROM is given. (more details in parent post)
 4. here's why I think this is the best/only way to resolve the problem.
Well... I was hoping someone in here can point me to a better solution if one exists.
 
 Reading your proposal, it's not clear what problem you're trying to 
 solve to begin with, so it's hard to get excited about the solution. 
 It's also difficult for others to offer you alternative solutions to the 
 problem, not knowing what the problem is to begin with.
Aah sorry, I hope this post clarifies things a bit.
 That said, the proposal sounds reminiscent of C++'s overloadable 
 (implicit) conversion operators, which have caused me more trouble than 
 benefit.  E.g.
     operator float() const { return aFloat; }
It my seems like my proposal is a reversed version of this. I.e. the conversion operators are defined in the target type instead of the source type (because overloading based on return type is not allowed).
 Item 5 from Scott Meyer's Effective C++:
 "Be wary of user-defined conversion functions
 ....you usually don't want to provide type conversion functions of *any* 
 ilk.  The fundamental problem is that such functions often end up being 
 called when you neither want nor expect them to be.  The result can be 
 incorrect and unintuitive program behavior that is maddeningly difficult 
 to diagnose.  ... In general, the more experience C++ programmers have, 
 the more likely they are to eschew type conversion operators."
I have no experience of this, but i can see how automatic invisible function calls all over the place can complicate debugging. But.... it would still be nice to have the option.
 In C++ you can now label these conversion functions as 'explicit', 
 meaning the above would require and explicit cast (i.e. (float)anObject) 
 to invoke it, but at that point you might as well avoid the magic syntax 
 and just provide a toFloat() method that will be both more obvious to 
 readers of the code and easier to find for users of your API trying to 
 find that functionality.
Any explicit declaration of intent would sort of defeat the purpose of the proposal. The aim of which is mainly to make using different types together less verbose (and get opAssign functionality). Anyway, its not a dealbreaker, just on my wishlist.
 
 --bb
Oct 25 2006
parent reply Kristian <kjkilpi gmail.com> writes:
On Thu, 26 Oct 2006 06:15:11 +0300, Jaak <jpdt telkomsa.net> wrote:
 Bill Baxter wrote:
[snip]
 That said, the proposal sounds reminiscent of C++'s overloadable  =
 (implicit) conversion operators, which have caused me more trouble th=
an =
 benefit.  E.g.
     operator float() const { return aFloat; }
Hmm, I have to say that the both sides have good points. Implicit type = casting can cause trouble. But in the other hand, it can be also very = useful in some situations, i.e. when the types are indeed compatible. In C++ I have had problems with signed/unsigned type casting. I can't ju= st = now recall what they were, but they were annoying, and the compiler shou= ld = have got them right. I think implicit type conversion can be made a lot safer (than it's in = C++ and in any other language I know of). It would require type concersi= on = rules. That is, more than simply telling the compiler that here are all = = the conversion operators, use them as you wish. For example, in C++: class C; class A { A &operator =3D(int); A &operator =3D(const C &); }; class B { operator int() const; operator C() const; }; void func() { A a; B b; a =3D b; } Should 'b' to converted to 'int' or 'C'? There is no way of knowing that= . However, if one could define that 'C' is prefered over 'int', there woul= d = be no problems. For example, the order in which the operator are defined would tell the = = preference order (or you could use some kind of numeric value, etc): class B { operator C() const; //1. operator int() const; //2. }; There could be some other rules as well. I mean, human can always tell = that what conversion should be used, if possible. Why not tell that to = compiler also? Is it possible to create such rules in compact way that would apply = (almost) all the cases? Is the preference ordering enough?
Oct 26 2006
parent Jaak <jpdt telkomsa.net> writes:
One could possibly require that the shortest conversion chain is always 
used, and it is an error if a conversion is required and more than one 
conversion chains of the same length exists.

In your example below A and B are implicitly compatible by two possible 
chains of conversions:
B => int => C
B => C => A

This could be flagged as ambigious. Making the compatibility explicit 
would  remove the error.
In the below example:

Add to class A a conversion from B:
A &operator =(const B &);

Add to class B a conversion to B:
operator A() const;

Its a tradeoff: no need for preference ordering, at the cost of having 
to define extra conversion functions.

The shortest conversion chain is then simply: B => A


Kristian wrote:
 On Thu, 26 Oct 2006 06:15:11 +0300, Jaak <jpdt telkomsa.net> wrote:
 Bill Baxter wrote:
[snip]
 That said, the proposal sounds reminiscent of C++'s overloadable 
 (implicit) conversion operators, which have caused me more trouble 
 than benefit.  E.g.
     operator float() const { return aFloat; }
Hmm, I have to say that the both sides have good points. Implicit type casting can cause trouble. But in the other hand, it can be also very useful in some situations, i.e. when the types are indeed compatible. In C++ I have had problems with signed/unsigned type casting. I can't just now recall what they were, but they were annoying, and the compiler should have got them right.
I've been bitten by that in D as well: if (array.length-5>0) // is always true as length property is unsigned But this is luckily not a mistake one keeps on making.
 
 I think implicit type conversion can be made a lot safer (than it's in 
 C++ and in any other language I know of). It would require type 
 concersion rules. That is, more than simply telling the compiler that 
 here are all the conversion operators, use them as you wish.
 
 For example, in C++:
 
 class C;
 
 class A {
     A &operator =(int);
     A &operator =(const C &);
 };
 
 class B {
     operator int() const;
     operator C() const;
 };
 
 void func() {
     A a;
     B b;
 
     a = b;
 }
 
 Should 'b' to converted to 'int' or 'C'? There is no way of knowing that..
 
 However, if one could define that 'C' is prefered over 'int', there 
 would be no problems.
 For example, the order in which the operator are defined would tell the 
 preference order (or you could use some kind of numeric value, etc):
 
 class B {
     operator C() const;    //1.
     operator int() const;  //2.
 };
 
 There could be some other rules as well. I mean, human can always tell 
 that what conversion should be used, if possible. Why not tell that to 
 compiler also?
 
 Is it possible to create such rules in compact way that would apply 
 (almost) all the cases? Is the preference ordering enough?
Oct 27 2006