[C++-sig] V2: wrapping int/double bug(?)

Pearu Peterson pearu at cens.ioc.ee
Tue May 14 12:14:04 CEST 2002


On Mon, 13 May 2002, David Abrahams wrote:

> ----- Original Message -----
> From: "Pearu Peterson" <pearu at cens.ioc.ee>
>
> > Actually, it was also a problem in Boost.Python v1 and then I solved this
> > issue by not using methods with int or float arguments but using
> > methods with PyObject* arguments and so avoiding the "smart" behaviour of
> > BPL and implementing conversion rules to my particular needs explicitly.
> 
> So, I take it there is a reason that a single overload using a double
> argument doesn't suit your needs? 

Yes, there is. As I mentioned in my previous message, the library that I
am wrapping is a symbolic algebra library where floats are inexact and
ints are exact numbers, roughly speaking. Trivial example: if I'd used
only double argument constructor:

  numeric(double)

then in Python

  numeric(2)/3

becomes inexact

  0.66666666666666663

while the library is designed to handle the above ratio as an exact 
rational number

  2/3

If I'd used only int argument constructor

  numeric(int)

then in python

  numeric(2.4)

becomes wrong

  2

without any complaints.

> I ask because in general it would seem
> like a bad idea to implement different semantics for f(4) and f(4.0), and
> doubles tend to be able to represent all the values of an integer.

It depends on the application, I guess. In numerical applications
nobody represents integers by doubles for various reasons (efficiency,
accuracy, etc). I understand this can be feasible for a number of
applications that hardly deal with numerics or if they do then only with
floating point numerics. And therefore they don't care if int becomes
float.

> > I don't know what is "CLOS-style multimethod.." (and therefore what
> > follows may be irrelevant)
> 
> Maybe. See http://www.sff.net/people/neelk/open-source/Multimethod.py for a
> Python-oriented take.

Thanks for the reference.

> > but I don't see how your suggestion would solve
> > the problem in hand. That is, if a wrapped class A defines methods
> >
> >  .foo(int)
> >  .foo(float)
> >
> > and a class B has both __int__ and __float__ methods, then in Python
> >
> >  A().foo(B())
> >
> > how can you tell which method, __int__ or __float__, should be called
> when
> > doing conversion? E.g. take B=int and then take B=float.
> 
> I assume you mean the cases where B() returns int or float. 

No. I mean that B() is an instance of the class B that represents either
int or float, or can be transformed to one. Take, for example, B is
Rational, or an arbitrary precision number.

> That's easy: Python int would correspond most-closely to C++ int, and
> Python float would correspond most-closely to C++ double.

I agree. But BPL does not seem to agree with that ;-)

> > In order to do it properly, at some point the responsible mechanism in
> BPL
> > should check whether an instance B() has more an "int nature" or more a
> > "float nature". This is easy, in principle, if B is int or float (or
> > a subclass of int or float) but not obvious at all if B is, for example,
> > str
> 
> neither the built-in function str() nor built-in strings have __int__ or
> __float__ methods, so in that case there would be no match at all.

And yet

>>> float("2.3")
2.2999999999999998
>>> int("2")
2

so I didn't look in to details of how these numbers are constructed.
str was a bad example. Forget it.

> > or an user defined class that defines both __int__ and __float__
> > methods.
> 
> In that case I would consider the call ambiguous.

Which is exactly the case for Python int and float:

>>> int.__int__ 
<slot wrapper '__int__' of 'int' objects>
>>> int.__float__ 
<slot wrapper '__float__' of 'int' objects>
>>> float.__float__ 
<slot wrapper '__float__' of 'float' objects>
>>> float.__int__ 
<slot wrapper '__int__' of 'float' objects>

> > Currently, I can workaround this by defining a single method
> >
> >   .foo(PyObject*)
> >
> > and explicitly checking whether an object is int or float and doing
> > the appropiate conversion in that method. This approach works fine with
> > methods with int/float arguments and I am happy with that.
> 
> I'm not, though ;-)

