[C++-sig] Re : How to wrap this overloaded member function whose return type is reference ?

David Abrahams dave at boost-consulting.com
Sat Feb 22 23:40:17 CET 2003


Nicodemus <nicodemus at globalite.com.br> writes:

> Hi David,
>
<snip>

> I will join the discussion, since I'm having a similar problem. 8)
>
> I'm trying to do that to export static members, but it does not work
> correctly.
>
>     struct C
>     {
>         static double x;
>     };        double C::x = 10;
>
>     double get_x()
>     {
>         return C::x;
>     }
>
>     // Module
>     ======================================================================
>     BOOST_PYTHON_MODULE(boost)
>     {
>         object C_scope =
>         class_< C >("C", init< const C & >())
>             .def(init<  >())
>         ;
>         C_scope.attr("x") = C::x;
>          def("get_x", &get_x);
>     }
>
>
>  From python:
>
>     >>> from boost import *
>     >>> C.x
>     10.0
>     >>> C.x = 3.4
>     >>> C.x
>     3.9999999999
>     >>> get_x()
>     10.0
>    The x static member is not being updated in the C++ side, which
>    seems obvious, since the call
>      C_scope.attr("x") = C::x;
>
> is passing C::x by value. I tried this instead
>
>     C_scope.attr("x") = &C::x;
>
> in the hopes that it would now update the variable correctly, but from
> python:
>
>     >>> from boost import *
>     Traceback (most recent call last):
>       File "<stdin>", line 1, in ?
>     TypeError: No to_python (by-value) converter found for C++ type: double
>
> which is also obvious: I am declaring the attribute x as a double*,
> and boost.python doesn't know how to handle a double*.
>
> So, how can I get the correct behaviour?

Well, if x were a class you could do:

   C_scope.attr("x") = boost::ref(C::x);

Which would wrap a reference to x.  Unfortunately, you have another
problem: x is a double, and Python already has a type for
double... which is immutable.

The solution is to use a property to intercept attribute reads and
assignments, so that we can programmatically set C::x:

[ You should read http://www.python.org/2.2.2/descrintro.html and
  http://www.python.org/peps/pep-0252.html if you haven't already.
  Why they can't get this material into the regular Python docs is
  beyond me
]

  >>> #define a property class
  ... class Prop(object):
  ...     def __get__(self, obj, type=None):
  ...         print '__get__', (self, obj, type)
  ...         return 'value'
  ...
  ...     def __set__(self, obj, type=None):
  ...         print '__set__', (self, obj, type)
  ...
  ...     def __delete__(self, obj, type=None):
  ...         print '__delete__', (self, obj, type)
  ...
  >>> # use it in a class C
  ... class C(object):
  ...     x = Prop()
  ...
  >>> a = C()

  >>> a.x # all accesses to a.x are intercepted
  __get__ (<__main__.Prop object at 0x00877BC8>, <__main__.C object at 0x00878108>, <class '__main__.C'>)
  'value'
  >>> a.x = 42 
  __set__ (<__main__.Prop object at 0x00877BC8>, <__main__.C object at 0x00878108>, 42)

  >>> C.x # Prop intercepts reads of the class attribute
  __get__ (<__main__.Prop object at 0x00877BC8>, None, <class '__main__.C'>)
  'value'
  >>> C.x = 1 # But not assignments
  >>> C.x
  1

  >>> class mc(object.__class__): # to intercept C.x assignment
  ...     x = Prop()              # I have to define this
  ...
  >>> class C(object):
  ...     __metaclass__ = mc
  ...
  >>> C.x # now all accesses to C.x are intercepted
  __get__ (<__main__.Prop object at 0x00876AB8>, <class '__main__.C'>, <class '__main__.mc'>)
  'value'
  >>> C.x = 1
  __set__ (<__main__.Prop object at 0x00876AB8>, <class '__main__.C'>, 1)
  >>> a = C() # But not accesses to a.x
  >>> a.x
  Traceback (most recent call last):
    File "<stdin>", line 1, in ?
  AttributeError: 'C' object has no attribute 'x'
  >>>

As you can see, the only way to intercept assignment to C.x is to
stick a property C's class, i.e. the metaclass (or to modify
__setattr__ in the metaclass, but it amounts to the same thing).  In
fact, to fully emulate the C++ syntaxes of:

      C::x = 1;
  and
      a.x = 1;

We need a property in both the metaclass and in the class.

This leaves us with two options, both of which involve changing
Boost.Python's C++ source:

  option 1:
    introduce a new metaclass for every wrapped class so that we have a
    place to stick a property object.

    option 1a:
        only do this if the user supplies a special extra template
        parameter to the class_<...> declaration

  option 2:
    Implement a special property type which allows us to easily
    identify property attributes which correspond to Boost.Python static
    data members

    Implement a special __setattr__ in the Boost.Python metaclass which
    looks up the attribute on the class to see if it has this special
    type; if so, it is called, and otherwise the default __setattr__
    behavior takes effect.

I guess I prefer option 1: it favors speed and code size over data
size.  The only downside I can see is that an extra object (the
metaclass object) is created for every wrapped class.  It's also
simpler to implement.

Thoughts?

-- 
Dave Abrahams
Boost Consulting
www.boost-consulting.com





More information about the Cplusplus-sig mailing list