wtorek, 5 sierpnia 2014

Using static_assert in class-scope

Recently I was working on some presentation (I'm going to post more about this in separate post) and I needed to verify some facts about static_assert in C++11. I decided to look straight in the proposal. Besides information I needed I found that static_assert can be applied in three scopes:
  1. namespace scope
  2. class scope
  3. block scope
I must admit that I only knew about the last option. It took me less than a minute to find out that class scope can be useful in case of my project - compile-time state machine (cfsm).

One example from cfsm is about ATMs. You can find similar code piece in there.
 1 namespace cfsm { // cfsm/Transition.hpp
 2     template <class StateType, StateType From, StateType To> class Transition;
 3 }
 5 enum class State {
 6     Welcome,
 7     SelectLanguage,
 8     CardError,
 9     // ...
10     PrintConfirmation,
11     Invalid
12 };
14 template <State From, State To> struct AtmTransition : private cfsm::Transition<State, From, To> { };
16 template <> struct AtmTransition<State::Welcome, State::SelectLanguage> { };
17 template <> struct AtmTransition<State::Welcome, State::CardError> { };
18 // ...
19 template <> struct AtmTransition<State::PrintConfirmation, State::Welcome> { };

Goal of this piece is to ensure that only defined transitions are possible. If a programmer makes a mistake, the compiler will yell that there's no specialization for given transition. After all, it is a lot better to catch such errors in compile-time, isn't it?
However, there are some drawbacks of this mechanism - compiler error messages aren't very readable. Compiler just complains about missing specialization. If the user knows what may be the cause it's not a problem. Otherwise he will be forced to go read header files, which are overloaded with templates. What can we do about it?

This is place where class-scope static_assert kicks in. Instead of not providing body for non-specialized transition template we can put one line with some message. It's going to look like following.

1 namespace cfsm { // cfsm/Transition.hpp
2     template <class StateType, StateType From, StateType To> class Transition {
3         static_assert(From == To && From != To, "Illegal transition or missing specialization.");
4     }
5 }

Note: Unfortunately we need to use such weird condition. Otherwise (e.g. in case of simple false) the compiler would do the assertion ad hoc.

There's one more problem to solve. Although custom error message says what the problem is, it doesn't contain enumerator values. This can be split into two separate issues:
  1. Assertion message should be somehow joined by the compiler, basing on multiple constant strings
  2. Enumerator values need to be represented as strings
It turns out that the later issue is addressed by another C++ proposal - N3815, so let's keep our fingers crossed! Anyway, the first problem is not going to be solved at the moment, which is obviously a bad thing.

With this little effort we gained an error message that tells almost everything about the core problem:

static.cpp: In instantiation of ‘class cfsm::Transition<State, (State)0, (State)4>’:
static.cpp:16:40:   required from ‘struct AtmTransition<(State)0, (State)4>’
static.cpp:25:51:   required from here
static.cpp:12:5: error: static assertion failed: Illegal transition or missing specialization.
     static_assert(From == To && From != To, "Illegal transition or missing specialization.");