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

前端 未结 1 470
傲寒
傲寒 2021-02-04 20:12

I want to wrap a function that returns a boost::optional. That is, given:

class Foo {
    boost::optional func();
};
1条回答
  •  日久生厌
    2021-02-04 20:49

    The ResultConverter concept is designed to solve this problem. The return_value_policy CallPolicy model uses a ResultConverterGenerator to create a ResultConverter, and the ResultConverter is used to modify the return value of a function being exposed to Python. In this case, a custom type that implements the ResultConverter concept could be used to either return Python None or instantiate an object with the appropriate Python class. While the documentation list all the type requirements, it may be easier to understood with something closer resembling code:

    /// @brief The ResultConverterGenerator.
    struct result_converter_generator
    {
      template 
      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;
      };
    };
    

    Often times, when creating a custom ResultConverter model, one can use template meta-programming to minimize the chances of runtime errors in conversions, and even catch errors at compile time and provide meaningful messages. Here is a complete example of return_optional, a ResultConverter model that takes a C++ boost::optional object, and converts it to the appropriate Python object.

    #include 
    #include 
    #include 
    #include 
    #include 
    
    /// @brief Mockup model.
    class spam {};
    
    /// @brief Mockup factory for model.
    boost::optional make_spam(bool x)
    {
      return x ? boost::optional(boost::in_place()) : boost::none;
    }
    
    namespace detail {
    
    /// @brief Type trait that determines if the provided type is
    ///        a boost::optional.
    template 
    struct is_optional : boost::false_type {};
    
    template 
    struct is_optional > : boost::true_type {};
    
    /// @brief Type used to provide meaningful compiler errors.
    template 
    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 
    struct to_python_optional
    {
      /// @brief Only supports converting Boost.Optional types.
      /// @note This is checked at runtime.
      bool convertible() const { return detail::is_optional::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  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,
          detail::to_python_optional,
          detail::return_optional_requires_a_optional_return_type
        >::type type;
      }; // apply
    };   // return_optional
    
    BOOST_PYTHON_MODULE(example)
    {
      namespace python = boost::python;
      python::class_("Spam")
        ;
    
      python::def("make_spam", &make_spam,
        python::return_value_policy());
    }
    

    Interactive usage:

    >>> import example
    >>> assert(isinstance(example.make_spam(True), example.Spam))
    >>> assert(example.make_spam(False) is None)
    

    The compile time type checking occurs when the return_optional ResultConvert is attempted to be used with a function that returns a value that is not a boost::optional. For instance, when the following is used:

    struct egg {};
    
    egg* make_egg();
    
    BOOST_PYTHON_MODULE(example)
    {
      namespace python = boost::python;
      python::def("make_egg", &make_egg,
        python::return_value_policy());
    }
    

    The compiler will select select the return_optional_requires_a_optional_return_type implementation, and fail compilation. Here is part of the compiler error message clang provides:

    error: no member named 'get_pytype' in
    'detail::return_optional_requires_a_optional_return_type'
    

    0 讨论(0)
提交回复
热议问题