C++ – constexpr, static_assert, and inlining

cc++11constexprinline()static-assert

I previously asked about function overloading based on whether the arguments are constexpr. I'm trying to work around the disappointing answer to that question to make a smarter assert function. This is roughly what I am trying to do:

inline void smart_assert (bool condition) {
    if (is_constexpr (condition))
        static_assert (condition, "Error!!!");
    else
        assert (condition);
}

Basically, the idea is that a compile-time check is always better than a run-time check if it's possible to check at compile time. However, due to things like inlining and constant folding, I can't always know whether a compile time check is possible. This means that there may be cases where assert (condition) compiles down to assert(false) and the code is just waiting for me to run it and execute that path before the I find out there is an error.

Therefore, if there were some way to check whether the condition is a constexpr (due to inlining or other optimizations), I could call static_assert when possible, and fall back on a run-time assert otherwise. Fortunately, gcc has the intrinsic __builtin_constant_p (exp), which returns true if exp is a constexpr. I don't know if other compilers have this intrinsic, but I was hoping that this would solve my problem. This is the code that I came up with:

#include <cassert>
#undef IS_CONSTEXPR

#if defined __GNUC__
    #define IS_CONSTEXPR(exp) __builtin_constant_p (exp)
#else
    #define IS_CONSTEXPR(exp) false
#endif
// TODO: Add other compilers

inline void smart_assert (bool const condition) { 
    static_assert (!IS_CONSTEXPR(condition) or condition, "Error!!!");
    if (!IS_CONSTEXPR(condition))
        assert (condition);
}

#undef IS_CONSTEXPR

The static_assert relies on the short circuit behavior of or. If IS_CONSTEXPR is true, then static_assert can be used, and the condition is !true or condition, which is the same as just condition. If IS_CONSTEXPR is false, then static_assert cannot be used, and the condition is !false or condition, which is just the same as true and the static_assert is ignored. If the static_assert cannot be checked because condition is not a constexpr, then I add a run-time assert to my code as a last-ditch effort. However, this does not work, thanks to not being able to use function arguments in a static_assert, even if the arguments are constexpr.

In particular, this is what happens if I try to compile with gcc:

// main.cpp
int main () {
    smart_assert (false);
    return 0;
}

g++ main.cpp -std=c++0x -O0

Everything is fine, compiles normally. There is no inlining with no optimization, so IS_CONSTEXPR is false and the static_assert is ignored, so I just get a run-time assert statement (that fails). However,

[david@david-desktop test]$ g++ main.cpp -std=c++0x -O1
In file included from main.cpp:1:0:
smart_assert.hpp: In function ‘void smart_assert(bool)’:
smart_assert.hpp:12:3: error: non-constant condition for static assertion
smart_assert.hpp:12:3: error: ‘condition’ is not a constant expression

As soon as I turn on any optimizations and thus potentially allow static_assert to be triggered, it fails because I cannot use function arguments in the static_assert. Is there some way to work around this (even if it means implementing my own static_assert)? I feel my C++ projects could theoretically benefit quite a bit from a smarter assert statement that catches errors as early as possible.

It doesn't seem like making smart_assert a function-like macro will solve the problem in the general case. It will obviously make it work in this simple example, but condition may have come from a function two levels up the call graph (but still becomes known to the compiler as a constexpr due to inlining), which runs into the same problem of using a function parameter in a static_assert.

Best Answer

This should help you start

template<typename T> 
constexpr typename remove_reference<T>::type makeprval(T && t) {
  return t;
}

#define isprvalconstexpr(e) noexcept(makeprval(e))