- Dimensional Analysis
- Higher-Order Metafunctions
- Handling Placeholders
- More Lambda Capabilities
- Lambda Details
- Details
- Exercises
3.2 Higher-Order Metafunctions
In the previous section we used two different forms—metafunction classes and placeholder expressions—to pass and return metafunctions just like any other metadata. Bundling metafunctions into "first class metadata" allows transform to perform an infinite variety of different operations: in our case, multiplication and division of dimensions. Though the idea of using functions to manipulate other functions may seem simple, its great power and flexibility [Hudak89] has earned it a fancy title: higher-order functional programming. A function that operates on another function is known as a higher-order function. It follows that transform is a higher-order metafunction: a metafunction that operates on another metafunction.
Now that we've seen the power of higher-order metafunctions at work, it would be good to be able to create new ones. In order to explore the basic mechanisms, let's try a simple example. Our task is to write a metafunction called twice, which—given a unary metafunction f and arbitrary metadata x—computes:
This might seem like a trivial example, and in fact it is. You won't find much use for twice in real code. We hope you'll bear with us anyway: Because it doesn't do much more than accept and invoke a metafunction, twice captures all the essential elements of "higher-orderness" without any distracting details.
If f is a metafunction class, the definition of twice is straightforward:
template <class F, class X> struct twice { typedef typename F::template apply<X>::type once; // f(x) typedef typename F::template apply<once>::type type; // f(f(x)) };
Or, applying metafunction forwarding:
template <class F, class X> struct twice : F::template apply< typename F::template apply<X>::type > {};
Given the need to sprinkle our code with the template keyword, it would be nice to reduce the syntactic burden of invoking metafunction classes. As usual, the solution is to factor the pattern into a metafunction:
template <class UnaryMetaFunctionClass, class Arg> struct apply1 : UnaryMetaFunctionClass::template apply<Arg> {};
Now twice is just:
template <class F, class X> struct twice : apply1<F, typename apply1<F,X>::type> {};
To see twice at work, we can apply it to a little metafunction class built around the add_pointer metafunction:
struct add_pointer_f { template <class T> struct apply : boost::add_pointer<T> {}; };
Now we can use twice with add_pointer_f to build pointers-to-pointers:
BOOST_STATIC_ASSERT(( boost::is_same< twice<add_pointer_f, int>::type , int** >::value ));