Conditional Statements with C++ Templates

about | archive


[ 2007-April-17 23:21 ]

I've been programming C++ for a long time, and I think I only recently started to really understand templates and what can be done with them. In general, I think that the mechanism, while extremely useful and powerful, is a failure because they are way too difficult to understand, use, maintain, and implement. However, this week I learned a way to write conditionals using templates. I figured it out while implementing a reference counting smart pointer. I started by creating a small wrapper object that contains a pointer to the real object and a reference count. All the smart pointers actually pointed to the wrapper, the wrapper deleted the object and itself when the reference count reached zero. However, this adds overhead. First, we need to allocate an extra object, and secondly we need to read an extra pointer each time we dereference the smart pointer.

I then implemented it so the reference counted objects needed to inherit from a base class which would contain the reference count. This avoids the overhead, but now I can only use specific types. I then looked for a way to unify the two implementations, so I could use my RefCountedPtr<T> with either the specific subclass or the generic object. This article describes the technique I used by way of an example. You can get the complete source code if you want to play along at home. To start, let's write two simple classes:

class A {
public:
    void bar() {
        printf("A::bar()\n");
    }
};

class B {
public:
    virtual void baz() {
        printf("B::baz()\n");
    }
};

For some reason we want to use these classes in a template class. Maybe we are creating a container or a smart pointer. I'm trying to keep this example simple, so we will just write a method called wrapped which will call the original method with some code before and after it. Here is what my main program looks like:

int main() {
    Wrapper<A> wrapped_a;
    Wrapper<B> wrapped_b;

    wrapped_a.wrapped();
    wrapped_b.wrapped();
}

How can we make that work when we need to call A::bar and B::baz? The secret is to specialize the template. This is a powerful tool, but it isn't the point of this article so I am not going to explain the details about how this works. I'll just give you the code:

template <typename T> class Wrapper;

template <> class Wrapper<A> {
public:
    void wrapped() {
        printf("Wrapper<A>::wrapped() begin:\n\t");
        contents.bar();
        printf("Wrapper<A>::wrapped() end\n");
    }

private:
    A contents;
};

template <> class Wrapper<B> {
public:
    void wrapped() {
        printf("Wrapper<B>::wrapped() begin:\n\t");
        contents.baz();
        printf("Wrapper<B>::wrapped() end\n");
    }

private:
    B contents;
};

The output of this program looks like this:

Wrapper<A>::wrapped() begin:
        A::bar()
Wrapper<A>::wrapped() end
Wrapper<B>::wrapped() begin:
        B::baz()
Wrapper<B>::wrapped() end

Now, what if we subclass B to create C:

class C : public B {
public:
    virtual void baz() {
        printf("C::baz\n");
    }
};

Since C is a subclass of B, we should be able to use Wrapper<C> and have it call C::baz, right? Unfortunately, our compiler disagrees:

templates-are-complicated.cc: In function 'int main()':
templates-are-complicated.cc:68: error: aggregate 'Wrapper<C> wrapped_c' has incomplete type and cannot be defined

The problem here is that Wrapper<B> and Wrapper<B> are completely unrelated types, despite the fact that their names look similar, and that B is a subclass of C. So now what? We can still unite these under a single template by creating a conditional. To do this, we need to define types to represent true and false at compile time. For this example, the following will work:

struct true_type {};
struct false_type {};

Now we give Wrapper an extra parameter to specify if T is a subclass of B or not. We then create two partial specializations of Wrapper for the true and false cases. This looks like this:

template <typename T, typename SubclassesB> class Wrapper;

template <typename T> class Wrapper<T, false_type> {
public:
    void wrapped() {
        printf("Wrapper<T, !(subclasses B)>::wrapped() begin:\n\t");
        contents.bar();
        printf("Wrapper<T, !(subclasses B)>::wrapped() end\n");
    }

private:
    T contents;
};

template <typename T> class Wrapper<T, true_type> {
public:
    void wrapped() {
        printf("Wrapper<T, (subclasses B)>::wrapped() begin:\n\t");
        contents.baz();
        printf("Wrapper<T, (subclasses B)>::wrapped() end\n");
    }

private:
    T contents;
};

And it produces the following output:

Wrapper<T, !(subclasses B)>::wrapped() begin:
        A::bar()
Wrapper<T, !(subclasses B)>::wrapped() end
Wrapper<T, (subclasses B)>::wrapped() begin:
        B::baz()
Wrapper<T, (subclasses B)>::wrapped() end
Wrapper<T, (subclasses B)>::wrapped() begin:
        C::baz
Wrapper<T, (subclasses B)>::wrapped() end

Now, this example makes this seem really silly. We have these ugly true_type and false_type things hanging off our variable declarations. However, you can combine this technique with other template hacks in order to automatically deduce the value for the boolean parameter. I'll admit: I still don't understand those techniques, so you'll have to figure that part out on your own. [Example Source Code]