wlav / cppyy

Other
387 stars 39 forks source link

Python function returning an instance of STL container cannot be wrapped in `std::function` #151

Open dyershov opened 1 year ago

dyershov commented 1 year ago

First of all, thanks for the great project!

I'm having an issue passing python function as std::function<std::vector<int>(std::vector<int>)> For example:

import cppyy
import cppyy.gbl as cpp

cppyy.cppdef(r"""\
int apply_transform(int value, const std::function<int(int)>& transform) { return transform(value); }
"""
)

def add(value : 'int') -> 'int':
    return value + 1

print("transformed 1 is {}".format(cpp.apply_transform(1, add_one)))

The code above succeeds with printing transformed 1 is 2.

However, if I modify function types to be STL container, e.g. std::vector, as follows:

import cppyy
import cppyy.gbl as cpp

cppyy.cppdef(r"""\
std::vector<int> apply_transform(std::vector<int> value, const std::function<std::vector<int>(std::vector<int>)>& transform) { return transform(value); }
"""
)

vector_int = cpp.std.vector[int]

def add_one(value : vector_int) -> vector_int:
    return vector_int([v+1 for v in value])

value = vector_int([2, 3])
print("transformed {} is {}".format(value, cpp.apply_transform(value, add_one)))

then I get an error:

Traceback (most recent call last):
  File "/home/yershov/SynologyDrive/code/emp/examples/function_test.py", line 15, in <module>
    print("transformed {} is {}".format(value, cpp.apply_transform(value, add_one)))
NotImplementedError: std::vector<int> ::apply_transform(std::vector<int> value, const std::function<std::vector<int>(std::vector<int,std::allocator<int> >)>& transform) =>
    NotImplementedError: could not convert argument 2 (this method cannot (yet) be called)

I experimented with std::array and other STL containers to no avail.

Currently, I'm on cppyy==2.4.1

wlav commented 1 year ago

For some reason the typedef resolution when applied to std::function<std::vector<int>(std::vector<int>)> produces complete gobbledygook. Then, the resulting type can no longer be decomposed it the parts necessary to wrap up the Python callable and the result is an argument conversion failure. :/

dyershov commented 1 year ago

Thanks @wlav . Does that mean, it's not possible (yet)?

dyershov commented 1 year ago

@wlav I found a solution. Clunky, but works:

import cppyy
import cppyy.gbl as cpp

cppyy.cppdef(r"""\
template<typename R, typename... Args>
struct Function {
    virtual ~Function() = default;

    virtual R run(Args... args) const = 0;
};

std::vector<int> apply_transform(std::vector<int> value, const Function<std::vector<int>, std::vector<int>>& transform) { return transform.run(value); }
"""
)

vector_int = cpp.std.vector[int]

class AddOne(cpp.Function[vector_int, vector_int]):
    def run(self, value: vector_int) -> vector_int:
        return vector_int([v+1 for v in value])

add_one = AddOne()

value = vector_int([2, 3])
print("transformed {} is {}".format(value, cpp.apply_transform(value, add_one)))
wlav commented 1 year ago

Yes, I was thinking along similar lines. The following exemplifies a similar workaround that can be removed once cppyy is fixed:

import cppyy
import cppyy.gbl as cpp

cppyy.cppdef(r"""\
std::vector<int> apply_transform(std::vector<int> value, const std::function<std::vector<int>(std::vector<int>)>& transform) {
    return transform(value);
}

template<class T>
T apply_transform_workaround(T value, T(*transform)(T)) {
    return apply_transform(value, transform);
}
""")

# temp workaround --
cppyy.gbl.apply_transform = cppyy.gbl.apply_transform_workaround
# -- temp workaround

# further code unaffected ...
vector_int = cpp.std.vector[int]

def add_one(value : vector_int) -> vector_int:
    return vector_int([v+1 for v in value])

value = vector_int([2, 3])
print("transformed {} is {}".format(value, cpp.apply_transform(value, add_one)))
dyershov commented 1 year ago

That's great. I didn't know that raw function pointers are not susceptible to this problem. Thanks!