Aqsis 1.2.0 released

Being an Aqsis fanboy, I'm happy to say that after a long time, the Aqsis team released the 1.2.0 stable release today. Take a look at the long changelog and try for yourself. Aqsis is a very nice, open source Renderman-renderer which is really solid and comparable to PRMan 3.9/3.10 (enough to do Final Fantasy :) ). If you are serious about offline rendering, I'd suggest to take a look at it.

C++ tricks, #1: Compile time checks

While writing a library, you have sometimes to prevent users from making mistakes. To accomplish this, you have two choices: Either include run-time checking and throw an appropriate exception or try to catch as much as possible during compile time. Of course, the latter should be prefered when possible as it adds no runtime costs and forces users to use your library properly. Read on for a small example for compile time checks.

Situation

Let's assume we have rolled out our own RTTI system and now we want to create a safe casting function. We only support single-inheritance to keep things simple. It should allow downcasting from a type to a further derived type, but not accross types. This means, if you have a class Main and two derived types, Left and Right, it should be possible to cast from Main to Left or Right but not from Left to Right (at least, not directly).

What can we check?

Well, let's start what we cannot check. We have no way at compile time to see whether a Main pointer points at runtime to a derived object or not, so we have to do this at runtime. What we can check though is if someone tries to assign a Left object to a Right pointer. How do we do that? We have to check if the target is derived from the source or the other way round - if so, one class is derived from the other. Moreover, we have to check if both are the same - this is always safe. Instead of implementing own is_base_of checks, we'll use the excellent boost/type_traits library. Here's the code of our checker:

#include <boost/type_traits/is_same.hpp>
#include <boost/type_traits/is_base_of.hpp>

struct Assign
{
    template <bool CastIsValid> struct AssignImplementation;

    template <> struct AssignImplementation <true>
    {
        template <class Left, class Right>
        static void assign (Left* l, Right* r)
        {
            l = static_cast<Left*> (r);    // Your runtime cast here
        }
    };

    template <class Left, class Right>
    static void assign (Left* l, Right* r)
    {
        AssignImplementation
            <
                boost::is_same<Left, Right>::value        ||
                boost::is_base_of<Left, Right>::value    ||
                boost::is_base_of<Right, Left>::value
            >::assign (l, r);
    }
};

template <class Target, class Source>
void safe_assign (Target* target, Source* source)
{
    Assign::assign (target, source);
}

We check at compile time if the types can be casted, if so, we pass on to a partially specialized template (AssignImplementation) which should do the run-time check. In this example, I just used static_cast, for the sake of simplicity. If the compile-time check fails, AssignImplementation<false> will be called, and as it does not exist it will result in a compile-time error like

error C2027: use of undefined type "Assign::AssignImplementation<CastIsValid>"
        with
        [
            CastIsValid=false
        ]

Example

Some example code so you can try for yourself - should work with any recent standards-conforming C++ compiler.

class Main
{
};

class Left : public Main
{
};

class Right : public Main
{
};

class LeftDerived : public Left
{
};

int main (int argc, char* argv[])
{
    Left* leftDerived = new LeftDerived ();
    LeftDerived* leftDerivedPointer = 0;
    Right* right = 0;

    safe_assign (leftDerivedPointer, leftDerived);

    safe_assign (right, leftDerived); // Error!
}