[C++-sig] function types as user-defined converters

troy d. straszheim troy at resophonic.com
Fri Nov 6 20:52:49 CET 2009


>> I'm wondering if there isn't motivation here to cleanly integrate a
>> general facility for additional c++-side type conversions. 


I got distracted with boost.cmake stuff for a while but just got some
things working.  Simple example:

//  type hidden from python
struct Hidden
{
   double value;
};

//
//  c++ interface that uses hidden type
//
std::string takes_hidden(Hidden h)
{
   return std::string("Got an Hidden containing ")
     + boost::lexical_cast<std::string>(h.value);
}

//
//  converter object
//
struct dbl_to_Hidden
{
   typedef Hidden result_type;

   result_type operator()(double v) const
   {
     Hidden h;
     h.value = v;
     return a;
   }
};

// the module
BOOST_PYTHON_MODULE(udconversions_ext)
{
   def("takes_hidden",
     as<std::string(dbl_to_Hidden(double))>(&takes_hidden));
}                  ^^^^^^^^^^^^^^^^^^^^^

And the python:

 >>> from udconversions_ext import *
 >>> takes_a(13)
'Got an A containing 13'


Here's another contrived example:


// Here we have a class that takes optional<int> arguments that
// python ought not know about:

struct S
{
   void set(optional<int> v)
   {
     i = v;
   }
   void add(boost::optional<int> oi)
   {
     if (i && oi)
       (*i) += (*oi);
   }
   std::string show()
   {
     if (i)
       return boost::lexical_cast<std::string>(*i);
     else
       return "<unset>";
   }
   optional<int> i;
};

// And a converter function object that converts a python::object
// to an optional<T> (holding a T) if the object is
// convertible to T, otherwise it returns an empty optional<T>:

template <typename T>
struct to_optional
{
   typedef optional<T> result_type;

   result_type operator()(const bp::object& o)
   {
     bp::extract<T> ex(o);
     if (ex.check())
       return optional<T>(ex());
     else
       return optional<T>();
   }
};


BOOST_PYTHON_MODULE(opt_ext)
{

   bp::class_<S>("S")
     .def("set", bp::as<void(S*, to_optional<int>(bp::object))>(&S::set))
     .def("add", bp::as<void(S*, to_optional<int>(bp::object))>(&S::add))
     .def("show", &S::show)      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     ;
};

And the result:

 >>> from opt_ext import *
 >>> s = S()
 >>> s.set(13)
 >>> s.show()
'13'
 >>> s.set(None)
 >>> s.show()
'<unset>'
 >>> s.set(None)
 >>> s.add(13)
 >>> s.show()
'<unset>'
 >>> s.set(13)
 >>> s.add(13)
 >>> s.show()
'26'

There are plenty of issues yet.

Currently, the converter type still leaks out to python in the signature:

     class S(Boost.Python.instance)
      |  add(...)
      |      add( (S)arg1, (object)arg2) -> None :
      |
      |          C++ signature :
      |              void add(S*,to_optional<int> 
(*)(boost::python::api::object))


converters won't nest:

   def("foo", as<returnvalue(arg1, conv1(conv2(arg2)))>(&somefn)); // nah

I dont have a mechanism for hooking a user defined converter into the
*return* type.  I'm going to have to think about that one a bit more.
specifying a function type, whose return value is a pointer to a
function, is kinda nasty anyway:

   def("foo", as<(converter(*)(int))(bool, converter(float))>(&fn));

I'm just not sure it gets one anything other than terseness.  Maybe
a special return value policy is in order.

Also, there isn't a way to use an object's constructor as a converter,
That is, I'd like to support this syntax:

struct X {
   X(double);
};

void takes_X(X x);

def("takes_x", as<void(X(double))>(&takes_x));

But I think this involves adding some scaffolding so that
boost.python can differentiate between what in proto-ese is a Callable
Transform (what now works) and an Object Transform (convert
via constructor).

-t



More information about the Cplusplus-sig mailing list