See what's going on with flipcode!




This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

 

  Compile-Time Asserts
  Submitted by



A compile-time assert (CTA) is basically a little trick you can do to force the compiler to generate errors during compilation based on conditions you supply. There are many times where you'll want a CTA instead of a run-time assert (RTA). CTA's are especially useful in a team environment, as often one programmer will change something that breaks something in a different area, but since often one doesn't have the time to test every single piece of functionality in an application before checking code in, these bugs can go untracked for a long time. Obviously in order to track all bugs of this type, you really need a full-time Quality Assurance team working during the entire life-cycle of the product. However, the earlier you catch bugs the better, and CTA's force you or others to reevaluate your code by preventing compilation.

CTA's are obviously only able to evaluate compile-time conditions, so RTA's still have their place. For instance, a CTA could not assert the value of a non-const variable. However, you can use CTA's to enforce things like class/struct size, template parameters, the value of const variables, and other types of information available at compile time.

Ok, so much for the introduction. The code for a simple CASSERT macro looks like this:

#define CASSERT(expn) typedef char __C_ASSERT__[(expn)?1:-1] 



So if "expn" evaluates to true, then the macro expands to:

typedef char __C_ASSERT__[1]; 



Which the compiler accepts silently. However, if the expression evaluates to false, the macro expands to:

typedef char __C_ASSERT__[-1]; 



Which the compiler will flag as an error, because negative subscripts are not valid.

Say you have a class that, for whatever reason, needs to occupy a specific number of bytes in memory (obviously this sort of thing should be avoided, but every once in a while it's necessary). You can do the following with the CASSERT macro:

CASSERT(sizeof(MyClass) == 64); 



If the size of MyClass changes, we will get the following error during compile-time (MS VC6):

error C2118: negative subscript or subscript is too large

We now successfully have enforced the size of a data type during compile-time! This is great, but the error message could be a tad more useful, don't you think? IMHO, this macro deals with most situations just fine as you can tell the people you work with how CASSERT works, and comment your asserts as to why they would assert (you should do this anyway). But there are certain circumstances where you want to give people more friendly errors. While there isn't a huge amount of flexibility in this area, you can get some control. The following is an example of such - this is off the top of my head, so there might be a better way:

#define CASSERT_MSG(exp, msg)                                           \
    {                                                                   \
        struct cassert_t                                                \
        {                                                               \
            template<bool cond                                         \
            struct checkCondition {};                                   \
                                                                        \
            template<                                                  \
            struct checkCondition<true { enum { CASSERT_##msg }; };    \
        };                                                              \
        enum { CASSERT_##msg };                                         \
        cassert_t::checkCondition<exp::CASSERT_##msg;                  \
    } 



This defines a somewhat more powerful version of CASSERT that takes the error message as an argument. First of all, the error message must be a single identifier, so you have to use things like "sizeof_MyClass_is_not_64" rather than "sizeof MyClass is not 64!!". Secondly, it won't be exactly the error message the offender will see, but it will appear within the error message.

The way it works is by leveraging template specialization. The default definition of the checkCondition template struct is empty. However, a specialization of the template is defined for the value "true". Inside of this specialization, an enum exists with the value of "CASSERT_" appended with the error message you want. The line "cassert_struct::checkCondition::CASSERT_##msg" attempts to access the enum inside of the template instance, where the template instantiated depends on whether the expression passed in is true. (FYI, the "enum { CASSERT_##msg };" on the second-to-last line is just a workaround for VC6 so that it only throws one error. This probably won't be needed for other compilers.)

For instance, consider the previous CTA, now using the new CASSERT_MSG macro:

CASSERT_MSG(sizeof(MyClass)==64, sizeof_MyClass_is_not_64); 



The error message the offender will see (again, in VC6):

error C2039: 'CASSERT_sizeof_MyClass_is_not_64' is not a member of 'cassert_struct::checkCondition'

This gives a much more clear indication to the offender as to what is happing.

As a final note, I'll cover compile-time type assertions, as this was something that was brought up by someone for my previous TOTD.

#define CASSERT_ISTYPEOF(targ, class)                   \
        (void) static_cast<class*((targ*)0) 



There is really nothing fancy going on here. All we are doing is forcing the compiler to try to cast a targ* to a class* using static_cast. If it fails, then we know that the class and targ datatypes are unrelated.

For instance, consider the following:

class A {};
class B : public A {};
class C {};

CASSERT_ISTYPEOF(B, A); CASSERT_ISTYPEOF(C, A); // error!

The error message received in VC6 is as follows: error C2440: 'static_cast': cannot covert from 'C*' to 'A*'.

This approach is useful in some template methods, where you want to make sure that the template arguments have a specific base class (occassionaly this is desired). If you want specific error messages for type asserts there are tricks you can do, but I'll leave it up as an exercise for the reader.

Compile-time asserts will definitely help you out, whether you're working alone or with a team. The biggest obstacle is learning when to use them. Together with runtime asserts, you can write a very stable codebase that will be more manageable and maintainable.

- Dan Ogles


The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

 

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.