Skip to content

Commit

Permalink
Add class_held_as_shared__regex / handle shared_ptr holder for classes
Browse files Browse the repository at this point in the history
cpp_to_python: standard_type_replacements, smart_ptr before vector
  • Loading branch information
pthom committed Oct 22, 2024
1 parent 5510d2a commit 5c0740d
Show file tree
Hide file tree
Showing 13 changed files with 294 additions and 5 deletions.
3 changes: 3 additions & 0 deletions src/litgen/integration_tests/autogenerate_mylib.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ def mylib_litgen_options() -> litgen.LitgenOptions:
options.class_exclude_by_name__regex = "Detail$"
options.enum_exclude_by_name__regex = "Detail$"

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

# Python modifiable immutables options
options.fn_params_replace_modifiable_immutable_by_boxed__regex = code_utils.join_string_by_pipe_char(
[
Expand Down
27 changes: 25 additions & 2 deletions src/litgen/integration_tests/bindings/lg_mylib/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -889,10 +889,33 @@ FLOAT = 3.14
# //////////////////////////////////////////////////////////////////////////////////////////////////////////////

# ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
# mylib/mylib_main/mylib.h continued //
# mylib/smart_ptr.h included by mylib/mylib_main/mylib.h //
# //////////////////////////////////////////////////////////////////////////////////////////////////////////////

##include "mylib/smart_ptr.h"
class SmartElem:
x: int = 0
def __init__(self, x: int = 0) -> None:
"""Auto-generated default constructor with named params"""
pass

def make_shared_elem(x: int) -> SmartElem:
pass

class ElemContainer:
def __init__(self) -> None:
pass
vec: List[SmartElem]
shared_ptr: SmartElem
vec_shared_ptrs: List[SmartElem]

# The signature below is incompatible with pybind11:
# None change_unique_elem(std::unique_ptr<Elem>& elem, int x) { ... }
# Reason: such a signature might change the pointer value! Example:
# None reset_unique_elem(std::unique_ptr<Elem>& elem) { elem.reset(new Elem()); }

# ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
# mylib/mylib_main/mylib.h continued //
# //////////////////////////////////////////////////////////////////////////////////////////////////////////////

# brace_init_default_value.h must be included last (see explanation inside test_change_decl_stmt_to_function_decl_if_suspicious)

Expand Down
30 changes: 30 additions & 0 deletions src/litgen/integration_tests/bindings/pybind_mylib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,36 @@ void py_init_module_lg_mylib(py::module& m)
//


auto pyClassSmartElem =
py::class_<SmartElem, std::shared_ptr<SmartElem>>
(m, "SmartElem", "")
.def(py::init<>([](
int x = 0)
{
auto r = std::make_unique<SmartElem>();
r->x = x;
return r;
})
, py::arg("x") = 0
)
.def_readwrite("x", &SmartElem::x, "")
;


m.def("make_shared_elem",
make_shared_elem, py::arg("x"));


auto pyClassElemContainer =
py::class_<ElemContainer>
(m, "ElemContainer", "")
.def(py::init<>())
.def_readwrite("vec", &ElemContainer::vec, "")
.def_readwrite("shared_ptr", &ElemContainer::shared_ptr, "")
.def_readwrite("vec_shared_ptrs", &ElemContainer::vec_shared_ptrs, "")
;


auto pyClassFooBrace =
py::class_<FooBrace>
(m, "FooBrace", "")
Expand Down
1 change: 1 addition & 0 deletions src/litgen/integration_tests/mylib/mylib_main/mylib.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "mylib/template_class_test.h"
#include "mylib/c_extern_c.h"
#include "mylib/class_default_ctor_test.h"
#include "mylib/smart_ptr.h"

// brace_init_default_value.h must be included last (see explanation inside test_change_decl_stmt_to_function_decl_if_suspicious)
#include "mylib/brace_init_default_value.h"
Expand Down
37 changes: 37 additions & 0 deletions src/litgen/integration_tests/mylib/smart_ptr.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include "api_marker.h"
#include <memory>
#include <vector>

struct SmartElem {
int x = 0;
};


MY_API inline std::shared_ptr<SmartElem> make_shared_elem(int x)
{
auto r = std::make_shared<SmartElem>();
r->x = x;
return r;
}


class ElemContainer
{
public:
ElemContainer():
vec { {1}, {2}},
shared_ptr(make_shared_elem(3)),
vec_shared_ptrs { make_shared_elem(4), make_shared_elem(5) }
{
}

std::vector<SmartElem> vec;
std::shared_ptr<SmartElem> shared_ptr;
std::vector<std::shared_ptr<SmartElem>> vec_shared_ptrs;
};


// The signature below is incompatible with pybind11:
// void change_unique_elem(std::unique_ptr<Elem>& elem, int x) { ... }
// Reason: such a signature might change the pointer value! Example:
// void reset_unique_elem(std::unique_ptr<Elem>& elem) { elem.reset(new Elem()); }
57 changes: 57 additions & 0 deletions src/litgen/integration_tests/mylib/smart_ptr.h.pydef.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// ============================================================================
// This file was autogenerated
// It is presented side to side with its source: smart_ptr.h
// It is not used in the compilation
// (see integration_tests/bindings/pybind_mylib.cpp which contains the full binding
// code, including this code)
// ============================================================================

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/functional.h>
#include <pybind11/numpy.h>
#include "mylib_main/mylib.h"

namespace py = pybind11;

// <litgen_glue_code> // Autogenerated code below! Do not edit!

// </litgen_glue_code> // Autogenerated code end


void py_init_module_mylib(py::module& m)
{
// <litgen_pydef> // Autogenerated code below! Do not edit!
//////////////////// <generated_from:smart_ptr.h> ////////////////////
auto pyClassSmartElem =
py::class_<SmartElem, std::shared_ptr<SmartElem>>
(m, "SmartElem", "")
.def(py::init<>([](
int x = 0)
{
auto r = std::make_unique<SmartElem>();
r->x = x;
return r;
})
, py::arg("x") = 0
)
.def_readwrite("x", &SmartElem::x, "")
;


m.def("make_shared_elem",
make_shared_elem, py::arg("x"));


auto pyClassElemContainer =
py::class_<ElemContainer>
(m, "ElemContainer", "")
.def(py::init<>())
.def_readwrite("vec", &ElemContainer::vec, "")
.def_readwrite("shared_ptr", &ElemContainer::shared_ptr, "")
.def_readwrite("vec_shared_ptrs", &ElemContainer::vec_shared_ptrs, "")
;
//////////////////// </generated_from:smart_ptr.h> ////////////////////

// </litgen_pydef> // Autogenerated code end
}
36 changes: 36 additions & 0 deletions src/litgen/integration_tests/mylib/smart_ptr.h.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# ============================================================================
# This file was autogenerated
# It is presented side to side with its source: smart_ptr.h
# (see integration_tests/bindings/lg_mylib/__init__pyi which contains the full
# stub code, including this code)
# ============================================================================
from typing import List

# type: ignore

# <litgen_stub> // Autogenerated code below! Do not edit!
#################### <generated_from:smart_ptr.h> ####################

class SmartElem:
x: int = 0
def __init__(self, x: int = 0) -> None:
"""Auto-generated default constructor with named params"""
pass

def make_shared_elem(x: int) -> SmartElem:
pass

class ElemContainer:
def __init__(self) -> None:
pass
vec: List[SmartElem]
shared_ptr: SmartElem
vec_shared_ptrs: List[SmartElem]

# The signature below is incompatible with pybind11:
# None change_unique_elem(std::unique_ptr<Elem>& elem, int x) { ... }
# Reason: such a signature might change the pointer value! Example:
# None reset_unique_elem(std::unique_ptr<Elem>& elem) { elem.reset(new Elem()); }
#################### </generated_from:smart_ptr.h> ####################

# </litgen_stub> // Autogenerated code end!
19 changes: 19 additions & 0 deletions src/litgen/integration_tests/mylib/smart_ptr_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import lg_mylib


def test_make_shared_elem():
elem = lg_mylib.make_shared_elem(3)
assert elem.x == 3


def test_elem_container():
container = lg_mylib.ElemContainer()

assert len(container.vec) == 2
assert container.vec[0].x == 1

assert container.shared_ptr is not None
assert container.shared_ptr.x == 3

assert len(container.vec_shared_ptrs) == 2
assert container.vec_shared_ptrs[0].x == 4
Original file line number Diff line number Diff line change
Expand Up @@ -1226,6 +1226,51 @@ namespace A

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// mylib/smart_ptr.h included by mylib/mylib_main/mylib.h //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////

struct SmartElem {
int x = 0;
};


MY_API inline std::shared_ptr<SmartElem> make_shared_elem(int x)
{
auto r = std::make_shared<SmartElem>();
r->x = x;
return r;
}


class ElemContainer
{
public:
ElemContainer():
vec { {1}, {2}},
shared_ptr(make_shared_elem(3)),
vec_shared_ptrs { make_shared_elem(4), make_shared_elem(5) }
{
}

std::vector<SmartElem> vec;
std::shared_ptr<SmartElem> shared_ptr;
std::vector<std::shared_ptr<SmartElem>> vec_shared_ptrs;
};


// The signature below is incompatible with pybind11:
// void change_unique_elem(std::unique_ptr<Elem>& elem, int x) { ... }
// Reason: such a signature might change the pointer value! Example:
// void reset_unique_elem(std::unique_ptr<Elem>& elem) { elem.reset(new Elem()); }


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// mylib/mylib_main/mylib.h continued //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// brace_init_default_value.h must be included last (see explanation inside test_change_decl_stmt_to_function_decl_if_suspicious)

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// mylib/brace_init_default_value.h included by mylib/mylib_main/mylib.h //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
7 changes: 6 additions & 1 deletion src/litgen/internal/adapted_types/adapted_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ def make_pyclass_creation_code() -> str:
code_template = code_utils.unindent_code(
"""
auto {pydef_class_var} =
{_i_}py::class_<{qualified_struct_name}{other_template_params}>{location}
{_i_}py::class_<{qualified_struct_name}{other_template_params}{maybe_shared_ptr_holder}>{location}
{_i_}{_i_}({pydef_class_var_parent}, "{class_name_python}"{maybe_py_is_final}{maybe_py_is_dynamic}, "{comment}")
""",
flag_strip_empty_lines=True,
Expand All @@ -621,6 +621,11 @@ def make_pyclass_creation_code() -> str:

replacements.comment = self._elm_comment_pydef_one_line()

if code_utils.does_match_regex(options.class_held_as_shared__regex, self.cpp_element().class_name):
replacements.maybe_shared_ptr_holder = f", std::shared_ptr<{qualified_struct_name}>"
else:
replacements.maybe_shared_ptr_holder = ""

pyclass_creation_code = code_utils.process_code_template(code_template, replacements)

return pyclass_creation_code
Expand Down
4 changes: 2 additions & 2 deletions src/litgen/internal/cpp_to_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,13 +514,13 @@ def standard_type_replacements() -> RegexReplacementList:
\bstd::function<(.*)\((.*)\)> -> Callable[[\2], \1]
\bstd::string\(\) -> ""
\bstd::string\b -> str
\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]
\bstd::tuple<(.*)> -> Tuple[\1]
\bstd::pair<(.*)> -> Tuple[\1]
\bstd::optional<(.*?)> -> Optional[\1]
\bstd::unique_ptr<(.*?)> -> \1
\bstd::shared_ptr<(.*?)> -> \1
\bstd::map<\s*([\w:]*)\s*,\s*([\w:]*)\s*> -> Dict[\1, \2]
\bvoid\s*\* -> Any
Expand Down
17 changes: 17 additions & 0 deletions src/litgen/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,23 @@ class LitgenOptions:
# options.class_iterables_infos.add_iterable_class(python_class_name__regex, iterable_python_type_name)
class_iterables_infos: ClassIterablesInfos

# 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 = ""


# ------------------------------------------------------------------------------
# Templated class options
# ------------------------------------------------------------------------------
Expand Down
16 changes: 16 additions & 0 deletions src/litgen/tests/internal/adapted_types/adapted_class_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,3 +616,19 @@ def __init__(self) -> None:
;
""",
)


def test_shared_holder():
code = "struct Foo {};"
options = litgen.LitgenOptions()
options.class_held_as_shared__regex = r"Foo"
generated_code = litgen.generate_code(options, code)
code_utils.assert_are_codes_equal(
generated_code.pydef_code,
"""
auto pyClassFoo =
py::class_<Foo, std::shared_ptr<Foo>>
(m, "Foo", "")
.def(py::init<>()) // implicit default constructor
;
""")

0 comments on commit 5c0740d

Please sign in to comment.