I was investigating some rather weird code-coverage results of constexpr
functions (compile-time calls can't get instrumented by the code-coverage tool I use), and noticed that some constexpr
functions were getting called as runtime functions, if the results of the function call were not stored.
It seems that, for constexpr
functions or methods, if you store the results of the call (either in a runtime variable [emphasis!!!] or a constexpr
variable), the call is a compile-time call (as long as the parameters are compile-time). If you ignore the results, the call is a runtime call. This has nothing to do with my code-coverage tool; the behavior seems to be repeatable in the simple example below.
You could argue that since constexpr
functions cannot have side-effects, it doesn't matter what the compiler does if you don't return / use a result. I'd think that for efficiency, the compiler would still make whatever it can be constexpr
, but that's neither here nor there... What I'm wondering is if this is even defined behavior.
Is this a portable way to guarantee your constexpr
functions will be invoked as runtime??? There aren't a ton of uses for that, but one use is: if you want to "take credit for tests that were called on constexpr
functions" in code coverage, by just calling the same function at the end of your unit test, and ignoring the result, so that they get instrumented.
There are other ways to force a function to get called as runtime, I know, I'm mostly curious about what's going on here. It was very unexpected when I first saw it. Unless this is portable, I'll probably just call my constexpr
methods through a runtime object (which seems to do the trick even for static methods), or through a runtime function pointer.
See example below. Live demo: https://onlinegdb.com/rJao0RNGP
// Modified from https://stackoverflow.com/a/40410624/12854372
extern bool no_symbol;
struct ContextIsConstexpr {
size_t s;
constexpr ContextIsConstexpr() : s(1) {}
constexpr void operator()() {
auto ignore = s ? 1 : no_symbol;
}
};
constexpr bool tryIgnoringResult()
{
ContextIsConstexpr()();
return true;
}
constexpr void thereIsNoResult() {
ContextIsConstexpr()();
}
int main()
{
constexpr auto result1 = tryIgnoringResult(); // OK, compile-time
auto result2 = tryIgnoringResult(); // OK, compile-time
// tryIgnoringResult(); // Unexpected, runtime!
// thereIsNoResult(); // Unexpected, runtime!
}