我想包装一个返回boost::optional<T>
的函数。那就是:
class Foo {
boost::optional<T> func();
};
我想以某种方式包装它,以便Python按值获得T
或None
:
class_<Foo>("Foo")
.def("func", func, return_value_policy<return_boost_optional???>);
通常如果只返回T
,我可以使用:
class_<Foo>("Foo")
.def("func", func, return_value_policy<return_by_value>());
但是,由于它返回boost::optional<T>
,它也可以返回boost::none
,我最终会以Python None
结束。
有没有办法用现有的转换器做到这一点?如果没有,是否有一些解决方法可以达到同样的效果?
答案 0 :(得分:10)
ResultConverter概念旨在解决此问题。 return_value_policy
CallPolicy模型使用ResultConverterGenerator创建ResultConverter,ResultConverter用于修改向Python公开的函数的返回值。在这种情况下,实现ResultConverter概念的自定义类型可用于返回Python None
或使用适当的Python类实例化对象。虽然文档列出了所有类型要求,但使用更接近类似代码的东西可能更容易理解:
/// @brief The ResultConverterGenerator.
struct result_converter_generator
{
template <typename T>
struct apply
{
struct result_converter
{
// Must be default constructible.
result_converter();
// Return true if T can be converted to a Python Object.
bool convertible();
// Convert obj to a PyObject, explicitly managing proper reference
// counts.
PyObject* operator(const T& obj);
// Returns the Python object that represents the type. Used for
// documentation.
const PyTypeObject* get_pytype();
};
/// @brief The ResultConverter.
typedef result_converter type;
};
};
通常,在创建自定义ResultConverter模型时,可以使用模板元编程来最小化转换中运行时错误的可能性,甚至可以在编译时捕获错误并提供有意义的消息。以下是return_optional
的完整示例,这是一个ResultConverter模型,它接受C ++ boost::optional<T>
对象,并将其转换为适当的Python对象。
#include <boost/mpl/if.hpp>
#include <boost/optional.hpp>
#include <boost/python.hpp>
#include <boost/type_traits/integral_constant.hpp>
#include <boost/utility/in_place_factory.hpp>
/// @brief Mockup model.
class spam {};
/// @brief Mockup factory for model.
boost::optional<spam> make_spam(bool x)
{
return x ? boost::optional<spam>(boost::in_place()) : boost::none;
}
namespace detail {
/// @brief Type trait that determines if the provided type is
/// a boost::optional.
template <typename T>
struct is_optional : boost::false_type {};
template <typename T>
struct is_optional<boost::optional<T> > : boost::true_type {};
/// @brief Type used to provide meaningful compiler errors.
template <typename>
struct return_optional_requires_a_optional_return_type {};
/// @brief ResultConverter model that converts a boost::optional object to
/// Python None if the object is empty (i.e. boost::none) or defers
/// to Boost.Python to convert object to a Python object.
template <typename T>
struct to_python_optional
{
/// @brief Only supports converting Boost.Optional types.
/// @note This is checked at runtime.
bool convertible() const { return detail::is_optional<T>::value; }
/// @brief Convert boost::optional object to Python None or a
/// Boost.Python object.
PyObject* operator()(const T& obj) const
{
namespace python = boost::python;
python::object result =
obj // If boost::optional has a value, then
? python::object(*obj) // defer to Boost.Python converter.
: python::object(); // Otherwise, return Python None.
// The python::object contains a handle which functions as
// smart-pointer to the underlying PyObject. As it will go
// out of scope, explicitly increment the PyObject's reference
// count, as the caller expects a non-borrowed (i.e. owned) reference.
return python::incref(result.ptr());
}
/// @brief Used for documentation.
const PyTypeObject* get_pytype() const { return 0; }
};
} // namespace detail
/// @brief Converts a boost::optional to Python None if the object is
/// equal to boost::none. Otherwise, defers to the registered
/// type converter to returs a Boost.Python object.
struct return_optional
{
template <class T> struct apply
{
// The to_python_optional ResultConverter only checks if T is convertible
// at runtime. However, the following MPL branch cause a compile time
// error if T is not a boost::optional by providing a type that is not a
// ResultConverter model.
typedef typename boost::mpl::if_<
detail::is_optional<T>,
detail::to_python_optional<T>,
detail::return_optional_requires_a_optional_return_type<T>
>::type type;
}; // apply
}; // return_optional
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
python::class_<spam>("Spam")
;
python::def("make_spam", &make_spam,
python::return_value_policy<return_optional>());
}
交互式使用:
>>> import example
>>> assert(isinstance(example.make_spam(True), example.Spam))
>>> assert(example.make_spam(False) is None)
当尝试将return_optional
ResultConvert与返回值不是boost::optional
的函数一起使用时,会发生编译时类型检查。例如,使用以下内容时:
struct egg {};
egg* make_egg();
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
python::def("make_egg", &make_egg,
python::return_value_policy<return_optional>());
}
编译器将选择选择return_optional_requires_a_optional_return_type
实现,并且编译失败。以下是clang提供的编译器错误消息的一部分:
error: no member named 'get_pytype' in 'detail::return_optional_requires_a_optional_return_type<egg *>'