
On Wed, May 14, 2008 at 5:50 AM, Giovanni Piero Deretta wrote:
On Wed, May 14, 2008 at 6:33 AM, Daniel Walker wrote:
As I understand it, the point of boost::function is that it can defer/hold arbitrary callable objects; i.e. it promotes second-class builtins to first-class. For consistency, it also holds function objects, though there's no other point to it since function objects are already first-class and already deferred.
Yep, exactly :). Their primary reason is to wrap function objects and let them "boldly go where no template has gone before".
I love that - "boldly go!" But just to be perfectly clear and to make sure everyone's on the same page, when I said arbitrary callable objects I meant any object f for which expressions like f() are well formed; i.e. function references and function pointers in addition to function objects. So, the primary reason is to wrap arbitrary callable objects into function objects; i.e. to promote second class builtin functions to first class. The light came on for me after reading 20.5.15.2 and 20.5.1 in N2588. <snip>
template<class T> T g(T t) { return t; }
g0(g<int>, g<float>);
overload_set< mpl::vector<int(int), float(float)> polymorphic_function<Magic> f0 = g0;
call(f0);
I have some ideas about the Magic involved with polymorphic_function, but I'll save that for another thread. My point is just that Marco's multi-signature function is a solution to one problem but not both.
Agree, see also my reply to Steven.
About the Magic in poly_function, I'm having some thoughts about hijacking BOOST_TYPEOF type registration machinery or postprocessing linker errors... I'll have to think about it.
Actually, just for polymorphic_function, what I have in mind is much simpler. It's so simple it doesn't really deserve to be called magic; it's more of a mere technique. Nonetheless, I sometimes think it's nothing less than the grace of God that I arrived at it at all! I was going to discuss this in response to some comments Shunsuke made in his Egg review (and I still plan on responding!), but since it came up again, I'll just tell the whole story here. First, Giovanni, I really do like your definition of polymorphic function objects, but for a moment let's consider polymorphism from a more abstract level. A more general definition of polymorphism could be two types representing the same thing. For some polymorphic types conversion is implicit, and for others, users are required to explicitly convert objects from one type to another. For example, take the relationship between the string "0" and the integer 0. These are two types that both refer to the same number - zero. In some languages, these types can be used completely interchangeably, but in C++ we have lexical_cast. Now, in a similar way, given a polymorphic version of std::plus, for example... struct plus { template <class> struct result; template <class T> struct result<plus(T,T)> { typedef T type; }; template<class T> T operator()(T t0, T t1) { return t0 + t1; } }; function<int(int,int)> add_ints = plus(); function<float(float,float)> add_floats = plus(); ... add_ints and add_floats are objects of two different types that both refer to the same function - plus. To convert between them, what is needed is a functional_cast. Now, boost::function already has infrastructure to support this via its target() member function. However, it does not retain the underlying polymorphic functor type. We need some protocol to encode/communicate this type. It seems to me that result_of's signature template argument already does just what we need! As a matter of explanation, call signatures, as I understand them, come from C and are of the form: return_type(first_argument_type, second_argument_type) These are the sorts of signatures that boost::function must be instantiated with. However, by convention, result_of uses the return_type position of a call signature to store the type of a callable object, which could be a polymorphic function object. Let's call this protocol a "polymorphic" signature. Comparing the two, in call signatures the return type position denotes the return type of a function; in "polymorphic" signatures the return type position denotes the type of a potentially polymorphic callable object. Giovanni, note that this corresponds nicely to your definition of polymorphic function objects as having variable return types. So, the "polymorphic" signatures used by result_of are of the form function_type(first_argument_type, second_argument_type) By using "polymorphic" signatures as the target of the cast, implementing functional_cast is trivial and allows you to do conversions like so: function<int(int,int)> f0 = functional_cast<plus(int,int)>(add_floats); function<float(float,float)> f1 = functional_cast<plus(float,float)>(add_ints); With this notion of two different signature protocols - boost::functions's call signature and result_of's "polymorphic" signature - implementing polymorphic_function can be reduced to the problem of writing a call-wrapper that supports both signature protocols and dispatches to the appropriate cast. Actually, it's even simpler than that. Since the call wrapper for polymorphic function objects will have access to the actual type of the wrapped object via the "polymorphic" signature, there's no need to cast anything. functional_cast is only needed to recover from boost::function's type erasure. polymorphic_function doesn't erase the wrapped type specified in the "polymorphic" signature and can therefore dispatch to the wrapped function object directly. However, result_of's "polymorphic" signature protocol does not yet encode all the information need to wrap a polymorphic function object. For example, what should the signature be for the following functor? struct f { template <class> struct result; template <class T> struct result<f(T,int)> { typedef T type; }; template<class T> T operator()(T t, int i); }; How do you specify that the first argument corresponds to a template parameter while the second argument is an int? As a solution, I think using MPL placeholders is adequate. So the fully polymorphic signature for the function object above would be f(_1,int). In other words, Magic = f(_1,int). ;-) Now polymorphic_function can be used in place of boost::function to wrap arbitrary callable objects without compromising polymorphism. // A function. int f(int i) { return i; } // A polymorphic functor. struct g { template <class> struct result; template <class T> struct result<g(T)> { typedef T type; }; template<class T> T operator()(T t) { return t; } }; // Treat call signatures the same as boost::function polymorphic_function<int(int)> f0 = f; polymorphic_function<int(int)> f1 = g(); // Treat polymorphic signatures polymorphically polymorphic_function<g(_1)> f2 = g(); I've attached a file that provides a simple unary implementation of all this as a proof of concept and to give an idea of what the boilerplate would look like. It should work for both C++03 and 0x. I tested with gcc 4.3. If there's interest, maybe this and Marco's work and possible parts of Egg could be organized into something really useful. Daniel Walker