Sep 202013

During the development in TDD manner of some generic C++ code in our project I stumbled over the problem that I wanted to forbid the usage of integral, floating point and pointer types as a certain template argument. Only types with a default constructor made sense in that context. My very first attempt was to use a static_assert like: 

template <typename T>
struct Foo
  static_assert(!std::is_integral<T>::value &&
                !std::is_floating_point<T>::value &&
                "The usage of integral, floating point and pointer types is not allowed in this context. Please use FooWithDefaultValue.");

I checked inside my file with the unit test, that the instantiation of Foo<int>() actually led to a compile time error. So fine so good. I made an #if 0 … #endif around this sample failure code and went on.

Later a colleague of mine reviewed the code and brought up the point, that a) this is not a real unit test and b) whenever someone changes the file containing the definition of Foo<> he has no knowledge about the commented code in the unit test file. He was right! But how to ensure that on one hand something does not compile and on the other hand how to guarantee that the unit test fails if the tested code gets changed in the future, e.g. during a refactoring? It seems to be a paradox. One could move this failure detection from compile time to runtime. But this would worse than the static_assert, because the advantage of early feedback to the developer would be gone. So I kept the compile time check.

Template Meta Programming is the trick that helps here further.

So let’s start: One simple way of disallowing the instantiation of an object is to make the default constructor private (and of course not having any other public one 🙂 The following simple class definition helps here:

// A generic class that helps here
template <typename T>
class disable_usage
  typedef T disable_marker_type;
  disable_usage() {} // c'tor is private!

template <typename T>
struct Foo

// The specialization for int shall be forbidden
template <>
struct Foo<int> : public disable_usage<Foo>

Foo<int> fooInt; // This does not compile

The reader might ask him- or herself about the funny typedef of T::disable_marker_type? We will see soon…

So half way we are done, the specialization is not possible. Let’s do the other half. Let’s use the Substitution Failure Is Not An Error (SFINAE) trick here. (For detailed explanation of SFINAE I recommend to read Andrej Alexandrescu’s book “Modern C++ Design” or other articles in the net.)

template <typename T>
class is_disabled_from_usage
  typedef char Yes;
  typedef struct No { char dummy[2]; };

  template <typename U>
  static Yes test(typename U::disable_marker_type* p);

  template <typename U>
  static No test(...);
  static const bool value = sizeof(test<T>(nullptr)) == sizeof(Yes);

// Line in unit test

So during compilation the compiler tries to get the value of the expression is_disabled_from_usage<Foo<int>>::value. This is defined as a result of a comparison of equality. So during the overload resolution of test<Foo<int>>(nullptr) it checks which of the two provided methods fit best. Since Foo<int> is derived from the class disable_usage, it has defined the type disable_marker_type and the 1st test method is a better match than the 2nd with the ellipsis parameter, which just serves as a fall back. So the return type of test() is in this case of type Yes. The comparison of the size of test’s return type is equal to the size of Yes. So the static const bool value becomes true.
All this is done by the compiler during compile time. Foo<int> is never instantiated.

And so the unit test statement EXPECT_TRUE() passes because actually during runtime there is only the statement left


So my headline “How to unit test static_assert in C++” is not completely valid any more, because I finally do not test the static_assert, but something similar from the point of functionality.

With C++14 Concepts this would be a different story.  But I don’t see so far how to test that a certain piece of code is defined with a certain Concept. That’s something for later. Currently I have to deal with the capabilities of VS 2010.

Feedback is always welcome!

Sorry, the comment form is closed at this time.