Closed torokati44 closed 1 year ago
That's an issue unique to global variable pointers: the wrapper proxy is a to an A**
(that is, &a
) to ensure that updates to the global pointer on the C++ side are visible on the Python side, and an A**
isn't related to a B**
through inheritance (offsets may change if a different derived type is set on the base pointer).
Probably can be improved by checking on access for possible auto downcast, rather than on bind as is currently the case (more efficient otherwise).
As for dynamic_cast
, it does exist:
import cppyy.ll
# ...
cppyy.ll.dynamic_cast[cppyy.gbl.B](cppyy.gbl.a).helloB()
which is functional, but not particularly efficient.
That's an issue unique to global variable pointers:
Ah, I did not know that. And indeed, switching to this now works fine:
import cppyy
cppyy.cppdef(
"""
// [...]
A *makeAB() { return new B(); }
""")
cppyy.gbl.makeAB().helloB()
Probably can be improved by checking on access for possible auto downcast, rather than on bind as is currently the case
Yes, that is what I would have expected. Although it looks like having this might not actually be important for me, because I only used a global variable in this example snippet, in my actual code I get the wrapper to it like this: cppyy.bind_object(objectAddress, "ns::type::name")
.
As for
dynamic_cast
, it does exist
Oh, I wasn't aware, thank you! I tried looking for something like this in the documentation: https://cppyy.readthedocs.io/en/latest/search.html?q=dynamic_cast&check_keywords=yes&area=default
But the only result for it was something like: _... There should never be a reason for a dynamic_cast
, since that only applies to objects, for which auto-casting ..._
Fortunately I already have access to the actual runtime type name of the objects I'm interested in downcasting right now (via demangling std::type_info::name
), so this will probably work for me.
I still don't know how to call cppyy.ll.dynamic_cast
properly.
The problem is, that I'm actually getting the object to downcast via its pointer from C++, passed across the language boundary as a simple long
/int
.
So, trying this:
import cppyy
import cppyy.ll
cppyy.cppdef(
"""
// [...]
intptr_t makeAB() {
A *a = new B();
return (intptr_t)reinterpret_cast<void*>(a);
}
""")
a = cppyy.bind_object(cppyy.gbl.makeAB(), "A")
cppyy.ll.dynamic_cast["B"](a).helloB()
I'm getting this error:
TypeError: Could not find "cppyy_dynamic_cast<B>" (set cppyy.set_debug() for C++ errors):
Failed to instantiate "cppyy_dynamic_cast<B>(A&)"
And a really similar one if I'm trying to cast to "B *"
.
It seems that the wrapper automatically removes the pointer's layer of indirection right when I call bind_object
, so dynamic_cast
can't work, as a
is now a reference, not a pointer. And I also can't use ll.addressof
to add back the indirection, as that returns a regular int
, not a wrapped pointer.
Could you perhaps guide me further on how to downcast in this case?
Sorry, didn't see the update. That said, cppyy.ll.dynamic_cast["B"](cppyy.gbl.a).helloB()
(the original problem) works fine for me. So the issue is only if return as an intptr_t
and use of bind_object
? Yes, bind_object
does not do an auto-cast, but it will do offset calculations if requested and actual differ, as the intend is taken to bind to the type given.
Rather, do:
a = cppyy.ll.reinterpret_cast["A*"](cppyy.gbl.makeAB())
which (perhaps counter-intuitively) will do the auto-downcast on return.
The line above gave me this error:
TypeError: Could not find "cppyy_reinterpret_cast<A*>" (set cppyy.set_debug() for C++ errors):
Failed to instantiate "cppyy_reinterpret_cast<A*>(B&)"
I found no way to refer to this object from Python as it's pointer, except for this:
b = cppyy.gbl.makeAB()
a = cppyy.ll.reinterpret_cast["A*"](cppyy.ll.addressof(b))
And even after this, helloB
is only in 'a' if the definition of B
is presented as source to cppyy
before the binding. This is completely understandable, however, fairy unfortunate.
I don't understand ... makeAB()
in the code above returns intptr_t
:
intptr_t makeAB() {
A *a = new B();
return (intptr_t)reinterpret_cast<void*>(a);
}
so it should not be possible for cppyy.ll.reinterpret_cast["A*"](cppyy.gbl.makeAB())
to try to instantiate cppyy_reinterpret_cast<A*>(B&)
since there is no B
involved anywhere?
Code below is a full example that works for me (TM):
import cppyy
import cppyy.ll
cppyy.cppdef(
"""
#include <iostream>
class A {
public:
void helloA() { std::cout << "A" << std::endl; }
virtual ~A() {} // <-- needed for the dynamic_cast in C++, but not enough for Cppyy - at least it causes a different error message
};
class B : public A {
public:
void helloB() { std::cout << "B" << std::endl; }
};
intptr_t makeAB() {
A *a = new B();
return (intptr_t)reinterpret_cast<void*>(a);
}
""")
a = cppyy.ll.reinterpret_cast["A*"](cppyy.gbl.makeAB())
print(a)
a.helloB()
And yes, for an auto-downcast to work, the type of B
needs to be known a priori. It's not possible to work in any other way and still be useful (opaque pointers are supported, but won't allow calling methods such as helloB()
).
Ah, yes, the key is this: intptr_t makeAB()
. When I already have a wrapped A
object (because I return a typed pointer from makeAB
instead of an integer address from C++), I have to use addressof
.
Closing as presumed clarified.
In a simple 2-level class hierarchy, I'm trying to call a method defined on the subclass, through a superclass pointer to an instance of the subclass
Consider this snippet:
I thought any wrapped object automatically acts as the most specialized class it is, regardless of the type of the pointer that was used to access this. How can I
dynamic_cast
the object to its most specialized type in Python?