...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Front Page / Tutorial: Metafunctions and Higher-Order Metaprogramming / Dimensional Analysis / Implementing Division |
Division is similar to multiplication, but instead of adding exponents, we must subtract them. Rather than writing out a near duplicate of plus_f, we can use the following trick to make minus_f much simpler:
struct minus_f { template <class T1, class T2> struct apply : mpl::minus<T1,T2> {}; };
Here minus_f::apply uses inheritance to expose the nested type of its base class, mpl::minus, so we don't have to write:
typedef typename ...::type type
We don't have to write typename here (in fact, it would be illegal), because the compiler knows that dependent names in apply's initializer list must be base classes. [2] This powerful simplification is known as metafunction forwarding; we'll apply it often as the book goes on. [3]
[2] | In case you're wondering, the same approach could have been applied to plus_f, but since it's a little subtle, we introduced the straightforward but verbose formulation first. |
[3] | Users of EDG-based compilers should consult the book's Appendix C for a caveat about metafunction forwarding. You can tell whether you have an EDG compiler by checking the preprocessor symbol __EDG_VERSION__, which is defined by all EDG-based compilers. |
Syntactic tricks notwithstanding, writing trivial classes to wrap existing metafunctions is going to get boring pretty quickly. Even though the definition of minus_f was far less verbose than that of plus_f, it's still an awful lot to type. Fortunately, MPL gives us a much simpler way to pass metafunctions around. Instead of building a whole metafunction class, we can invoke transform this way:
typename mpl::transform<D1,D2, mpl::minus<_1,_2> >::type
Those funny looking arguments (_1 and _2) are known as placeholders, and they signify that when the transform's BinaryOperation is invoked, its first and second arguments will be passed on to minus in the positions indicated by _1 and _2, respectively. The whole type mpl::minus<_1,_2> is known as a placeholder expression.
Note
MPL's placeholders are in the mpl::placeholders namespace and defined in boost/mpl/placeholders.hpp. In this book we will usually assume that you have written:
#include<boost/mpl/placeholders.hpp> using namespace mpl::placeholders;
so that they can be accessed without qualification.
Here's our division operator written using placeholder expressions:
template <class T, class D1, class D2> quantity< T , typename mpl::transform<D1,D2,mpl::minus<_1,_2> >::type > operator/(quantity<T,D1> x, quantity<T,D2> y) { typedef typename mpl::transform<D1,D2,mpl::minus<_1,_2> >::type dim; return quantity<T,dim>( x.value() / y.value() ); }
This code is considerably simpler. We can simplify it even further by factoring the code that calculates the new dimensions into its own metafunction:
template <class D1, class D2> struct divide_dimensions : mpl::transform<D1,D2,mpl::minus<_1,_2> > // forwarding again {}; template <class T, class D1, class D2> quantity<T, typename divide_dimensions<D1,D2>::type> operator/(quantity<T,D1> x, quantity<T,D2> y) { return quantity<T, typename divide_dimensions<D1,D2>::type>( x.value() / y.value()); }
Now we can verify our "force-on-a-laptop" computation by reversing it, as follows:
quantity<float,mass> m2 = f/a; float rounding_error = std::abs((m2 - m).value());
If we got everything right, rounding_error should be very close to zero. These are boring calculations, but they're just the sort of thing that could ruin a whole program (or worse) if you got them wrong. If we had written a/f instead of f/a, there would have been a compilation error, preventing a mistake from propagating throughout our program.