2014-10-21 14 views
5

Desidero eseguire il wrapping di una funzione che restituisce un boost::optional<T>. Cioè, dato:Come avvolgere una funzione C++ che restituisce boost :: opzionale <T>?

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

Vorrei avvolgere che in qualche modo in modo che sia Python ottiene un T per valore, o None:

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

Normalmente, se solo restituito un T, ho potuto utilizzare:

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

Ma dal momento che restituisce un boost::optional<T>, potrebbe anche tornare boost::none, che mi piacerebbe finire come Python del None.

C'è un modo per farlo con i convertitori esistenti? In caso contrario, c'è qualche soluzione per ottenere lo stesso effetto?

risposta

10

Il concetto ResultConverter è progettato per risolvere questo problema. Il modello CallPolicy return_value_policy utilizza un ResultConverterGenerator per creare un ResultConverter e il ResultConverter viene utilizzato per modificare il valore restituito di una funzione esposta a Python. In questo caso, è possibile utilizzare un tipo personalizzato che implementa il concetto ResultConverter per restituire Python None o creare un'istanza di un oggetto con la classe Python appropriata. Mentre la lista di documentazione tutti i requisiti di tipo, può essere più facile da comprendere con qualcosa di più vicino che assomiglia codice:

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

Spesso, quando si crea un modello di ResultConverter personalizzato, si può utilizzare la maschera di meta-programmazione per ridurre al minimo le probabilità di errori di runtime nelle conversioni e persino rilevare errori in fase di compilazione e fornire messaggi significativi. Ecco un esempio completo di return_optional, un modello ResultConverter che accetta un oggetto C++ boost::optional<T> e lo converte nell'oggetto Python appropriato.

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

utilizzo interattivo:

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

Il tempo di compilazione controllo di tipo si verifica quando il return_optional ResultConvert si cerca di essere utilizzato con una funzione che restituisce un valore che non è un boost::optional. Per esempio, viene utilizzato quando il seguente:

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

Il compilatore selezionerà selezionare l'attuazione return_optional_requires_a_optional_return_type, e non riescono compilazione. Qui è parte del messaggio di errore del compilatore clang fornisce:

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

Questo dovrebbe davvero essere parte della libreria boost :: python. Veramente bello! – Felix

+0

Questa è una risposta davvero fenomenale. – Mohan