Thread overview
Implicit cast of int to a class
Nov 03, 2007
Matthias Walter
Nov 04, 2007
torhu
Nov 04, 2007
Bill Baxter
Nov 04, 2007
Bill Baxter
Nov 04, 2007
Matthias Walter
Nov 04, 2007
Bill Baxter
Nov 04, 2007
Matthias Walter
November 03, 2007
Hello,

I have a class called mpz_class which in particular implements the following:

1. this (int other) { ... }
2. opAssign (int other) { ... }

I now want to write templated code with T = int, T = long and T = mpz_class.
My mpz_class should work and feel like the orginal integers, which mostly works.

I'd like to know, how I can coax my class to implicitely convert integer literals to instances of it. There are 2 cases which don't compile for me and one which is semantically wrong:

// 1.
void func (T) (T input = 0) // error:  cannot implicitly convert expression (0) of type int to gmp.mpz.mpz_class
{
  // ...
}

//...

// 2. T a = 1; // I guess, a points to 0x1 now and not to a result of mpz_class.this(1)

// 3.
func !(T) (a); // works
func !(T) (1); // error: function func1!(mpz_class).func1 (mpz_class) does not match parameter types (int)

Can someone help me here?

For full sourcecode, see:

http://dsource.org/projects/gmp4d/browser/trunk/Test.d
November 04, 2007
Matthias Walter wrote:
> Hello,
> 
> I have a class called mpz_class which in particular implements the following:
> 
> 1. this (int other) { ... }
> 2. opAssign (int other) { ... }
> 
> I now want to write templated code with T = int, T = long and T = mpz_class.
> My mpz_class should work and feel like the orginal integers, which mostly works.
> 
> I'd like to know, how I can coax my class to implicitely convert integer literals to instances of it. There are 2 cases which don't compile for me and one which is semantically wrong:
> 
> // 1.
> void func (T) (T input = 0) // error:  cannot implicitly convert expression (0) of type int to gmp.mpz.mpz_class

I don't think it's possible to use defaults args like this.  It seems that opAssign isn't used for default args.  You need to remove the default, overload the function, specialize the template, or redesign the whole thing.


> // 2. T a = 1; // I guess, a points to 0x1 now and not to a result of mpz_class.this(1)

Even if it tried to call opAssign, there's no object allocated.  It would just crash.  You have to use 'new' somewhere.  Maybe it's better to use a struct with static opCall overloads, instead of a class?  A struct is a better fit if you want it to behave like ints and longs, for sure.

Mixing built-in types with user-defined types in D needs to be more explicit than in C++, because there are no implicit conversions.  Except for the very limited opAssign.

One option is to find a single point in your code where you can convert ints and longs to mpz_class, and then the rest of the code uses that.

Of course, if all you want is to make code that can work with both ints and longs, things would probably be a lot simpler.  But I guess you're just trying out stuff?

Your design seems a bit too complex for its own good, although I'm not sure what you're trying to achieve. :)
November 04, 2007
torhu wrote:
> Matthias Walter wrote:
>> Hello,
>>
>> I have a class called mpz_class which in particular implements the following:
>>
>> 1. this (int other) { ... }
>> 2. opAssign (int other) { ... }
>>
>> I now want to write templated code with T = int, T = long and T = mpz_class.
>> My mpz_class should work and feel like the orginal integers, which mostly works.
>>
>> I'd like to know, how I can coax my class to implicitely convert integer literals to instances of it. There are 2 cases which don't compile for me and one which is semantically wrong:
>>
>> // 1.
>> void func (T) (T input = 0) // error:  cannot implicitly convert expression (0) of type int to gmp.mpz.mpz_class


For structs, I think this should work, but it doesn't.  I was going to file a bug about it, but never got around to it.  In my particular case I was trying to make an array-like struct, and got bitten by the fact that I could use my opAssign to use "MyStruct foo=null" as a default argument.   For classes, as torhu points out, there's no LHS object allocated, so it's not going to work without some bigger change to the language.


> I don't think it's possible to use defaults args like this.  It seems that opAssign isn't used for default args.  You need to remove the default, overload the function, specialize the template, or redesign the whole thing.
> 
> 
>> // 2. T a = 1; // I guess, a points to 0x1 now and not to a result of mpz_class.this(1)
> 
> Even if it tried to call opAssign, there's no object allocated.  It would just crash.  You have to use 'new' somewhere.  Maybe it's better to use a struct with static opCall overloads, instead of a class?  A struct is a better fit if you want it to behave like ints and longs, for sure.



--bb
November 04, 2007
>>> // 1.
>>> void func (T) (T input = 0) // error:  cannot implicitly convert expression (0) of type int to gmp.mpz.mpz_class
> 
> 
> For structs, I think this should work, but it doesn't.  I was going to file a bug about it, but never got around to it.  In my particular case I was trying to make an array-like struct, and got bitten by the fact that I could use my opAssign to use "MyStruct foo=null" as a default 

could *not* use it as a defaulted argument, that is...

--bb
November 04, 2007
Bill Baxter Wrote:

