C++ tricks, #1: Compile time checks

This post is very old. Please bear in mind that information here might be incorrect or obsolete, and links can be broken. If something seems wrong, please feel free to comment or contact me and I'll update the post.

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!
}

Related posts:

  1. Optimising compile times with precompiled headers
  2. C++ tricks, #6: Explicit template instantiation
  3. C++ tricks, #4: Binding to overloaded member functions

This entry was posted in Programming and tagged . Bookmark the permalink.

2 Responses to C++ tricks, #1: Compile time checks

  1. hehejo says:

    Ah, das ist doch mal eine schicke Sache.
    Wie sinnvoll Templates doch sein können.

    Ich mag jetzt aber auch gar nicht wissen, was die Fehlerausgabe vom GCC wäre..

  2. Anteru says:

    Na gut hier gehts auch ein bissi weiter, aber die erste Zeile ist hilfreich. Volle Meldung, auf deutsch:
    .\main.cpp(25) : error C2027: Verwendung des undefinierten Typs "Assign::AssignImplementation"
    with
    [
    CastIsValid=false
    ]
    .\main.cpp(32): Siehe Verweis auf die Instanziierung der gerade kompilierten Funktions-template "void Assign::assign(Left *,Right *)".
    with
    [
    Target=Right,
    Source=Left,
    Left=Right,
    Right=Left
    ]
    .\main.cpp(59): Siehe Verweis auf die Instanziierung der gerade kompilierten Funktions-template "void safe_assign(Target *,Source *)".
    with
    [
    Target=Right,
    Source=Left
    ]

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>