Search code examples
c++c++11assertconstexprstatic-assert

How to dispatch between assert() and static_assert(), dependend if in constexpr context?


In C++11 constexpr functions, a second statement such as an assert() is not possible. A static_assert() is fine, but wouldn't work if the function is called as 'normal' function. The comma operator could come to help wrto. the assert(), but is ugly and some tools spit warnings about it.

Consider such 'getter' which is perfectly constexpr-able beside the assertion. But I would like to keep some kind of assertion for runtime and compile time, but cannot just overload depending on the 'constexpr' context.

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement( int idx ) const
  {
    ASSERT( idx < Size ); // a no-go for constexpr funcs in c++11
    // not possible, even in constexpr calls as being pointed out, but what I would like:
    static_assert( idx < Size, "out-of-bounds" );
    return m_vals[idx];
  }
};

Side conditions: C++11, no heap, no exceptions, no compiler specifics.

Note as commenters pointed out (thanks!), static_assert on the argument is not possible (but would be nice). The compiler gave me a different error on out-of-bounds access in that situation.


Solution

  • Something like

    void assert_impl() { assert(false); } // Replace body with own implementation
    
    #ifdef NDEBUG // Replace with own conditional
    #define my_assert(condition) ((void)0)
    #else
    #define my_assert(condition) ((condition) ? (void()) : (assert_impl(), void()))
    #endif
    
    template<int Size>
    struct Array {
      int m_vals[Size];
      constexpr const int& getElement( int idx ) const
      {
        return my_assert(idx < Size), m_vals[idx];
      }
    };
    

    It will give a compile time error on assertion failure if used in a context requiring a constant expression (because it will call a non-constexpr function).

    Otherwise it will fail at runtime with a call to assert (or your analogue).

    This is the best that you can do as far as I know. There is no way to use the value of idx to force a check at compile-time outside of context requiring constant expressions.

    The comma operator syntax is not nice, but C++11 constexpr functions are very limited.

    Of course, as you already noted, undefined behavior will be diagnosed anyway if the function is used in a context requiring a constant expression.

    If you know that assert (or your analogue) doesn't expand to anything that is forbidden in a constant expression if the condition evaluates to true but does so if it evaluates to false, then you can use it directly instead of my_assert and skip the indirection that I build in my code.