Iterating over CxxWrap-ed vector

#1

I’m experimenting with CxxWrap, and within my C++ code, I’m defining a class called Point2D. One of the things I want to do is to wrap a std::vector and then iterate over it on the Julia side. In my C++, I’ve got code that looks like:


    JLCXX_MODULE define_point2d_module(jlcxx::Module& types) {
        .
        .
        .
        types.add_type("PointVec")
             .method("length", &PointVec::size)
             .method("getindex", [][const PointVec& vec, size_t index) {
                                                 return vec.at(index);
                                             });
         .
         .
         .
     }

What is mysterious to me is how to implement a Julia iterator over my PointVec type. I’ve looked at descriptions of how to write Julia iterators, but it’s not at all clear how I’d connect my PointVec methods.

0 Likes

#2

I’ve implemented something similar in my package. Maybe code like this can help:

0 Likes

#3

Thank you! This definitely looks promising. I’m experimenting in the REPL, and get a Julia error when I try to iterate over one of my PointVec objects:


ERROR: MethodError: no method matching size(::Main.Point2DWrapper.PointVecAllocated)

I’m using Julia 1.1.0, if that is relevant. Is there some additional export I need to do to bring “size” (and presumably “at”) into the current namespace?

0 Likes

#4

size and at are the names on the C++ side.
Your code already translates that correctly to the Julia nomenclature length and getindex, so you want to use those names.

In other words, I think you only need lines 39 and 40 above, and you can ignore the rest.
(And you’ll want to replace StdVecs with PointVec)

0 Likes

#5

Actually, I’ve got another vector type, so I did find it useful to mimic your StdVecs approach


import Base: getindex, length, convert, iterate

const StdVecs = Union{PointVec, PolygonVec}
iterate(it::StdVecs) = length(it) > 0 ? (it[1], 2) : nothing
iterate(it::StdVecs, i) = i <= length(it) ? (it[i], i+1) : nothing
length(it::StdVecs) = size(it)
getindex(it::StdVecs, i) = at(it, convert(Uint64, i - 1))
eltype(::Type{PointVec}) = Point2D
eltype(::Type{PolygonVec}) = Polygon2D

In case this provides a clue, here’s what I’m doing to import the CxxWrap types:


module Scene

module Point2DWrapper
using CxxWrap
@wrapmodule("libboost_wrap", :define_point2d_module)
function __init__()
    @initcxx
end
export Point2D, PointVec, getx, gety, size, at
end
using Scene.Point2DWrapper

I’ve tried both explicitly exporting ‘size’ and ‘at’, and leaving them out of the export statement, which is why I was referring to “current namespace” in my previous post.
Thanks again for your help with this.

0 Likes

#6

I don’t think exporting is your problem. Your code maps the C++ size to Julia’s length.
size is just not a known name on the Julia side.
Just leave your C++ code as is and get rid of size and at on the Julia side completely.

0 Likes

#7

In other words, remove these two lines

0 Likes

#8

That doesn’t seem to do it. The error message I get is now:


ERROR: LoadError: MethodError: no method matching length(::Main.Point2DWrapper.PointVecAllocated)

I’m guessing that the relationship between “PointVecAllocated” and “PointVec” needs to be made explicit.

0 Likes

#9

I’ve streamlined my example a bit, and am getting results:


module BoostWrapper
using CxxWrap
@wrapmodule("libboost_wrap")
function __init__()
    @initcxx
end
export Point2D, PointVec, getx, gety,
    Polygon2D, PolygonVec, add_vertex, scale_polygon, get_vertices,
    poly_intersection, intersection_point
end
using Main.BoostWrapper

import Base: getindex, length, convert, iterate, size

iterate(it::PointVec) = length(it) > 0 ? (it[1], 2) : nothing
iterate(it::PointVec, i) = i <= length(it) ? (it[i], i+1) : nothing
length(it::PointVec) = Main.BoostWrapper.size(it)
getindex(it::PointVec, i) = Main.BoostWrapper.at(it, convert(UInt64, i - 1))

eltype(::Type{PointVec}) = Point2D

p1 = Point2D(-10.0, 10.0)
p2 = Point2D(10.0, 10.0)
p3 = Point2D(10.0, -10.0)
p4 = Point2D(-10.0, -10.0)
obstacle = Polygon2D()
add_vertex(obstacle, p1)
add_vertex(obstacle, p2)
add_vertex(obstacle, p3)
add_vertex(obstacle, p4)

pts = get_vertices(obstacle)

for pt in pts
    println("current pt: ", getx(pt), ", ", gety(pt))
end

I’m not sure what will happen when I add the other vector type; I may resort to duplicating the iterator code, with unique names for the “at” and “size” methods for the different vectors.

Future me chiming in: I didn’t need to disambiguate “at” and “size” for the different wrapped vectors.

0 Likes

#10

You can make the vector a parametric type, that should avoid the code duplication. The parametric example should show how to do this.

For what it’s worth, built-in support for std::vector (and others) is on my todo list, but unfortunately my time for working on it has been limited lately.

1 Like

#11

Thanks for the tip - I’ll have a look. For the moment, the code duplication burden isn’t too serious.

0 Likes

#12

Can’t wait to see the built-in support :heart: :heart: :heart:

1 Like