Templates Tricks

This page contains a discussion of several neat template tricks which have been used in parts of the Aqsis codebase. At the moment there's no overarching theme, but feel free to add to the page.

Heterogeneous containers

boost::any

Sometimes it's useful to hold many different types in the same container. There are a number of ways this can be done in C++. The most obvious is to derive all the types from some generic parent class, however this isn't very convenient for a number of reasons:

  • It's impossible to put built-in types like int into the container.
  • You have to modify the definitions of any class you want to contain.

It's possible to solve these problems by considered use of templates and RTTI; boost::any is one such solution. boost::any is a container for a single object with value semantics. Given this we can go ahead and define multi-object containers which hold arbitrary types, for example std::vector<boost::any>.

Here's an example of how to use boost::any:

#include <boost/any.hpp>
#include <iostream>
 
// A user-defined point type - chosen purely for illustrative purposes.
struct Point
{
    double x;
    double y;
    Point(double x, double y)
        : x(x), y(y)
    { }
};
 
std::ostream& operator<<(std::ostream& out, const Point& p)
{
    out << "(" << p.x << "," << p.y << ")";
}
 
int main()
{
    // a can hold anything!
    boost::any anything;
 
    // put in an integer:
    anything = 10;
    std::cout << boost::any_cast<int>(anything) << "\n";
 
    try
    {
        // The following will throw a boost::bad_any_cast, since "anything"
        // doesn't hold a Point type.  The types have to match exactly when
        // using any_cast - for instance, you can't any_cast an int to a float.
        std::cout << boost::any_cast<Point>(anything) << "\n";
    }
    catch(boost::bad_any_cast& e)
    {
        std::cout << "Oops, exception: \"" << e.what() << "\"\n";
    }
 
    // We can easily add user-defined types...
    anything = Point(1,2);
    // Now "anything" holds a point, so it works perfectly
    std::cout << boost::any_cast<Point>(anything) << "\n";
 
    return 0;
}

The templated holder-class technique used by boost::any is an instructive trick, so here's a description of a very minimal implementation of the basic idea. First, we define a holder class:

struct Holder
{
    virtual ~Holder() {}
};

We then define a template class which is a child of Holder, and can hold a single value of any type which is amenable to being passed and held by value.

template<typename T>
struct HolderTyped : public Holder
{
    T value;
    HolderTyped(const T& val)
        : value(val)
    { }
};

Now, as long as we hold things via pointers, we can think of Holder as a generic container for any type as follows:

int main()
{
    Holder* h = 0;
    h = new HolderTyped<int>(10);
    // h holds an integer now
 
    // ... 
 
    std::cout << "h = " << dynamic_cast<HolderTyped<int>*>(h)->value << "\n";
 
    delete h;
    h = new HolderTyped<Point>(Point(1,2));
    // h now holds a Point instead.
 
    // ...
 
    std::cout << "h = " << dynamic_cast<HolderTyped<Point>*>(h)->value << "\n";
 
    return 0;
};

Of course, all the above is very manual and involves lots of messing about with explicit dynamic_casts. Another drawback is that you must hold Handle via pointer or reference, not by value, so it hides the desired value-semantics. However, it's possible to wrap all this up in a nice class. boost::any essentially does this, holding something equivalent to Handle* internally, and adds the necessary assignment operators and casting constructs for ease of use.

The general technique of templatized child classes is very powerful, and has been used in a few places in the Aqsis source. One example may be found in the image channel source/sink classes used by piqsl to do alpha blending between image channels of different type. (See IqImageChannelSource in the file imagechannel.h.)

Typesafe heterogeneous containers; tag structs

One problem which appears whenever you have a heterogeneous container is that it's possible to accidentally try to retrieve the wrong type for some element of the container. Sometimes this is unavoidable because the underlying situation is dynamic. However at other times we may know a lot about the types which need to be held in our container. In this case we want the compiler do some of the checking for us.

Example: A container for image attributes

One place where the problem of dynamic vs compile-time checking cropped up was when writing an image attributes container - a container for the arbitrary types which may be present in the header of an image file. In this case we know a lot about the types which we're interested in at compile time. For example, we know that any description of the software which created the file will be stored in the in the header as a string. Similarly, we know that a description of the world to camera transformation at the time the image was rendered will always come as a matrix. We want an “image header” container which can hold either of these types (and more!). The image header should be able to associate these types with some kind of tag which lets us retrieve the values from the container.

