在 constexpr 评估上分支/在 constexpr 上重载

gnz*_*lbg 5 c++ simd intrinsics constexpr c++14

设置:

我有一个使用 SIMD 内在函数的函数,并希望在一些 constexpr 函数中使用它。

为此,我需要使其成为 constexpr。但是,SIMD 内在函数没有标记为 constexpr,编译器的常量评估器无法处理它们。

我尝试用执行相同操作的 C++ constexpr 实现替换 SIMD 内在函数。该函数在运行时变慢了 3.5 倍,但我能够在编译时使用它(是吗?)。

问题

如何在常量表达式中使用此函数而不会在运行时减慢程序速度?

一些想法:

  • 为所有编译器添加对编译器常量表达式求值器的常量求值所有 SIMD 内在函数的支持:可能是正确的解决方案,但这是一项不可能完成的任务。

更务实的解决方案是:

  • 重载函数取决于它是否在常量表达式中执行(即提供 constexpr 和非 constexpr 版本)。
  • 或者,在 constexpr 和运行时实现之间的 constexpr 函数内以某种方式分支(即,在分支中检测函数是否在常量表达式内执行)。

无论如何,我愿意接受任何解决我问题的建议。

提示

  • @RMartinhoFernandes 在 Lounge 中建议使用__builtin_constant_p来检测函数参数是否都是常量表达式,在这种情况下,编译器希望至少尝试在编译时评估函数。

失败的尝试

  • @ Jarod42 提出了只使用两个独立函数的直接建议。我想简单地指出为什么这行不通,因为它不是微不足道的。该解决方案假设在调用站点知道函数是否会被 constexpr 评估。但这种情况并非如此。考虑一个调用我的函数的 constexpr 函数,它应该选择哪个版本的函数?它必须选择 constexpr 一个才能编译,但“外部” constexpr 函数仍然可以在运行时评估。在这种情况下,它将使用“慢”编译时实现,因此,这种方法不能解决问题。

Joh*_*itb 4

我会这样做

constexpr int doit(int input, bool inconst = false) {
   return inconst ? doitconsty(input) : doitfast(input);
}
Run Code Online (Sandbox Code Playgroud)

如果调用是在可以调用以在运行时或编译时执行某些操作的函数doit内部constexpr,则只需转发标志

constexpr int f(int n, bool inconst = false) {
   /* ... */
   int importantInt = doit(n / 42, inconst);
   /* ... */
   return magicResult;
}
Run Code Online (Sandbox Code Playgroud)

如果我没记错的话,任何constexpr评价都有其开始的地方。通过inconst那里

enum foo { bar = f(256, true) }
Run Code Online (Sandbox Code Playgroud)

如果您处于运行时世界,只需f像其他任何事情一样调用即可

int main() { std::cout << "test-case: " << f(256); }
Run Code Online (Sandbox Code Playgroud)

应该注意的是,这对运算符不起作用,因为你不能在那里添加布尔参数。相反,您可以以某种不同的方式传递该值,如果这适合您的话(对于像intand这样的原始值bool,我们也不能重载运算符)。

template<typename T>
struct maybe_const_value {
   T t;
   bool isconst;
};

enum foo { bar = maybe_const_value{256, true} % magicTransform }; 

int main() { return maybe_const_value{265} % magicTransform; }
Run Code Online (Sandbox Code Playgroud)

然后运算符函数可以检查input.isconst并用作input.t实际值。