Static assert in C++

Code, CodeProject

Just a quick little note today, to clarify something I mentioned in passing the other day in Bounds, and staying within them. I said I “I added a static assert to validate the template parameters at compile time”, and it’s probably worthwhile to spell out how that works for those who haven’t seen it before.

As a rule, the earlier you find an error, the easier it is to identify and fix. The errors spotted by your compiler are, naturally, easier to fix than the errors exhibited by your program as it is running. A static assert helps by letting you sanity-check the code you write, and generating a compiler error if you write code that is syntactically correct, but logically incorrect.

For instance, the bounds class takes a lower and upper boundary as template parameters, and assumes them to be ordered. Say we didn’t have a static assert, and used it to find out if a randomly generated world in a space game is suitable for colonisation, and what animals can be introduced.

typedef bounds<int, -5, -30> polarbear_temp;
...
// generating a randomworld and it's temperature
...
// Adding various animals
if (polarbear_temp::in_bounds(randomworld_temp))
...

This would lead to a universe completely devoid of polar bears (which I’m sure we all can agree would be a bad thing), because there is no temperature that can be both greater than -5 and less than -30.

Because the limits are known at compile time, it makes sense to check them at compile time, too.

There are a number of ways of writing a static assert, but in general they tend to rely on some syntactical trickery that makes the compiler complain if an expression evaluates to false, and generate no or little code at all if an expression evaluates to true. There are a couple of examples in C in Wikipedia, but curiously no specific C++ example.

I can’t recall where I first saw an example of the implementation I used in the bounds class, but it’s a quite well known method, using the expression being evaluated as a template parameter.

// Assertion type
template <bool b> 
struct static_assert{};
// Specialisation with member function
template <>
struct static_assert<true> 
{ 
  static void valid_expression() {}; 
};
...
// Usage - replace TEST with the static expression to evaluate
static_assert< TEST >::valid_expression();

The way this works is through template specialisation. If the template expression evaluates to true, there is a type static_assert<true> that has a static member function valid_expression(), so the code compiles tidily (and the function call is likely optimised away). If, on the other hand, the expression evaluates to false, the compiler can only find the generic static_assert<bool b> to use, and that type doesn’t have a valid_expression().

(The ‘trick’ here is to remember that even if they have the same name, static_assert<true> and static_assert<bool b> are completely different and unrelated types, since they have different template arguments.)

So, because compiler sees the static_assert<false> type as static_assert<bool b>, you’ll get a complaint along the lines of ‘valid_expression’ : is not a member of ‘static_assert<b>’, and the file and line where it’s been invoked.

As often as not, I tend to re-write these as private members of a class that needs static data validation, because it lets me provide a custom error message. For instance, in bounds (abridged here):

template <typename T,                           // data type
  T lower_,                                     // lower limit
  T upper_ >                                    // upper limit
struct bounds
{
private:
  // Validate ordering of template parameters at compile time
  template <bool b> struct bounds_validation{};
  template <> struct bounds_validation<true> 
  { static void lower_larger_than_upper() {}; };
public:
  // Type name for templated type
  typedef T type;

  // Check if value is within bounds
  static bool in_bounds(const type& val)
  {
    bounds_validation<lower_ <= upper_>::lower_larger_than_upper();
    return L::less(lower_, val) && U::less(val, upper_);
  }
...

Here, the declaration bounds<int, 5, 1> would generate the compiler error ‘lower_larger_than_upper’ : is not a member of ‘bounds< T, lower_,upper_ >::bounds_validation<b>’, which expresses more clearly what the error is.

Update: I should mention that there is – of course – a BOOST_STATIC_ASSERT, and there will be a static_assert in C++ 0x.

Advertisements

One thought on “Static assert in C++

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s