diff --git a/documentation/content/pages/python-cpp-new/Implementation.rst b/documentation/content/pages/python-cpp-new/Implementation.rst index 67fabb7e..e76319fa 100644 --- a/documentation/content/pages/python-cpp-new/Implementation.rst +++ b/documentation/content/pages/python-cpp-new/Implementation.rst @@ -10,47 +10,77 @@ the Python/C API but we provides templates to automate as much as possible the boring tasks (and code duplication). This way, if we need a very specific feature at some point, we can still revert back to the pure Python/C API. -The key features of our wrapper are: +The wrapper basically provides support for three kind of operations: + +1. Encapsulate C++ object *in* Python ones, done by ``cToPy<>()`` template. + Template specializations are defined for the ``POD`` and basic ``STL`` + types like ``std::string``. + + To add more easily new specializations, they resides in the top level + scope (**not** inside ``Isobar``). + +2. Decapsulate a C++ object *from* a Python one, done by ``pyToC()``. + It's implementation is slightly different from the one of ``cToPy<>()`` + in the sense that it is a mix of normal C++ functions and template + functions. I was having trouble inside the ``callMethod()`` to access + to templates specialization defined *after* that point, so function be + it. + + There are two mutually exclusives versions of the ``pyToC<>()`` for + objects managed through the type manager. One is for value types and + the other for pointer types. + +3. Wrap C/C++ functions & methods inside C-linkage ``PyCFunction``, that is + ``PyOject* (*)(PyObject*, PyObject*)``. This is done respectively through + ``callFunction<>()`` and ``callMethod<>()``. ``callMethod<>()`` having + two variants, one for directly calling the function member, if there is + no overload and one for calling one given flavor of the overloaded + function member. + + In addition we provides special variants for Python unary, binary and + operator functions. + +In addition to those user exposed features, we have: * The ``PyTypeManager`` and it's derived classes to store and share informations about all our newly defined ``PyTypeObjects``. -* The ``callMethod<>()`` and ``callFunction<>()`` function templates to - automatically generate a wrapper around C++ object methods or functions. - -* The ``PyBoject* objectLink( CppT* )`` functions templates that provides C++ to Python - object translation. - -* The ``bool pyAs( PyObject*, CppT*& )`` functions templates that provides Python to C++ - object translation. +* Various wrapper *classes* to wrap functions & methods. They are not directly + exposed because the template class intanciation needs the template parameters + to be explicitely given, wich is tedious. Instead we create them *through* + a function template call, which will perform for us the template type + deduction. We creates only two kind of ``PyObject`` (but many ``PyTypeObject``): -* ``PyVoidpointer`` which encapsulate one void pointer to the C++ associated +* ``PyOneVoid`` which encapsulate one void pointer to the C++ associated object. .. code-block:: Python extern "C" { - typedef struct PyVoidPointer { + typedef struct PyOneVoid { PyObject_HEAD - void* _object; + void* _object1; }; } -* ``PyIteratorPointer`` which encapsulate one void pointer to the C++ associated - iterator and one another to the ``PyObject`` of the container. +* ``PyTwoVoid`` which encapsulate one void pointer to the C++ associated + object (an iterator) and one another to the ``PyObject`` of the container. .. code-block:: Python extern "C" { - typedef struct PyVoidPointer { + typedef struct PyTwoVoid { PyObject_HEAD - void* _object; // C++ iterator. - PyVoidPointer* _container; // Python wrapped container. + void* _object1; // C++ iterator. + PyObject* _object2; // Python wrapped container. }; } + +.. note:: A ``PyTwoVoid`` can be casted/accessed as a ``PyOneVoid``. + 2.1 PyTypeManager ~~~~~~~~~~~~~~~~~ @@ -62,42 +92,41 @@ We creates only two kind of ``PyObject`` (but many ``PyTypeObject``): * Provide a non-template abstract base class for all the derived ``PyTypeObject``. As said, it is not a template class but it supplies function member - template. Derived classes are provides for different kind of C++ - class. + template. Derived classes are provided to manage different kind of C++ + classes. * :cb:`PyTypeManagerVTrunk` - Is an intermediate between the non-template base class and all the - templatized others. + templatized others (do **not** use it directly). * :cb:`PyTypeManagerNonDBo` - Template for standalone C++ classes that are not derived from ``DBo``. For example ``Box`` or ``Parameter``. * :cb:`PyTypeManagerDBo` - Template for C++ classes that *are* not derived from ``DBo``. For example ``Cell`` or ``Instance``. + * :cb:`PyTypeManagerDerivedDBo` + Template for a ``CppT`` class derived derived from a ``BaseT`` class. + ``CppT`` doesn'y need to be a direct derived of ``BaseT``. ``BaseT`` + needs to derive from ``DBo`` + * :cb:`PyTypeManagerVector`, template for C++ ``std::vector``. * :cb:`PyTypeManagerVectorIterator` - Template for C++ ``std::vector::iterator``, automatically created from the vector registration. * :cb:`PyTypeManagerMap`, template for C++ ``std::map``. * :cb:`PyTypeManagerMapIterator` - Template for C++ ``std::vector::iterator``, automatically created from the map registration. * :cb:`PyTypeManagerCollection<,CppT>`, template for C++ ``Hurricane::Collection``. * :cb:`PyTypeManagerCollectionIterator<,CppT>` - Template for C++ ``Hurricane::Locator``, automatically created from the collection registration. @@ -108,7 +137,7 @@ We creates only two kind of ``PyObject`` (but many ``PyTypeObject``): Functions of a ``PyTypeObject`` like the *tp* methods (``tp_alloc``, ``tp_print``, ``tp_hash``, ...) must have a C-linkage. So we create *one* function per slot that we want to use, set that *same* function for all the created ``PyTypeObject``, and -perform a dispact in it. The drawback is that for each access we have to perform +perform a dispacth in it. The drawback is that for each access we have to perform a map lookup. Hope it is fast. Excerpt from the code: @@ -123,7 +152,7 @@ Excerpt from the code: extern long _tpHash ( PyObject* self ) { // Dispatch towards the relevant class, based on ob_type pointer. - return PyTypeManager::get( self->ob_type )->_getTpHash( asVPtr(self) ); + return PyTypeManager::get( Py_TYPE(self)->_getTpHash( self ); } } @@ -132,7 +161,7 @@ Excerpt from the code: public: void PyTypeManager::_setupPyType () // Derived classes must implement it as they see fit. - virtual long _getTpHash ( PyVoidPointer* ) = 0; + virtual long _getTpHash ( PyObject* ) = 0; template static PyTypeManager* _get(); private: @@ -158,63 +187,101 @@ Excerpt from the code: 2.3 Going From Python to C++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To convert a C++ object (pointer) into a Python object, a set of -:cb:`pyAs<>` templates functions are supplieds. +To convert a C++ object (pointer) into a Python object, a mix of +:cb:`pyToC<>()` templates functions and real functions are supplieds. + +Templates functions are used for all types/classes managed through +the ``PyTypeManger``. They come in two flavors: + +2. **Value as pointer version** (C++ argment type is ``T*``): + The encapsulated C++ object is still a pointer, + but to a *stand-alone* one which has been created for the sole + purpose of this ``PyObject``. Typical example is the ``Box``. + Then, we *copy by value* the contents of the pointed object into + the contents of the pointer argument that we where given. + +3. **Pointer version** (C++ argument type is ``T**``): + The encapsulated C++ object is truly a pointer + to an element of the data-structure, then we just extract the + C++ pointer value. + +Normal function overload are used for ``POD`` types (``bool``, ``int``, +``long``, ``double``, ...) and basic ``STL`` types (``std::string``, ...). Specialization for all POD type that can be directly translated into Python types must be provideds (``bool``, ``int``, ``long``, ``double``, ``std::string``, ...). -Partial specialization for type availables through the ``PyTypeManager`` -is supplied. It checks the manager for the type's existence. +Those templates/functions are the ones the ``Isobar::parse_objects()`` recursive +template function call in turn for each ``PyObject*`` argument. + +.. note:: ``Hurricane::Name`` are *not* exposed to the Python interface, they + must be treated as ``std::string``. .. code-block:: C++ - // Most generic template, should *not* be used. - // So issue an error message and report a failed conversion. - template - inline bool pyAs ( PyObject* pyArg, T& arg ) + // Template/Pointer to a value flavor. + template< typename T + , typename std::enable_if< !std::is_pointer::value, bool >::type = true > + inline bool pyToC ( PyObject* pyArg, T* arg ) { - std::cerr << "Isobar3.pyAs(): Unsupported type \"" - << demangle(typeid(T).name()) << "\"" << endl; - return false; + typedef typename std::remove_cv::type NonConstT; + Isobar3::PyTypeManager* manager = Isobar3::PyTypeManager::_get(); + if (not manager) { + std::cerr << "Isobar3::pyToC<>(const T*): Unsupported type." << std::endl; + return false; + } + if (Py_TYPE(pyArg) != manager->_getTypeObject()) return false; + *(const_cast< NonConstT* >(arg)) = *(( T* )( Isobar3::object1( pyArg ))); + return true; } - // Template specialization for POD type "int". - template<> - inline bool pyAs ( PyObject* pyArg, int& arg ) + // Template/Pointer to a pointer flavor. + template + inline bool pyToC ( PyObject* pyArg, T** arg ) { - if (PyInt_Check (pyArg)) { arg = PyInt_AsLong ( pyArg ); } - else if (PyLong_Check(pyArg)) { arg = PyLong_AsLong( pyArg ); } + Isobar3::PyTypeManager* manager = Isobar3::PyTypeManager::_get(); + if (not manager) { + std::cerr << "Isobar3::pyToC(T*&): Unsupported type \"" << typeid(T).name() << "\"" << std::endl; + return false; + } + *arg = (T*)( Isobar3::object1( pyArg )); + return true; + } + + // True function overload for std::string. + inline bool pyToC ( PyObject* pyArg, std::string* arg ) + { + if (not PyUnicode_Check(pyArg)) return false; + PyObject* pyBytes = PyUnicode_AsASCIIString( pyArg ); + *arg = PyBytes_AsString( pyBytes ); + Py_DECREF( pyBytes ); + return true; + } + + // True function overload for bool. + inline bool pyToC ( PyObject* pyArg, bool* arg ) + { + if (not PyBool_Check(pyArg)) return false; + *arg = (pyArg == Py_True); + return true; + } + + // True function overload for int. + inline bool pyToC ( PyObject* pyArg, int* arg ) + { + if (PyLong_Check(pyArg)) { *arg = PyLong_AsLong( pyArg ); } else return false; return true; } - // Template partial specialization for any type registered in - // the PyTypeManager. - template - inline bool pyAs ( PyObject* pyArg, T*& arg ) - { - PyTypeManager* manager = PyTypeManager::_get(); - if (not manager) { - std::cerr << "Isobar3.pyAs(): Unsupported type \"" - << typeid(T).name() << "\"" << endl; - return false; - } - arg = (T*)( asVPtr( pyArg )->_object ); - return true; - } - - template - inline bool pyAs ( PyVoidPointer* pyArg, T*& arg ) - { return pyAs( (PyObject*)pyArg, arg ); } 2.4 Going From C++ to Python ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To convert a Python object into a C++ object, a set of -:cb:`objectLink<>` templates functions are supplieds. +:cb:`cToPy<>` templates functions are supplieds. We completely disable the partially specialized templates for objects that are non-POD as the compiler seems to be unable to @@ -230,48 +297,65 @@ method. **why** we need the intermediate ``PyTypeManagerVTrunk`` template class. +.. note:: **Different C++ templates.** You may notice that the two following templates + may look like specializations of the same one: + + * ``template PyObject* cToPy ( CppT object )`` + * ``template PyObject* cToPy ( CppT* object )`` + + Which would be illegal (function templates are not allowed to have *partial* + specialization), but they are *not*. The two pairs + ``(template parameter,function parameter)``, that is ``(CppT,CppT)`` and + ``(CppT,CppT*)`` cannot be made to be a specialization of each other. + .. code-block:: C++ - - // The most generic template, should *not* be used. So raise a Python exception. + + // Generic template for values. template< typename CppT > - inline PyObject* objectLink ( CppT object ) + inline PyObject* cToPy ( CppT object ) { - std::string message = "Overload for Isobar3::objectLink< " - + demangle(typeid(CppT).name()) + " >() is missing."; - PyErr_SetString( HurricaneError, message.c_str() ); - return NULL; + if (not Isobar3::PyTypeManager::hasType()) { + std::string message = "Overload for Isobar3::cToPy< " + + Hurricane::demangle(typeid(CppT).name()) + " >() Type not registered in the manager."; + PyErr_SetString( Isobar3::HurricaneError, message.c_str() ); + return NULL; + } + return Isobar3::PyTypeManager::link( new CppT (object) ); } - - // Disable this template if "CppT" is an already supported type. + + // Disabled for POD & STL types, pointer flavor. template< typename CppT , typename std::enable_if< !std::is_same::value && !std::is_same::value && !std::is_same::value - && !std::is_same::value - ,bool>::type = true > - inline PyObject* objectLink ( CppT* object ) - { return PyTypeManager::link( object ); } - - // Disable this template if "CppT" is an already supported type. + && !std::is_same::value,bool>::type = true > + inline PyObject* cToPy ( CppT* object ) + { return Isobar3::PyTypeManager::link( object ); } + + // Disabled for POD & STL types, const pointer flavor. template< typename CppT , typename std::enable_if< !std::is_same::value && !std::is_same::value && !std::is_same::value - && !std::is_same::value - ,bool>::type = true > - inline PyObject* objectLink ( const CppT* object ) - { return PyTypeManager::link( const_cast( object )); } - - // Specializations for POD type "int" . - template<> inline PyObject* objectLink ( int i ) - { return PyInt_FromLong( i ); } - - template<> inline PyObject* objectLink ( const int i ) - { return PyInt_FromLong( i ); } - - template<> inline PyObject* objectLink ( const int* i ) - { return PyInt_FromLong( *i ); } + && !std::is_same::value,bool>::type = true > + inline PyObject* cToPy ( const CppT* object ) + { return Isobar3::PyTypeManager::link( const_cast( object )); } + + // Specialization for booleans. + template<> + inline PyObject* cToPy ( bool b ) + { if (b) Py_RETURN_TRUE; Py_RETURN_FALSE; } + + // Specialization for STL std::string. + template<> inline PyObject* cToPy< std::string> ( std::string s ) { return PyUnicode_FromString( s.c_str() ); } + template<> inline PyObject* cToPy ( const std::string s ) { return PyUnicode_FromString( s.c_str() ); } + template<> inline PyObject* cToPy ( const std::string* s ) { return PyUnicode_FromString( s->c_str() ); } + + // Specialization for POD int. + template<> inline PyObject* cToPy< int > ( int i ) { return PyLong_FromLong( i ); } + template<> inline PyObject* cToPy ( const int i ) { return PyLong_FromLong( i ); } + template<> inline PyObject* cToPy ( const int* i ) { return PyLong_FromLong( *i ); } 2.5 Object Methods Wrappers @@ -309,8 +393,8 @@ Basically, we want to fit: to a sequence of ``PyObject*`` (that must match the number of arguments of it's C++ function conterpart). -So, the problem is to change a tuple which size is only kown at *runtime* -into a list of C/C++ parameters known at *compile time*. +So, the problem is to change a Python tuple which size is only kown at +*runtime* into a list of C/C++ parameters known at *compile time*. I am not such an expert in template programming so I can find a *generic* solution able to handle any number of parameters. Instead I did write @@ -320,7 +404,22 @@ to the C++ function call and the duplicated code needed for each template is kept to a minimum. To translate the Python tuple into an ordered list (vector like) of C++ -object *of different types*, the obvious choice is C++ ``std::tuple<>``. +object *of different types*, the obvious choice should have been ``std::tuple<>``, +but I did encouter problems when the functions signature did contains +references. So to manage that I did implement: + +* A ``BaseArg`` class and it's template derived ``Arg`` to hold + one value of a type (more or less like ``std::any<>``). + The type of the value attribute of ``Arg`` is ``T`` *stripped* + from reference and constness. This internal type is accessible + through ``Arg::ValueT``. + +* A template list of arguments ``Args`` analogous to + ``std::tuple<>`` which holds a table of ``BaseArg`` to convert all the + arguments. + +* A recursive template converter function ``parse_pyobjects<>``, which is + called through the ``Args<>::parse()`` function. Another challenge is the return type. I distinguish three flavor of return type: @@ -332,50 +431,96 @@ return type: To uniformize the return type we create four templates ``_callMethodReturn<>()`` that takes whatever the C++ return type is, and turn it into a ``PyObject*``. -Except for the functions returning ``void``, we call ``objectLink<>()`` to -wrap the value. +Except for the functions returning ``void``, we call ``cToPy<>()`` to +wrap the value. Given the return type of the method, only one template +will match. But as functions template do not allow partial specialization, +only one must be defined for that method (the one *matching* it's +return type), so we make the template mutually exclusives based on +the ``TR`` type (with the ``std::enable_if<>`` clause). + + +.. note:: In the various ``_callMethodReturn<>`` we have *two* sets for the + method parameters types : ``TArgsF...`` and ``TArgsW...``. This is to + allow a wider range of matching in the template as the type of the + arguments of the method (``TArgsF...``) may not *exactly* matches the + one passed by the wrapper (``TArgsW...``), typically the method has + a ``const`` parameter which is non-``const`` in the wrapper. Here is an excerpt of the code: .. code-block:: C++ - + + // Flavor for "return by value" (seems to match std::is_object<>) + template< typename TC, typename TR, typename... TArgsF, typename... TArgsW + , typename std::enable_if< !std::is_reference::value + && !std::is_pointer ::value + && !std::is_void ::value,bool>::type = true > + inline PyObject* _callMethodReturn ( TR(TC::* method)(TArgsF...), TC* cppObject, TArgsW... args ) + { + TR value = (cppObject->*method)( args... ); + return cToPy( value ); + } + + // Flavor for "return by reference" + template< typename TC, typename TR, typename... TArgsF, typename... TArgsW + , typename std::enable_if< std::is_reference::value + && !std::is_pointer ::value + && !std::is_void ::value,bool>::type = true > + inline PyObject* _callMethodReturn ( TR(TC::* method)(TArgsF...), TC* cppObject, TArgsW... args ) + { + TR rvalue = (cppObject->*method)( args... ); + return cToPy( rvalue ); + } + // Flavor for "return by pointer". - template< typename TC, typename TR, typename... TArgs + template< typename TC, typename TR, typename... TArgsF, typename... TArgsW , typename std::enable_if::value,bool>::type = true > - inline PyObject* _callMethodReturn ( TR(TC::* method)(TArgs...), TC* cppObject, TArgs... args ) + inline PyObject* _callMethodReturn ( TR(TC::* method)(TArgsF...), TC* cppObject, TArgsW... args ) { TR pvalue = (cppObject->*method)( args... ); - return objectLink( pvalue ); + return cToPy( pvalue ); } - + // Flavor for "return void". - template< typename TC, typename TR, typename... TArgs + template< typename TC, typename TR, typename... TArgsF, typename... TArgsW , typename std::enable_if::value,bool>::type = true > - inline PyObject* _callMethodReturn ( TR(TC::* method)(TArgs...), TC* cppObject, TArgs... args ) + inline PyObject* _callMethodReturn ( TR(TC::* method)(TArgsF...), TC* cppObject, TArgsW... args ) { (cppObject->*method)( args... ); Py_RETURN_NONE; } - - // Function without argument. + + // Make the translation call for a method without arguments. template< typename TC, typename TR > - inline PyObject* _callMethod ( TR(TC::* method)(), TC* cppObject, std::tuple<> ) + inline PyObject* _callMethod ( TR(TC::* method)(), TC* cppObject, Args<>& ) { return _callMethodReturn( method, cppObject ); } - - // Function with one argument. + + // Make the translation call for a method one argument. template< typename TC, typename TR, typename TA0 > - inline PyObject* _callMethod ( TR(TC::* method)(TA0), TC* cppObject, std::tuple args ) - { return _callMethodReturn( method, cppObject, std::get<0>(args) ); } - - // Function with two arguments. + inline PyObject* _callMethod ( TR(TC::* method)(TA0), TC* cppObject, Args& args ) + { return _callMethodReturn( method, cppObject, as( args[0] ) ); } + + // Make the translation call for a method two argument. template< typename TC, typename TR, typename TA0, typename TA1 > - PyObject* _callMethod ( TR(TC::* method)(TA0,TA1), TC* cppObject, std::tuple args ) - { return _callMethodReturn( method, cppObject, std::get<0>(args), std::get<1>(args) ); } + PyObject* _callMethod ( TR(TC::* method)(TA0,TA1), TC* cppObject, Args& args ) + { return _callMethodReturn( method, cppObject, as( args[0] ), as( args[1] ) ); } -The complete work of translating the Python tuple into a ``std::tuple<>`` and error -handling is done with a dedicated template class ``PyMethodWrapper`` and it's ``call()`` -method. +The complete work of translating the Python tuple into a ``Args<>`` is done inside +a dedicated template class ``PyWrapper`` and it's ``call()`` method. +C++ exceptions are catched and translated into Python ones. + +* ``PyWrapper`` the base class wich handle both C++ and Python exceptions. + Provides the ``call()`` function which in turn wraps the ``_call()`` that + must be overloaded in derived classes. + +* ``PyFunctionWrapper<>``, template derived class for C/C++ normal functions. + +* ``PyMethodWrapper<>``, template derived class for C++ class methods. + Two flavors are supported, the real method and a function build upon a + method (first argument beaing the object itself). The later is used when + we need to desambiguate overloaded functions, we must create one *function* + per overload. As a class template cannot guess the template parameters, we wrap them into a function template which can perform the guess. The ``callMethod<>`` template function. @@ -384,7 +529,7 @@ In the end, what the user can write is simply: .. code-block:: C++ - static PyObject* PyParameter_addValue ( PyVoidPointer* self, PyObject* args ) + static PyObject* PyParameter_addValue ( PyObject* self, PyObject* args ) { return callMethod("Parameter.addValue",&Parameter::addValue,self,args); } PyMethodDef PyParameter_Methods[] = @@ -400,7 +545,7 @@ In the end, what the user can write is simply: 2.6 Case of C++ overloaded functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This apply to both overloaded functions and functions with arguments values. +This apply to both overloaded functions and functions with default arguments values. In that case, the only solution is to create a set of different functions with differents arguments to expose all the various signature of the function. @@ -433,7 +578,7 @@ support both case (through different constructors). , Parameter::Priority pri ) { return self->setString(value,pri,flags); } - static PyObject* PyParameter_setString ( PyVoidPointer* self, PyObject* args ) + static PyObject* PyParameter_setString ( PyObject* self, PyObject* args ) { PyObject* rvalue = callMethod("Parameter.setString",&setString3,self,args); if (not rvalue) rvalue = callMethod("Parameter.setString",&setString2,self,args); @@ -484,7 +629,7 @@ the ``Priority`` ``enum``. .. code-block:: C++ template<> - inline void pyTypePostModuleInit ( PyTypeObject* typeObject ) + inline void pyTypePostInit ( PyTypeObject* typeObject ) { PyTypeManagerNonDBo::create( (PyObject*)typeObject , Cfg::PyParameterPriority_Methods @@ -493,7 +638,7 @@ the ``Priority`` ``enum``. } template<> - inline void pyTypePostModuleInit ( PyTypeObject* typeObject ) + inline void pyTypePostInit ( PyTypeObject* typeObject ) { // Parameter::Priority enum. addConstant( typeObject, "UseDefault" , Cfg::Parameter::UseDefault ); diff --git a/documentation/content/pages/python-cpp-new/Introduction.rst b/documentation/content/pages/python-cpp-new/Introduction.rst index e0221a5a..10551fc0 100644 --- a/documentation/content/pages/python-cpp-new/Introduction.rst +++ b/documentation/content/pages/python-cpp-new/Introduction.rst @@ -12,7 +12,7 @@ directly your functions with the original API or any mix between. You only have to respect some naming convention. -* Coriolis is build against Python 2.7. +* Coriolis is build against Python 3.6. 1.1 About Technical Choices @@ -52,10 +52,10 @@ Some would say, why not use *off the shelf* wrappers like ``swig``, #. **Linking accross modules.** As far as I understand, the wrappers are for monolithic libraries. That is, you wrap the entire library - in one go. But Hurricane has a modular design, the core database + in one go. But Coriolis has a modular design, the core database then various tools. We do not, and cannot, have one gigantic wrapper that would encompass all the libraries in one go. We do one Python - module for one C++ library. + module for each C++ library. This brings another issue, at Python level this time. The Python modules for the libraries have to share some functions. Python diff --git a/documentation/output/pages/python-cpp-new.html b/documentation/output/pages/python-cpp-new.html index 4fe139b7..06c32538 100644 --- a/documentation/output/pages/python-cpp-new.html +++ b/documentation/output/pages/python-cpp-new.html @@ -153,7 +153,7 @@ the standard Python C/API. That is, you may not use them and write directly your functions with the original API or any mix between. You only have to respect some naming convention. -
  • Coriolis is build against Python 2.7.
  • +
  • Coriolis is build against Python 3.6.
  • 1.1 About Technical Choices

    @@ -189,10 +189,10 @@ by the PyTypeManager::_link()
  • Linking accross modules. As far as I understand, the wrappers are for monolithic libraries. That is, you wrap the entire library -in one go. But Hurricane has a modular design, the core database +in one go. But Coriolis has a modular design, the core database then various tools. We do not, and cannot, have one gigantic wrapper that would encompass all the libraries in one go. We do one Python -module for one C++ library.

    +module for each C++ library.

    This brings another issue, at Python level this time. The Python modules for the libraries have to share some functions. Python provides a mechanism to pass C function pointers accross modules, @@ -218,83 +218,107 @@ hides the Python/C API. Instead we keep mostly visible the classic structure of the Python/C API but we provides templates to automate as much as possible the boring tasks (and code duplication). This way, if we need a very specific feature at some point, we can still revert back to the pure Python/C API.

    -

    The key features of our wrapper are:

    +

    The wrapper basically provides support for three kind of operations:

    +
      +
    1. Encapsulate C++ object in Python ones, done by cToPy<>() template. +Template specializations are defined for the POD and basic STL +types like std::string.

      +

      To add more easily new specializations, they resides in the top level +scope (not inside Isobar).

      +
    2. +
    3. Decapsulate a C++ object from a Python one, done by pyToC(). +It's implementation is slightly different from the one of cToPy<>() +in the sense that it is a mix of normal C++ functions and template +functions. I was having trouble inside the callMethod() to access +to templates specialization defined after that point, so function be +it.

      +

      There are two mutually exclusives versions of the pyToC<>() for +objects managed through the type manager. One is for value types and +the other for pointer types.

      +
    4. +
    5. Wrap C/C++ functions & methods inside C-linkage PyCFunction, that is +PyOject* (*)(PyObject*, PyObject*). This is done respectively through +callFunction<>() and callMethod<>(). callMethod<>() having +two variants, one for directly calling the function member, if there is +no overload and one for calling one given flavor of the overloaded +function member.

      +

      In addition we provides special variants for Python unary, binary and +operator functions.

      +
    6. +
    +

    In addition to those user exposed features, we have:

    • The PyTypeManager and it's derived classes to store and share informations about all our newly defined PyTypeObjects.
    • -
    • The callMethod<>() and callFunction<>() function templates to -automatically generate a wrapper around C++ object methods or functions.
    • -
    • The PyBoject* objectLink<CppT>( CppT* ) functions templates that provides C++ to Python -object translation.
    • -
    • The bool pyAs<CppT>( PyObject*, CppT*& ) functions templates that provides Python to C++ -object translation.
    • +
    • Various wrapper classes to wrap functions & methods. They are not directly +exposed because the template class intanciation needs the template parameters +to be explicitely given, wich is tedious. Instead we create them through +a function template call, which will perform for us the template type +deduction.

    We creates only two kind of PyObject (but many PyTypeObject):

      -
    • PyVoidpointer which encapsulate one void pointer to the C++ associated +

    • PyOneVoid which encapsulate one void pointer to the C++ associated object.

      extern "C" {
      -  typedef struct PyVoidPointer {
      +  typedef struct PyOneVoid {
           PyObject_HEAD
      -    void* _object;
      +    void* _object1;
         };
       }
       
    • -
    • PyIteratorPointer which encapsulate one void pointer to the C++ associated -iterator and one another to the PyObject of the container.

      +
    • PyTwoVoid which encapsulate one void pointer to the C++ associated +object (an iterator) and one another to the PyObject of the container.

      extern "C" {
      -  typedef struct PyVoidPointer {
      +  typedef struct PyTwoVoid {
           PyObject_HEAD
      -    void*          _object;     // C++ iterator.
      -    PyVoidPointer* _container;  // Python wrapped container.
      +    void*     _object1;  // C++ iterator.
      +    PyObject* _object2;  // Python wrapped container.
         };
       }
       
    +
    +

    Note

    +

    A PyTwoVoid can be casted/accessed as a PyOneVoid.

    +

    2.1 PyTypeManager

    PyTypeManager has two usage:

    -
      -
    • Act as a registry of all the created PyTypeObject, and serve as a -dispatcher for the PyTypeObject tp like methods.

      -
    • -
    • Provide a non-template abstract base class for all the derived PyTypeObject. +

        +
      • Act as a registry of all the created PyTypeObject, and serve as a +dispatcher for the PyTypeObject tp like methods.
      • +
      • Provide a non-template abstract base class for all the derived PyTypeObject. As said, it is not a template class but it supplies function member -template. Derived classes are provides for different kind of C++ -class.

        -
          -
        • PyTypeManagerVTrunk<CppT>

          -

          Is an intermediate between the non-template base class and all the -templatized others.

          -
        • -
        • PyTypeManagerNonDBo<CppT>

          -

          Template for standalone C++ classes that are not derived from DBo. -For example Box or Parameter.

          -
        • -
        • PyTypeManagerDBo<CppT>

          -

          Template for C++ classes that are not derived from DBo. -For example Cell or Instance.

          -
        • -
        • PyTypeManagerVector<CppT>, template for C++ std::vector<CppT*>.

          -
        • -
        • PyTypeManagerVectorIterator<CppT>

          -

          Template for C++ std::vector<CppT*>::iterator, automatically created -from the vector registration.

          -
        • -
        • PyTypeManagerMap<CppK,CppT>, template for C++ std::map<CppK*,CppT*>.

          -
        • -
        • PyTypeManagerMapIterator<CppK,CppT>

          -

          Template for C++ std::vector<CppK*,CppT*>::iterator, automatically created -from the map registration.

          -
        • -
        • PyTypeManagerCollection<,CppT>, template for C++ Hurricane::Collection<CppT*>.

          -
        • -
        • PyTypeManagerCollectionIterator<,CppT>

          -

          Template for C++ Hurricane::Locator<CppT*>, automatically created from -the collection registration.

          -
        • +template. Derived classes are provided to manage different kind of C++ +classes.
            +
          • PyTypeManagerVTrunk<CppT> +Is an intermediate between the non-template base class and all the +templatized others (do not use it directly).
          • +
          • PyTypeManagerNonDBo<CppT> +Template for standalone C++ classes that are not derived from DBo. +For example Box or Parameter.
          • +
          • PyTypeManagerDBo<CppT> +Template for C++ classes that are not derived from DBo. +For example Cell or Instance.
          • +
          • PyTypeManagerDerivedDBo<CppT,BaseT> +Template for a CppT class derived derived from a BaseT class. +CppT doesn'y need to be a direct derived of BaseT. BaseT +needs to derive from DBo
          • +
          • PyTypeManagerVector<CppT>, template for C++ std::vector<CppT*>.
          • +
          • PyTypeManagerVectorIterator<CppT> +Template for C++ std::vector<CppT*>::iterator, automatically created +from the vector registration.
          • +
          • PyTypeManagerMap<CppK,CppT>, template for C++ std::map<CppK*,CppT*>.
          • +
          • PyTypeManagerMapIterator<CppK,CppT> +Template for C++ std::vector<CppK*,CppT*>::iterator, automatically created +from the map registration.
          • +
          • PyTypeManagerCollection<,CppT>, template for C++ Hurricane::Collection<CppT*>.
          • +
          • PyTypeManagerCollectionIterator<,CppT> +Template for C++ Hurricane::Locator<CppT*>, automatically created from +the collection registration.
        @@ -304,7 +328,7 @@ the collection registration.

        Functions of a PyTypeObject like the tp methods (tp_alloc, tp_print, tp_hash, ...) must have a C-linkage. So we create one function per slot that we want to use, set that same function for all the created PyTypeObject, and -perform a dispact in it. The drawback is that for each access we have to perform +perform a dispacth in it. The drawback is that for each access we have to perform a map lookup. Hope it is fast.

        Excerpt from the code:

        namespace Isobar3 {
        @@ -315,7 +339,7 @@ a map lookup. Hope it is fast.

        extern long _tpHash ( PyObject* self ) { // Dispatch towards the relevant class, based on ob_type pointer. - return PyTypeManager::get( self->ob_type )->_getTpHash( asVPtr(self) ); + return PyTypeManager::get( Py_TYPE(self)->_getTpHash( self ); } } @@ -324,7 +348,7 @@ a map lookup. Hope it is fast.

        public: void PyTypeManager::_setupPyType () // Derived classes must implement it as they see fit. - virtual long _getTpHash ( PyVoidPointer* ) = 0; + virtual long _getTpHash ( PyObject* ) = 0; template<typename CppT> static PyTypeManager* _get(); private: @@ -349,57 +373,94 @@ a map lookup. Hope it is fast.

        2.3 Going From Python to C++

        -

        To convert a C++ object (pointer) into a Python object, a set of -pyAs<> templates functions are supplieds.

        +

        To convert a C++ object (pointer) into a Python object, a mix of +pyToC<>() templates functions and real functions are supplieds.

        +

        Templates functions are used for all types/classes managed through +the PyTypeManger. They come in two flavors:

        +
          +
        1. Value as pointer version (C++ argment type is T*): +The encapsulated C++ object is still a pointer, +but to a stand-alone one which has been created for the sole +purpose of this PyObject. Typical example is the Box. +Then, we copy by value the contents of the pointed object into +the contents of the pointer argument that we where given.
        2. +
        3. Pointer version (C++ argument type is T**): +The encapsulated C++ object is truly a pointer +to an element of the data-structure, then we just extract the +C++ pointer value.
        4. +
        +

        Normal function overload are used for POD types (bool, int, +long, double, ...) and basic STL types (std::string, ...).

        Specialization for all POD type that can be directly translated into Python types must be provideds (bool, int, long, double, std::string, ...).

        -

        Partial specialization for type availables through the PyTypeManager -is supplied. It checks the manager for the type's existence.

        -
        // Most generic template, should *not* be used.
        -// So issue an error message and report a failed conversion.
        -template<typename T>
        -inline bool  pyAs ( PyObject* pyArg, T& arg )
        +

        Those templates/functions are the ones the Isobar::parse_objects() recursive +template function call in turn for each PyObject* argument.

        +
        +

        Note

        +

        Hurricane::Name are not exposed to the Python interface, they +must be treated as std::string.

        +
        +
        // Template/Pointer to a value flavor.
        +template< typename T
        +        , typename std::enable_if< !std::is_pointer<T>::value, bool >::type = true >
        +inline bool  pyToC ( PyObject* pyArg, T* arg )
         {
        -  std::cerr << "Isobar3.pyAs<T>(): Unsupported type \""
        -            << demangle(typeid(T).name()) << "\"" << endl;
        -  return false;
        +  typedef typename std::remove_cv<T>::type  NonConstT;
        +  Isobar3::PyTypeManager* manager = Isobar3::PyTypeManager::_get<T>();
        +  if (not manager) {
        +    std::cerr << "Isobar3::pyToC<>(const T*): Unsupported type." << std::endl;
        +    return false;
        +  }
        +  if (Py_TYPE(pyArg) != manager->_getTypeObject()) return false;
        +  *(const_cast< NonConstT* >(arg)) = *(( T* )( Isobar3::object1( pyArg )));
        +  return true;
         }
         
        -// Template specialization for POD type "int".
        -template<>
        -inline bool  pyAs ( PyObject* pyArg, int& arg )
        +// Template/Pointer to a pointer flavor.
        +template<typename T>
        +inline bool  pyToC ( PyObject* pyArg, T** arg )
         {
        -  if      (PyInt_Check (pyArg)) { arg = PyInt_AsLong ( pyArg ); }
        -  else if (PyLong_Check(pyArg)) { arg = PyLong_AsLong( pyArg ); }
        +  Isobar3::PyTypeManager* manager = Isobar3::PyTypeManager::_get<T>();
        +  if (not manager) {
        +    std::cerr << "Isobar3::pyToC<T>(T*&): Unsupported type \"" << typeid(T).name() << "\"" << std::endl;
        +    return false;
        +  }
        +  *arg = (T*)( Isobar3::object1( pyArg ));
        +  return true;
        +}
        +
        +// True function overload for std::string.
        +inline bool  pyToC ( PyObject* pyArg, std::string* arg )
        +{
        +  if (not PyUnicode_Check(pyArg)) return false;
        +  PyObject* pyBytes = PyUnicode_AsASCIIString( pyArg );
        +  *arg = PyBytes_AsString( pyBytes );
        +  Py_DECREF( pyBytes );
        +  return true;
        +}
        +
        +// True function overload for bool.
        +inline bool  pyToC ( PyObject* pyArg, bool* arg )
        +{
        +  if (not PyBool_Check(pyArg)) return false;
        +  *arg = (pyArg == Py_True);
        +  return true;
        +}
        +
        +// True function overload for int.
        +inline bool  pyToC ( PyObject* pyArg, int* arg )
        +{
        +  if (PyLong_Check(pyArg)) { *arg = PyLong_AsLong( pyArg ); }
           else return false;
           return true;
         }
        -
        -// Template partial specialization for any type registered in
        -// the PyTypeManager.
        -template<typename T>
        -inline bool  pyAs ( PyObject* pyArg, T*& arg )
        -{
        -  PyTypeManager* manager = PyTypeManager::_get<T>();
        -  if (not manager) {
        -    std::cerr << "Isobar3.pyAs<T>(): Unsupported type \""
        -              << typeid(T).name() << "\"" << endl;
        -    return false;
        -  }
        -  arg = (T*)( asVPtr( pyArg )->_object );
        -  return true;
        -}
        -
        -template<typename T>
        -inline bool  pyAs ( PyVoidPointer* pyArg, T*& arg )
        -{ return pyAs( (PyObject*)pyArg, arg ); }
         

        2.4 Going From C++ to Python

        To convert a Python object into a C++ object, a set of -objectLink<> templates functions are supplieds.

        +cToPy<> templates functions are supplieds.

        We completely disable the partially specialized templates for objects that are non-POD as the compiler seems to be unable to choose the fully specialized template in this case (or I still @@ -414,45 +475,64 @@ method.

        why we need the intermediate PyTypeManagerVTrunk<CppT> template class.

        -
        // The most generic template, should *not* be used. So raise a Python exception.
        +
        +

        Note

        +

        Different C++ templates. You may notice that the two following templates +may look like specializations of the same one:

        +
          +
        • template<typename CppT> PyObject* cToPy ( CppT  object )
        • +
        • template<typename CppT> PyObject* cToPy ( CppT* object )
        • +
        +

        Which would be illegal (function templates are not allowed to have partial +specialization), but they are not. The two pairs +(template parameter,function parameter), that is (CppT,CppT) and +(CppT,CppT*) cannot be made to be a specialization of each other.

        +
        +
        // Generic template for values.
         template< typename CppT >
        -inline PyObject* objectLink ( CppT object )
        +inline PyObject* cToPy ( CppT object )
         {
        -  std::string message = "Overload for Isobar3::objectLink< "
        -                      + demangle(typeid(CppT).name()) + " >() is missing.";
        -  PyErr_SetString( HurricaneError, message.c_str() );
        -  return NULL;
        +  if (not Isobar3::PyTypeManager::hasType<CppT>()) {
        +    std::string message = "Overload for Isobar3::cToPy< "
        +                        + Hurricane::demangle(typeid(CppT).name()) + " >() Type not registered in the manager.";
        +    PyErr_SetString( Isobar3::HurricaneError, message.c_str() );
        +    return NULL;
        +  }
        +  return Isobar3::PyTypeManager::link<CppT>( new CppT (object) );
         }
         
        -// Disable this template if "CppT" is an already supported type.
        +// Disabled for POD & STL types, pointer flavor.
         template< typename CppT
                 , typename std::enable_if<   !std::is_same<CppT,bool>::value
                                           && !std::is_same<CppT,int >::value
                                           && !std::is_same<CppT,std::string>::value
        -                                  && !std::is_same<CppT,const std::string>::value
        -                                  ,bool>::type = true >
        -inline PyObject* objectLink ( CppT* object )
        -{ return PyTypeManager::link<CppT>( object ); }
        +                                  && !std::is_same<CppT,const std::string>::value,bool>::type = true >
        +inline PyObject* cToPy ( CppT* object )
        +{ return Isobar3::PyTypeManager::link<CppT>( object ); }
         
        -// Disable this template if "CppT" is an already supported type.
        +// Disabled for POD & STL types, const pointer flavor.
         template< typename CppT
                 , typename std::enable_if<   !std::is_same<CppT,bool>::value
                                           && !std::is_same<CppT,int >::value
                                           && !std::is_same<CppT,std::string>::value
        -                                  && !std::is_same<CppT,const std::string>::value
        -                                  ,bool>::type = true >
        -inline PyObject* objectLink ( const CppT* object )
        -{ return PyTypeManager::link<CppT>( const_cast<CppT*>( object )); }
        +                                  && !std::is_same<CppT,const std::string>::value,bool>::type = true >
        +inline PyObject* cToPy ( const CppT* object )
        +{ return Isobar3::PyTypeManager::link<CppT>( const_cast<CppT*>( object )); }
         
        -// Specializations for POD type "int" .
        -template<> inline PyObject* objectLink<int > ( int  i )
        -{ return PyInt_FromLong(  i ); }
        +// Specialization for booleans.
        +template<>
        +inline PyObject* cToPy<bool> ( bool b )
        +{ if (b) Py_RETURN_TRUE; Py_RETURN_FALSE; }
         
        -template<> inline PyObject* objectLink<const int > ( const int  i )
        -{ return PyInt_FromLong(  i ); }
        +// Specialization for STL std::string.
        +template<> inline PyObject* cToPy<      std::string>  (       std::string  s ) { return PyUnicode_FromString( s.c_str() ); }
        +template<> inline PyObject* cToPy<const std::string > ( const std::string  s ) { return PyUnicode_FromString( s.c_str() ); }
        +template<> inline PyObject* cToPy<const std::string*> ( const std::string* s ) { return PyUnicode_FromString( s->c_str() ); }
         
        -template<> inline PyObject* objectLink<const int*> ( const int* i )
        -{ return PyInt_FromLong( *i ); }
        +// Specialization for POD int.
        +template<> inline PyObject* cToPy<      int > (       int  i ) { return PyLong_FromLong(  i ); }
        +template<> inline PyObject* cToPy<const int > ( const int  i ) { return PyLong_FromLong(  i ); }
        +template<> inline PyObject* cToPy<const int*> ( const int* i ) { return PyLong_FromLong( *i ); }
         
        @@ -484,8 +564,8 @@ to a sequence of PyObject* (that must match th of it's C++ function conterpart).

      -

      So, the problem is to change a tuple which size is only kown at runtime -into a list of C/C++ parameters known at compile time.

      +

      So, the problem is to change a Python tuple which size is only kown at +runtime into a list of C/C++ parameters known at compile time.

      I am not such an expert in template programming so I can find a generic solution able to handle any number of parameters. Instead I did write a set of templates managing the translation from zero to ten parameters. @@ -493,7 +573,21 @@ I did delay that translation as much as possible so it happens very close to the C++ function call and the duplicated code needed for each template is kept to a minimum.

      To translate the Python tuple into an ordered list (vector like) of C++ -object of different types, the obvious choice is C++ std::tuple<>.

      +object of different types, the obvious choice should have been std::tuple<>, +but I did encouter problems when the functions signature did contains +references. So to manage that I did implement:

      +
        +
      • A BaseArg class and it's template derived Arg<T> to hold +one value of a type (more or less like std::any<>). +The type of the value attribute of Arg<T> is T stripped +from reference and constness. This internal type is accessible +through Arg<T>::ValueT.
      • +
      • A template list of arguments Args<typename... Ts> analogous to +std::tuple<> which holds a table of BaseArg to convert all the +arguments.
      • +
      • A recursive template converter function parse_pyobjects<>, which is +called through the Args<>::parse() function.
      • +

      Another challenge is the return type. I distinguish three flavor of return type:

        @@ -504,49 +598,95 @@ return type:

      To uniformize the return type we create four templates _callMethodReturn<>() that takes whatever the C++ return type is, and turn it into a PyObject*. -Except for the functions returning void, we call objectLink<>() to -wrap the value.

      +Except for the functions returning void, we call cToPy<>() to +wrap the value. Given the return type of the method, only one template +will match. But as functions template do not allow partial specialization, +only one must be defined for that method (the one matching it's +return type), so we make the template mutually exclusives based on +the TR type (with the std::enable_if<> clause).

      +
      +

      Note

      +

      In the various _callMethodReturn<> we have two sets for the +method parameters types : TArgsF... and TArgsW.... This is to +allow a wider range of matching in the template as the type of the +arguments of the method (TArgsF...) may not exactly matches the +one passed by the wrapper (TArgsW...), typically the method has +a const parameter which is non-const in the wrapper.

      +

      Here is an excerpt of the code:

      -
      // Flavor for "return by pointer".
      -template< typename TC, typename TR, typename... TArgs
      +
      // Flavor for "return by value" (seems to match std::is_object<>)
      +template< typename TC, typename TR, typename... TArgsF, typename... TArgsW
      +        , typename std::enable_if<   !std::is_reference<TR>::value
      +                                  && !std::is_pointer  <TR>::value
      +                                  && !std::is_void     <TR>::value,bool>::type = true >
      +inline PyObject* _callMethodReturn ( TR(TC::* method)(TArgsF...), TC* cppObject, TArgsW... args )
      +{
      +  TR value = (cppObject->*method)( args... );
      +  return cToPy( value );
      +}
      +
      +// Flavor for "return by reference"
      +template< typename TC, typename TR, typename... TArgsF, typename... TArgsW
      +        , typename std::enable_if<    std::is_reference<TR>::value
      +                                  && !std::is_pointer  <TR>::value
      +                                  && !std::is_void     <TR>::value,bool>::type = true >
      +inline PyObject* _callMethodReturn ( TR(TC::* method)(TArgsF...), TC* cppObject, TArgsW... args )
      +{
      +  TR rvalue = (cppObject->*method)( args... );
      +  return cToPy( rvalue );
      +}
      +
      +// Flavor for "return by pointer".
      +template< typename TC, typename TR, typename... TArgsF, typename... TArgsW
               , typename std::enable_if<std::is_pointer<TR>::value,bool>::type = true >
      -inline PyObject* _callMethodReturn ( TR(TC::* method)(TArgs...), TC* cppObject, TArgs... args )
      +inline PyObject* _callMethodReturn ( TR(TC::* method)(TArgsF...), TC* cppObject, TArgsW... args )
       {
         TR pvalue = (cppObject->*method)( args... );
      -  return objectLink( pvalue );
      +  return cToPy( pvalue );
       }
       
       // Flavor for "return void".
      -template< typename TC, typename TR, typename... TArgs
      +template< typename TC, typename TR, typename... TArgsF, typename... TArgsW
               , typename std::enable_if<std::is_void<TR>::value,bool>::type = true >
      -inline PyObject* _callMethodReturn ( TR(TC::* method)(TArgs...), TC* cppObject, TArgs... args )
      +inline PyObject* _callMethodReturn ( TR(TC::* method)(TArgsF...), TC* cppObject, TArgsW... args )
       {
         (cppObject->*method)( args... );
         Py_RETURN_NONE;
       }
       
      -// Function without argument.
      +// Make the translation call for a method without arguments.
       template< typename TC, typename TR >
      -inline PyObject* _callMethod ( TR(TC::* method)(), TC* cppObject, std::tuple<> )
      +inline PyObject* _callMethod ( TR(TC::* method)(), TC* cppObject, Args<>& )
       { return _callMethodReturn<TC,TR>( method, cppObject ); }
       
      -// Function with one argument.
      +// Make the translation call for a method one argument.
       template< typename TC, typename TR, typename TA0 >
      -inline PyObject* _callMethod ( TR(TC::* method)(TA0), TC* cppObject, std::tuple<TA0> args )
      -{ return _callMethodReturn( method, cppObject, std::get<0>(args) ); }
      +inline PyObject* _callMethod ( TR(TC::* method)(TA0), TC* cppObject, Args<TA0>& args )
      +{ return _callMethodReturn( method, cppObject, as<TA0>( args[0] ) ); }
       
      -// Function with two arguments.
      +// Make the translation call for a method two argument.
       template< typename TC, typename TR, typename TA0, typename TA1 >
      -PyObject* _callMethod ( TR(TC::* method)(TA0,TA1), TC* cppObject, std::tuple<TA0,TA1> args )
      -{ return _callMethodReturn( method, cppObject, std::get<0>(args), std::get<1>(args) ); }
      +PyObject* _callMethod ( TR(TC::* method)(TA0,TA1), TC* cppObject, Args<TA0,TA1>& args )
      +{ return _callMethodReturn( method, cppObject, as<TA0>( args[0] ), as<TA1>( args[1] ) ); }
       
      -

      The complete work of translating the Python tuple into a std::tuple<> and error -handling is done with a dedicated template class PyMethodWrapper and it's call() -method.

      +

      The complete work of translating the Python tuple into a Args<> is done inside +a dedicated template class PyWrapper and it's call() method. +C++ exceptions are catched and translated into Python ones.

      +
        +
      • PyWrapper the base class wich handle both C++ and Python exceptions. +Provides the call() function which in turn wraps the _call() that +must be overloaded in derived classes.
      • +
      • PyFunctionWrapper<>, template derived class for C/C++ normal functions.
      • +
      • PyMethodWrapper<>, template derived class for C++ class methods. +Two flavors are supported, the real method and a function build upon a +method (first argument beaing the object itself). The later is used when +we need to desambiguate overloaded functions, we must create one function +per overload.
      • +

      As a class template cannot guess the template parameters, we wrap them into a function template which can perform the guess. The callMethod<> template function.

      In the end, what the user can write is simply:

      -
      static PyObject* PyParameter_addValue ( PyVoidPointer* self, PyObject* args )
      +
      static PyObject* PyParameter_addValue ( PyObject* self, PyObject* args )
       { return callMethod("Parameter.addValue",&Parameter::addValue,self,args); }
       
       PyMethodDef PyParameter_Methods[] =
      @@ -561,7 +701,7 @@ function template which can perform the guess. The 
       

      2.6 Case of C++ overloaded functions

      -

      This apply to both overloaded functions and functions with arguments values.

      +

      This apply to both overloaded functions and functions with default arguments values.

      In that case, the only solution is to create a set of different functions with differents arguments to expose all the various signature of the function. We then create a function wrapper that calls them in decreasing number of @@ -589,7 +729,7 @@ support both case (through different constructors).

      , Parameter::Priority pri ) { return self->setString(value,pri,flags); } -static PyObject* PyParameter_setString ( PyVoidPointer* self, PyObject* args ) +static PyObject* PyParameter_setString ( PyObject* self, PyObject* args ) { PyObject* rvalue = callMethod("Parameter.setString",&setString3,self,args); if (not rvalue) rvalue = callMethod("Parameter.setString",&setString2,self,args); @@ -631,7 +771,7 @@ or constants values.

      set some constant values in Priority. This was we emulate the behavior of the Priority enum.

      template<>
      -inline void  pyTypePostModuleInit<Cfg::Parameter> ( PyTypeObject* typeObject )
      +inline void  pyTypePostInit<Cfg::Parameter> ( PyTypeObject* typeObject )
       {
         PyTypeManagerNonDBo<Cfg::Parameter::Priority>::create( (PyObject*)typeObject
                                                              , Cfg::PyParameterPriority_Methods
      @@ -640,7 +780,7 @@ the Priority enum
       }
       
       template<>
      -inline void  pyTypePostModuleInit<Cfg::Parameter::Priority> ( PyTypeObject* typeObject )
      +inline void  pyTypePostInit<Cfg::Parameter::Priority> ( PyTypeObject* typeObject )
       {
       // Parameter::Priority enum.
         addConstant( typeObject, "UseDefault"        , Cfg::Parameter::UseDefault );
      diff --git a/documentation/output/pdfs/CheckToolkit.pdf b/documentation/output/pdfs/CheckToolkit.pdf
      index 46216962..699a4913 100644
      Binary files a/documentation/output/pdfs/CheckToolkit.pdf and b/documentation/output/pdfs/CheckToolkit.pdf differ
      diff --git a/documentation/output/pdfs/PythonCpp.pdf b/documentation/output/pdfs/PythonCpp.pdf
      index 5c4dbcfb..70ff8777 100644
      Binary files a/documentation/output/pdfs/PythonCpp.pdf and b/documentation/output/pdfs/PythonCpp.pdf differ
      diff --git a/documentation/output/pdfs/PythonTutorial.pdf b/documentation/output/pdfs/PythonTutorial.pdf
      index 74465d57..365fae89 100644
      Binary files a/documentation/output/pdfs/PythonTutorial.pdf and b/documentation/output/pdfs/PythonTutorial.pdf differ
      diff --git a/documentation/output/pdfs/RDS.pdf b/documentation/output/pdfs/RDS.pdf
      index 9170d8fe..b3aa99c9 100644
      Binary files a/documentation/output/pdfs/RDS.pdf and b/documentation/output/pdfs/RDS.pdf differ
      diff --git a/documentation/output/pdfs/Stratus.pdf b/documentation/output/pdfs/Stratus.pdf
      index c80ec59d..c58d406a 100644
      Binary files a/documentation/output/pdfs/Stratus.pdf and b/documentation/output/pdfs/Stratus.pdf differ
      diff --git a/documentation/output/pdfs/UsersGuide.pdf b/documentation/output/pdfs/UsersGuide.pdf
      index aefbd7be..596f1bf3 100644
      Binary files a/documentation/output/pdfs/UsersGuide.pdf and b/documentation/output/pdfs/UsersGuide.pdf differ
      diff --git a/hurricane/src/configuration/PyTypeManager.cpp b/hurricane/src/configuration/PyTypeManager.cpp
      index cdeb173b..0073fcea 100644
      --- a/hurricane/src/configuration/PyTypeManager.cpp
      +++ b/hurricane/src/configuration/PyTypeManager.cpp
      @@ -224,95 +224,95 @@ namespace Isobar3 {
         { }
       
       
      -  PyObject* PyWrapper::call ( PyObject* self, PyObject* args )
      +  PyObject* PyWrapper::_call ( PyObject* self, PyObject* args )
         {
      -    throw Error( "PyWrapper::call(PyObject*,PyObject*): Base class method must never be called." );
      +    throw Error( "PyWrapper::_call(PyObject*,PyObject*): Base class method must never be called." );
           return NULL;
         }
       
       
      -  PyObject* PyWrapper::call ( PyObject* args )
      +  PyObject* PyWrapper::_call ( PyObject* args )
         {
      -    throw Error( "PyWrapper::call(PyObject*): Base class method must never be called." );
      +    throw Error( "PyWrapper::_call(PyObject*): Base class method must never be called." );
           return NULL;
         }
       
       
      -  int  PyWrapper::predicate ( PyObject* args )
      +  int  PyWrapper::_predicate ( PyObject* args )
         {
      -    throw Error( "PyWrapper::predicate(PyObject*): Base class method must never be called." );
      +    throw Error( "PyWrapper::_predicate(PyObject*): Base class method must never be called." );
           return 0;
         }
       
       
      -  PyObject* exceptionWrapper ( PyWrapper& wrapper, PyObject* self, PyObject* args )
      +  PyObject* exceptionWrapper ( PyWrapper* wrapper, PyObject* self, PyObject* args )
         {
           try {
      -      return wrapper.call( self, args );
      +      return wrapper->_call( self, args );
           } catch ( const Warning& w ) {
      -      wrapper.message() += "\n" + getString(w);
      +      wrapper->message() += "\n" + getString(w);
           } catch ( const Error& e ) {
      -      wrapper.message() += "\n" + getString(e);
      -      if (not e.where().empty()) wrapper.message() += "\n" + e.where();
      +      wrapper->message() += "\n" + getString(e);
      +      if (not e.where().empty()) wrapper->message() += "\n" + e.where();
           } catch ( const Bug& e ) {
      -      wrapper.message() += "\n" + getString(e);
      +      wrapper->message() += "\n" + getString(e);
           } catch ( const Exception& e ) {
      -      wrapper.message() += "\nUnknown Hurricane::Exception";
      +      wrapper->message() += "\nUnknown Hurricane::Exception";
           } catch ( const std::exception& e )  {
      -      wrapper.message() += "\n" + std::string(e.what());
      +      wrapper->message() += "\n" + std::string(e.what());
           } catch ( ... ) {
      -      wrapper.message() += "\nUnmanaged exception, neither a Hurricane::Error nor"
      -                           " a std::exception.";
      +      wrapper->message() += "\nUnmanaged exception, neither a Hurricane::Error nor"
      +                            " a std::exception.";
           }
      -    PyErr_SetString( HurricaneError, wrapper.message().c_str() );
      +    PyErr_SetString( HurricaneError, wrapper->message().c_str() );
           return NULL;
         }
       
       
      -  PyObject* exceptionWrapper ( PyWrapper& wrapper, PyObject* args )
      +  PyObject* exceptionWrapper ( PyWrapper* wrapper, PyObject* args )
         {
           try {
      -      return wrapper.call( args );
      +      return wrapper->_call( args );
           } catch ( const Warning& w ) {
      -      wrapper.message() += "\n" + getString(w);
      +      wrapper->message() += "\n" + getString(w);
           } catch ( const Error& e ) {
      -      wrapper.message() += "\n" + getString(e);
      -      if (not e.where().empty()) wrapper.message() += "\n" + e.where();
      +      wrapper->message() += "\n" + getString(e);
      +      if (not e.where().empty()) wrapper->message() += "\n" + e.where();
           } catch ( const Bug& e ) {
      -      wrapper.message() += "\n" + getString(e);
      +      wrapper->message() += "\n" + getString(e);
           } catch ( const Exception& e ) {
      -      wrapper.message() += "\nUnknown Hurricane::Exception";
      +      wrapper->message() += "\nUnknown Hurricane::Exception";
           } catch ( const std::exception& e )  {
      -      wrapper.message() += "\n" + std::string(e.what());
      +      wrapper->message() += "\n" + std::string(e.what());
           } catch ( ... ) {
      -      wrapper.message() += "\nUnmanaged exception, neither a Hurricane::Error nor"
      -                           " a std::exception.";
      +      wrapper->message() += "\nUnmanaged exception, neither a Hurricane::Error nor"
      +                            " a std::exception.";
           }
      -    PyErr_SetString( HurricaneError, wrapper.message().c_str() );
      +    PyErr_SetString( HurricaneError, wrapper->message().c_str() );
           return NULL;
         }
       
       
      -  int  exceptionPredicateWrapper ( PyWrapper& wrapper, PyObject* self )
      +  int  exceptionPredicateWrapper ( PyWrapper* wrapper, PyObject* self )
         {
           try {
      -      return wrapper.predicate( self );
      +      return wrapper->_predicate( self );
           } catch ( const Warning& w ) {
      -      wrapper.message() += "\n" + getString(w);
      +      wrapper->message() += "\n" + getString(w);
           } catch ( const Error& e ) {
      -      wrapper.message() += "\n" + getString(e);
      -      if (not e.where().empty()) wrapper.message() += "\n" + e.where();
      +      wrapper->message() += "\n" + getString(e);
      +      if (not e.where().empty()) wrapper->message() += "\n" + e.where();
           } catch ( const Bug& e ) {
      -      wrapper.message() += "\n" + getString(e);
      +      wrapper->message() += "\n" + getString(e);
           } catch ( const Exception& e ) {
      -      wrapper.message() += "\nUnknown Hurricane::Exception";
      +      wrapper->message() += "\nUnknown Hurricane::Exception";
           } catch ( const std::exception& e )  {
      -      wrapper.message() += "\n" + std::string(e.what());
      +      wrapper->message() += "\n" + std::string(e.what());
           } catch ( ... ) {
      -      wrapper.message() += "\nUnmanaged exception, neither a Hurricane::Error nor"
      -                           " a std::exception.";
      +      wrapper->message() += "\nUnmanaged exception, neither a Hurricane::Error nor"
      +                            " a std::exception.";
           }
      -    PyErr_SetString( HurricaneError, wrapper.message().c_str() );
      +    PyErr_SetString( HurricaneError, wrapper->message().c_str() );
           return 0;
         }
       
      diff --git a/hurricane/src/configuration/hurricane/configuration/PyTypeManager.h b/hurricane/src/configuration/hurricane/configuration/PyTypeManager.h
      index c680fa2d..7b97c1e2 100644
      --- a/hurricane/src/configuration/hurricane/configuration/PyTypeManager.h
      +++ b/hurricane/src/configuration/hurricane/configuration/PyTypeManager.h
      @@ -1576,28 +1576,6 @@ namespace Isobar3 {
       // Standard Python to C types converters.
       
       
      -template< typename T
      -        , typename std::enable_if< std::is_pointer::value,bool >::type = true >
      -inline bool  pyToC ( PyObject* pyArg, T* arg )
      -{
      -  typedef typename std::remove_pointer::type  ValueT;
      -  if (std::is_pointer::value) {
      -    Isobar3::PyTypeManager* manager = Isobar3::PyTypeManager::_get();
      -    if (not manager) {
      -      std::cerr << "Isobar3::pyToC<>(T**): Unsupported type." << std::endl;
      -      return false;
      -    }
      -    if (Py_TYPE(pyArg) != manager->_getTypeObject()) return false;
      -    *arg = (T)( Isobar3::object1( pyArg ));
      -    return true;
      -  }
      -  
      -  std::cerr << "Isobar3::pyToC(T*): Unsupported type \""
      -            << Hurricane::demangle(typeid(T).name()) << "\"" << std::endl;
      -  return false;
      -}
      -
      -
       template< typename T
               , typename std::enable_if< !std::is_pointer::value, bool >::type = true >
       inline bool  pyToC ( PyObject* pyArg, T* arg )
      @@ -1754,23 +1732,28 @@ namespace Isobar3 {
             inline               PyWrapper  ();
             virtual             ~PyWrapper  ();
             inline  std::string& message    ();
      -      virtual PyObject*    call       ( PyObject* self, PyObject* args );
      -      virtual PyObject*    call       ( PyObject* args );
      -      virtual int          predicate  ( PyObject* self );
      +      inline  PyObject*    call       ( PyObject* self, PyObject* args );
      +      inline  PyObject*    call       ( PyObject* args );
      +      inline  int          predicate  ( PyObject* self );
      +      virtual PyObject*    _call      ( PyObject* self, PyObject* args );
      +      virtual PyObject*    _call      ( PyObject* args );
      +      virtual int          _predicate ( PyObject* self );
             inline  void         setMessage ( std::string header );
           private:
             std::string  _message;
         };
       
       
      +  extern PyObject* exceptionWrapper          ( PyWrapper* wrapper, PyObject* self, PyObject* args );
      +  extern PyObject* exceptionWrapper          ( PyWrapper* wrapper, PyObject* args );
      +  extern int       exceptionPredicateWrapper ( PyWrapper* wrapper, PyObject* self );
      +
         inline               PyWrapper::PyWrapper  () : _message("Wrapper(): Base class.") {}
         inline  std::string& PyWrapper::message    () { return _message; }
         inline  void         PyWrapper::setMessage ( std::string header ) { _message = header; }
      -
      -
      -  extern PyObject* exceptionWrapper          ( PyWrapper& wrapper, PyObject* self, PyObject* args );
      -  extern PyObject* exceptionWrapper          ( PyWrapper& wrapper, PyObject* args );
      -  extern int       exceptionPredicateWrapper ( PyWrapper& wrapper, PyObject* self );
      +  inline PyObject*     PyWrapper::call       ( PyObject* self, PyObject* args ) { return exceptionWrapper( this, self, args ); }
      +  inline PyObject*     PyWrapper::call       ( PyObject* args ) { return exceptionWrapper( this, args ); }
      +  inline int           PyWrapper::predicate  ( PyObject* self ) { return exceptionPredicateWrapper( this, self ); }
       
       
       // -------------------------------------------------------------------
      @@ -1785,7 +1768,7 @@ namespace Isobar3 {
             inline               PyFunctionWrapper ( std::string fname, FunctionType method )
                                                    : PyWrapper(), _funcName(fname), _method(method) { };
             inline  std::string  funcName          () const { return _funcName; };
      -      virtual PyObject*    call              ( PyObject* fargs );
      +      virtual PyObject*    _call             ( PyObject* fargs );
           private:
             std::string   _funcName;
             FunctionType  _method;
      @@ -1793,7 +1776,7 @@ namespace Isobar3 {
       
         
         template< typename TR, typename... TArgs >
      -  PyObject* PyFunctionWrapper::call ( PyObject* fargs )
      +  PyObject* PyFunctionWrapper::_call ( PyObject* fargs )
         {
           PyErr_Clear();
         //std::cerr << "_call() " << demangle(typeid(FunctionType).name()) << std::endl;
      @@ -1855,7 +1838,7 @@ namespace Isobar3 {
                                                  , _fMethod(method)
                                                  { };
             inline  std::string  funcName        () const { return _funcName; };
      -      virtual PyObject*    call            ( PyObject* self, PyObject* fargs );
      +      virtual PyObject*    _call           ( PyObject* self, PyObject* fargs );
           private:
             std::string  _funcName;
             OMethodType  _oMethod;
      @@ -1864,7 +1847,7 @@ namespace Isobar3 {
       
       
         template< typename TC, typename TR, typename... TArgs >
      -  inline PyObject* PyMethodWrapper::call ( PyObject* self, PyObject* fargs )
      +  inline PyObject* PyMethodWrapper::_call ( PyObject* self, PyObject* fargs )
         {
           PyErr_Clear();
           size_t nargs   = sizeof...(TArgs);
      @@ -1925,7 +1908,7 @@ namespace Isobar3 {
                                                   , _oMethod(mbinary)
                                                   { };
             inline  std::string  funcName         () const { return _funcName; };
      -      virtual PyObject*    call             ( PyObject* self, PyObject* arg );
      +      virtual PyObject*    _call            ( PyObject* self, PyObject* arg );
           private:
             std::string  _funcName;
             OMethodType  _oMethod;
      @@ -1933,7 +1916,7 @@ namespace Isobar3 {
       
       
         template< typename TC, typename TArg >
      -  inline PyObject* PyMBinaryWrapper::call ( PyObject* pyObject, PyObject* pyArg )
      +  inline PyObject* PyMBinaryWrapper::_call ( PyObject* pyObject, PyObject* pyArg )
         {
           PyErr_Clear();
           setMessage( funcName() + "(): " );
      @@ -1962,14 +1945,14 @@ namespace Isobar3 {
                                                    , _funcName (fname)
                                                    { };
             inline  std::string  funcName          () const { return _funcName; };
      -      virtual PyObject*    call              ( PyObject* self, PyObject* arg );
      +      virtual PyObject*    _call             ( PyObject* self, PyObject* arg );
           private:
             std::string   _funcName;
         };
       
       
         template< typename TC, template class OperatorT >
      -  inline PyObject* PyOperatorWrapper::call ( PyObject* pyObject, PyObject* pyArg )
      +  inline PyObject* PyOperatorWrapper::_call ( PyObject* pyObject, PyObject* pyArg )
         {
           PyErr_Clear();
           setMessage( funcName() + "(): " );
      @@ -2002,7 +1985,7 @@ namespace Isobar3 {
                                                           , _oInPlace(minplace)
                                                           { };
             inline  std::string  funcName                 () const { return _funcName; };
      -      virtual PyObject*    call                     ( PyObject* self, PyObject* arg );
      +      virtual PyObject*    _call                    ( PyObject* self, PyObject* arg );
           private:
             std::string   _funcName;
             OInPlaceType  _oInPlace;
      @@ -2010,7 +1993,7 @@ namespace Isobar3 {
       
       
         template< typename TC, typename TArg >
      -  inline PyObject* PyInPlaceOperatorWrapper::call ( PyObject* pyObject, PyObject* pyArg )
      +  inline PyObject* PyInPlaceOperatorWrapper::_call ( PyObject* pyObject, PyObject* pyArg )
         {
           PyErr_Clear();
           setMessage( funcName() + "(): " );
      @@ -2044,7 +2027,7 @@ namespace Isobar3 {
                                                     , _oPredicate(pred)
                                                     { };
             inline  std::string  funcName           () const { return _funcName; };
      -      virtual int          predicate          ( PyObject* self );
      +      virtual int          _predicate         ( PyObject* self );
           private:
             std::string     _funcName;
             OPredicateType  _oPredicate;
      @@ -2052,7 +2035,7 @@ namespace Isobar3 {
       
       
         template< typename TC >
      -  inline int  PyPredicateWrapper::predicate ( PyObject* self )
      +  inline int  PyPredicateWrapper::_predicate ( PyObject* self )
         {
           PyErr_Clear();
           setMessage( funcName() + "(): " );