[C++-sig] Returning values to Python in C++ reference arguments

Holger Joukl Holger.Joukl at LBBW.de
Tue May 26 09:41:28 CEST 2015


> Von: Jason Addison
>
> How can results be returned in function arguments?
>
> I've include my example C++ extension and Python code below.
>
> I've tried, what I think, are some obvious approaches (Python objects,
> ctypes), though none have worked.
>
> It seems like this should be doable.

As already mentioned, you can use helper functions and return tuples.

Or you could expose custom "ref-object" classes to Python, s.th. along the
lines of:

// ref_object.hpp
#if !defined REF_OBJECT
#define REF_OBJECT


#include <boost/python.hpp>
#include <algorithm>


namespace bp = boost::python;

namespace refob {


// Basic RefObject
template<typename T>
struct RefObject {
public:
    T t;

    RefObject();
    // callable operator
    T operator()();

    // conversion functions: implicit castability
    operator T&() { return t; }
};


// RefObject for handling C-style arrays
// This provides an operator() that allows access to
// a list of values.
// NOTE: If numElements > the actual stored number of elements then
// THIS WILL CRASH! It is essential to correctly apply set_num() after
// this has been used as a function argument, in some thin wrappers
// There is only some very rudimentary safety in that _numElements is
// initialized to 0
// I really don't see a better way to make this safer
template<typename T>
struct RefObject<T const *> {
    T const * t;

    RefObject();
    // callable operator
    bp::list operator()(unsigned int numElements=0);

    // conversion functions: implicit castability
    operator T const *&() { return t; }

    void set_num(unsigned int const & numElements);

private:
    unsigned int _numElements;
};


// RefObject specialization for void*
template<>
struct RefObject<void *> {
    void* t;

    RefObject();
    // callable operator
    void* operator()();
};


// RefObject specialization for void const* (where we cast away const for
the
// operator() call result to make boost work automagically
template<>
struct RefObject<void const *> {
    void const * t;

    RefObject();
    // callable operator
    void* operator()();
};


// RefObject specialization for char const* which boost.python will
automatically
// handle as a python string (necessary to distingish from the C-style
array
// handling RefObject, see above
template<>
struct RefObject<char const *> {
    char const * t;

    RefObject();
    // callable operator
    char const* operator()();
};


// Is inlining the way to go to avoid multiply defined symbols here?
// Note: Remember that
//  * a specialization is not a template but a concrete (member function in
//     this case)
//  * as ref_object.hpp are
//    #included from every module there will be multiple definitions (one
//    in each compilation unit)
//  ==> linker chokes
// Using inline this can be avoided
// See
// http://msdn.microsoft.com/en-us/magazine/cc163769.aspx
// http://www.parashift.com/c++-faq-lite/inline-functions.html
// for background

template <typename T>
RefObject<T>::RefObject(): t() {
    //std::cout << "RefObject<" << typeid(T).name() << ">::RefObject()" <<
std::endl;
}


template <typename T>
RefObject<T const *>::RefObject(): t(), _numElements(0) {
    //std::cout << "RefObject<" << typeid(T).name() << " const
*>::RefObject()" << std::endl;
}


inline RefObject<void *>::RefObject(): t() {
    //std::cout << "RefObject<void *>::RefObject()" << std::endl;
}


inline RefObject<void const *>::RefObject(): t() {
    //std::cout << "RefObject<void const *>::RefObject()" << std::endl;
}


inline RefObject<char const *>::RefObject(): t() {
    //std::cout << "RefObject<char const *>::RefObject()" << std::endl;
}


template <typename T>
T RefObject<T>::operator()()
{
    // Do we need to care about pointer data issues here?
    return t;
}


template<typename T>
bp::list RefObject<T const *>::operator()(unsigned int numElements)
{
    bp::list list_object;

    unsigned int maxElements = std::min(numElements, _numElements);
    for (unsigned int i=0; i < maxElements; i++) {
        bp::object item(*t);
        list_object.append(item);
    }
    return list_object;
}


inline void* RefObject<void const *>::operator()()
{
    void* non_const_ptr = const_cast<void*>(t);
    //std::cout << "returning non-const void* t" << std::endl;
    return non_const_ptr;
}


inline void* RefObject<void *>::operator()()
{
    return t;
}


inline char const* RefObject<char const *>::operator()()
{
    return t;
}


template<typename T>
void RefObject<T const *>::set_num(unsigned int const & numElements)
{
    _numElements = numElements;
}


} // namespace refob

