Description
Required prerequisites
- Make sure you've read the documentation. Your issue may be addressed there.
- Search the issue tracker and Discussions to verify that this hasn't already been reported. +1 or comment there if it has.
- Consider asking first in the Gitter chat room or in a Discussion.
Problem description
return_value_policy_override
is not being applied correctly in optional_caster
in two ways.
- The
is_lvalue_reference
condition referencesT
, which is theoptional<T>
type parameter from the class, when it should useT_
, which is the parameter to thecast
function.T_
can potentially be a reference type, butT
will never be. - The type parameter passed to
return_value_policy_override
should beT::value_type
, notT
. 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))