Home > Articles > Programming > C/C++

  • Print
  • + Share This
This chapter is from the book

3.3 Handling Placeholders

Our implementation of twice already works with metafunction classes. Ideally, we would like it to work with placeholder expressions, too, much the same as mpl::transform allows us to pass either form. For example, we would like to be able to write:

    template <class X>
    struct two_pointers
        : twice<boost::add_pointer<_1>, X>
    {};

But when we look at the implementation of boost::add_pointer, it becomes clear that the current definition of twice can't work that way.

    template <class T>
    struct add_pointer
    {
        typedef T* type;
    };

To be invokable by twice, boost::add_pointer<_1> would have to be a metafunction class, along the lines of add_pointer_f. Instead, it's just a nullary metafunction returning the almost senseless type _1*. Any attempt to use two_pointers will fail when apply1 reaches for a nested ::apply metafunction in boost::add_pointer<_1> and finds that it doesn't exist.

We've determined that we don't get the behavior we want automatically, so what next? Since mpl::transform can do this sort of thing, there ought to be a way for us to do it too—and so there is.

3.3.1 The lambda Metafunction

We can generate a metafunction class from boost::add_pointer<_1>, using MPL's lambda metafunction:

    template <class X>
    struct two_pointers
      : twice<typename mpl::lambda<boost::add_pointer<_1> >::type, X>
    {};

    BOOST_STATIC_ASSERT((
        boost::is_same<
            typename two_pointers<int>::type
          , int**
        >::value
    ));

We'll refer to metafunction classes like add_pointer_f and placeholder expressions like boost::add_pointer<_1> as lambda expressions. The term, meaning "unnamed function object," was introduced in the 1930s by the logician Alonzo Church as part of a fundamental theory of computation he called the lambda-calculus. [4] MPL uses the somewhat obscure word lambda because of its well-established precedent in functional programming languages.

Although its primary purpose is to turn placeholder expressions into metafunction classes, mpl::lambda can accept any lambda expression, even if it's already a metafunction class. In that case, lambda returns its argument unchanged. MPL algorithms like transform call lambda internally, before invoking the resulting metafunction class, so that they work equally well with either kind of lambda expression. We can apply the same strategy to twice:

    template <class F, class X>
    struct twice
       : apply1<
             typename mpl::lambda<F>::type
           , typename apply1<
                 typename mpl::lambda<F>::type
               , X
             >::type
         >
    {};

Now we can use twice with metafunction classes and placeholder expressions:

    int* x;

    twice<add_pointer_f, int>::type          p = &x;
    twice<boost::add_pointer<_1>, int>::type q = &x;

3.3.2 The apply Metafunction

Invoking the result of lambda is such a common pattern that MPL provides an apply metafunction to do just that. Using mpl::apply, our flexible version of twice becomes:

    #include <boost/mpl/apply.hpp>

    template <class F, class X>
    struct twice
       : mpl::apply<F, typename mpl::apply<F,X>::type>
    {};

You can think of mpl::apply as being just like the apply1 template that we wrote, with two additional features:

  1. While apply1 operates only on metafunction classes, the first argument to mpl::apply can be any lambda expression (including those built with placeholders).
  2. While apply1 accepts only one additional argument to which the metafunction class will be applied, mpl::apply can invoke its first argument on any number from zero to five additional arguments. [5] For example:
    // binary lambda expression applied to 2 additional arguments
    mpl::apply<
        mpl::plus<_1,_2>
      , mpl::int_<6>
      , mpl::int_<7>
    >::type::value // == 13
  • + Share This
  • 🔖 Save To Your Account