Good.

> > BUT, there is a real problem with constructors. Namely, one cannot define
> > an additional constructor (that is needed for being more explicit with
> > conversions for the same reasons as discussed above for methods)
> >
> >   A(PyObject*)
> >
> > without introducing a lightweighted wrapper to a library class A.
> 
> Well, of course there is a way, but it's not in the library's public
> interface.

And that way might be far over my C++ head ;-)

> > And that approach is unacceptable because it would mean that all methods
> > (that I would like to use from Python) of A, must be re-wrapped as
> > well.
> 
> No, I don't think so. You only need to wrap them once in
> class_<A,A_wrapper>. It's only constructors which need to be duplicated in
> A_wrapper.

I tried that, but it didn't work out. I'll try to sketch the real
situation from my memory: the GiNaC library contains the following class
tree (only partially exposed here, see
  http://www.ginac.de/reference/hierarchy.html
for a complete tree):

class basic
  class symbol(basic)
  class numeric(basic)
  class expairseq(basic)
    class add(expairseq)
  ..

class ex /* this is an holder of basic's pointer and used to
            pass different basic instances to various methods
            as well as to return the results of calculations
          */

In the BPL based wrapper I did

  boost::python::module m("_ginac");
  m
    .add(
    	 boost::python::class_<GiNaC::ex>("ex")
	 .def_init(boost::python::args<const GiNaC::ex &>())
	 .def_init(boost::python::args<const GiNaC::basic &>())
         /* snip number of methods */
    )
    .add(boost::python::class_<GiNaC::basic>("_basic")
	 .def_init(boost::python::args<const GiNaC::basic &>())
         /* snip number of methods */
   );
   m
    .add(boost::python::class_<GiNaC::numeric,
	 boost::python::bases<GiNaC::basic> >("numeric")
	 .def_init(boost::python::args<const numeric &>())
         /* Wished to have
          .def_init(boost::python::args<int>())
          .def_init(boost::python::args<double>())
         */
         /* snip methods */
   )

It is not clear to me how to include

  class numeric_wrapper: public GiNaC::numeric {
    numeric_wrapper(PyObject*);
  }

into this tree. If I remember correctly then

    .add(boost::python::class_<GiNaC::numeric,
         boost::python::bases<GiNaC::basic, numeric_wrapper> >("numeric")
         .def_init(boost::python::args<const numeric &>())
         ...

didn't work because the constructor of numeric_wrapper was never called -
BPL seemed to forget the constructors of the parent classes (or again
choosed the first found match).

If the above still does not make sense, I'll try to produce a working
example of the above demonstrating my situation.

> > In my particular case the number of relevant methods can be more
> > than 50 and therefore this lightweighted wrapper would get quite
> > "heavy".
> 
> I don't understand that part.

I meant that I ended up with implementing methods like

  basic_add_basic
  basic_add_pyobj
  basic_add_ex
  basic_add_numeric
  ex_add_basic
  ex_add_pyobj
  ex_add_ex
  ex_add_numeric
  numeric_add_basic    /* Though numeric is derived from basic, its
  numeric_add_numeric     arithmetics is done in a different route */
  numeric_add_ex        
  numeric_add_py

Similarly for mul,div,sub,pow,etc.

And then I noticed that the size of the wrapper was becoming close to the
size of the library itself, which stopped me and made me doubt if
this approach is a good one.

> > Now, the question is whether it is possible to introduce additional
> > constructors to library classes without deriving a new class for that?
< snip >
> > What do you think? Would the above be possible in principle?
> 
> In principle. my_A_init has to build and install a Holder; it can't just
> return a new A copy... at least, that's the way the library works now.

Thanks for the hint. I'll try to figure out that if nothing else works.

still-not-convinced-that-bpl-cannot-wrap-real-libraries'ly yours

Pearu






More information about the Cplusplus-sig mailing list