9.1. for_eachThe simplest STL algorithm ought to have an MPL analogue, and so it does. Just to review, std::for_each traverses a (runtime) sequence and invokes some (runtime) function object on each element. Similarly, mpl::for_each traverses a compile-time sequence and invokes some runtime function object on it. Although std::for_each operates only at runtime, mpl::for_each is a hybrid, straddling the compile-time and runtime worlds.
9.1.1. Type PrintingHave you been wondering how to get a look at the contents of your type sequences? Provided we're using a compiler that produces meaningful strings from std::type_info::name, we can print each element of a type sequence as follows: struct print_type { template <class T> void operator() (T) const { std::cout << typeid(T).name() << std::endl; } }; typedef mpl::vector<int, long, char*> s; int main () { mpl::for_each<s>(print_type()); } There are a few things we'd like you to notice about this code. First of all, print_type's function-call operator is templatized, because it has to handle whatever types happen to appear in our sequence. Except when you want to process sequences whose elements are all convertible to one type, your mpl::for_each function objects will need a templated (or at the very least, overloaded) function call operator. Next, note that for_each passes us each sequence element as a value-initialized object of the corresponding element type.[1] This form is particularly convenient if you are iterating over a sequence of integral constant wrappers, which, if you remember, are implicitly convertible to their corresponding runtime constants. On the other hand, it requires some special care when iterating over an ordinary type sequence: If the element turns out to be a reference type, a class type with no default constructor, or simply void, the algorithm will fail to compile since none of those types can be value-initialized.
We can avoid this pitfall by transforming the sequence through a little wrapper template to smooth out its rough edges: template <class T> struct wrap {}; // contains references typedef mpl::vector<int&, long&, char*&> s; mpl::for_each< mpl::transform<s, wrap<_1> >::type >(print_type()); We'll also need to adjust our function object's signature, to account for the change in the types of arguments that will be passed:
struct print_type
{
template <class T>
void operator()(wrap<T>) const // deduce T
{
std::cout << typeid(T).name() << std::endl;
}
};
Because this is such a common idiom, MPL provides a second form of for_each that takes a transformation metafunction as an additional template argument. By using this second form, we can avoid building a whole new sequence of wrap specializations:
mpl::for_each<s, wrap<_1> >(print_type());
For each element T of s, the print_type object will be invoked with a wrap<T> argument. 9.1.2. Type VisitationFor a more general solution to the problem of smoothing out types at the function boundary, we can apply the Visitor pattern [GHJV95]: struct visit_type // generalized visitation function object { template <class Visitor> void operator()(Visitor) const { Visitor::visit(); } }; template <class T> // specific visitor for type printing struct print_visitor { static void visit() { std::cout << typeid(T).name() << std::endl; } }; int main() { mpl::for_each<s, print_visitor<_1> >(visit_type()); } Here, the visit_type function object expects its argument type to have a static visit member function, and we can build new visitor objects for any purpose. This is a subtle change from our earlier examples with for_each, but note: print_visitor::visit is never passed a T object. Instead, for_each passes an instance of print_visitor<T>, for each T in our sequence, to visit_type. The information about the type of T is transmitted in print_visitor's template parameter. |