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

时间:2014-10-21 23:12:26

标签: c++ boost-python

我想包装一个返回boost::optional<T>的函数。那就是:

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

我想以某种方式包装它,以便Python按值获得TNone

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结束。

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

1 个答案:

答案 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 *>'