Skip to content

[smart_holder] Auto select return value policy for clif_automatic #4381

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

Merged
merged 9 commits into from
Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions include/pybind11/detail/smart_holder_type_casters.h
Original file line number Diff line number Diff line change
Expand Up @@ -630,19 +630,30 @@ struct smart_holder_type_caster : smart_holder_type_caster_load<T>,
static handle cast(T const &src, return_value_policy policy, handle parent) {
// type_caster_base BEGIN
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
|| policy == return_value_policy::automatic_reference
|| policy == return_value_policy::_clif_automatic) {
policy = return_value_policy::copy;
}
return cast(&src, policy, parent);
// type_caster_base END
}

static handle cast(T &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::_clif_automatic) {
if (std::is_move_constructible<T>::value) {
policy = return_value_policy::move;
} else {
policy = return_value_policy::automatic;
}
}
return cast(const_cast<T const &>(src), policy, parent); // Mutbl2Const
}

static handle cast(T const *src, return_value_policy policy, handle parent) {
auto st = type_caster_base<T>::src_and_type(src);
if (policy == return_value_policy::_clif_automatic) {
policy = return_value_policy::copy;
}
return cast_const_raw_ptr( // Originally type_caster_generic::cast.
st.first,
policy,
Expand All @@ -653,6 +664,9 @@ struct smart_holder_type_caster : smart_holder_type_caster_load<T>,
}

static handle cast(T *src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::_clif_automatic) {
policy = return_value_policy::reference;
}
return cast(const_cast<T const *>(src), policy, parent); // Mutbl2Const
}

Expand Down Expand Up @@ -867,7 +881,8 @@ struct smart_holder_type_caster<std::unique_ptr<T, D>> : smart_holder_type_caste
static handle cast(std::unique_ptr<T, D> &&src, return_value_policy policy, handle parent) {
if (policy != return_value_policy::automatic
&& policy != return_value_policy::reference_internal
&& policy != return_value_policy::move) {
&& policy != return_value_policy::move
&& policy != return_value_policy::_clif_automatic) {
// SMART_HOLDER_WIP: IMPROVABLE: Error message.
throw cast_error("Invalid return_value_policy for unique_ptr.");
}
Expand Down
102 changes: 102 additions & 0 deletions tests/test_return_value_policy_override.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,75 @@
#include <pybind11/smart_holder.h>

#include "pybind11_tests.h"

namespace test_return_value_policy_override {

struct some_type {};

struct obj {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

obj is usually used for instances, it's surprising as a type name.

The combinations of copyable/movable keep coming up, it would be good have some systematic naming that we could use throughout. What do you think about

struct type_cp1_mv1
struct type_cp1_mv0
struct type_cp0_mv1
struct type_cp0_mv0

?

With a comment like:

// cp = copyable, mv = movable, 1 = yes, 0 = no

Question that's maybe beyond this PR: do we have (or would it be safest) to add tests for the other two combinations?

IIUC here you have only type_cp1_mv1 and type_cp0_mv1, is that correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your suggestions. I updated the PR with more tests.

std::string mtxt;
obj(const std::string &mtxt_) : mtxt(mtxt_) {}
obj(const obj &other) { mtxt = other.mtxt + "_CpCtor"; }
obj(obj &&other) { mtxt = other.mtxt + "_MvCtor"; }
obj &operator=(const obj &other) {
mtxt = other.mtxt + "_CpCtor";
return *this;
}
obj &operator=(obj &&other) {
mtxt = other.mtxt + "_MvCtor";
return *this;
}
};

struct nocopy {
std::string mtxt;
nocopy(const std::string &mtxt_) : mtxt(mtxt_) {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you still need to delete the copy ctor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out. I forgot this. I deleted the copy ctor.

nocopy(const nocopy &) = delete;
nocopy(nocopy &&other) { mtxt = other.mtxt + "_MvCtor"; }
nocopy &operator=(const nocopy &) = delete;
nocopy &operator=(nocopy &&other) {
mtxt = other.mtxt + "_MvCtor";
return *this;
}
};

obj *return_pointer() {
static obj value("pointer");
return &value;
}

const obj *return_const_pointer() {
static obj value("const_pointer");
return &value;
}

obj &return_reference() {
static obj value("reference");
return value;
}

const obj &return_const_reference() {
static obj value("const_reference");
return value;
}

std::shared_ptr<obj> return_shared_pointer() {
return std::shared_ptr<obj>(new obj("shared_pointer"));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be std::make_shared, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially I used std::make_shared and std::make_unqiue, but then I see errors note: ‘std::make_unique’ is only available from C++14 onwards, so I modified both of them. I should be able to use std::make_shared with C++11. I will update the PR.

}

std::unique_ptr<obj> return_unique_pointer() {
return std::unique_ptr<obj>(new obj("unique_pointer"));
}

nocopy &return_reference_nocopy() {
static nocopy value("reference_nocopy");
return value;
}

} // namespace test_return_value_policy_override

using test_return_value_policy_override::nocopy;
using test_return_value_policy_override::obj;
using test_return_value_policy_override::some_type;

namespace pybind11 {
Expand Down Expand Up @@ -51,6 +115,9 @@ struct type_caster<some_type> : type_caster_base<some_type> {
} // namespace detail
} // namespace pybind11

PYBIND11_SMART_HOLDER_TYPE_CASTERS(obj)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(nocopy)

TEST_SUBMODULE(return_value_policy_override, m) {
m.def("return_value_with_default_policy", []() { return some_type(); });
m.def(
Expand Down Expand Up @@ -79,4 +146,39 @@ TEST_SUBMODULE(return_value_policy_override, m) {
return &value;
},
py::return_value_policy::_clif_automatic);

py::classh<obj>(m, "object").def(py::init<std::string>()).def_readonly("mtxt", &obj::mtxt);
m.def(
"return_object_value_with_policy_clif_automatic",
[]() { return obj("value"); },
py::return_value_policy::_clif_automatic);
// test_return_value_policy_override::return_pointer with default policy
// causes crash
m.def("return_object_pointer_with_policy_clif_automatic",
&test_return_value_policy_override::return_pointer,
py::return_value_policy::_clif_automatic);
// test_return_value_policy_override::return_const_pointer with default
// policy causes crash
m.def("return_object_const_pointer_with_policy_clif_automatic",
&test_return_value_policy_override::return_const_pointer,
py::return_value_policy::_clif_automatic);
m.def("return_object_reference_with_policy_clif_automatic",
&test_return_value_policy_override::return_reference,
py::return_value_policy::_clif_automatic);
m.def("return_object_const_reference_with_policy_clif_automatic",
&test_return_value_policy_override::return_const_reference,
py::return_value_policy::_clif_automatic);
m.def("return_object_unique_ptr_with_policy_clif_automatic",
&test_return_value_policy_override::return_unique_pointer,
py::return_value_policy::_clif_automatic);
m.def("return_object_shared_ptr_with_policy_clif_automatic",
&test_return_value_policy_override::return_shared_pointer,
py::return_value_policy::_clif_automatic);

py::classh<nocopy>(m, "nocopy")
.def(py::init<std::string>())
.def_readonly("mtxt", &nocopy::mtxt);
m.def("return_nocopy_reference_with_policy_clif_automatic",
&test_return_value_policy_override::return_reference_nocopy,
py::return_value_policy::_clif_automatic);
}
28 changes: 28 additions & 0 deletions tests/test_return_value_policy_override.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from pybind11_tests import return_value_policy_override as m


Expand All @@ -11,3 +13,29 @@ def test_return_pointer():
assert m.return_pointer_with_default_policy() == "automatic"
assert m.return_pointer_with_policy_move() == "move"
assert m.return_pointer_with_policy_clif_automatic() == "_clif_automatic"


@pytest.mark.parametrize(
"func, expected",
[
(m.return_object_value_with_policy_clif_automatic, "value_MvCtor"),
(m.return_object_pointer_with_policy_clif_automatic, "pointer"),
(
m.return_object_const_pointer_with_policy_clif_automatic,
"const_pointer_CpCtor",
),
(m.return_object_reference_with_policy_clif_automatic, "reference_MvCtor"),
(
m.return_object_const_reference_with_policy_clif_automatic,
"const_reference_CpCtor",
),
(m.return_object_unique_ptr_with_policy_clif_automatic, "unique_pointer"),
(m.return_object_shared_ptr_with_policy_clif_automatic, "shared_pointer"),
(
m.return_nocopy_reference_with_policy_clif_automatic,
"reference_nocopy_MvCtor",
),
],
)
def test_clif_automatic_return_value_policy_override(func, expected):
assert func().mtxt == expected