1) Introduction This section is intended for people not familiar with Type Erasure. Type Erasure is a library that enables manipulating objects whose types have a similar behaviour under a unique name generically and without using inheritance or templates (in the user code). If you have ever wished that you could access iterators from the different containers without relying on templates in header files, then this library is for you. Without Type Erasure, there are two traditional ways for using objects generically: - Classic polymorphism: declare a common base class for all concerned types with a set of abstract virtual functions to be implemented by each. The usual problems with this approach are: * If you need to work with a type you can't change you need write wrappers. * You are limited to pointers or references for dynamic dispatching to work, you can't work with values. - Then, one could use templates functions: they accept any types that makes their body valid C++, removing the limitations of inheritance. Other problems arise though: * There is no real way to know what the expected "concept" is except reading the code or the documentation (that must be manually written). * The source for the function will be compiled many times and must be available everywhere. With Type Erasure you can give the same meaning to your code without most of the problems. It may come at cost in memory usage and performance for the most complex usages but the added flexibility will usually be worth these costs. This is called concept-based polymorphism. Here, a "concept" is set of functionalities that an object must support in order to be used generically. Boost already provides implementation of concept-based polymorphism: - Boost::any for types whose instances can be manipulated as values (i.e. can be copied). - Boost::function for objects that can be called like functions. - Boost::range provides "any_iterator" Type Erasure provides a generalization for any concept (and provides a set of useful predefinitions). 2) How to read this documentation You'll need to know about inheritance, RIIA virtual function tables, templates and basic meta programming to understand how to use the library. 3) Basic usage Now let's try to see how to use this in practice. Let's consider the C++ standard library containers. Many of them have a "push_back" method that adds an element to the container. Here is how to write a function that can add an element to any container with a push_back method: #include #include #include #include using namespace std; namespace te = boost::type_erasure; // Define the concept "The type has a member function named push_back that takes a single argument.". // Concept name <==> "HasPushBack", in the namespace "MyNamespace" // Single argument <==> "1" BOOST_TYPE_ERASURE_MEMBER((HasPushBack), push_back, 1) // A short hand for an otherwise long name. // This type is the type we can use to store a reference to any object that has a push_back method // that accepts a single int argument. NB. "void(int)" is the signature of the method. typedef te::any, te::_self&> AnyPushBack; // The method doing the job void AppendMany(AnyPushBack& container) { for (int i = 0; i < 10; ++i) container.push_back(i); } vector v; list l; AnyPushBack va(v); va.push_back(-1); AppendMany(va); AnyPushBack la(l); la.push_back(-1); AppendMany(la); // l and v now contain the ints from -1 to 9. As usual with minimal code samples, this would make more sense in a bigger context, where, for instance AppendMany cannot be a template (in a different compilation unit or library). !! Wait : this is not the end of it, you can do many more powerful 4) Adapting non conforming types Now we can go further with our example: let's consider a third type we would like to manipulate with AppendMany but that doesn't have a push_back member function, we'll choose std::set. For the sake of the example, we'll add a constraint that all elements contained in AnyPushBack types are unique. The way to "teach" Type Erasure how to apply a concept to a type is by specializing the concept template. In our case this template was created automatically by the call to the BOOST_TYPE_ERASURE_MEMBER macro. The specialization should have a single static method named "apply" that takes the object and the function parameters, like the following example: template <> struct HasPushBack > { static void apply(std::set& set_, int newValue) { set_.insert(newValue); }; }; Now we can use std::set instances as initializers for AnyPushBack: std::set s; AnyPushBack sa(s); s.insert(-1); AppendMany(sa); This saved us from creating an extensively verbose inheritance hierarchy of that would awkwardly try mimicking the containers and their behaviour. Concepts can also be created directly without ever relying on a given member function has a base. How to do this will be described later. 5) Composing concepts In the former example we have simulated a class that would look like this: class HasPushBack { virtual void push_back(int i) = 0; }; While this is nice already, real life use cases will have many methods to implement. More complex (but still very frequent) cases will use several abstract base classes inheriting each one another in a complex patterns. The same is possible with concepts. The "HasPushBack" concept we defined earlier is called a "primitive" concept because is defines one possible function call (think one entry in the virtual function table). Type Erasure offers a few of those that will be listed later. Primitive concepts can be composed with an "AND" relationship. If we have concepts A, B and C, we can create an any container for types that satisfy the three of them in the following way: typedef te::any, B<...>, C<...> >, te::_self&> AnyABAndC; (Where mpl is a shorthand for boost::mpl) The definition is recursive A, B and C may be mpl::vectors themselves. You can use up to BOOST_TYPE_ERASURE_MAX_FUNCTIONS types in your concept definition. Now a nice feature is that you can define AnyAAndB that only matches A and B and automatically "downcast" from AnyABAndC: AnyABAndC abc; AnyAAndB ab(abc); 6) Defining custom concepts [ ... current example good enough though I stupidly reuse the same in my 3rd paragraph ... ] 7) How it works For each concept in the parameter list of any, an entry in a function pointer table (vtable) is created. Each instance of any carries with itself it's own vtable (unlike inheritance where the vtable is unique for a given type). It is created at the same time as the any depending on the underlying type. Each call to a function on an any will first trigger a lookup in the vtable; incuring a cost similar to virtual functions. 8) Using values and operators So far we have always defined concepts capturing by reference, i.e. the instance of any was keeping a kind of pointer to the existing object and that object must remain valid while the any is in use. It is possible to lift that requirement for types that are copy constructible using the pre-defined "copy_constructible" concept. Other predefined concepts allow using the common C++ operators such as +, ++ and <<. [ ... concrete example here ... ] When to use references ? You can value anys, references to value anys, reference anys and reference to reference anys ... Sometimes too much choice is too much. Here are some reasonnable defaults : - Prefer references over values. Copying objects and anys in particular is costly (requires a dynamic allocation). - Use (const) references to any when passing anys to functions. - Use a "value any" if you don't want to tie the lifetime of the any with the source Any may holds references to const values. 9) Advanced features 9.1) Getting back the original object with RTTI 9.2) Multiple arguments 9.3) Construction / Destruction 9.4) Overloading 9.5) Associated types 9.6) Simulating the current boost libraries (any, function and anyiterator)