如何包装返回 boost::optional 的 C++ 函数<T>?

How to wrap a C++ function that returns boost::optional<T>?

本文关键字:C++ 函数 gt lt optional 何包装 包装 boost 返回      更新时间:2023-10-16

我想要包装一个返回boost::optional<T>的函数。也就是说,给定:

class Foo {
    boost::optional<T> func();
};

我想以某种方式将其包装起来,以便Python通过值或None:获得T

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

有没有办法用现有的转换器做到这一点?如果没有,是否有一些变通方法可以达到同样的效果?

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提供的编译器错误消息的一部分:

错误:中没有名为"get_pytype"的成员'详细信息::return_optional_requires_a_optional_return_type<鸡蛋*> '