Asserts in constexpr functions

Assertions (like C-style macro assert) are not an ideal, but still useful tool for indicating assumptions about program correctness, and help finding programmer bugs. In this post we will see how we can use assertions in constexpr functions. This works different in C++11 and C++14.

First: the problem description. I have a function with a precondition. Suppose I am implementing a fixed-size string (which stores characters in a raw array):

template <unsigned N>
class FixedStr
{
  char _array [N + 1]; // for terminating 0

public:
  char operator[](unsigned i) const
    /* expects: i < N */
    { return _array[i]; }

  // ...
};

Among other things, I provide per-character access with operator[]. But I expect that you give me the index small enough that it fits into the array. You could argue that i == N is also a valid index. It is, in the sense that I have some value (zero) to be read from under that index, but in a way it is my implementation detail that I do not want to expose: if you are reading value form under index N you are doing something wrong, even if I know how to fix it — I do not want such (ab)usages to be understood as valid code.

You might not agree with my choice, but one thing remains uncontroversial: I have a precondition; that is, not every value of type unsigned works for this function. This illustrates one important thing: preconditions are not only for protecting against undefined behavior. They also protect against logic errors in the program: situations, where program is a valid C++ program but does something else than you intended.

However, there is no way within the language to statically enforce that users of my class will only pass the expected index values. Some people in this case choose to change the semantics of the class interface, e.g. to:

char operator[](unsigned i) const
  /* expects: any i */
  { 
    if (i >= N) throw SomeException{}; // or return 0
    return _array[i];
  }

Now, there is no precondition: any value of i is acceptable. But now the function does two things: it either returns the requested character or starts stack unwinding. Using it becomes more complicated. Also, now the bugs are guaranteed not to be detected until run-time (or production), which is much too late. Also, the function is now slower because of the additional branch.

I do not want to go that route, so the only other thing I can do is to insert some instrumentation code in test builds, so that no semantics or performance is compromised in production builds, but at least in testing bad usages can be detected. This is what the assert is for:

char operator[](unsigned i) const
  /* expects: i < N */
  { 
    assert (i < N);
    return _array[i];
  }

I do not consider this to be the proper use of assertions. As indicated in another post, you should assert something you know to be true. In my case above, I do not know how other programmers will be using my function, and if they will respect my preconditions, so I cannot take responsibility for their actions. But still, in practice, instrumenting test builds like this will help detect miscommunication between programmers.

Now, how does the picture change if I want to make my class FixedStr a literal type; that is, a type that can be constructed and inspected at compile-time?

Literal types

let’s start with a definition.

constexpr LT val1 = /* ... */ ;
constexpr LT val2 { /* ... */ };

If you can declare and initialize an object in one of this ways, it means that LT is a literal type. It means, provided that the above actually compiles, that the state of val1 and val2 can be inspected at compile-time. Type LT has to meet a number of conditions to be usable in this compile-time context:

  1. Be either a scalar type or be composed of other literal types.
  2. Have a trivial destructor.
  3. Have no constructors (be an aggregate), or have at least one non-copy, non-move constructor, which is declared as constexpr, which we will use for static initialization.

The rules are in fact a bit more complicated, but for the purpose of this post, they will suffice.

Our type FixedStr<N>, as it is, meets these criteria:

constexpr FixedString<8> s {};

We initialized our s via value-initialization. The array inside will be filled with zeros. This is not how normal people initialize strings, but I want this post to be focused on one subject. I will cover the design of a decent compile-time string in a separate post.

The point of focus now is that we have a constant that we can inspect at compile-time. But we have to do such inspections with constexpr functions.

Constexpr functions

Let’s rewrite our operator[]:

constexpr char operator[](unsigned i) const
  /* expects: i < N */
  { 
    return _array[i];
  }

And test it:

constexpr FixedString<8> s {};
static_assert(s[0] == 0, "***");

This works. But the current implementation of operator[] does not check the validity of the index. How can we implement it?

First, in most of the cases, when calling constexpr functions at compile-time, we get some validity checks for free:

constexpr FixedString<8> s {};
constexpr char c = s[10]; // fails to compile!

This fails to compile! The compiler is required to detect at compile-time any UB (Undefined Behavior) related to expressions (as opposed to UB caused by the Standard Library requirements), and report them as compile-time errors!

Just think of it; this is a huge task imposed on compiler vendors, but what you get instead is the ability to test your functions against UB, provided that you can afford turning them into constexpr functions.

