Doc. No.: | N2965=09-0155 |
---|---|
Date: | 2009-09-25 |
Author: | Michael Spertus |
Email: | mike_spertus@symantec.com |
This paper describes a very simple and powerful type trait that enumerates the base classes of a class
The rationale for this can be summed up quickly.
bases<C>::type is the
type tuple<all base
classes of C>.
direct_bases<C>::type is the
type tuple<all direct base
classes of C>.
Notes:
class E {}; class D {}; class C : virtual public D, private E {}; class B : virtual public D, public E {}; class A : public B, public C {};It follows that bases<A>::type is tuple<D, B, E, C, E>
Similarly, direct_bases<A>::type is
tuple<B, C>
Motivating examples
Writing (roughly) parallel type hierarchies is central to many
programming best practices.
Indeed, code generation tools like Java's EMF (Eclipse Modeling Framework) automatically generate classes in three parallel class hierarchies for each UML class; An interface class; an implementation class; and a presentation class (referred to as the provider class). Any time a change is made to the inheritance hierarchy, all the code needs to be regenerated, creating all kinds of merge problems with any modifications to the previously generated code skeleton (I am experiencing this repeatedly in an EMF project I am involved with).
In the code below, we suppose that we have a hierarchy of interface classes that only contain abstract methods and inherit virtually (i.e., like Java and C# interfaces).
The following code can go in an impl.h header that automatically generates implementation classes from an interface and its superclasses.
impl.h |
---|
template<class T> class impl_base; template<class T> class impl_helper : virtual public T, public impl_base<typename direct_bases<T>::type> {}; template<class T> class impl : public impl_helper<T> {}; template<> class impl_base<tuple<>> {}; template<class T, typename... remainder> class impl_base<T, remainder...>> : virtual public impl<T>, public impl_base<tuple<remainder...>> { }; |
#include "impl.h" template<> impl<PushPutton> : public impl_helper<PushButton> { void buttonPushed() { ... } // Implement PushButton::buttonPushed };and your own class is automatically placed where it belongs in the inheritance hierarchy.
Note that programmers that use impl.h don't need to understand type traits or its internals, any more than users of STL need to understand the STL sources.
We can build on this example to create an impl_family.h header that can automatically generate implementation families parallel to a given interface hierarchy, such as Qt<PushButton> and Gtk<PushButton>, etc.
impl_family.h |
---|
template<template <typename> class Family, class T> class impl_family_base; template<template <typename> class Family, class T> class impl_family_helper : virtual public T, public impl_family_base<Family, typename direct_bases<T>::type>{}; template<template <typename> class Family, class T> class impl_family : public impl_family_helper<Family, T> {}; template<template<typename> class Family> class impl_family_base<Family, tuple<>> {}; template<template<typename> class Family, class T, typename... remainder> class impl_family_base<Family, tuple<T, remainder...>> : virtual public Family<T>, public impl_family_base<Family,tuple<remainder...>> {}; |
We have programs used at my company where this would automatically eliminate thousands of lines of intricate and difficult to maintain boilerplate template code.
It is our hope that examples such as these will convince the reader that inquiring about base classes are sufficiently fundamental to "type traits" that there will be many good uses (even beyond building parallel hierarchies).
In Section 20.6.2 (Header <type_traits> synopsis), insert where indicated between the gray context lines,
template <class... T> struct common_type; template<class T>struct bases; template<class T>struct direct_bases; } // namespace stdAdd the following two rows to the end the table in 20.6.7 (Other transformations), :
template <class T> struct bases | If T is a class, the member typedef type shall be a tuple whose types are all base classes of T in the order specified for initializing bases in 20.6.2. If T is not a class, then the member typedef type shall be a tuple with zero arguments | ||
template <class T> struct direct_bases | If T is a class, the member typedef type shall be a tuple whose types are all direct base classes of T in the order specified for initializing bases in 20.6.2. If T is not a class, then the member typedef type shall be a tuple with zero arguments |
Add the following to the end of 20.6.7:
[Example
class E {}; class D {}; class C : virtual public D, private E {}; class B : virtual public D, public E {}; class A : public B, public C {};// the following assertions hold:
assert((is_same<bases<A>::type, tuple<D, B, E, C, E>>::value)); assert((is_same<direct_bases<A>::type, tuple<B, C>>::value));— end example]
The important thing to note is that there is no harm and much advantage in implementing the traits above at the earliest opportunity, even if we add a more full-featured framework later. The main point is that the bases and direct_bases are fully consistent with the approach currently taken by is_base_of to model base class relations. If we have is_base_of, we might as well have bases, as it gives us much more power without being committal about the future. Indeed, the sketch below also includes a base_occurrences type trait that adds all the same detail to is_base_of. In the author's opinion, there is no reason to hold back on adding bases and direct_bases at soon as possible because of fear that something better (perhaps different than described below) could be implemented down the road.
Since C++ supports many types of base classes, as a preliminary to describing the bases type trait, we define a base_info class whose template parameters describe the relationship of a class to its parent to allow accurate reconstruction of the inheritance hierarchy.
namespace std { enum member_accessibility { public_accessibility, protected_accessibility, private_accessibility, inaccessible }; template<class baseT, bool direct, bool virt, member_accessibility access> class base_info { public: typedef baseT base; const static bool is_direct = direct; const static bool is_virtual = virt; const static member_accessibility accessibility = access; }; }Now we are ready to describe the describe_bases type trait.
describe_bases<C>::type is the type tuple<base_info for all base classes of C>.
Base classes are listed in the order in which they are initialized (12.6.2p10).
describe_direct_bases<C>::type is the type tuple<base_info for all direct base classes of C>.
The existing is_base_of type trait says that a class is a base class of another, but does not say anything about what kind of base class. We can extend the is_base_of<B, D> with the following type trait: base_occurrences<B, D> is tuple<base_info for all occurrences of B as a base class of D>.
class E {}; class D {}; class C : virtual public D, private E {}; class B : virtual public D, public E {}; class A : public B, public C {};It follows that describe_bases<A>::type is
tuple<base_info<D, false, true, public_accessibility>, base_info<B, true, false, public_accessibility>, base_info<E, false, false, public_accessibility>, base_info<C, true, false, public_accessibility>, base_info<E, false, false, inaccessible>> // Not accessible in A
and that describe_direct_bases<A>::type is
tuple<base_info<B, true, false, public_accessibility>, base_info<C, true, false, public_accessibility>>
We also have that base_occurrences<E, A>::type is
tuple<base_info<E, false, false, public_accessibility>, base_info<E, false, false, inaccessible>>
Of course, base_occurrences<B, C>::type is just tuple<>.
The following code shows how to produce a file equivalent to the impl.h header described in the Motivating examples above.
template<class T> class impl_base; template<class T> class impl_helper : virtual public T, public impl_base<typename describe_direct_bases<T>::type> {}; template<class T> class impl : public impl_helper<T> {}; template<>class impl_base<tuple<>> {}; template<class T, typename... remainder> class impl_base<tuple<base_info<T, true, true, public_accessibility >,remainder...>> : virtual public impl<T>, public impl_base<tuple<remainder...>> { };This acts like the previous impl.h. However, it produces a compiler diagnostic if the assumption of virtual inheritance is among the interfaces is violated. It also can be fine tuned to handle different base class accessibilities as appropriate, unlike the previous one, which inappropriately propagates private base classes to the implementation hierarchy.
Note:This impl.h has also been confirmed to run perfectly on g++ 4.3.3 (assuming the presence of the describe_direct_bases template).