Skip to content

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

Closed
@ryancahoon-zoox

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))

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