Skip to content

[BUG]: return_value_policy_override in optional_caster does not use the right types #3330

Description

@ryancahoon-zoox

Required prerequisites

Problem description

return_value_policy_override is not being applied correctly in optional_caster in two ways.

  • The is_lvalue_reference condition references T, which is the optional<T> type parameter from the class, when it should use T_, which is the parameter to the cast function. T_ can potentially be a reference type, but T will never be.
  • The type parameter passed to return_value_policy_override should be T::value_type, not T. This matches the way that the other STL container type casters work.

The result of these issues is that a method/property definition which uses a reference or reference_internal return value policy will create a Python value that's bound by reference to a temporary C++ object, resulting in undefined behavior. For reasons that I was not able to figure out, it seems like this causes problems when using boost::optional, but not with the libstdc++ implementation of std::optional. The issue (that the override to return_value_policy::move is never being applied) is present for all implementations, it just seems like that somehow doesn't result in problems for the libstdc++ implementation.

I believe the following patch is the correct resolution. I would open a PR directly, except I'm not sure how to write a regression test for this case.

diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h
index 2c017b4f..5c0592f8 100644
--- a/include/pybind11/stl.h+++ b/include/pybind11/stl.h@@ -252,8 +252,8 @@ template<typename T> struct optional_caster {
static handle cast(T_ &&src, return_value_policy policy, handle parent) {
if (!src)
return none().inc_ref();
- if (!std::is_lvalue_reference<T>::value) {- policy = return_value_policy_override<T>::policy(policy);+ if (!std::is_lvalue_reference<T_>::value) {+ policy = return_value_policy_override<typename T::value_type>::policy(policy);
}
return value_conv::cast(*std::forward<T_>(src), policy, parent);
}

Reproducible example code

#include <boost/optional.hpp>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
enum class E {
k0 = 0,
k1 = 1,
};
struct A {
boost::optional<E> value = E::k1;
};
namespace pybind11 {
namespace detail {
template <typename T>
struct type_caster<boost::optional<T>> : optional_caster<boost::optional<T>> {};
} // namespace detail
} // namespace pybind11
PYBIND11_MODULE(test_lib, m) {
pybind11::enum_<E>(m, "E")
.value("k0", E::k0)
.value("k1", E::k1);
pybind11::class_<A>(m, "A")
.def(pybind11::init<>())
.def_property_readonly("by_ref",
[](A& a) -> boost::optional<E>& { return a.value; })
.def_property_readonly("by_copy",
[](A& a) -> boost::optional<E> { return a.value; });
}
import test_lib
print(int(test_lib.A().by_ref))
print(int(test_lib.A().by_copy))

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions