I'm making a math library and I need to use sqrt()
.
As sqrt()
is not a constexpr
function I've implemented a constexpr
version of sqrt()
and a faster that std::sqrt()
that uses assembly, so must be used on run-time.
Now, I'm using all this to calculate the length of a vector, and this function can be constexpr
because I'm making constexpr
everything possible in the struct.
constexpr inline Real length() const { return const_sqrt<Real>(lengthSquared());}
This will work, if the lenght()
function is called in a non constexpr
context, it simply run in run-time, but I have a faster run-time implementation of sqrt()
than const_sqrt()
.
How can I switch to use one implementation or another based on if the function is executed on compile-time or run-time.
Something like this:
constexpr inline Real length() const { return IN_RUN_TIME ? fast_sqrt<Real>(lengthSquared()) : const_sqrt<Real>(lengthSquared()); }
The only standard-compliant solution is to use std::is_constant_evaluated
, as suggested by cigien:
constexpr inline Real length() const
{
return std::is_constant_evaluated()
? fast_sqrt<Real>(lengthSquared())
: const_sqrt<Real>(lengthSquared());
}
The problem with this approach is that std::is_constant_evaluated()
will only return true
if the return value of length()
is used to initialize a constexpr
variable, or is otherwise required to be constexpr
.
This is suboptimal in situations where the value of lengthSquared()
is known at compile-time (thus const_sqrt
could be used), but the return value of length()
is not required to be constexpr
. Then is_constant_evaluated
will return false
, so fast_sqrt
is going to be used instead, unnecessarily postponing1 the computation to runtime.
The workaround for that is to use a non-standard GCC built-in (also supported by Clang): __builtin_constant_p
. Unlike std::is_constant_evaluated
, it has an 'expression' parameter and checks if the value of the expression is known at compile-time (which might depend on optimization settings).
I suggest that __builtin_constant_p
should be used if it's available, falling back to std::is_constant_evaluated
otherwise. (And if you're using a pre-C++20 compiler, this built-in is your only option.)
#ifdef __GNUC__ // Defined by GCC and Clang
#define KNOWN_AT_COMPILE_TIME(...) __builtin_constant_p(__VA_ARGS__)
#else
#define KNOWN_AT_COMPILE_TIME(...) std::is_constant_evaluated()
#endif
constexpr inline Real length() const
{
return KNOWN_AT_COMPILE_TIME(lengthSquared())
? fast_sqrt<Real>(lengthSquared())
: const_sqrt<Real>(lengthSquared());
}
1 I assume fast_sqrt
doesn't work at compile-time. Otherwise there's no point in having a separate const_sqrt
.