9.5. Class CompositionIf we can use structure selection once to control the structure of a class, we can use it over and over to create class structures in fine-grained steps. For example, to generate a struct whose members have types given by a type sequence, we could apply the fold algorithm: // fine-grained struct element; stores a T and inherits More template <class T, class More> struct store : More { T value; }; typedef mpl::vector<short[2], long, char*, int> member_types; struct empty {}; mpl::fold< member_types, empty, store<_2,_1> >::type generated; Yielding an object generated, of type store<int , store<char* , store<long , store<short[2], empty> > > > Each specialization of store shown above represents a layer of inheritance containing a member of one of the types in member_types. Actually using classes composed in this way can be tricky unless they are carefully structured. Although generated does indeed contain members of each of the types in member_types, they're hard to get at. The most obvious problem is that they're all called value: We can't access any other than the first one directly, because the rest are hidden by layers of inheritance. Unfortunately, there's nothing we can do about the repetition; it is a fact of life when applying class composition, because although we can easily generate member types, there's no way to generate member names using templates.[5]
Moreover, it's difficult to access the value member of a given type even by casting to an appropriate base class. To see why, consider what's involved in accessing the long value stored in generated. Because each store specialization is derived from its second argument, we'd have to write:
long& x = static_cast<
store<long, store<short[2], empty> >&
>(generated).value;
In other words, accessing any member of store requires knowing all the types following its type in the original sequence. We could let the compiler's function argument deduction mechanism do the work of figuring out the base class chain for us: template <class T, class U> store<T,U> const& get(store<T,U> const& e) { return e; } char* s = get<char*>(generated).value; In the example above, get's first template argument is constrained to be char*, and the effective function parameter becomes store<char*,U> const&, which matches the base class of generated containing a char* member. A slightly different pattern allows us to solve this problem a bit more neatly. As usual, the Fundamental Theorem of Software Engineering[6] applies. We'll just add a layer of indirection: // fine-grained struct element; wraps a T template <class T> struct wrap { T value; }; // one more level of indirection template <class U, class V> struct inherit : U, V {}; typedef mpl::vector<short[2], long, char*, int> member_types; struct empty {}; mpl::fold< member_types, empty, inherit<wrap<_2>,_1> >::type generated; Now the type of generated is: inherit<wrap<int> , inherit<wrap<char*> , inherit<wrap<long> , inherit<wrap<short[2]> , empty > > > > Since inherit<U,V> is derived from both U and V, the type above is (indirectly) derived from wrap<T> for each T in the sequence. We can now access a value member of type long with:
long& x = static_cast<wrap<long> &>(generated).value;
Class generation along these lines is a common metaprogramming activity, so MPL provides ready-made tools for that purpose. In particular, we can replace empty and inherit with mpl::empty_base and mpl::inherit. The library also contains an appropriately named inherit_linearly metafunction that calls fold for us with a default initial type of mpl::empty_base: template <class Types, class Node, class Root = empty_base> struct inherit_linearly : fold<Types,Root,Node> { }; With these tools in hand, we can rewrite our last example more conveniently as: #include <boost/mpl/inherit.hpp> #include <boost/mpl/inherit_linearly.hpp> #include <boost/mpl/vector.hpp> // fine-grained struct element template <class T> struct wrap { T value; }; typedef mpl::vector<short[2], long, char*, int> member_types; mpl::inherit_linearly< member_types, mpl::inherit<wrap<_2>,_1> >::type generated; Practical applications of these class composition patterns have been extensively explored by Andrei Alexandrescu [Ale01]. For example, he uses class composition to generate visitor classes for a generic multiple dispatch framework. ![]() |