Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failure to bind vector of smart pointers #21

Open
grayj2 opened this issue Oct 21, 2024 · 3 comments
Open

Failure to bind vector of smart pointers #21

grayj2 opened this issue Oct 21, 2024 · 3 comments
Labels

Comments

@grayj2
Copy link

grayj2 commented Oct 21, 2024

Starting to use litgen to make bindings and ran into an issue when using vectors of smart points. litgen does not seem to interpret vectors of smart pointers correctly.

example.h

#include <vector>

class ClassA {};

class ClassB {
  public:
    std::vector<std::shared_ptr<ClassA>> vec_shared_ptrs;
    std::vector<std::unique_ptr<ClassA>> vec_unique_ptrs;
    std::vector<ClassA> vec;
    std::shared_ptr<ClassA> shared_ptr;
    std::unique_ptr<ClassA> unique_ptr;
};

resulting stubs with no options specified which are obviously wrong and do not throw any errors when generating other than black formatting errors.

class ClassA:
    def __init__(self) -> None:
        """Autogenerated default constructor"""
        pass

class ClassB:
    vec_shared_ptrs: std.vector<ClassA>
    vec_unique_ptrs: std.vector<ClassA>
    vec: List[ClassA]
    shared_ptr: ClassA
    unique_ptr: ClassA
    def __init__(self) -> None:
        """Autogenerated default constructor"""
        pass

I was able to get around this and get something more expected using the following type replacement option and regex

    options = litgen.LitgenOptions()
    options.type_replacements.add_first_replacement(r"std::vector<std::(?:unique_ptr|shared_ptr)<(.*)>>", r"std::vector<\1>")

This results in stubs looking like this

class ClassA:
    def __init__(self) -> None:
        """Autogenerated default constructor"""
        pass

class ClassB:
    vec_shared_ptrs: List[ClassA]
    vec_unique_ptrs: List[ClassA]
    vec: List[ClassA]
    shared_ptr: ClassA
    unique_ptr: ClassA
    def __init__(self) -> None:
        """Autogenerated default constructor"""
        pass

Im not sure if this is best approach or just the naive solution. Considering how smart pointers of a class and a vector of classes gets bound, I would expect to get this result. Would appreciate your insight.

@grayj2 grayj2 changed the title Failure to bind smart pointers Failure to bind vector of smart pointers Oct 21, 2024
@pthom
Copy link
Owner

pthom commented Oct 22, 2024

Binding smart pointers with pydind11 is complex.

Concerning the stubs, your solution was a step in the right direction.
I fixed it inside cpp_to_python, by applying the changes to unique_ptr and shared_ptr before vector:

\bstd::unique_ptr<(.*?)> -> \1
\bstd::shared_ptr<(.*?)> -> \1
\bstd::vector\s*<\s*([\w:]*)\s*> -> List[\1]
\bstd::array\s*<\s*([\w:]*)\s*,\s*([\w:]*)\s*> -> List[\1]

Now, concerning your usage:

  • publishing a member under the form of unique_ptr (or a vector<unique_ptr> might lead to multiple issues (as copying it as reference inside python is prohibited)
  • it works for shared_ptr. However, you need to change the holder type.

For an example, see this integration test:

https://github.com/pthom/litgen/blob/main/src/litgen/integration_tests/mylib/smart_ptr.h

And its bindings:

https://github.com/pthom/litgen/blob/main/src/litgen/integration_tests/mylib/smart_ptr.h.pyi

Where I had to specify the holder type for SmartElem

# For smart_ptr_test: SmartElem will be held in (vector of) shared_ptr
options.class_held_as_shared__regex = "^SmartElem$"

Read here the doc for class_held_as_shared__regex:

# class_held_as_shared__regex:
# Regex specifying the list of class names that should be held using std::shared_ptr in the generated bindings.
#
# **Purpose:**
# By default, pybind11 uses `std::unique_ptr` as the holder type for bound classes.
#
# **When to Use:**
# If your C++ code uses `std::shared_ptr` to manage instances of a class (e.g., as member variables, return types,
# or parameters), and you expose that class to Python, you need to ensure that pybind11 uses `std::shared_ptr` as
# the holder type for that class.
#
# **References:**
# - [pybind11 Documentation: Smart Pointers](https://pybind11.readthedocs.io/en/stable/advanced/smart_ptrs.html)
# - [Understanding Holder Types in pybind11](https://pybind11.readthedocs.io/en/stable/advanced/classes.html#custom-smart-pointers)
class_held_as_shared__regex: str = ""

@pthom
Copy link
Owner

pthom commented Oct 22, 2024

Note: you will need to update litgen to the latest commit

@grayj2
Copy link
Author

grayj2 commented Oct 24, 2024

Thank you for the updates! This did the trick.

Understood about the caveats on smart pointers. Mainly just tinkering at this point to understand how to use litgen. So far it seems pretty straight forward.

@pthom pthom added the faq label Oct 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants