Monday, June 10, 2013

Blog Series: Effective compile-time assertions in C++ (1 of n)

This is the first of at least five blog posts covering the effective use of compile-time assertions in C++. Many thanks to those who have participated in reviewing this series: Tom Kirby-Green, @matt_dz, Marshall Clow, and James McNellis


The Basics

When implementing user-defined types or functions, there are assumptions that the developer must make about the input provided to the system or about the state of the system when the code is executed.

These assumptions fall into a few categories:

  • Assumptions that can be verified at compile-time
  • Assumptions that can be verified at run-time
  • Assumptions that cannot be verified
  • Unrecognized assumptions


Developers should prefer to verify as many assumptions as possible and to perform this verification as early in the development process as possible.

The C++ standard's run-time assertion capabilities are provided by the assert macro (from the <cassert> header).  It will turn into a no-op if the NDEBUG macro is defined (i.e. in "release" builds) when you include the header file.  You can use it like this.

#include <cassert>

const char * dayOfWeek (unsigned short which)
{
    assert(("There are only 7 days in a week", which <= 6));

    const char* days[] = { "Sunday", "Monday",
                           "Tuesday", "Wednesday",
                           "Thursday", "Friday",
                           "Saturday" };
    return days[which];
}

* There is a bit of a trick inside that assertion.  See herehere, and here for more information.

As of 2011, the C++ standard now includes a compile-time assertion facility that is built into the language, static_assert.  Unlike assert, static_assert never generates any code in the resulting binary, and thus cannot not be disabled.  Since it is a language keyword, it requires no header file.  It takes the following form.

static_assert ( constant-expression , string-literal ) ;

constant-expression must be convertible (at compile-time) to a bool.  If the value of constant-expression evaluates to true, then there are no side effects, otherwise the compiler will produce an error message that includes the value of string-literal.

static_assert(sizeof(char) <= sizeof(short), "Your compiler is no good."); should have no effect (unless you have a really REALLY bad compiler).  If you were to invert that comparison such that the assertion looked like static_assert(sizeof(char) > sizeof(short), "Your compiler is bad");, then you would get an error similar to this.

error: static assertion failed: Your compiler is bad

Those are the basics!

Availability

Unlike many other C++11 features, most shipping C++ compilers already implement static_assert, making it a great candidate for adoption into your coding practice.

Upcoming topics

I hope to get entries up for the rest of the series fairly soon.  Check back later for the rest of the topics in the series.
  • Monitoring external types for changes
  • Better template error messages
  • Future-proofing against buffer overflows
  • Future-proofing against unexpected overload resolution
  • Protecting against undesirable memory growth
  • Learn about the language and your compiler
  • TBD?
  • Possible futures for compile-time assertions