constexpr, static_assert, and inlining
c++, c++11, constexpr, inline, static-assert
Solution
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))
Problem
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`.