static_assert

Assertions

In computer programming an assertion is a predicate placed in a program to indicate that the developer thinks that the predicate is always true at that place. For example:

1
int x = 9; 

x > 8 always evaluates to true, here x > 8 is a predicate that will evaluate the condition and return either true or false. Ok, so now why do we want to assert? Many assertion implementations will also halt the program’s execution — this is useful, since if the program continued to run after an assertion violation occurred it might corrupt its state and make the cause of the problem more difficult to locate. Using the information provided by the assertion failure (such as the location of the failure and perhaps a stack trace, or even the full program state if the environment supports core dumps or if the program is running in a debugger), the programmer can usually fix the problem. Thus assertions provide a very powerful tool in debugging. Programmers can use assertions to specify or enforce the pre-conditions and post-conditions of states under which the programmer expects the code to execute. This is very useful while developing application/code as it helps to find out defects/bugs before the application is shipped. This was about runtime assertion, where the predicate is evaluated based on the runtime state of application. What about library developers that want to ensure that their libraries are used with correct type?

Need for static_assert

Well, library developers can write out the boilerplate code for each type that they expect and ship out the code, this way they can ensure there are no surprises. But for supporting new types, new APIs will have to be added. This can become big pain. Second option is to use C++ Templates, write once use everywhere. This everywhere can soon be quite painful. With templates the function/class will be generated for the specified type, even if you don’t want them to be generated.

For example: I have my own class hierarchy and have smart pointers that do memory management from a managed memory region. The memory is allocated and deallocated from this region only for types in my hierarchy, so how do I constrain the creation of smart pointers so that any other class out of hierarchy will not work with my smart pointers, rather than corrupt memory at runtime.

There are several ways: - Document and blame users if they screw up - Enforce requirement during code-reviews (works internally only) - Fail compilation of such code

First two options are difficult to practice, third seems to be an interesting choice. Ok, so here is my class hierarchy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class A
{
//...
};

class B : public A
{
//...
};

class C
{
//...
};

And here is my implementation of SmartPointer, this works with classes deriving from A only.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
template<class T>
class SmartPointer
{
private:
    T* ptr;

public:
    SmartPointer() : ptr(NULL)
    {}
    
    SmartPointer(T* pointer) : ptr(pointer)
    {}
    
    ~SmartPointer()
    {
    // special deletion logic
    //
    }
};

With these implementations in place, I can write code like this and it compiles and links, but it will corrupt memory at run time.

1
2
3
4
5
int main()
{
    SmartPointer<C> cp;
    return 0;
}

The problem is templates are used as code generation mechanism but constraints are missing for types and that’s where static_assert comes into picture. So if I add one more line to my SmartPointer class to enforce hierarchical constraint.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
template<class T>
class SmartPointer
{
private:
    T* ptr;

    static_assert(::boost::is_base_of&lt;A, T&gt;::value,
    "The parametric type must derive from A");

public:
    SmartPointer() : ptr(NULL)
    {}
    
    SmartPointer(T* pointer) : ptr(pointer)
    {}
    
    ~SmartPointer()
    {
    // special deletion logic
    //
    }
};

With this definition in place, above code fails to compile with error message: alt text

One more application of static_assert can be to create stack allocated array of elements. This is very similar to creating stack allocated array of several elements. But we want to constrain how much memory this array can consume. Stack, typically, has 1 MB memory to start with and can have max of 2MB. Stack memory is very fast and very premium for high performance softwares.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
template<typename T, int numElems>
class StackArray
{
private:
  
    //  just declared privately so cannot be invoked to create
    //  an object on heap, also not implented so linking
    //  will fail
    //  
    void *operator new(size_t size);
    void operator delete(void *p);
    void *operator new[](size_t size);
    void operator delete[](void *p);

private:
    //  create an array of NumElems
    T arr[NumElems];

    //  enforce that total memory consumed by this array does not exceed 400 bytes
    //
    static_assert( sizeof(T)*NumElems &lt; 401, "StackArray cannot consume more than 400 bytes of memory");

    void assign(const StackArray& other)
    {
        for(int idx = 0; idx &lt; NumElems; ++idx )
        {
            arr[idx] = other.arr[idx];
        }
    }

public:
    StackArray()
    {
        for(int idx = 0; idx &lt; NumElems; ++idx )
        {
            arr[idx] = T();
        }
    }

    StackArray(const StackArray& other)
    {
        this->assign(other);
    }

    StackArray& operator=(const StackArray& other)
    {
        if( this != &other )
        {
            this->assign(other);
        }
        return *this;
    }

    //  indexing operator
    //
    T& operator[](int idx)
    {
        return arr[idx];
    }

    const T& operator[](int idx) const
    {
        return arr[idx];
    }

    //  begin and end so that this array can be used with STL algorithms
    //  this will qualify to be random access iterator
    //
    T* begin()
    {
        return arr;
    }

    T* end()
    {
        return arr+NumElems;
    }
};


//  size of big is 200 bytes
struct Big
{
    double arr[25];
};

int main()
{
    //  memory consumed is 20 bytes
    //
    StackArray<int,5> arr;
    for( auto iter = arr.begin(); iter != arr.end(); ++iter )
    {
        std::cout << *iter << std::endl;
    }

    //  this compiles fine as the total memory consumed by this array is
    //  400 bytes
    StackArray<Big,2> arr2;

    //  fails with compilation error
    //  StackArray cannot consume more than 400 bytes of memory
    StackArray<Big,3> arr3;

    return 0;
}

static_assert

static_assert(constant_expression, string_literal); where constant_expression is an integral constant that can be converted to boolean and string_literal is some sensible message to be displayed when constant_expression evaluates to 0 or false. In our case:

static_assert(::boost::is_base_of<A, T>::value, "The parametric type must derive from A");

the constant_expression is ::boost::is_base_of<A, T>::value. It’s one of the type_traits available in boost library. is_base_of<A,T>::value evaluates to true only if the T publicly derives from A. Well this is just one of the scenarios where static_assert can be used. static_assert can be helpful to convert runtime time errors to compilation errors.

I hope this post will prove useful for readers to get started with this C++11 feature.