A first attempt at a container for image attributes might be simply to use strings for the tags, and boost::any to hold the values of arbitrary type. Associating these together using a std::map, we have something which is quite close to what we want:

typedef std::map<std::string, boost::any> ImageHeaderTry1;

Insertion and retrieval from an image header of this type would look as follows:

ImageHeaderTry1 header;
 
// Put some stuff into the header:
header["Software"] = std::string("Aqsis");
header["WorldToCameraMatrix"] = CqMatrix(/* some initialization */);
 
// ...
 
// Read something back:
std::cout << "This image was created using " << boost::any_cast<std::string>(header["Software"]) << "\n";

The problem with this is that we need to do all our checking at runtime, and there's two types of mistakes that the compiler won't be able to help us catch:

// 1) error: typos in the tag.
std::string softwareString = boost::any_cast<std::string>(header["Sotfware"]);
 
// 2) error: Trying to cast to the wrong type, for example SqMatrix2D instead
//    of CqMatrix
CqMatrix worldToCam = boost::any_cast<SqMatrix2D>(header["WorldToCameraMatrix"]);

Happily, we can resolve both these issues through the use of tag structs.

Tag structs to the rescue

A tag struct is a struct which introduces a new “tag” identifier into the scope. The tag may have associated with it a set of types (using typedefs), and values (using static members variables or functions). In this way, we may think of a tag as a mapping from identifiers to types and/or values.

A tag struct for the world to camera transformation mentioned above might look like the following:

struct WorldToCamera
{
    typedef CqMatrix type;
    static int id = 42;  // for example; not needed in ImageHeaderTry2 below.
};

We see that this associates the type CqMatrix and the value 42 with the identifier WorldToCamera. Because the compiler is actually aware of the tag, it can be checked for typos in the usual way, in contrast to the case above where the tag was a simple string. The fact that we have the typedef means that it's also possible to design an ImageHeader class interface which knows about the type of WorldToCamera and checks it at compile-time.

In particular, we can define something like the following find and set interface functions:

class ImageHeaderTry2
{
    private:
        // There's a number of ways to implement a container for attributes.
        // One easy method is to use RTTI to provide a some_key type for each
        // tag struct based on the typeid of the tag.
        std::map<some_key, boost::any> m_attributeMap;
    public:
        // Find the value for a tag in the header, and return the correct type.
        template<typename AttrTag>
        typename const AttrTag::type& find() const
        {
            return any_cast<const typename AttrTag::type&>(
                    m_attributeMap.find(/* get a key from AttrTag somehow,
                        possibly using typeid(AttrTag) or AttrTag::id */)->second);
        }
 
        // Set the value for a tag in the header.
        template<typename AttrTag>
        void set(const AttrTag::type& value)
        {
            m_attributeMap[/* get a key from AttrTag somehow, possibly using
                              typeid(AttrTag) or AttrTag::id*/] = value;
        }
};

With the above template magic, using our new heterogeneous container is easy:

ImageHeaderTry2 header;
 
CqMatrix worldToCam;
// initialize worldToCam somehow
 
// setting the attribute is simple, typesafe, and checked at compile-time.
header.set<WorldToCamera>(worldToCam);
 
// Reading it back is just as easy:
worldToCam = header.find<WorldToCamera>();
 
// We can define an arbitrary number of tag structs (probably inside a special
// namespace in practise).  Suppose we'd defined a Software tag; in exactly the
// same way as above, we'd have
std::string softwareName = header.find<Software>();

The beauty of this approach is that everything in sight is checked by the compiler, so it's much more difficult to make mistakes which will manifest later as confusing runtime errors.

Emulating virtual template members

For fundamental implementation reasons, the C++ standard disallows virtual template member functions. Here we discuss some techniques which may be used in place of such.

Overloaded functions

One obvious technique is to overload a function for a certain subset of types which you know are going to be needed. This has the advantage that it's straightforward, but can result in rather a lot of boilerplate code.

FIXME Add further discussion.


Personal Tools