> torhu wrote:
> > Matthias Walter wrote:
> >> Hello,
> >>
> >> I have a class called mpz_class which in particular implements the following:
> >>
> >> 1. this (int other) { ... }
> >> 2. opAssign (int other) { ... }
> >>
> >> I now want to write templated code with T = int, T = long and T =
> >> mpz_class.
> >> My mpz_class should work and feel like the orginal integers, which
> >> mostly works.
> >>
> >> I'd like to know, how I can coax my class to implicitely convert integer literals to instances of it. There are 2 cases which don't compile for me and one which is semantically wrong:
> >>
> >> // 1.
> >> void func (T) (T input = 0) // error:  cannot implicitly convert
> >> expression (0) of type int to gmp.mpz.mpz_class
> 
> 
> For structs, I think this should work, but it doesn't.  I was going to file a bug about it, but never got around to it.  In my particular case I was trying to make an array-like struct, and got bitten by the fact that I could use my opAssign to use "MyStruct foo=null" as a default argument.   For classes, as torhu points out, there's no LHS object allocated, so it's not going to work without some bigger change to the language.
> 
> 
> > I don't think it's possible to use defaults args like this.  It seems that opAssign isn't used for default args.  You need to remove the default, overload the function, specialize the template, or redesign the whole thing.
> > 
> > 
> >> // 2. T a = 1; // I guess, a points to 0x1 now and not to a result of mpz_class.this(1)
> > 
> > Even if it tried to call opAssign, there's no object allocated.  It would just crash.  You have to use 'new' somewhere.  Maybe it's better to use a struct with static opCall overloads, instead of a class?  A struct is a better fit if you want it to behave like ints and longs, for sure.
> 
> 
> 
> --bb

Okay, I found one solution for everything which at least works:

a function num (T) (int n), which returns n if T is a native integer or "new T (n)" if not.
This work's as follows:

| void func (T) (T input = num !(T) (0))
| {
|   // ...
| }
|
| T a = num !(T) (1); // is correctly initialized.
|
| func !(T) (); // works with default parameter 0
| func !(T) (num !(T) (2)); // works

This is ugly typing (at least for a library like GMP, where one likes to do things like with normal integers!), so the problem could be solved, if

int i = new int (1);

would be semantically equivalent to

int i = 1;

So no allocation should happen! Although this isn't very nice syntax, because the reader might think that there happens allocation, but it would make work with GMP much easier!

best regards
Matthias
November 04, 2007
Matthias Walter wrote:
> 
> Okay, I found one solution for everything which at least works:
> 
> a function num (T) (int n), which returns n if T is a native integer or "new T (n)" if not.
> This work's as follows:
> 
> | void func (T) (T input = num !(T) (0))
> | {
> |   // ...
> | }
> |
> | T a = num !(T) (1); // is correctly initialized.
> |
> | func !(T) (); // works with default parameter 0
> | func !(T) (num !(T) (2)); // works
> 
> This is ugly typing (at least for a library like GMP, where one likes to do things like with normal integers!), 

If you want things to behave like normal integers, then you really should be using a struct instead of a class.  The class reference semantics mean that assignment will always act very unlike a normal integer.  Is there some reason you can't make it a struct?  Do you need inheritance?

> so the problem could be solved, if
> 
> int i = new int (1);
> 
> would be semantically equivalent to
> 
> int i = 1;

Probably not going to happen.  But I could maybe see Walter making initializers like that call this(int).  In fact for structs I think something like that already tries to call static opCall(int) if it exists.  Maybe that works for classes too?

--bb
November 04, 2007
Bill Baxter Wrote:

> Matthias Walter wrote:
> > 
> > Okay, I found one solution for everything which at least works:
> > 
> > a function num (T) (int n), which returns n if T is a native integer or "new T (n)" if not.
> > This work's as follows:
> > 
> > | void func (T) (T input = num !(T) (0))
> > | {
> > |   // ...
> > | }
> > |
> > | T a = num !(T) (1); // is correctly initialized.
> > |
> > | func !(T) (); // works with default parameter 0
> > | func !(T) (num !(T) (2)); // works
> > 
> > This is ugly typing (at least for a library like GMP, where one likes to do things like with normal integers!),
> 
> If you want things to behave like normal integers, then you really should be using a struct instead of a class.  The class reference semantics mean that assignment will always act very unlike a normal integer.  Is there some reason you can't make it a struct?  Do you need inheritance?

Yes, indeed, there is a good reason: If you work with references, the use of temporaries is not a slowdown, because copying a reference is really cheap. If I'd use structs and thus call-by-value, there would be a real temporary for simple things like:

a = b + c;

To avoid this creation of temporaries, one has to make heavy use of templates like the GMP guys did for the C++ headers. (They encoded the expression type in the template type and resolved everything, including assignment operations via templates to achieve a minimal number of temporaries) They did this for all basic stuff and some functions like abs, cmp, etc.
Currently, I only have my mpz_class, which, written in a straightforward way, already has 2k lines due to all those possible functions and operator combinations and unittests. With the template approach, I guess this would be increased by a factor of 4, making the code really unmaintainable. (If you like, you can inspect your personal /usr/include/gmpxx.h :) )
Additionally, some ideas from Ben Hinkle (Recycling of GMP variables to avoid allocations) cannot be easily implemented with the template implementation.
Last but not least, I don't know, whether the template approach can be done in D at all - e.g. operators must belong to a class, which is not the case in GMP C++ headers, so I don't know, if this problem is feasible.

Any ideas, how to handle this problem?

> 
> > so the problem could be solved, if
> > 
> > int i = new int (1);
> > 
> > would be semantically equivalent to
> > 
> > int i = 1;
> 
> Probably not going to happen.  But I could maybe see Walter making initializers like that call this(int).  In fact for structs I think something like that already tries to call static opCall(int) if it exists.  Maybe that works for classes too?
> 
> --bb

I'm going to check this out, thanks!

Matthias Walter