#endif // #if !defined REF_OBJECT


And you might expose those to Python:

// ref_object.cpp
#include <boost/python.hpp>
#include "ref_object.hpp"


namespace bp = boost::python;


namespace refob
{


// dummy function, expose this to force creation of void* converters in
boost registry
void* void_ptr_from_void_ptr(void* void_ptr) {
    return void_ptr;
}


void export_reference_type_classes()
{

//     // dummy function registration to force creation of void* converters
//     // (not necessary if there is an exposed function returning void*)
//     bp::def("void_ptr_from_void_ptr", &void_ptr_from_void_ptr,
//             bp::return_value_policy<bp::return_opaque_pointer>());

    // const-pointer types

    // 1. char

    // 1.1: const char*
    bp::class_<RefObject<const char*>, boost::noncopyable>
("const_char_ptr_ref")
        .def(bp::init<>())
        .def("__call__", &RefObject<const char*>::operator())
        ;


    // 2. numeric types (float/double/int)

    // Note: signed and unsigned char are *numeric* types usage-wise
    // const signed char*
//     bp::class_<RefObject<const signed char*>, boost::noncopyable>
("const_signed_char_ptr_ref")
//         .def(bp::init<>())
//         .def("__call__", &RefObject<const signed char*>::operator())
//         ;


//     // const unsigned char*
//     bp::class_<RefObject<const unsigned char*>, boost::noncopyable>
("const_unsigned_char_ptr_ref")
//         .def(bp::init<>())
//         .def("__call__", &RefObject<const unsigned char*>::operator())
//         ;

    // const double*
    bp::class_<RefObject<const double*>, boost::noncopyable>
("const_double_ptr_ref")
        .def(bp::init<>())
        .def("__call__", &RefObject<const double*>::operator())
        ;
    bp::implicitly_convertible< RefObject<const double*>, double const * >
();

//     // const float*
//     bp::class_<RefObject<const float*>, boost::noncopyable>
("const_float_ptr_ref")
//         .def(bp::init<>())
//         .def("__call__", &RefObject<const float*>::operator())
//         ;

//     // const short*
//     bp::class_<RefObject<const short*>, boost::noncopyable>
("const_short_ptr_ref")
//         .def(bp::init<>())
//         .def("__call__", &RefObject<const short*>::operator())
//         ;

//     // const unsigned short*
//     bp::class_<RefObject<const unsigned short*>, boost::noncopyable>
("const_unsigned_short_ptr_ref")
//         .def(bp::init<>())
//         .def("__call__", &RefObject<const unsigned short*>::operator())
//         ;

//     // const int*
//     bp::class_<RefObject<const int*>, boost::noncopyable>
("const_int_ptr_ref")
//         .def(bp::init<>())
//         .def("__call__", &RefObject<const int*>::operator())
//         ;

//     // const unsigned int*
//     bp::class_<RefObject<const unsigned int*>, boost::noncopyable>
("const_unsigned_int_ptr_ref")
//         .def(bp::init<>())
//         .def("__call__", &RefObject<const unsigned int*>::operator())
//         ;


//     // const long long*
//     bp::class_<RefObject<const long long*>, boost::noncopyable>
("const_long_long_ptr_ref")
//         .def(bp::init<>())
//         .def("__call__", &RefObject<const long long*>::operator())
//         ;

//     // const unsigned long long*
//     bp::class_<RefObject<const unsigned long long*>, boost::noncopyable>
("const_unsigned_long_long_ptr_ref")
//         .def(bp::init<>())
//         .def("__call__", &RefObject<const unsigned long long*>::operator
())
//         ;


