Is there a way to automatically convert a smart_ptr to a held_type (try 2)
Note: I posted this message a very long time ago on this list and still haven't found a solution. Several months ago David suggested that I repost the message again to see if anyone can help out. I am finally getting back to this project so here is the repost. I am wrapping a C++ library with boost.python that has multiple types of smart ptrs for it's objects. It has a ptr<> type that holds the object and provides a ref counting interface (through and addRef()/subRef() interface). It then has a ref_ptr<> type that acts like a "standard" smart pointer by holding a ptr<> internally and calling the ref handling operations upon construction and destruction. To correctly handle the memory for the objects, I want my boost.python class wrappers for a class T to be held using ref_ptr<T>. I can do this with: class_<T, bases<...>, ref_ptr<T> >("T",...); This all works fine. I run into a problem though when I want to use wrapped method that return ptr<>'s to objects instead of a full ref_ptr<>. For example if I wrap a method like this: ptr<Base> createObject() { ... } then when I call this from boost.python it needs to figure out that it should convert the ptr<Base> to a ref_ptr<Base>. I know that I can use register_ptr_to_python<ptr<Base> > to register a to_python converter for the type, but as I understand it this will cause the python wrapper to store the C++ object using a ptr<> type internally. This won't work correctly because it will not do any memory reference counting. What I need is a way to tell boost python that anytime it needs to convert a ptr<Base> to python it should first convert it to a ref_ptr<Base> (using a standard copy constructor in ref_ptr<>) and then use this ref_ptr as the held type like normal. At first I thought that implicitly_convertible<> would help: implicitly_convertible<ptr<Base>, ref_ptr<Base> >(); But this doesn't seem to do anything for me. (this really isn't too surprising since this isn't what implicitly_convertible is for, but it seemed worth a try). Has anyone else ran into anything similar? Any ideas? Thanks, Allen
What I need is a way to tell boost python that anytime it needs to convert a ptr<Base> to python it should first convert it to a ref_ptr<Base> (using a standard copy constructor in ref_ptr<>) and then use this ref_ptr as the held type like normal.
At first I thought that implicitly_convertible<> would help:
implicitly_convertible<ptr<Base>, ref_ptr<Base> >();
But this doesn't seem to do anything for me. (this really isn't too surprising since this isn't what implicitly_convertible is for, but it seemed worth a try).
Has anyone else ran into anything similar? Any ideas?
At first glance, it seems like you want to register a to-python conversion for all your ptr<T> types. Note that all code I write in this message is untested: template <class T> struct ptr_to_python { static PyObject *convert(ptr<T> const &p) { return Py_INCREF(object(ref_ptr<T>(p)).ptr()); } }; Then you need to do this for each class you register: to_python_converter<ptr<T>, ptr_to_python<T> >(); You could automate this in a custom def visitor: struct ref_ptr_stuff : def_visitor<ref_ptr_stuff> { friend class def_visitor_access; template <typename Class> void visit(Class &c) const { typedef typename Class::wrapped_type T; to_python_converter<ptr<T>, ptr_to_python<T> >(); } }; Then when you wrap your classes you can say: class<T, bases<...>, ref_ptr<T> >("T",...) .def(ref_ptr_stuff()) Note that this gives you a convenient way to put common methods on all ref_ptr types. For example, you could .def() a method that returns the ref count in the ref_ptr_stuff visitor. Also note that when you get to doing polymorphic wrappers, you're going to want to be able to distinguish the polymorphic wrapper type from the "logical" wrapped type T. To do that, you'll need to either roll your own or be willing to use non-public boost.python api. (detail::unwrap_wrapper). Hope that helps, Alex
Alex Mohr <amohr@pixar.com> writes:
What I need is a way to tell boost python that anytime it needs to convert a ptr<Base> to python it should first convert it to a ref_ptr<Base> (using a standard copy constructor in ref_ptr<>) and then use this ref_ptr as the held type like normal.
At first I thought that implicitly_convertible<> would help:
implicitly_convertible<ptr<Base>, ref_ptr<Base> >();
But this doesn't seem to do anything for me. (this really isn't too surprising since this isn't what implicitly_convertible is for, but it seemed worth a try).
Has anyone else ran into anything similar? Any ideas?
At first glance, it seems like you want to register a to-python conversion for all your ptr<T> types.
Good idea.
Note that all code I write in this message is untested:
template <class T> struct ptr_to_python { static PyObject *convert(ptr<T> const &p) { return Py_INCREF(object(ref_ptr<T>(p)).ptr()); } };
Then you need to do this for each class you register:
to_python_converter<ptr<T>, ptr_to_python<T> >();
Looks good so far.
You could automate this in a custom def visitor:
struct ref_ptr_stuff : def_visitor<ref_ptr_stuff> { friend class def_visitor_access; template <typename Class> void visit(Class &c) const { typedef typename Class::wrapped_type T; to_python_converter<ptr<T>, ptr_to_python<T> >(); } };
That is a very clever and elegant abuse of def_visitor, I must say! -- Dave Abrahams Boost Consulting www.boost-consulting.com
Is there a way while access a wrapped object in python to ask it the type of the currently in use held_type? In other words, is there a way I can write a test case in python that will make sure things are behaving as I want them and that I am not using a ptr<Base> to hold the C++ object behind the scenes? -Allen On 8/30/06, David Abrahams <dave@boost-consulting.com> wrote:
Alex Mohr <amohr@pixar.com> writes:
What I need is a way to tell boost python that anytime it needs to convert a ptr<Base> to python it should first convert it to a ref_ptr<Base> (using a standard copy constructor in ref_ptr<>) and then use this ref_ptr as the held type like normal.
At first I thought that implicitly_convertible<> would help:
implicitly_convertible<ptr<Base>, ref_ptr<Base> >();
But this doesn't seem to do anything for me. (this really isn't too surprising since this isn't what implicitly_convertible is for, but it seemed worth a try).
Has anyone else ran into anything similar? Any ideas?
At first glance, it seems like you want to register a to-python conversion for all your ptr<T> types.
Good idea.
Note that all code I write in this message is untested:
template <class T> struct ptr_to_python { static PyObject *convert(ptr<T> const &p) { return Py_INCREF(object(ref_ptr<T>(p)).ptr()); } };
Then you need to do this for each class you register:
to_python_converter<ptr<T>, ptr_to_python<T> >();
Looks good so far.
You could automate this in a custom def visitor:
struct ref_ptr_stuff : def_visitor<ref_ptr_stuff> { friend class def_visitor_access; template <typename Class> void visit(Class &c) const { typedef typename Class::wrapped_type T; to_python_converter<ptr<T>, ptr_to_python<T> >(); } };
That is a very clever and elegant abuse of def_visitor, I must say!
-- Dave Abrahams Boost Consulting www.boost-consulting.com
_______________________________________________ C++-sig mailing list C++-sig@python.org http://mail.python.org/mailman/listinfo/c++-sig
Thanks Alex, this looks very good. I will try it later today. On 8/30/06, Alex Mohr <amohr@pixar.com> wrote: [snip]
Also note that when you get to doing polymorphic wrappers, you're going to want to be able to distinguish the polymorphic wrapper type from the "logical" wrapped type T. To do that, you'll need to either roll your own or be willing to use non-public boost.python api. (detail::unwrap_wrapper).
I think I am a little lost here or missing something. I am definitely going to be using this code in in the presence of polymorphism. Can you explain this part further or do you have a code example where you have had to do this? -Allen
Hope that helps,
Alex _______________________________________________ C++-sig mailing list C++-sig@python.org http://mail.python.org/mailman/listinfo/c++-sig
Also note that when you get to doing polymorphic wrappers, you're going to want to be able to distinguish the polymorphic wrapper type from the "logical" wrapped type T. To do that, you'll need to either roll your own or be willing to use non-public boost.python api. (detail::unwrap_wrapper).
I think I am a little lost here or missing something. I am definitely going to be using this code in in the presence of polymorphism.
Can you explain this part further or do you have a code example where you have had to do this?
The basic problem is that if you use the def_visitor approach I posted, then for classes exposed as polymorphic on the python side, Class::wrapped_type will be the polymorphic wrapper type (a class derived from boost::python::wrapper<T>) rather than T itself. This is important because when you register your to-python converter, you want it to be for ptr<T> not ptr<wrapper<T> >, so you need to get the unwrapped T. Doing this means either using detail::unwrap_wrapper, or rolling your own version of the same. If you don't use the def_visitor, then you won't have this problem because you'll use the right T explicitly for each wrapped class. However, I would recommend considering the def_visitor approach if you have a large system because it gives you a centralized place to add stuff to whole categories of classes. Does this make more sense? Here's what my def visitor could potentially look like, if you were comfortable with using non-public api: struct ref_ptr_stuff : def_visitor<ref_ptr_stuff> { friend class def_visitor_access; template <class Class, class Wrapper, class T> void register_conversion(Class &c, Wrapper *, T *) { to_python_converter<ptr<T>, ptr_to_python<T> >(); } template <class Class> void visit(Class &c) const { typedef typename Class::wrapped_type T; register_conversion(c, (T *)0, detail::unwrap_wrapper((T *)0)); } }; Of course, in this example I don't need Wrapper in register_conversion, but I thought I'd leave it to be illustrative. Also, let me reemphasize that this is non-public api. If you're able to do partial specialization, I believe you can roll your own unwrap_wrapper that works in a nicer way. Alex
On 8/30/06, Alex Mohr <amohr@pixar.com> wrote:
What I need is a way to tell boost python that anytime it needs to convert a ptr<Base> to python it should first convert it to a ref_ptr<Base> (using a standard copy constructor in ref_ptr<>) and then use this ref_ptr as the held type like normal.
At first I thought that implicitly_convertible<> would help:
implicitly_convertible<ptr<Base>, ref_ptr<Base> >();
But this doesn't seem to do anything for me. (this really isn't too surprising since this isn't what implicitly_convertible is for, but it seemed worth a try).
Has anyone else ran into anything similar? Any ideas?
At first glance, it seems like you want to register a to-python conversion for all your ptr<T> types. Note that all code I write in this message is untested:
template <class T> struct ptr_to_python { static PyObject *convert(ptr<T> const &p) { return Py_INCREF(object(ref_ptr<T>(p)).ptr()); } };
Then you need to do this for each class you register:
to_python_converter<ptr<T>, ptr_to_python<T> >();
Thanks Alex, this worked great. I had to change the Py_INCREF to a bp::incref but otherwise things are all good. But now I have a rather strange followup question. It seems that Boost.Python is doing some magic behind the scenes to convert an object coming from C++ as a smart pointer whose wrapped pointer is NULL into a Python "None". The is great, and was a wonderful surprise. But now I need to find a way to make it happen in the other direction. How does boost.python convert a parameter of "None" into an object/smart_ptr to pass to C++? I need to find a way to call a C++ method like: void setObject(ptr<Base> obj); from Python and call it in Python as "setObject(None)" and instruct boost.python to convert this into a call behind the scenes in C++ to: setObject(NullPtr); where "NullPtr" is a global const value defined by the library that is used to signify a NULL shared pointer throughout the system. (the reason it needs something like this is a little complex, but it is not my library so I can't change this part much.) Alternatively, I could probably make it work if I could get boost.python to make a call like: setObject(ref_ptr<Base>(NULL) ) as I think I could get the library to automatically convert ref_ptr<Base>(NULL) into a NullPtr. Any ideas? -Allen
Thanks Alex, this worked great. I had to change the Py_INCREF to a bp::incref but otherwise things are all good.
But now I have a rather strange followup question.
It seems that Boost.Python is doing some magic behind the scenes to convert an object coming from C++ as a smart pointer whose wrapped pointer is NULL into a Python "None". The is great, and was a wonderful surprise. But now I need to find a way to make it happen in the other direction.
How does boost.python convert a parameter of "None" into an object/smart_ptr to pass to C++?
It happens in the from_python conversion. It just checks for None and (I believe) default-constructs a the pointer object. Is it the case that default-constructing one of your smart pointers does not produce the intended NULL? It probably wouldn't be hard to add a customization (paralleling get_pointer and pointee) to boost python. But in the meantime...
I need to find a way to call a C++ method like:
void setObject(ptr<Base> obj);
from Python and call it in Python as "setObject(None)" and instruct boost.python to convert this into a call behind the scenes in C++ to:
setObject(NullPtr);
where "NullPtr" is a global const value defined by the library that is used to signify a NULL shared pointer throughout the system. (the reason it needs something like this is a little complex, but it is not my library so I can't change this part much.)
Alternatively, I could probably make it work if I could get boost.python to make a call like:
setObject(ref_ptr<Base>(NULL) )
as I think I could get the library to automatically convert ref_ptr<Base>(NULL) into a NullPtr.
Any ideas?
I think you can do either version by registering your own rvalue from_python converters for all your pointer types. You can do this in the def_visitor along with the to_python conversion we discussed earlier. Just replace the relevant bits below ("Deal with the "None" case") with whatever you want. Again, I'm just typing this so it may not compile, and no guarantees of course... template <class Ptr> struct smart_ptr_from_python { typedef typename boost::python::pointee<Ptr>::type Pointee; smart_ptr_from_python() { converter::registry::insert(&convertible, &construct, type_id<Ptr>()); } private: static void *convertible(PyObject *p) { // can always produce a pointer from None. if (p == Py_None) return p; // Otherwise, we can do it if we can get the pointee out. void *result = converter::get_lvalue_from_python (p, converter::registered<Pointee>::converters); return result; } static void construct(PyObject* source, converter:: rvalue_from_python_stage1_data* data) { void* const storage = ((converter::rvalue_from_python_storage<Ptr>*)data)-> storage.bytes; // Deal with the "None" case. if (data->convertible == source) new (storage) Ptr(); // Or whatever you want. else new (storage) Ptr(static_cast<Pointee*>(data->convertible)); data->convertible = storage; } }; Alex
On 9/5/06, Alex Mohr <amohr@pixar.com> wrote:
Thanks Alex, this worked great. I had to change the Py_INCREF to a bp::incref but otherwise things are all good.
But now I have a rather strange followup question.
It seems that Boost.Python is doing some magic behind the scenes to convert an object coming from C++ as a smart pointer whose wrapped pointer is NULL into a Python "None". The is great, and was a wonderful surprise. But now I need to find a way to make it happen in the other direction.
How does boost.python convert a parameter of "None" into an object/smart_ptr to pass to C++?
It happens in the from_python conversion. It just checks for None and (I believe) default-constructs a the pointer object. Is it the case that default-constructing one of your smart pointers does not produce the intended NULL? It probably wouldn't be hard to add a customization (paralleling get_pointer and pointee) to boost python. But in the meantime...
I will try to find this in the boost.python code and verify the behavior.
I need to find a way to call a C++ method like:
void setObject(ptr<Base> obj);
from Python and call it in Python as "setObject(None)" and instruct boost.python to convert this into a call behind the scenes in C++ to:
setObject(NullPtr);
where "NullPtr" is a global const value defined by the library that is used to signify a NULL shared pointer throughout the system. (the reason it needs something like this is a little complex, but it is not my library so I can't change this part much.)
Alternatively, I could probably make it work if I could get boost.python to make a call like:
setObject(ref_ptr<Base>(NULL) )
as I think I could get the library to automatically convert ref_ptr<Base>(NULL) into a NullPtr.
Any ideas?
I think you can do either version by registering your own rvalue from_python converters for all your pointer types. You can do this in the def_visitor along with the to_python conversion we discussed earlier.
Just replace the relevant bits below ("Deal with the "None" case") with whatever you want.
Again, I'm just typing this so it may not compile, and no guarantees of course...
All I can say is "wow!!". You seem to understand the conversion process inside boost.python much more then I do. I will give these methods a try tomorrow. I will need to alter them slightly since I am not using the def_visitor method you outlined. (I am using Py++ to generate the bindings so it really doesn't take any more typing to just have it put the registration calls in the right place). I will let you know how it goes. As a side note, if you don't mind me asking, how did you learn so much about how the conversion process works. I have always had trouble understanding the full process and am looking for any advice about how to learn it better. (did you learn from the code, the API reference, the mailing list, other??) If you ever have any time or interest and wanted to contribute some to a little documentation effort I am working on in my spare time, take a look at http://tinyurl.com/zb76h . Thanks again. -Allen
template <class Ptr> struct smart_ptr_from_python { typedef typename boost::python::pointee<Ptr>::type Pointee; smart_ptr_from_python() { converter::registry::insert(&convertible, &construct, type_id<Ptr>()); } private: static void *convertible(PyObject *p) { // can always produce a pointer from None. if (p == Py_None) return p; // Otherwise, we can do it if we can get the pointee out. void *result = converter::get_lvalue_from_python (p, converter::registered<Pointee>::converters); return result; }
static void construct(PyObject* source, converter:: rvalue_from_python_stage1_data* data) { void* const storage = ((converter::rvalue_from_python_storage<Ptr>*)data)-> storage.bytes; // Deal with the "None" case. if (data->convertible == source) new (storage) Ptr(); // Or whatever you want. else new (storage) Ptr(static_cast<Pointee*>(data->convertible)); data->convertible = storage; } };
Alex _______________________________________________ C++-sig mailing list C++-sig@python.org http://mail.python.org/mailman/listinfo/c++-sig
As a side note, if you don't mind me asking, how did you learn so much about how the conversion process works. I have always had trouble understanding the full process and am looking for any advice about how to learn it better. (did you learn from the code, the API reference, the mailing list, other??) If you ever have any time or interest and wanted to contribute some to a little documentation effort I am working on in my spare time, take a look at http://tinyurl.com/zb76h .
I learned from a combination of all the things you mention. I currently find reading the code to be the most useful. I don't know if there's a better resource, other than getting David to respond to your questions directly if you can. :) Alex
I've been trying to learn more about the calling and argument conversion process today by stepping through the code with ddd/gdb. I am learning some things but I have to admit it is pretty difficult with all the preprocessor library and MPL usage (this stuff confuses the debugger quite a bit). Hopefully by later today I will understand enough to know what you code samples are trying to do. :) -Allen On 9/6/06, Alex Mohr <amohr@pixar.com> wrote:
As a side note, if you don't mind me asking, how did you learn so much about how the conversion process works. I have always had trouble understanding the full process and am looking for any advice about how to learn it better. (did you learn from the code, the API reference, the mailing list, other??) If you ever have any time or interest and wanted to contribute some to a little documentation effort I am working on in my spare time, take a look at http://tinyurl.com/zb76h .
I learned from a combination of all the things you mention. I currently find reading the code to be the most useful. I don't know if there's a better resource, other than getting David to respond to your questions directly if you can. :)
Alex _______________________________________________ C++-sig mailing list C++-sig@python.org http://mail.python.org/mailman/listinfo/c++-sig
On 9/5/06, Alex Mohr <amohr@pixar.com> wrote:
Thanks Alex, this worked great. I had to change the Py_INCREF to a bp::incref but otherwise things are all good.
But now I have a rather strange followup question.
It seems that Boost.Python is doing some magic behind the scenes to convert an object coming from C++ as a smart pointer whose wrapped pointer is NULL into a Python "None". The is great, and was a wonderful surprise. But now I need to find a way to make it happen in the other direction.
How does boost.python convert a parameter of "None" into an object/smart_ptr to pass to C++?
It happens in the from_python conversion. It just checks for None and (I believe) default-constructs a the pointer object. Is it the case that default-constructing one of your smart pointers does not produce the intended NULL? It probably wouldn't be hard to add a customization (paralleling get_pointer and pointee) to boost python. But in the meantime...
I need to find a way to call a C++ method like:
void setObject(ptr<Base> obj);
from Python and call it in Python as "setObject(None)" and instruct boost.python to convert this into a call behind the scenes in C++ to:
setObject(NullPtr);
where "NullPtr" is a global const value defined by the library that is used to signify a NULL shared pointer throughout the system. (the reason it needs something like this is a little complex, but it is not my library so I can't change this part much.)
Alternatively, I could probably make it work if I could get boost.python to make a call like:
setObject(ref_ptr<Base>(NULL) )
as I think I could get the library to automatically convert ref_ptr<Base>(NULL) into a NullPtr.
Any ideas?
I think you can do either version by registering your own rvalue from_python converters for all your pointer types. You can do this in the def_visitor along with the to_python conversion we discussed earlier.
Just replace the relevant bits below ("Deal with the "None" case") with whatever you want.
I am sorting through this code right now. Am I missing something or is there no documentation in the reference guide about how to register a converter directly with the registry? Additionally, I was looking at the documentation for implicitly_convertible and I am wondering why I can't just use that in my case. I want to call: setObject(ptr<Base> val) with a NoneType. And I have all my objects (ie. Base) wrapped and set with held types of ref_ptr<>'s. RefPtr's are implicitly convertible to ptr<>'s, so If I register this with boost.python using: bp::implicitly_convertible< ret_ptr<Base>, ptr<Base> >(); Then shouldn't boost.python be able to convert NoneType --> ref_ptr<Base> --> ptr<Base> ? From the documentation in implicitly_convertible it seems like this should be the case. But I have to admit I have not found the place in the boost.python code where it handles the case of taking a NoneType and automatically converting it into a NULL smart pointer to start with. -Allen
Again, I'm just typing this so it may not compile, and no guarantees of course...
template <class Ptr> struct smart_ptr_from_python { typedef typename boost::python::pointee<Ptr>::type Pointee; smart_ptr_from_python() { converter::registry::insert(&convertible, &construct, type_id<Ptr>()); } private: static void *convertible(PyObject *p) { // can always produce a pointer from None. if (p == Py_None) return p; // Otherwise, we can do it if we can get the pointee out. void *result = converter::get_lvalue_from_python (p, converter::registered<Pointee>::converters); return result; }
static void construct(PyObject* source, converter:: rvalue_from_python_stage1_data* data) { void* const storage = ((converter::rvalue_from_python_storage<Ptr>*)data)-> storage.bytes; // Deal with the "None" case. if (data->convertible == source) new (storage) Ptr(); // Or whatever you want. else new (storage) Ptr(static_cast<Pointee*>(data->convertible)); data->convertible = storage; } };
Alex _______________________________________________ C++-sig mailing list C++-sig@python.org http://mail.python.org/mailman/listinfo/c++-sig
I am sorting through this code right now. Am I missing something or is there no documentation in the reference guide about how to register a converter directly with the registry?
No, but the code I posted does it in the constructor.
Additionally, I was looking at the documentation for implicitly_convertible and I am wondering why I can't just use that in my case.
I want to call:
setObject(ptr<Base> val)
with a NoneType. And I have all my objects (ie. Base) wrapped and set with held types of ref_ptr<>'s. RefPtr's are implicitly convertible to ptr<>'s, so If I register this with boost.python using:
bp::implicitly_convertible< ret_ptr<Base>, ptr<Base> >();
Then shouldn't boost.python be able to convert NoneType --> ref_ptr<Base> --> ptr<Base> ? From the documentation in implicitly_convertible it seems like this should be the case. But I have to admit I have not found the place in the boost.python code where it handles the case of taking a NoneType and automatically converting it into a NULL smart pointer to start with.
Sure -- if your types meet those requirements, then that should work. The rvalue converters I posted are more general. implicitly_convertible just registers an rvalue converter using the same underlying mechanism. From your message, I was under the impression that default-constructing one of your smart pointers would not produce the desired "null" value. Alex
On 9/6/06, Alex Mohr <amohr@pixar.com> wrote:
I am sorting through this code right now. Am I missing something or is there no documentation in the reference guide about how to register a converter directly with the registry?
No, but the code I posted does it in the constructor.
Additionally, I was looking at the documentation for implicitly_convertible and I am wondering why I can't just use that in my case.
I want to call:
setObject(ptr<Base> val)
with a NoneType. And I have all my objects (ie. Base) wrapped and set with held types of ref_ptr<>'s. RefPtr's are implicitly convertible to ptr<>'s, so If I register this with boost.python using:
bp::implicitly_convertible< ret_ptr<Base>, ptr<Base> >();
Then shouldn't boost.python be able to convert NoneType --> ref_ptr<Base> --> ptr<Base> ? From the documentation in implicitly_convertible it seems like this should be the case. But I have to admit I have not found the place in the boost.python code where it handles the case of taking a NoneType and automatically converting it into a NULL smart pointer to start with.
Sure -- if your types meet those requirements, then that should work. The rvalue converters I posted are more general. implicitly_convertible just registers an rvalue converter using the same underlying mechanism.
From your message, I was under the impression that default-constructing one of your smart pointers would not produce the desired "null" value.
A default construction of the RefPtr Type should, but using implicitly_convertible didn't work. It acted like it didn't know that there was an option to convert through the implicit chains. I have now implemented something based on the code you posted and am working on testing it. Hopefully it will work out. I will let you kno. -Allen
Alex _______________________________________________ C++-sig mailing list C++-sig@python.org http://mail.python.org/mailman/listinfo/c++-sig
On 9/5/06, Alex Mohr <amohr@pixar.com> wrote:
Thanks Alex, this worked great. I had to change the Py_INCREF to a bp::incref but otherwise things are all good.
But now I have a rather strange followup question.
It seems that Boost.Python is doing some magic behind the scenes to convert an object coming from C++ as a smart pointer whose wrapped pointer is NULL into a Python "None". The is great, and was a wonderful surprise. But now I need to find a way to make it happen in the other direction.
How does boost.python convert a parameter of "None" into an object/smart_ptr to pass to C++?
It happens in the from_python conversion. It just checks for None and (I believe) default-constructs a the pointer object. Is it the case that default-constructing one of your smart pointers does not produce the intended NULL? It probably wouldn't be hard to add a customization (paralleling get_pointer and pointee) to boost python. But in the meantime...
I need to find a way to call a C++ method like:
void setObject(ptr<Base> obj);
from Python and call it in Python as "setObject(None)" and instruct boost.python to convert this into a call behind the scenes in C++ to:
setObject(NullPtr);
where "NullPtr" is a global const value defined by the library that is used to signify a NULL shared pointer throughout the system. (the reason it needs something like this is a little complex, but it is not my library so I can't change this part much.)
Alternatively, I could probably make it work if I could get boost.python to make a call like:
setObject(ref_ptr<Base>(NULL) )
as I think I could get the library to automatically convert ref_ptr<Base>(NULL) into a NullPtr.
Any ideas?
I think you can do either version by registering your own rvalue from_python converters for all your pointer types. You can do this in the def_visitor along with the to_python conversion we discussed earlier.
Just replace the relevant bits below ("Deal with the "None" case") with whatever you want.
Again, I'm just typing this so it may not compile, and no guarantees of course...
template <class Ptr> struct smart_ptr_from_python { typedef typename boost::python::pointee<Ptr>::type Pointee; smart_ptr_from_python() { converter::registry::insert(&convertible, &construct, type_id<Ptr>()); } private: static void *convertible(PyObject *p) { // can always produce a pointer from None. if (p == Py_None) return p; // Otherwise, we can do it if we can get the pointee out. void *result = converter::get_lvalue_from_python (p, converter::registered<Pointee>::converters); return result; }
static void construct(PyObject* source, converter:: rvalue_from_python_stage1_data* data) { void* const storage = ((converter::rvalue_from_python_storage<Ptr>*)data)-> storage.bytes; // Deal with the "None" case. if (data->convertible == source) new (storage) Ptr(); // Or whatever you want. else new (storage) Ptr(static_cast<Pointee*>(data->convertible)); data->convertible = storage; } };
This code allowed the setObject(None) to work now. Unfortunately it broke the functionality that had been working where I could call setObject(PythonObject). More details (extracted from above): I have: // set method void setObject(ptr<Base> obj); // Implicit conversion for ref_ptr to ptr bp::implicitly_convertible< ref_ptr<Base>, ptr<Base> ); // From original e-mail: this works to automatically convert // ptr<Base> returns into ref_ptr<Base> as the held type bp::to_pyton_converter< ptr<Base>, ptr_to_python< ptr<Base> >() // Most recent discussion: way to get None from python as ptr<Base> // - This works to let me pass None into wrapped setObject method bp::converter::registry::insert(&convertible, &construct, bp::type_id<ptr<Base> >()); The problem is that now when I try to call something like this in python: new_obj = objtype.create() setObject(None) setObject(new_obj) Both of these calls to setObject try to convert the object to a ptr<Base> using the manually registered converter. The converter only really knows how to deal with the conversion from a None type. In the case of a real object I just want boost.python to convert the held type (ref_ptr<Base> ) into a ptr<Base> using the implicit conversion. How do I get this to work? Can I have multiple converters registered and if so how do I tell boost.python to only use the manually created one for the NoneType? -Allen
This code allowed the setObject(None) to work now. Unfortunately it broke the functionality that had been working where I could call setObject(PythonObject).
More details (extracted from above):
I have:
// set method void setObject(ptr<Base> obj);
// Implicit conversion for ref_ptr to ptr bp::implicitly_convertible< ref_ptr<Base>, ptr<Base> );
// From original e-mail: this works to automatically convert // ptr<Base> returns into ref_ptr<Base> as the held type bp::to_pyton_converter< ptr<Base>, ptr_to_python< ptr<Base> >()
// Most recent discussion: way to get None from python as ptr<Base> // - This works to let me pass None into wrapped setObject method bp::converter::registry::insert(&convertible, &construct, bp::type_id<ptr<Base> >());
The problem is that now when I try to call something like this in python:
new_obj = objtype.create() setObject(None) setObject(new_obj)
Both of these calls to setObject try to convert the object to a ptr<Base> using the manually registered converter. The converter only really knows how to deal with the conversion from a None type. In the case of a real object I just want boost.python to convert the held type (ref_ptr<Base> ) into a ptr<Base> using the implicit conversion.
How do I get this to work? Can I have multiple converters registered and if so how do I tell boost.python to only use the manually created one for the NoneType?
The rvalue converter should handle both cases. In convertible, it checks if it is getting None -- if it is, it says yes, I can make a pointer. If it gets something other than None, it says I can make a pointer iff I can get an lvalue pointer to the Pointee from the python object. That's the code after the if. It uses already registered lvalue converters to do this. The construct method has a similar structure -- in the None case (first part of if) it will just placement new a default-constructed pointer. In the else part of the if, it will placement new a pointer pointing to the c++ instance. Can you tell me what's going on in convertible and construct when you do this?
setObject(new_obj)
Alex
template <class Ptr> struct smart_ptr_from_python { typedef typename boost::python::pointee<Ptr>::type Pointee; smart_ptr_from_python() { converter::registry::insert(&convertible, &construct, type_id<Ptr>()); } private: static void *convertible(PyObject *p) { // can always produce a pointer from None. if (p == Py_None) return p; // Otherwise, we can do it if we can get the pointee out. void *result = converter::get_lvalue_from_python (p, converter::registered<Pointee>::converters); return result; }
static void construct(PyObject* source, converter:: rvalue_from_python_stage1_data* data) { void* const storage = ((converter::rvalue_from_python_storage<Ptr>*)data)-> storage.bytes; // Deal with the "None" case. if (data->convertible == source) new (storage) Ptr(); // Or whatever you want. else new (storage) Ptr(static_cast<Pointee*>(data->convertible)); data->convertible = storage; } };
On 9/6/06, Alex Mohr <amohr@pixar.com> wrote:
This code allowed the setObject(None) to work now. Unfortunately it broke the functionality that had been working where I could call setObject(PythonObject).
More details (extracted from above):
I have:
// set method void setObject(ptr<Base> obj);
// Implicit conversion for ref_ptr to ptr bp::implicitly_convertible< ref_ptr<Base>, ptr<Base> );
// From original e-mail: this works to automatically convert // ptr<Base> returns into ref_ptr<Base> as the held type bp::to_pyton_converter< ptr<Base>, ptr_to_python< ptr<Base> >()
// Most recent discussion: way to get None from python as ptr<Base> // - This works to let me pass None into wrapped setObject method bp::converter::registry::insert(&convertible, &construct, bp::type_id<ptr<Base> >());
The problem is that now when I try to call something like this in python:
new_obj = objtype.create() setObject(None) setObject(new_obj)
Both of these calls to setObject try to convert the object to a ptr<Base> using the manually registered converter. The converter only really knows how to deal with the conversion from a None type. In the case of a real object I just want boost.python to convert the held type (ref_ptr<Base> ) into a ptr<Base> using the implicit conversion.
How do I get this to work? Can I have multiple converters registered and if so how do I tell boost.python to only use the manually created one for the NoneType?
The rvalue converter should handle both cases. In convertible, it checks if it is getting None -- if it is, it says yes, I can make a pointer. If it gets something other than None, it says I can make a pointer iff I can get an lvalue pointer to the Pointee from the python object. That's the code after the if. It uses already registered lvalue converters to do this.
The construct method has a similar structure -- in the None case (first part of if) it will just placement new a default-constructed pointer. In the else part of the if, it will placement new a pointer pointing to the c++ instance.
Can you tell me what's going on in convertible and construct when you do this?
The problem for me was that the rvalue converter when passed a non-None could not do the placement new created of the ptr<Base> because the constructor is protected. The library I am wrapping requires all objects to be created using static factory methods: ref_ptr<Base> obj = Class.create() So there is something going on in the implicit conversion for the smart pointers that just handles things better. I ended up getting the code to work though by making it so the rvalue convertible method return NULL if p != NonType. This ends up meaning that the rvalue converter is only used in the case of passing None, but it works for me now. Thanks for your help. I will let you know if anything else crops up but I think I am starting to understand this part of boost.python much better now so hopefully I will be able to fix it myself. :) Thanks, Allen
setObject(new_obj)
Alex
template <class Ptr> struct smart_ptr_from_python { typedef typename boost::python::pointee<Ptr>::type Pointee; smart_ptr_from_python() { converter::registry::insert(&convertible, &construct, type_id<Ptr>()); } private: static void *convertible(PyObject *p) { // can always produce a pointer from None. if (p == Py_None) return p; // Otherwise, we can do it if we can get the pointee out. void *result = converter::get_lvalue_from_python (p, converter::registered<Pointee>::converters); return result; }
static void construct(PyObject* source, converter:: rvalue_from_python_stage1_data* data) { void* const storage = ((converter::rvalue_from_python_storage<Ptr>*)data)-> storage.bytes; // Deal with the "None" case. if (data->convertible == source) new (storage) Ptr(); // Or whatever you want. else new (storage) Ptr(static_cast<Pointee*>(data->convertible)); data->convertible = storage; } };
C++-sig mailing list C++-sig@python.org http://mail.python.org/mailman/listinfo/c++-sig
The problem for me was that the rvalue converter when passed a non-None could not do the placement new created of the ptr<Base> because the constructor is protected. The library I am wrapping requires all objects to be created using static factory methods:
ref_ptr<Base> obj = Class.create()
But the converter's not trying to create a new *object* -- just a new smart pointer. If you can write code that obtains a correct new smart pointer from a raw object pointer, then just fill in that code in the right place in the converter and it should work. The only other thing that's required is that the smart pointer objects be copy-constructible, which I would think they ought to be. Otherwise they're very strange.
So there is something going on in the implicit conversion for the smart pointers that just handles things better.
I ended up getting the code to work though by making it so the rvalue convertible method return NULL if p != NonType. This ends up meaning that the rvalue converter is only used in the case of passing None, but it works for me now.
Sure -- that sounds like it will be okay given your situation.
Thanks for your help. I will let you know if anything else crops up but I think I am starting to understand this part of boost.python much better now so hopefully I will be able to fix it myself. :)
No problem. Alex
On 9/6/06, Alex Mohr <amohr@pixar.com> wrote:
The problem for me was that the rvalue converter when passed a non-None could not do the placement new created of the ptr<Base> because the constructor is protected. The library I am wrapping requires all objects to be created using static factory methods:
ref_ptr<Base> obj = Class.create()
But the converter's not trying to create a new *object* -- just a new smart pointer. If you can write code that obtains a correct new smart pointer from a raw object pointer, then just fill in that code in the right place in the converter and it should work.
This is the part I can't do. I can make a ref_ptr<> from a ptr<> as vice versa, but I can't build either of them from a raw pointer. I may take a look at this later and see if I can make it work better so I just have the one converter.
The only other thing that's required is that the smart pointer objects be copy-constructible, which I would think they ought to be. Otherwise they're very strange.
So there is something going on in the implicit conversion for the smart pointers that just handles things better.
I ended up getting the code to work though by making it so the rvalue convertible method return NULL if p != NonType. This ends up meaning that the rvalue converter is only used in the case of passing None, but it works for me now.
Sure -- that sounds like it will be okay given your situation.
I agree. NoneType --> ptr<> was always a special case for me since I want everything held by ref_ptrs all the time anyway. Thanks again. -Allen
Thanks for your help. I will let you know if anything else crops up but I think I am starting to understand this part of boost.python much better now so hopefully I will be able to fix it myself. :)
No problem.
Alex _______________________________________________ C++-sig mailing list C++-sig@python.org http://mail.python.org/mailman/listinfo/c++-sig
Thanks for your help. I will let you know if anything else crops up but I think I am starting to understand this part of boost.python much better now so hopefully I will be able to fix it myself. :)
It looks like everything related to the auto-converting to ref_ptr held type and handling of converting to and from None. I have one remaining problem that I know of with this code. How do I make it possible to compare the wrapped ref_ptrs for equality? I am assuming that I need to add an __eq__ operator for the ref_ptr types but since they are used as held_type I am not really exposing them in the "normal" way. This is how it is behaving right now (note: all the objects here have ref_ptr held type)
n = osg.Node.create() g = osg.Group.create() n.setCore(g) g <osg._osg.Group object at 0xb785fcac> n.getCore() <osg._osg.Group object at 0xb785f16c> g == n.getCore() False
Does anyone know if this is also an issue when wrapping an API and using boost::shared_ptr<> as the held type? If so, how is that case handled? Thanks, Allen
I have one remaining problem that I know of with this code. How do I make it possible to compare the wrapped ref_ptrs for equality? I am assuming that I need to add an __eq__ operator for the ref_ptr types but since they are used as held_type I am not really exposing them in the "normal" way.
Yup -- you'll have to implement __eq__.
<osg._osg.Group object at 0xb785fcac>
n.getCore() <osg._osg.Group object at 0xb785f16c> g == n.getCore() False
Does anyone know if this is also an issue when wrapping an API and using boost::shared_ptr<> as the held type? If so, how is that case handled?
It works sometimes, and not others. As you discovered, boost python will store the python object in a custom shared_ptr deleter. Converting to-python, I believe it will see if the shared_ptr has its custom deleter and will use the python object in there if it is. The problem is that if you're using your own shared_ptrs, then you may pass a shared_ptr to boost python which doesn't have the special custom deleter, and you'll end up with multiple python objects representing the same c++ object. What you really want is to have just a single python object associated with each C++ instance. I call this problem "object identity", and it's been discussed on this list before. It is *possible* to solve this problem with boost python as is, but it requires doing *very* nasty things using internal api. I think the real solution is to extend boost python so that: - You can tell boost python that your c++ object can manage its python identity. - As soon as boost python produces a python object for your c++ object, it calls you to establish the identity. - Whenever (a pointer to) that c++ instance is to be converted to-python, boost python will consult you to find the python object. Incidentally, wrapper<T> already does this stuff mostly right. I hope to make a patch to support this someday, but it probably won't be that soon. Anyway, in the meantime, if you just implement __eq__ in the obvious way, it should work. Alex
On 9/7/06, Alex Mohr <amohr@pixar.com> wrote:
I have one remaining problem that I know of with this code. How do I make it possible to compare the wrapped ref_ptrs for equality? I am assuming that I need to add an __eq__ operator for the ref_ptr types but since they are used as held_type I am not really exposing them in the "normal" way.
Yup -- you'll have to implement __eq__.
<osg._osg.Group object at 0xb785fcac>
n.getCore() <osg._osg.Group object at 0xb785f16c> g == n.getCore() False
Does anyone know if this is also an issue when wrapping an API and using boost::shared_ptr<> as the held type? If so, how is that case handled?
It works sometimes, and not others. As you discovered, boost python will store the python object in a custom shared_ptr deleter. Converting to-python, I believe it will see if the shared_ptr has its custom deleter and will use the python object in there if it is.
The problem is that if you're using your own shared_ptrs, then you may pass a shared_ptr to boost python which doesn't have the special custom deleter, and you'll end up with multiple python objects representing the same c++ object.
What you really want is to have just a single python object associated with each C++ instance.
I call this problem "object identity", and it's been discussed on this list before.
It is *possible* to solve this problem with boost python as is, but it requires doing *very* nasty things using internal api.
I think the real solution is to extend boost python so that:
- You can tell boost python that your c++ object can manage its python identity.
- As soon as boost python produces a python object for your c++ object, it calls you to establish the identity.
- Whenever (a pointer to) that c++ instance is to be converted to-python, boost python will consult you to find the python object.
Incidentally, wrapper<T> already does this stuff mostly right. I hope to make a patch to support this someday, but it probably won't be that soon.
Anyway, in the meantime, if you just implement __eq__ in the obvious way, it should work.
The non-obvious part of this for me is: - Does this mean that I now need to explicitly wrap (expose using bp::class_ ) all the ref_ptr<> types? I haven't had to do that thus far because it was handled internally somehow because I used it has the held_type for all my classes. If I expose them manually do I run the risk of breaking any of the held_type functionality? -Allen
The non-obvious part of this for me is:
- Does this mean that I now need to explicitly wrap (expose using bp::class_ ) all the ref_ptr<> types?
I haven't had to do that thus far because it was handled internally somehow because I used it has the held_type for all my classes. If I expose them manually do I run the risk of breaking any of the held_type functionality?
I'm not sure what you mean... If ref_ptr<T> is the held type for T, then just provide __eq__ in terms of ref_ptr<T>... template <class T> static bool ref_ptr_eq(ref_ptr<T> const &self, ref_ptr<T> const &other) { return self == other; } class_<T, ... ref_ptr<T> ... >(...) .def("__eq__", ref_ptr_eq<T>) ; Am I misunderstanding your situation? Alex
On 9/7/06, Alex Mohr <amohr@pixar.com> wrote:
The non-obvious part of this for me is:
- Does this mean that I now need to explicitly wrap (expose using bp::class_ ) all the ref_ptr<> types?
I haven't had to do that thus far because it was handled internally somehow because I used it has the held_type for all my classes. If I expose them manually do I run the risk of breaking any of the held_type functionality?
I'm not sure what you mean... If ref_ptr<T> is the held type for T, then just provide __eq__ in terms of ref_ptr<T>...
template <class T> static bool ref_ptr_eq(ref_ptr<T> const &self, ref_ptr<T> const &other) { return self == other; }
class_<T, ... ref_ptr<T> ... >(...) .def("__eq__", ref_ptr_eq<T>) ;
Am I misunderstanding your situation?
Ahhh.... No I see what you are meaning. So I need the __eq__ op on the class itself. Ok. That makes more sense. I thought I may have to wrap the ref_ptr<T> as a class a and put the __eq__ op on it. Something like: class_<ref_ptr<T>, ..>(..) .def("__eq__", ref_ptr_eq<T>) ; Thanks for the feedback. I will try your method out and reply to the list with the results so other people following along can see what ended up working. Thanks again, Allen
Alex _______________________________________________ C++-sig mailing list C++-sig@python.org http://mail.python.org/mailman/listinfo/c++-sig
On 9/7/06, Allen Bierbaum <abierbaum@gmail.com> wrote:
On 9/7/06, Alex Mohr <amohr@pixar.com> wrote:
The non-obvious part of this for me is:
- Does this mean that I now need to explicitly wrap (expose using bp::class_ ) all the ref_ptr<> types?
I haven't had to do that thus far because it was handled internally somehow because I used it has the held_type for all my classes. If I expose them manually do I run the risk of breaking any of the held_type functionality?
I'm not sure what you mean... If ref_ptr<T> is the held type for T, then just provide __eq__ in terms of ref_ptr<T>...
template <class T> static bool ref_ptr_eq(ref_ptr<T> const &self, ref_ptr<T> const &other) { return self == other; }
class_<T, ... ref_ptr<T> ... >(...) .def("__eq__", ref_ptr_eq<T>) ;
Am I misunderstanding your situation?
Ahhh....
No I see what you are meaning. So I need the __eq__ op on the class itself. Ok. That makes more sense. I thought I may have to wrap the ref_ptr<T> as a class a and put the __eq__ op on it.
Something like:
class_<ref_ptr<T>, ..>(..) .def("__eq__", ref_ptr_eq<T>) ;
Thanks for the feedback. I will try your method out and reply to the list with the results so other people following along can see what ended up working.
(I apologize for the length of this e-mail, I just want to make sure I describe everything in case there is something basic I am missing) This did not work out well. It still has major problems because: - Object allocated in python, passed to C++, and then returned from C++ can't be compared - Objects can't be compared to None anymore Here is what I have done so far, if anyone has suggestions please let me know (code generated from py++). This is a combination of all the ideas discussed in this thread. You will notice that instead of defining an __eq__ I defined __cmp__ so all the comparison operations would work. typedef ::boost::python::class_< Group_wrapper, ::boost::python::bases< ::osg::GroupBase >, OSG::GroupRefPtr, ::boost::noncopyable > Group_exposer_t; Group_exposer_t Group_exposer = Group_exposer_t( "Group", ::boost::python::no_init ); ... Group_exposer.def("__cmp__",pyopensg::cmp_ref_ptrs<OSG::GroupRefPtr>); Group_exposer.def("__hash__",pyopensg::hash_ref_ptr<OSG::GroupRefPtr>); register_fcptr_to_python<RefPtrType>::execute(); fcptr_from_python<typename RefPtrType::FCPtrType>(); bp::implicitly_convertible< OSG::GroupRefPtr, OSG::GroupPtr >(); bp::implicitly_convertible< OSG::GroupRefPtr, OSG::NodeCoreRefPtr >(); } } // Helpers are attached to the mail There is also a class Node involved in the example code below. In the example I use two methods from there: void Node::setCore(ptr<Base>); ptr<Base> Node::getCore(); Here is some code that shows the problems
g = osg.Group.create() g2 = osg.Group.create() g3 = g g == g2 False g == g3 True hash(g) 163 hash(g2) 164 n = osg.Node.create() n.setCore(g) core_g = n.getCore() core_g <osg._osg.Group object at 0xafbf722c> g <osg._osg.Group object at 0xb781fdac>
// Next line shows problem where the object coming back // out of C++ can't be compared. It is like it thinks it has // a different type.
g == core_g Traceback (most recent call last): File "<input>", line 1, in ? ArgumentError: Python argument types in Group.__cmp__(Group, Group) did not match C++ signature: __cmp__(osg::RefPtr<osg::FCPtr<osg::FCPtr<osg::AttachmentContainerPtr, osg::NodeCore>, osg::Group> >, osg::RefPtr<osg::FCPtr<osg::FCPtr<osg::AttachmentContainerPtr, osg::NodeCore>, osg::Group> >)
// This line shows the same problem with __hash__
hash(core_g) Traceback (most recent call last): File "<input>", line 1, in ? ArgumentError: Python argument types in Group.__hash__(Group) did not match C++ signature: __hash__(osg::RefPtr<osg::FCPtr<osg::FCPtr<osg::AttachmentContainerPtr, osg::NodeCore>, osg::Group> >)
// And here is the problem comparing to None
g == None Traceback (most recent call last): File "<input>", line 1, in ? ArgumentError: Python argument types in Group.__cmp__(Group, NoneType) did not match C++ signature: __cmp__(osg::RefPtr<osg::FCPtr<osg::FCPtr<osg::AttachmentContainerPtr, osg::NodeCore>, osg::Group> >, osg::RefPtr<osg::FCPtr<osg::FCPtr<osg::AttachmentContainerPtr, osg::NodeCore>, osg::Group> >)
I am at a loss to see what is causing this signature problem. I don't know of any way to ask boost.python to give me the "signature" of python object at run-time, so I can't compare this signature with the "good" signatures that work above. From what I see though they look very much the same. I am going to try to trace back through the mailing list and look for the object identity discussions you mentioned. Maybe something in those threads will provide a workaround that I can use. As always, feel free to tell me if something looks way off. Thanks, Allen
I don't have time to do a complete analysis right now, but maybe I can help understand these signatures: "Allen Bierbaum" <abierbaum@gmail.com> writes:
// Next line shows problem where the object coming back // out of C++ can't be compared. It is like it thinks it has // a different type.
g == core_g Traceback (most recent call last): File "<input>", line 1, in ? ArgumentError: Python argument types in Group.__cmp__(Group, Group) did not match C++ signature: __cmp__(osg::RefPtr<osg::FCPtr<osg::FCPtr<osg::AttachmentContainerPtr, osg::NodeCore>, osg::Group> >, osg::RefPtr<osg::FCPtr<osg::FCPtr<osg::AttachmentContainerPtr, osg::NodeCore>, osg::Group> >)
It's not that the Python types are different (both are Group). It's much more likely that they have a different way of holding their internal C++ osg::Group object such that one can be converted to that RefPtr type and the other can't. What can be converted is up to the converters you've registered.
// This line shows the same problem with __hash__
hash(core_g) Traceback (most recent call last): File "<input>", line 1, in ? ArgumentError: Python argument types in Group.__hash__(Group) did not match C++ signature: __hash__(osg::RefPtr<osg::FCPtr<osg::FCPtr<osg::AttachmentContainerPtr, osg::NodeCore>, osg::Group> >)
// And here is the problem comparing to None
g == None Traceback (most recent call last): File "<input>", line 1, in ? ArgumentError: Python argument types in Group.__cmp__(Group, NoneType) did not match C++ signature: __cmp__(osg::RefPtr<osg::FCPtr<osg::FCPtr<osg::AttachmentContainerPtr, osg::NodeCore>, osg::Group> >, osg::RefPtr<osg::FCPtr<osg::FCPtr<osg::AttachmentContainerPtr, osg::NodeCore>, osg::Group> >)
This could be a case of the first (self) object not being convertible to the RefPtr. -- Dave Abrahams Boost Consulting www.boost-consulting.com
participants (3)
-
Alex Mohr -
Allen Bierbaum -
David Abrahams