[This article appeared in the October 1997 issue of C/C++ Users Journal.
There may be small differences between this and the published version.]

COMPILE-TIME ASSERTIONS IN C++


As a general rule, it's best to catch your programming errors as early
as possible.  The assert() macro can help a lot here, by detecting
violated assumptions before your code has a chance to act on those
assumptions.  But the best time to catch an error is before your
program runs -- at compile time.  If your assertion is a constant
expression, then in principle the compiler ought to be able to check
it for you.

In practice, it's not entirely straightforward.  The most obvious
approach is to make use of the preprocessor:

  #if !(Assertion)
  #error You blew it, pinhead!
  #endif

This approach has some severe limitations.  Since the preprocessor knows
very little about C++, an #if directive can't use the sizeof()
operator or refer to enum values, class members, const variables, or
integer-valued template parameters.  It's OK for checking requirements
on #define'd symbols, but that's about it.


AN EXAMPLE

Consider using a multiplicative congruential algorithm to generate
pseudorandom numbers, using Schrage's algorithm to avoid integer
overflow.  This requires a pair of integers m and a satisfying the
following restriction:

  0 < a && a < m && a % m < m / a

Being a believer in generic programming, you don't want to just write
a particular random-number generator; you want to write a
random-number generator template:

  template <unsigned m, unsigned a>
  class randgen {
  public:
    randgen(unsigned seed);
    unsigned operator()() const;
  private:
    ...
  };

To create a random-number generator the user then writes

  randgen<M, A> x(seed);

using his favorite constants M and A, and thereafter calls x() to
generate a new random number.  However, being an experienced
programmer, you know that some idiot is going to ignore your
carefully-crafted warning comments and use randgen<M,A> with
constants M and A that don't satisfy the restriction.  So
you'd like to associate some sort of compile-time check with the
randgen<> class template itself.  Clearly, the preprocessor will be no
help at all with this.


THE ctassert<> CLASS TEMPLATE

Luckily, the ctassert<> class template described in Listing 1 will do
the job.  All you have to do is insert the declaration

  ctassert<
    0 < a && a < m && a % m < m / a
  > foo;

someplace within the definition of of the randgen<> class template.
This will force a compile-time error if the randgen<> template is ever
instantiated with arguments m and a not satisfying the given restriction.

Here's how it works.  Declaring a dummy variable of type ctassert<E>, where
E is a constant, bool- or integer-valued expression, results in the
declaration

  static char A[1];

if E is true (non-zero), and

  static char A[-1];

if E is false (zero).  The latter is an error, so the compiler will
complain loudly if E happens to be false.  Furthermore, it will point
to the declaration of the variable of type ctassert<E> as a source
of the error, telling the programmer exactly what he did wrong.

Note that an object of type ctassert<E> has no internal storage, and
so the size of an object is not increased by adding a data member
of this type.

One word of warning: when writing a compile-time assertion
ctassert<E>, make sure that the expression E does not contain the >
operator, or your compiler is likely to consider it a closing angle
bracket.  Rewrite your expression to use < instead.


A MORE INVOLVED EXAMPLE

Now let's look at another example, one that incidentally gives us a
chance to play with those nifty numeric_limits<> traits classes in the
draft C++ standard library.  Sometimes you know exactly what size of
integer -- 8, 16, or 32-bit -- you want to use.  Unfortunately, the
language definition gives you no portable way of specifying a
particular size of integer.  The best you can do is write a header
file -- call it sized_types.h -- giving typedefs for int8, int16,
int32, etc., that are appropriate for the particular platform you are
running on right now.  Then you have to hope that someone remembers to
change the typedefs (or add appropriate #ifdef's) when you move to a
new platform.

The C++ standardization committee has helped out here.  The
proposed standard library includes a class template numeric_limits<>,
which is specialized for each of the numeric fundamental types.  The
class numeric_limits<T> includes the following members:

  static const bool is_integer;
    // true if T is an integer type
  static const bool is_signed;
    // true if T is a signed type
  static const int radix;
    // for integer types, specifies
    // the base of the representation
  static const int digits;
    // for built-in integer types,
    // the number of non-sign bits 

You can use these to write compile-time assertions to check that the
typedefs in sized_types.h are correct.  Then if you move to another
platform and have the wrong typedefs, the compiler will not-so-gently
remind you to get your act together.

Listing 2 shows how to write the compile-time checks.  There are a lot
of them, and they are in their own namespace to avoid polluting the
global namespace with the names of dummy variables.  For each sized
type we check that it is in fact an integral type; that it is signed
or unsigned as desired; that it uses a base-2 representation; and that
it has the appropriate number of bits.


CONCLUSION

Assertions: they're not just for runtime anymore!