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:
- Be either a scalar type or be composed of other literal types.
- Have a trivial destructor.
- 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!
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.
Yes,
return XXX_ASSERT(CHECK), EXPR;
looks like an even better solution. How could I miss that!Regarding the
noexcept
functions with preconditions inside STL, I think that this was just a conservative decision taken in the last moment, which left the door open for the future relaxation of this constraint.I have updated the post with your
XXX_ASSERT(CHECK)
. Thanks!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.
Thanks for sharing!
That’s my implementation for constexpr_assert https://gist.github.com/oliora/928424f7675d58fadf49c70fdba70d2f and it works in both clang and gcc.
Thanks for sharing. Your implementation has one advantage over mine: assertions still fail at compile time when NDEBUG is defined.
Yes, that’s by purpose. I don’t see a reason to optimize out compile time checks for any kind of build.
Thanks for the great blog!
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
I do not think this is anything to do with GCC 7.2.0. My tests still work on this compiler: see the wandbox.org example.
Maybe something in the implementation of assign_p.
Hmm, the signature is `constexpr yz & assign_p(p_type const p)`, and the body contains only the ternary stmt with the lambda:
“`
return
((p < max_value_size) ?
static_cast (0) :
[]{ assert(false); }()), *this;
“`
can you build an example in Wandbox?
Sorry for noise, playing in Wandbox, it turned out that whenever the condition is not satisfied, the compiler will print out the misleading error msg `’nonnegative(int)::’ is not usable as a constexpr function` (see https://wandbox.org/permlink/TExgKTUJKHffGJnJ). Conclusion: break your problem down and play with it 🙂
But I will use Wandbox to play a bit around. Didn’t know it before!
Nice article. What about asserts in noexcept functions?
Well, there is no special interaction between assertions and noexcept functions, so you can just use them inside. The only problem could occur if you try to throw from assertions.
Generally, it’s a bad practice to mark narrow contract functions with noexcept. Only wide contract functions should be noexcept and such functions does not have preconditions thus barely need to assert.
That’s a good article on the topic https://quuxplusone.github.io/blog/2018/04/25/the-lakos-rule/
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
I should clarify: It was a bug before GCC9 in some contexts, now it is fixed:
https://wandbox.org/permlink/i2Zf7xwlPHKwKu0f
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86678
Pingback: clang tidy warning on assert in constexpr function? – Windows Questions