#47: Static Asserts
And so continues our crusade against warnings.
In this case, most of our warnings were implicit float
to double
conversion. These were mainly resolved by amending our function interfaces to accept float
.
One exception is the hyperTanFirstAntiDeriv
. This function has been
the source of overflows for a while now. Earlier I figured that the cosh
call was the culprit, as that function blows up for silly large numbers.
Because of this, I implemented a wrapper function that first clips input
that would cause an overflow.
One thing I neglected to consider is that the range to which input is clipped
is determined by the type in use. Originally, this was all implemented using
double
. These changes, however, changed everything to float
, as that is
the type we expect from calling code (maybe we can template that some time).
Doing this brought the overflows, NaN, and denormals back.
So, I figured I could use different ranges for float
and double
. More about that
below. This work ended up solving the issue. float
was now working without
overflow…most of the time. I got another CI failure in Windows, and figured that it
would be best to err on the side of caution and just cast to double
when doing
the antiderivative and casting back to float
on the way out.
Nevermind the fancy constexpr
magic I worked to “cleanly” set those
clampedCosine
bounds. I learned a lot about templates, specifically how
branching works for if constexpr
expression.
Here’s what I wanted to do: create a function that returned a pair of float types, depending on the function’s type:
template <typename T>
constexpr std::pair<T, T> coshRange()
{
if constexpr (std::is_same<T, double>::value)
{
return { range };
}
else if constexpr (std::is_same<T, float>::value)
{
return { a different range };
}
else
{
static_assert (false, "ClampedCosh only supports float types");
}
}
I was extremely confused when I saw the static_assert
failing, even when instantiating a
function with an accepted type.
Some searching lead me to this answer on Stack Overflow, which I understand as meaning: a static_assert
is always compiled. Therefore,
writing something like static_assert(false,"message")
will always fail.
The solution is to, with the help of type_traits
, create a helper type that always evaluates to false and, most importantly, only is compiled when instantiated:
template <typename T>
struct dependent_false : std::false_type
{
};
Now we can write that else branch as:
static_assert (dependent_false<T>, "ClampedCosh only supports float types");
And the static_assert will correctly (successfully?) fail if the function was instantiated with an incompatible type.
After all of this, I ended up not using the specialization that all this fuss was for—as I mentioned above,
I still got some overflows and ended up doing that part of the calculation with double
. But
it was a fun bit of learning that I’ve chosen to keep in.