
On Thu, May 15, 2008 at 4:06 AM, Daniel Walker <daniel.j.walker@gmail.com> wrote:
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.
Hum, but aren't function pointers already first class? I think that std::function supports them only for completeness. Its main reason is to wrap stateful) function objects (i.e. closures). I'll read N2588 again... yes, it says explicitly in 20.5.15.2.1 that "The function class template [...] [allows] functions to be first class objects". Maybe the distinction is between function and pointer-to-function. Anyways, it is really a detail.
<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;
Please ignore my comment above, I wasn't (and still I'm not) sure what I was thinking of.
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.
where 'thing' is a common interface, I guess. (i.e., a concept in parametric polymorphism or an actual pure base class in subtype polymorphism). So, ok, I agree with this.
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.
I'm not sure if explicit conversion count: you lose the ability to treat different types in the same way. Of course you can add another level of indirection, by making also the conversion function a parameter and itself parametric: // generic indirection layer template<typename To, template<class,class> Convert, typename From> To operation_that_requires_a_generic_type_conversion(From f) { return Convert<From,To>(f); }
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();
<interlude may_skip=if-you-prefer> I'm a lowly engineer, and know nothing about type theory, but by reading the wikipedia article about Type Polymorphism, I thik you can call this a form of Rank-1 (Prenex) polymorphism: "In a prenex polymorphic system, type variables may not be instantiated with polymorphic types" I.e. you loose the ability to be polymorphic when you actually construct a variable of a polymorphic type, as you have to fix the type. If c++ didn't allow creating variables of type 'plus' and only allowed variables of type function<sig>, it would have a Rank-1 Polymorphic Type system. A variable of type 'plus' is instead 'Rank-n' polymorphic, I.e. it retains its polymorphic behavior no matter how many levels of nesting are applyed (i.e. you can pass it to an higher order function which in turn is passed to an higher order function and so on). Wikpedia definition doesn't make it clear: "Rank-n polymorphism is polymorphism in which quantifiers may appear to the left of arbitrarily many arrows." but FC++ authors explicitly says that C++ polymorphic function objects (from now on PFO) are Rank-n polymorphic and I'll believe them :). The problem is of course that to maintain the polymorphic behavior of a C++ PFO, you have to use them in a template context (i.e. all higher order functions must be templated). It would be interesting to know if constrained templates (i.e. ConceptCPP allows Rank-n polymorphism, but I'm not sure: you have to specify in the concept requirement of an higher order function the signature of the passed function object, but the fact that you can define it in term of other template arguments might be an escape hatch: in fact I think that as long as everything is explicitly typed, that is, no type inference, there isn't much distinction between all rank of polymorphism) </interlude> ok, I'm still following you :)
... 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.
I think this is in direct contrast with the primary reason of std::function (or any other type erasure wrapper), which is to erase the type of the wrapped function and only retain its behavior. I do not see how you can retain the type and still preserve it...
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.
ok.
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.
In a PFO, the result type is (meta) function of the argument types. Thus in result_of, the type in the result position, in a sense, denotes (indirectly) the metafunction to be used to compute the result value. So yes, I'm still with you (even if I do not think that this is the reasoning done by the authors of result_of: it is a kind of ret-connecting).
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.
Well, this is not exactly what I meant: a PFO does not necessarily have a variable return type nor this is its principal charateristics. This is a detail though, let's move on.
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);
Ok, but you need to know the function type (i.e. plus). So, what is the point of using std::function then? Note that in many cases where std::function is useful (for example to hold the result of a complex lambda or bind expression), you do not know the stored function type.
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();
this of course works, but you are no longer erasing the type of g here!!! It is right there, encoded in polymorphic_function instantiation: I cannot use it to pass a PFO to a function in another translation unit, I cannot use it to store a lambda function because I do not know its type and I cannot build a type homogeneous container which stores wrappers to different PFOs! What is the point of using a wrapper then? I do not think that there is a (non contrived) use case where a non erasing polymorphic_function object is more useful than using 'g' directly. function_cast might have some limited usability, but I cannot see when I would use your variant of polymorphic_function. Am I missing something? -- gpd