But clever as this is, it does not solve all our problems. This protects against the UB when our function is invoked at compile-time, but not necessarily so, when it is invoked at run-time. And as it is the case with constexpr functions, they need to work in either context. Second, there is the case of s[8], the last terminating null character: reading it is not an UB, but it is still a precondition violation, and we still want to report it, both at compile-time and at run-time.

Addressing this in C++14 is trivial:

// C++14 :
constexpr char operator[](unsigned i) const
  /* expects: i < N */
  { 
    assert (i < N);
    return _array[i];
  }

The language guarantees that if the expression inside the assertion evaluates to true (at compile-time), the assertion is a “constant subexpression”, and does not impede the compilation/evaluation of your function.

Constexpr functions in C++11

Now, in C++14 constexpr functions are treated as other functions, except that they can be invoked during compilation. It was not the case in C++11, where constexpr functions were only meant to be replacements for short expressions. Hence the constraint that a constexpr function can only contain a single return statement in its body.

return _EXPRESSION_;

This sounds very constraining, but, C++ (taking after C) is full of clever operators, and one of them can be used to squeeze a number of expressions into one:

return _EXPRESSION_1_, _EXPRESSION_2_;

This almost looks like the solution:

constexpr char operator[](unsigned i) const
  /* expects: i < N */
  { 
    return assert(i < N), _array[i];
  }

Indeed, assert is guaranteed to be an expression, so there are reasons to believe it should work. And if we test it in Clang (with -std=c++11) indeed, it works. But it doesn’t work in GCC: you can never perform a “compile-time call” to such function, even with correct arguments. GCC is allowed to do it. C++11’s constexpr functions were not design with such clever usages in mind. In particular, assert is not guaranteed to be a “constant subexpression” if the condition evaluates to true.

In fact, the reason for assert not working in GCC is that it is defined more-less like this:

