You have missed the namespace qualifier of std::uint64_t. It seems that std::uint_fast64_t should be sufficient, to make the code more portable. The exact-width type is probably appropriate, but we should confirm that using static_assert(sizeof (double) == sizeof (std::uint64_t).
We probably want to ensure that the types have the same endianness - probably the easiest way to do that is follow the definition with a compile-time test like
static_assert(safe_isnan(std::numeric_limits<double>::quiet_NaN()));
It looks like the test could be simplified. Instead of those >= and <= comparisons, just check the relevant bits are set:
auto constexpr ones_mask = 0x7ff0000000000000;
auto constexpr zero_mask = 0x000fffffffffffff;
return (x & ones_mask) == ones_mask && (x & zero_mask) != 0;
We should probably conditionalise on std::numeric_limits<double>::is_iec559 since we depend on the floating-point layout.
The whole thing should be unnecessary - but if you've you've selected -ffast-math, then your compiler can assume that these bit patterns are impossible. You probably need to compile this function separately without that option for it to work reliably. But in that case, you might as well just compile a tiny (non-fastmath) wrapper that ensures the standard isnan() is called:
bool safe_isnan(double val) noexcept { return std::isnan(val); }
(Note: not constexpr, and take steps to avoid inlining - or link-time optimisation)
Another alternative is to use (or reimplement) std::fpclassify() and use that to implement your versions of isnan(), isfinite(), etc.