EDIT 2: Following your suggestion in the comments below, the original code now works out-of-the-box with cppyy release 1.6.2. That is, if the Python proxy contains a pointer type, then indexing is taken to mean it represents an array.
As-is Foo*
is taken as a pointer to Foo
, not to an array of Foo
. Yes, it could point to an array, but low-level C++ (C, really) is ambiguous and it being a pointer to an object is by far the common case. If something is an array, array syntax does work, as it becomes unambiguous again:
import cppyy
cppyy.cppdef("""
struct Foo
{
float var;
};
struct Bar
{
Foo foo[1];
};
""")
bar = cppyy.gbl.Bar()
print(len(bar.foo))
print(bar.foo[0])
which prints:
1
<cppyy.gbl.Foo object at 0x7f85ace370f0>
as expected.
If you don't know the length of the array, is there any option that you can use modern C++ constructs such as std::unique_ptr instead of heritage C? Those are completely unambiguous and thus easily automatically bound (they also make memory management a lot easier):
import cppyy
cppyy.cppdef("""
struct Foo
{
float var;
};
struct Bar
{
Bar(int num_foo) : foo(std::unique_ptr<Foo[]>{new Foo[num_foo]}) {
for (int i = 0; i < num_foo; ++i)
foo[i].var = (float)2.*i;
}
std::unique_ptr<Foo[]> foo;
};
""")
num_foo = 4
bar = cppyy.gbl.Bar(num_foo)
for i in range(num_foo):
print(bar.foo[i].var)
which prints the expected:
0.0
2.0
4.0
6.0
If you really are in the unenviable position of maintaining legacy code, I recommend patching things up a little with C++ (through the JIT with cppdef) and Python helpers (more details on pythonization here: https://cppyy.readthedocs.io/en/latest/pythonizations.html). For example:
import cppyy
cppyy.cppdef("""
struct Foo
{
float var;
};
struct Bar
{
Bar() : foo(new Foo{}) { foo[0].var = 42.f; }
// memory mgmt here ...
Foo* foo;
};
Foo* Bar_get_foo_indexed(Bar* b, int idx) {
return &b->foo[idx];
}
""")
# pythonize the getter
class FooGetter(object):
def __get__(self, obj, kls=None):
self.obj = obj
return self
def __getitem__(self, idx):
return cppyy.gbl.Bar_get_foo_indexed(self.obj, idx)
cppyy.gbl.Bar.foo = FooGetter()
bar = cppyy.gbl.Bar()
print(bar.foo[0].var)
which prints the expected:
42.0
EDIT: some more ideas based on your question. First, a casting example on the C++ side:
cppyy.cppdef("""
struct Foo
{
float var;
};
struct Bar
{
Bar() : foo(new Foo[2]) { foo[0].var = 42.f; foo[1].var = 13.f; }
// memory mgmt here ...
Foo* foo;
};
template<int N>
struct FooArrayWrapper {
Foo foo[N];
};
template<int N>
FooArrayWrapper<N>* cast_foo_array(Foo*& f) {
return reinterpret_cast<FooArrayWrapper<N>*>(f);
}
""")
def make_foo_wrapper_init(func):
def wrapper_init(self, foo_size, *args, **kwargs):
func(self)
self.__dict__['foo_array'] = cppyy.gbl.cast_foo_array[foo_size](self.foo).foo
return wrapper_init
cppyy.gbl.Bar.__init__ = make_foo_wrapper_init(cppyy.gbl.Bar.__init__)
bar = cppyy.gbl.Bar(2)
print(bar.foo_array[0].var)
print(bar.foo_array[1].var)
and a casting example using pointer arithmetic on the Python side:
cppyy.cppdef("""
struct Foo
{
float var;
};
struct Bar
{
Bar() : foo(new Foo[2]) { foo[0].var = 42.f; foo[1].var = 13.f; }
// memory mgmt here ...
Foo* foo;
};
""")
bar = cppyy.gbl.Bar()
for i in range(2):
print(cppyy.bind_object(cppyy.addressof(bar.foo)+i*cppyy.sizeof(cppyy.gbl.Foo), cppyy.gbl.Foo).var)
both print the expected:
42.0
13.0