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 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 colonization, and what animals can be introduced.
typedef bounds<int, -5, -30> polarbear_temp;
...
...
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.
template <bool b>
struct static_assert{};
template <>
struct static_assert<true>
{
static void valid_expression() {};
};
...
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 optimized 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 need static
data validation, because it lets me provide a custom error message. For instance, in bounds
(abridged here):
template <typename T, T lower_, T upper_ > struct bounds
{
private:
template <bool b> struct bounds_validation{};
template <> struct bounds_validation<true>
{ static void lower_larger_than_upper() {}; };
public:
typedef T type;
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.
Tagged:
C++,
template