Section 9.1.  for_each
Team LiB
Previous Section Next Section

9.1. for_each

The 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.

Why Runtime Function Objects?

If you're wondering why mpl::for_each takes a runtime function object instead of a metafunction, think of it this way: Normally, the function object used with std::for_each returns void, but even if it does have a result, that result is discarded. In other words, that function object, if it does anything at all, has to modify the program state somehow. Since functional programming is inherently stateless and template metaprograms are functional, there wouldn't be much point in invoking a metafunction on each element of the sequence unless we were going to do something with the result.


9.1.1. Type Printing

Have 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.

[1] The concept of value-initialization was added to the C++ standard in its first "technical corrigendum" (TC1). To value-initialize an object of type T means:

  • If T is a class type (clause 9) with a user-declared constructor (12.1), then the default constructor for T is called.

  • If T is a non-union class type without a user-declared constructor, then every nonstatic data member and base-class component of T is value-initialized.

  • If T is an array type, then each element is value-initialized.

  • Otherwise, the object is zero-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 Visitation

For 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.

    Team LiB
    Previous Section Next Section