2014-10-21 7 views
5

Ich möchte eine Funktion umbrechen, die eine boost::optional<T> zurückgibt. Das heißt, gegeben:Wie wickeln Sie eine C++ Funktion, die boost :: optional <T> zurückgibt?

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

Ich möchte, dass irgendwie wickeln, so dass Python entweder einen T von Wert bekommt, oder None:

class_<Foo>("Foo") 
    .def("func", func, return_value_policy<return_boost_optional???>); 

Normalerweise, wenn es nur eine T zurückkehrte, konnte ich Verwendung:

class_<Foo>("Foo") 
    .def("func", func, return_value_policy<return_by_value>()); 

Aber da es ein boost::optional<T> zurückkehrt, könnte es auch boost::none zurückkehren, was Ich mag würde als Pythonenden.

Gibt es eine Möglichkeit, dies mit den vorhandenen Konvertern zu tun? Wenn nicht, gibt es einen Workaround, um denselben Effekt zu erzielen?

Antwort

10

Das Konzept ResultConverter wurde entwickelt, um dieses Problem zu lösen. Das CallPolicy-Modell return_value_policy verwendet einen ResultConverterGenerator, um einen ResultConverter zu erstellen, und der ResultConverter wird verwendet, um den Rückgabewert einer Funktion zu ändern, die Python ausgesetzt ist. In diesem Fall könnte ein benutzerdefinierter Typ, der das ResultConverter-Konzept implementiert, verwendet werden, um entweder Python None zurückzugeben oder ein Objekt mit der entsprechenden Python-Klasse zu instanziieren. Während die Dokumentation Liste aller Art Anforderungen, kann es näher mit etwas einfacher zu verstehen sein Code ähnelt:

/// @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; 
    }; 
}; 

Oft, wenn eine benutzerdefinierte ResultConverter Modell erstellen, eine Vorlage Meta-Programmierung verwenden können, die Chancen zu minimieren Laufzeitfehler in Konvertierungen und sogar Fehler bei der Kompilierung und liefern aussagekräftige Nachrichten. Hier finden Sie ein vollständiges Beispiel für return_optional, ein ResultConverter-Modell, das ein C++ boost::optional<T>-Objekt verwendet und es in das entsprechende Python-Objekt konvertiert.

#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>()); 
} 

Interaktive Nutzung:

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

Die Kompilierung Typüberprüfung erfolgt, wenn der return_optional ResultConvert versucht wird, mit einer Funktion verwendet werden, die einen Wert zurückgibt, der kein boost::optional ist. Wenn zum Beispiel die folgenden verwendet:

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>()); 
} 

Der Compiler wählt wählen Sie die return_optional_requires_a_optional_return_type Implementierung und Kompilierung fehlschlagen. Hier ist ein Teil des Compilers Fehlermeldung call bietet:

 
error: no member named 'get_pytype' in 
'detail::return_optional_requires_a_optional_return_type<egg *>' 
+0

Dies sollte wirklich ein Teil der Boost :: Python-Bibliothek sein. Wirklich nett! – Felix

+0

Dies ist eine wirklich phänomenale Antwort. – Mohan