我怎样才能在 constexpr 函数中有回退 运行-time 实现

How can I have a fallback run-time implementation in a constexpr function

上下文

我正在制作一个数学库,我需要使用 sqrt()
由于 sqrt() 不是 constexpr 函数,我已经实现了 sqrt()constexpr 版本,并且比使用汇编的 std::sqrt() 更快,因此必须在运行-时间。
现在,我正在使用所有这些来计算向量的长度,这个函数可以是 constexpr 因为我正在 constexpr 结构中的所有可能。

constexpr inline Real length() const { return const_sqrt<Real>(lengthSquared());}

如果在非 constexpr 上下文中调用 lenght() 函数,它会在 运行-time 中简单地 运行,但我有一个更快运行-执行时间sqrt()const_sqrt().

问题

如何根据函数是在编译时还是 运行 时执行来切换使用一种实现还是另一种实现。
像这样:

constexpr inline Real length() const { 
    return IN_RUN_TIME 
         ? fast_sqrt<Real>(lengthSquared()) 
         : const_sqrt<Real>(lengthSquared());
}

在c++20中,函数std::is_constant_evaluated可用于此目的:

constexpr Real length() const { 
    return ! std::is_constant_evaluated()
         ? fast_sqrt(lengthSquared()) 
         : const_sqrt(lengthSquared());
}

据我所知,在 c++20 之前的版本中,无法实现这种效果,这就是将其添加到语言中的原因。

此外,请注意 constexpr 意味着 inline,因此该关键字在这里是多余的。

唯一符合标准的解决方案是使用 std::is_constant_evaluated, as :

constexpr inline Real length() const
{ 
    return std::is_constant_evaluated()
         ? fast_sqrt<Real>(lengthSquared()) 
         : const_sqrt<Real>(lengthSquared());
}

这种方法的问题在于,如果 length() 的 return 值用于初始化一个 constexpr 变量,否则 需要 constexpr.

这在 lengthSquared() 的值在编译时已知的情况下是次优的(因此可以使用 const_sqrt ),但是 return length() 的值不需要是 constexpr。然后 is_constant_evaluated 将 return false,因此将使用 fast_sqrt,不必要地将 1 计算推迟到运行时。

解决方法是使用内置的非标准 GCC(也受 Clang 支持):__builtin_constant_p。与 std::is_constant_evaluated 不同,它有一个 'expression' 参数并检查表达式的值在编译时是否已知(这可能取决于优化设置)。

我建议如果 __builtin_constant_p 可用,则应使用它,否则回退到 std::is_constant_evaluated。 (如果您使用的是 C++20 之前的编译器,则此内置是您唯一的选择。)

#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 我假设 fast_sqrt 在编译时不起作用。否则没有必要单独 const_sqrt.