[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