![]() | Home | Libraries | People | FAQ | More |
Table of Contents
Direct functoids enable the creation of functions which are simultaneously higher-order and polymorphic. Consider the map() function described in the previous section: map() takes two arguments—a function and a list—and applies the function to every element of the list. In Haskell we would describe the type signature of map like this:
map :: [a] -> (a -> b) -> [b]The letters a and b are placeholder type variables (like the T in template <class T>). The signature says that map() takes two arguments—a list of a objects and a function from a objects to b objects—and returns a list of b objects. Thus map() is an example of a higher-order (it takes a function as an argument) polymorphic (it can be instantiated for all types a and b) function.
Representing such a function in C++ is non-trivial. Suppose we try to implement map ourselves, and we call our version mymap. There are two key issues. The first issue is this: if we represent mymap as a template function:
template <class F, class T>
... mymap( F some_func, list<T> some_list ) { ... }
then the symbol mymap does not name a C++ object. This means we
cannot pass mymap to another higher-order function:
some_other_func( mymap, ... ); // illegal if mymap is template functionThis problem is relatively straightforward to surmount using a function object:
struct mymap_type {
template <class F, class T>
... operator()( F some_func, list<T> some_list ) const { ... }
} mymap;
Now mymap is an instance of struct
mymap_type, which has a template member
operator(). As a result, we can call mymap
using the same syntax as before, but now the symbol mymap
does name a C++ object, and thus it can itself be passed as a
parameter.
The second issue has to do with the return type. What should the return type of mymap be? It should be a list<U>, where U is the result type of applying an F function to a T object. Since the C++ language lacks a typeof operator, we need to represent the return type information ourselves. By convention, in FC++, we represent the return type of a function using a nested template member struct named sig:
struct mymap_type {
template <class F, class L>
struct sig {
typedef list< typename F::template sig<
typename L::value_type >::result_type >
result_type;
};
...
} mymap;
More generally, the expression
typename F::template sig<X>::result_type // F::sig<X>::result_type without the "noise" wordsrepresents the result type when a function of type F is applied to an argument of type X.
As a result, we could define mymap as
struct mymap_type {
template <class F, class L>
struct sig {
typedef list< typename F::template sig<
typename L::value_type >::result_type >
result_type;
};
template <class F, class T>
typename sig< F, list<T> >::result_type
operator()( F some_func, list<T> some_list ) const { ... }
} mymap;
This is our first example of a functoid. A
functoid is an instance of a struct which contains a (possibly
templated) operator() method, as well as a nested template
member struct named sig which works as a return-type
computer.
To simplify naming return types, we have the RT helper. Rather than say
typename F::template sig<X,Y>::result_typewe just say
typename RT<F,X,Y>::result_typeThat is, RT<F,A1,...An> computes the result type of a function of type F being applied to arguments with type Ai.
FC++ was the first C++ library to use this scheme as a general mechanism for representing higher-order polymorphic functions. Since then, a number of other libraries have arisen that all use variations of the same trick to enable return-type deduction.
A relatively new proposal standardizes the return-type deduction methods. It uses conventions and syntax different from FC++. FC++ "full functoids" (Section 7) ensure that functoids are forward-compatible with the new standard. At the same time, the RT type computer makes FC++ code backward compatible with functoids using the extant sig structures. As a result, FC++ interoperates with other libraries like boost::bind.
Now, with all of the explanation out of the way, we can finally show the definition of the mymap functoid (Figure 1). This is an example of a polymorphic direct functoid. It has a sig structure, which is a template over the parameter types which computes the result type, as well as a templated operator() function, which uses the sig to name its own result type. Note that, rather than defining the result_type typedef directly, we can inherit it from the fun_type class.
struct mymap_type {
template <class F, class L> struct sig : public fun_type<
list<typename RT<F,typename L::value_type>::result_type> > {};
template <class F, class T>
typename sig<F, list<T> >::result_type
operator()( const F& f, const list<T>& l ) const {
// code to actually implement mymap() function elided
}
} mymap;
The definition of mymap given here is what we call a basic direct functoid. In Section 7, we will show how to promote mymap into a full direct functoid, which adds useful capabilities.
Finally, it should be noted that monomorphic direct functoids can be defined without explicitly coding a sig structure, by inheriting from the c_fun_type template class. For example, here is the definition of a function that increments an integer:
struct inc_type : public c_fun_type<int,int> {
int operator()( int x ) const {
return x+1;
}
} inc;
The c_fun_type class defines a monomorphic sig
structure which is inherited by the functoid directly. Note that the
c_fun_type class takes (N+1) template
arguments which represent the N parameter types and
the result type.
| Last revised: October 03, 2003 at 23:27:22 GMT | Copyright © 2000-2003 Brian McNamara and Yannis Smaragdakis |