#define assert(e)                                             \
 ((e) ?                                                       \
  static_cast<void> (0) :                                     \
  __assert_fail (#e, __FILE__, __LINE__, __PRETTY_FUNCTION__) \
 )                                                            \

Macros __FILE__ and __LINE__ are standard. Macro __PRETTY_FUNCTION__ is a GCC extension, and is supposed to expand to the name of the function.

But what we say about evaluating functions at compile-time is just a metaphor. constexpr functions are not “evaluated” at compile-time, in the sense that some assembly instructions get executed. It is just that the compiler inspects the definitions and replaces the notation of a call to a function with the expected result value. It is probably for this reason that it does not know how to expand macro __PRETTY_FUNCTION__ in these contexts, so it leaves it as it is: __PRETTY_FUNCTION__ is pasted as though it were an identifier, causing the body of the function to be incorrect. This can be tested with -E option. After pre-processing phase, our function definition looks more-less like this:

return
  ((i < N) ?
    static_cast<void> (0) :
    __assert_fail ("i < N", "main.cpp", 50, __PRETTY_FUNCTION__)
  ),
  _array[i];

__PRETTY_FUNCTION__ remains unexpanded, and is interpreted as an identifier that has not been declared. If I just manually put some string literal in place of __PRETTY_FUNCTION__, the program starts to do what I expect, but I cannot afford that in a platform-agnostic library. After all, function __assert_fail is platform-specific.

Interestingly, the situation gets better when I add a lambda into the mix. I will emulate the mechanics of an assert but in a portable way:

return
  ((i < N) ?
    static_cast<void> (0) :
    []{ assert(false); }()  // define and call a lambda
  ),
  _array[i];

I am not even sure why this works. Probably, when evaluating our function at compile-time, the compiler is only interested in that we will be invoking a non-constexpr function, and does not even want to see or parse its body:

return
  ((i < N) ?
    static_cast<void> (0) :
    __ANONYMOUS_CLASS__{}()  // operator() is non-constexpr
  ),
  _array[i];

Now, in order to generalize this solution, we can create a macro for assertions in C++11 constexpr functions/constructors:

#if defined NDEBUG
# define X_ASSERT(CHECK) void(0)
#else
# define X_ASSERT(CHECK) \
    ( (CHECK) ? void(0) : []{assert(!#CHECK);}() )
#endif

It applies some further tricks. First both “then” and “else” sub-expressions have the same type. Second, rather than just typing false in the nested assertion, we generate !"i < N". A negated string literal also evaluates to false, but the messages from run-time assertion failures are more informative.

Now, we can declare our operator[] as follows:

constexpr char operator[](unsigned i) const
  /* expects: i < N */
  { 
    return X_ASSERT(i < N), _array[i];
  }

And, since we are writing a macro that might be used in many places, we might adapt certain optimization from Boost.Assert, to potentially speed-up slow execution of test builds, by disabling the branch prediction and manually setting the preference for the positive case (on compilers that support this: Clang and GCC):

#if defined __GNUC__
# define LIKELY(EXPR)  __builtin_expect(!!(EXPR), 1)
#else
# define LIKELY(EXPR)  (!!(EXPR))
#endif

#if defined NDEBUG
# define X_ASSERT(CHECK) void(0)
#else
# define X_ASSERT(CHECK) \
    ( LIKELY(CHECK) ?  void(0) : []{assert(!#CHECK);}() )
#endif

And that’s it. Maybe you consider this subject too academical, but I really needed it in the reference implementation of std::optional. Also, in the next post we will see how a practical problem is solved with literal types (and assertions inside).

Acknowledgements

The idea to use a lambda for avoiding problems with assert in C++11 constexpr functions was suggested to me by GitHub user j4cbo. Thank you.

The whole solution with a two-expression macro was inspired by a conversation in Boost Developers list, about the same subject.

Vicente J. Botet Escriba showed in the comment below, how to make my solution a single-argument macro, rather than a 2-argument ASSERTED_EXPRESSION(COND, EXPR). Thanks!

This entry was posted in programming and tagged , , , , . Bookmark the permalink.

20 Responses to Asserts in constexpr functions

  1. viboes says:

    IMO, if the precondition is not satisfied we fall under undefined behavior and in this case the implementation can do anything. So the use of assertions to check preconditions seems to me a valid use.

    I don’t understand why the standard don’t want to qualify a function as noexcept when it could throw only if the preconditions are not satisfied. I don’t think we should favor throwing when the precondition is not satisfied to declare the function noexcept. noexcept helps a lot the compiler while optimizing the code generation.

    I would prefer

    “`
    return XXX_ASSERT(CHECK), EXPR;
    “`

    than

    “`
    return ASSERTED_EXPRESSION(CHECK, EXPR);
    “`

    as `XXX_ASSERT(CHECK)` could be reused in non-constexpr functions. We need just that `XXX_ASSERT(CHECK),EXPR`compile with all the comilers, thing that I believe is possible with the lambda trick.

  2. Norbert says:

    That already bit me a few times. But I was lazy and removed the assertion. So thank you for investigating and solving this issue.
    And I want to highlight that I really like the way you link (and thank) your sources.

  3. Nick Wu says:

    Thanks for sharing!

  4. oliora says:

    That’s my implementation for constexpr_assert https://gist.github.com/oliora/928424f7675d58fadf49c70fdba70d2f and it works in both clang and gcc.

  5. Hello,
    do you have an idea whether this behaviour changed in GCC 7.2.0? When trying the lambda trick (did the one on this page and the one posted by oliora), I don’t get the error that assert is a non-constexpr function anymore, but now the compiler is complaining about the lambda (error msg for version of oliora):
    error: call to non-constexpr function ‘void xy::constexpr_assert_failed(Assert&&) [with Assert = xy::yz::assign_p(xy::yz::p_type)::]’
    ((void)((cond) ? 0 : (constexpr_assert_failed([](){ assert(!#cond);}), 0)))
    ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
    note: in expansion of macro ‘constexpr_assert’
    return constexpr_assert(p < max_value_size), *this;

    msg for your direct lambda version above:

    error: call to non-constexpr function 'xy::p::assign_p(xy::p::p_type)::’
    []{ assert(false); }()),
    ~~~~~~~~~~~~~~~~~~~~^~
    note: xy::p::assign_p(xy::p::phred_type)::’ is not usable as a constexpr function because:
    []{ assert(false); }()),
    ^
    At least it looks like as if the lambda expression is now the problem and not the contained assert statement. What do you think?

    best,
    Marie

  6. KeyC0de says:

    Nice article. What about asserts in noexcept functions?

  7. Mariusz Jaskółka says:

    Hello Andrzej,
    looks like this article needs an update since C++17 changed the think a bit.

    “`
    GCC is allowed to do it. C++11’s constexpr functions were not design with such clever usages in mind. In particular, assert is not guaranteed to be a “constant subexpression” if the condition evaluates to true.
    “`

    Since GCC still refuses to compile any assert, even those evaluated to true in constexpr functions (with C++17 enabled), this is a GCC bug now. https://cplusplus.github.io/LWG/issue2234

  8. Pingback: clang tidy warning on assert in constexpr function? – Windows Questions

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.