pybind11
pybind11 copied to clipboard
[QUESTION]Can different return value and call policies be used depending on the actual type of std::variant?
Recently, I want to export a function whose return type is std::variant. However, different return value policies and additional call policies should be applied according to the actual return value type, for example: When return type is A, it needs to be py::return_value_policy::copy. When return type is B, it needs to be py::return_value_policy::take_ownership with additional call policy py::keep_alive<0, 1>(). When return type is C, it needs to be py::return_value_policy::reference_internal.
Are there any ways to specify the different return_value_policies for each return type of std::variant<...>? Many thanks!
#include <pybind11/pybind11.h>
#include <variant>
#include <string>
#include <memory>
namespace py = pybind11;
// Example classes/structs for A, B, C
struct A {
int value;
A(int v) : value(v) {}
};
struct B {
std::unique_ptr<int> ptr;
B(int v) : ptr(std::make_unique<int>(v)) {}
};
struct C {
std::string text;
C(const std::string& t) : text(t) {}
};
// The original function returning a std::variant
std::variant<A, B, C> get_variant(int choice) {
if (choice == 0) return A{42}; // Returns A
if (choice == 1) return B{100}; // Returns B (owns a unique_ptr)
return C{"Hello, Python!"}; // Returns C
}
// Wrapper function to apply dynamic return value policies
py::object get_variant_wrapper(int choice) {
auto result = get_variant(choice);
return std::visit(
[](auto&& arg) -> py::object {
using T = std::decay_t<decltype(arg)>;
// Case 1: Type A - Use copy
if constexpr (std::is_same_v<T, A>) {
return py::cast(arg, py::return_value_policy::copy);
}
// Case 2: Type B - Use take_ownership with keep_alive<0, 1>
else if constexpr (std::is_same_v<T, B>) {
// Move the unique_ptr to Python, transferring ownership
auto obj = py::cast(std::move(arg.ptr), py::return_value_policy::take_ownership);
return obj.attr("self") = py::cast(&arg, py::return_value_policy::move)
.with_(py::keep_alive<0, 1>()); // Keep B alive as long as the returned object is
}
// Case 3: Type C - Use reference_internal
else if constexpr (std::is_same_v<T, C>) {
return py::cast(arg, py::return_value_policy::reference_internal);
}
// Should never reach here due to variant completeness
throw std::runtime_error("Unknown variant type");
},
result
);
}
// pybind11 module definition
PYBIND11_MODULE(example, m) {
// Bind the structs
py::class_<A>(m, "A")
.def(py::init<int>())
.def_readwrite("value", &A::value);
py::class_<B>(m, "B")
.def(py::init<int>())
.def_readwrite("ptr", &B::ptr);
py::class_<C>(m, "C")
.def(py::init<std::string>())
.def_readwrite("text", &C::text);
// Bind the wrapper function
m.def("get_variant", &get_variant_wrapper, "Get a variant with dynamic return policy",
py::arg("choice"));
}