    // 3. void

    // const void*
    bp::class_<RefObject<const void*>, boost::noncopyable>
("const_void_ptr_ref")
        .def(bp::init<>())
        .def("__call__", &RefObject<const void*>::operator(),
             bp::return_value_policy<bp::return_opaque_pointer>())
        ;



    // NON-const-pointer types:

    // void*
    bp::class_<RefObject<void*>, boost::noncopyable>("void_ptr_ref")
        .def(bp::init<>())
        .def("__call__", &RefObject<void*>::operator(),
             bp::return_value_policy<bp::return_opaque_pointer>())
        ;


    // Basic types

    // 1. char types

    // char
    bp::class_<RefObject<char>, boost::noncopyable>("char_ref")
        .def(bp::init<>())
        .def("__call__", &RefObject<char>::operator())
        ;


    // 2. numeric types

    // Note: signed and unsigned char are *numeric* types usage-wise
    // const signed char*
    // signed char
    bp::class_<RefObject<signed char>, boost::noncopyable>
("signed_char_ref")
        .def(bp::init<>())
        .def("__call__", &RefObject<signed char>::operator())
        ;

    // unsigned char
    bp::class_<RefObject<unsigned char>, boost::noncopyable>
("unsigned_char_ref")
        .def(bp::init<>())
        .def("__call__", &RefObject<unsigned char>::operator())
        ;

    // double
    bp::class_<RefObject<double>, boost::noncopyable>("double_ref")
        .def(bp::init<>())
        .def("__call__", &RefObject<double>::operator())
        ;

    // float
    bp::class_<RefObject<float>, boost::noncopyable>("float_ref")
        .def(bp::init<>())
        .def("__call__", &RefObject<float>::operator())
        ;

    // short
    bp::class_<RefObject<short>, boost::noncopyable>("short_ref")
        .def(bp::init<>())
        .def("__call__", &RefObject<short>::operator())
        ;

    // unsigned short
    bp::class_<RefObject<unsigned short>, boost::noncopyable>
("unsigned_short_ref")
        .def(bp::init<>())
        .def("__call__", &RefObject<unsigned short>::operator())
        ;

    // int
    bp::class_<RefObject<int>, boost::noncopyable>("int_ref")
        .def(bp::init<>())
        .def("__call__", &RefObject<int>::operator())
        ;

    // unsigned int
    bp::class_<RefObject<unsigned int>, boost::noncopyable>
("unsigned_int_ref")
        .def(bp::init<>())
        .def("__call__", &RefObject<unsigned int>::operator())
        ;
    bp::implicitly_convertible< RefObject<unsigned int>, unsigned int >();

    // long long
    bp::class_<RefObject<long long>, boost::noncopyable>("long_long_ref")
        .def(bp::init<>())
        .def("__call__", &RefObject<long long>::operator())
        ;

    // unsigned long long
    bp::class_<RefObject<unsigned long long>, boost::noncopyable>
("unsigned_long_long_ref")
        .def(bp::init<>())
        .def("__call__", &RefObject<unsigned long long>::operator())
        ;

};

} // namespace refob


Add to your module (or consider putting into a separate extension module):


namespace refob {
    //forward declaration must be in appropriate namespace
    void export_reference_type_classes(); // helpers/ref_object.cpp
}
BOOST_PYTHON_MODULE(my_module) {

        refob::export_reference_type_classes();
...

You'd then have to use the specialized ref objects as function arguments,
much
like what you tried with the ctypes stuff:

import my_module
f = my_module.Foo()
a = my_module.int_ref(0)
b = my_module.double_ref(0)
f.bar(a, b)

print "a result:", a()

If such effort makes sense depends on how "reference-call-littered"
the C++ API you wrap is.

Note: I hand-edited the original code a little to remove some irrelevant
stuff
and didn't try to compile and run it afterwards. But you get the idea.

Holger

Landesbank Baden-Wuerttemberg
Anstalt des oeffentlichen Rechts
Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz
HRA 12704
Amtsgericht Stuttgart



More information about the Cplusplus-sig mailing list