在 constexpr 评估上分支/在 constexpr 上重载
Branching on constexpr evaluation / overloading on constexpr
设置:
我有一个使用 SIMD 内在函数的函数,想在一些 constexpr 函数中使用它。
为此,我需要将其设为 constexpr。但是,SIMD 内在函数没有标记为 constexpr,编译器的常量求值器无法处理它们。
我尝试用执行相同操作的 C++ constexpr 实现替换 SIMD 内在函数。该函数在 运行 时变慢了 3.5 倍,但我能够在编译时使用它(是吗?)。
问题:
如何在常量表达式中使用这个函数,而不会在 运行 时减慢我的程序?
一些想法:
- 为编译器常量表达式求值器添加对所有 SIMD 内在函数的常量求值支持,适用于所有编译器:可能是正确的解决方案,但却是一项不可能完成的艰巨任务。
更务实的解决方案是:
- 根据函数是否在常量表达式中执行来重载函数(即提供一个 constexpr 和一个非 constexpr 版本)。
- 或者,以某种方式在 constexpr 和 运行-time 实现之间的 constexpr 函数内部分支(即,在分支中检测函数是否在常量表达式内部执行)。
无论如何,我愿意接受任何能解决我的问题的建议。
提示:
- @RMartinhoFernandes 在 Lounge 中建议使用
__builtin_constant_p
来检测函数参数是否都是常量表达式,在这种情况下,编译器有望至少尝试在编译时评估函数。
尝试失败:
- @Jarod42 提出了直接使用两个独立函数的建议。我想简要地指出为什么这行不通,因为它不是微不足道的。此解决方案假定在调用站点已知函数是否将被 constexpr 评估。但这种情况并非如此。考虑一个调用我的 constexpr 函数,它应该选择我的函数的哪个版本?它必须选择 constexpr 一个才能编译,但是 "outer" constexpr 函数仍然可以在 运行 时计算。在那种情况下,它将使用 "slow" 编译时实现,因此,这种方法不能解决问题。
我会这样做
constexpr int doit(int input, bool inconst = false) {
return inconst ? doitconsty(input) : doitfast(input);
}
如果对 doit
的调用在 constexpr
函数内部,可以在运行时或编译时调用它来执行某些操作,只需转发标志
constexpr int f(int n, bool inconst = false) {
/* ... */
int importantInt = doit(n / 42, inconst);
/* ... */
return magicResult;
}
任何 constexpr
评估都有它开始的地方,如果我没记错的话。将 inconst
传递到那里
enum foo { bar = f(256, true) }
如果您在运行时世界中,只需调用 f
就像调用其他任何东西一样
int main() { std::cout << "test-case: " << f(256); }
需要注意的是,这对运算符不起作用,因为你不能在那里添加布尔参数。相反,您可以以某种不同的方式传递值,如果这适合您(对于像 int
和 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; }
运算符函数然后可以检查 input.isconst
并使用 input.t
作为实际值。
设置:
我有一个使用 SIMD 内在函数的函数,想在一些 constexpr 函数中使用它。
为此,我需要将其设为 constexpr。但是,SIMD 内在函数没有标记为 constexpr,编译器的常量求值器无法处理它们。
我尝试用执行相同操作的 C++ constexpr 实现替换 SIMD 内在函数。该函数在 运行 时变慢了 3.5 倍,但我能够在编译时使用它(是吗?)。
问题:
如何在常量表达式中使用这个函数,而不会在 运行 时减慢我的程序?
一些想法:
- 为编译器常量表达式求值器添加对所有 SIMD 内在函数的常量求值支持,适用于所有编译器:可能是正确的解决方案,但却是一项不可能完成的艰巨任务。
更务实的解决方案是:
- 根据函数是否在常量表达式中执行来重载函数(即提供一个 constexpr 和一个非 constexpr 版本)。
- 或者,以某种方式在 constexpr 和 运行-time 实现之间的 constexpr 函数内部分支(即,在分支中检测函数是否在常量表达式内部执行)。
无论如何,我愿意接受任何能解决我的问题的建议。
提示:
- @RMartinhoFernandes 在 Lounge 中建议使用
__builtin_constant_p
来检测函数参数是否都是常量表达式,在这种情况下,编译器有望至少尝试在编译时评估函数。
尝试失败:
- @Jarod42 提出了直接使用两个独立函数的建议。我想简要地指出为什么这行不通,因为它不是微不足道的。此解决方案假定在调用站点已知函数是否将被 constexpr 评估。但这种情况并非如此。考虑一个调用我的 constexpr 函数,它应该选择我的函数的哪个版本?它必须选择 constexpr 一个才能编译,但是 "outer" constexpr 函数仍然可以在 运行 时计算。在那种情况下,它将使用 "slow" 编译时实现,因此,这种方法不能解决问题。
我会这样做
constexpr int doit(int input, bool inconst = false) {
return inconst ? doitconsty(input) : doitfast(input);
}
如果对 doit
的调用在 constexpr
函数内部,可以在运行时或编译时调用它来执行某些操作,只需转发标志
constexpr int f(int n, bool inconst = false) {
/* ... */
int importantInt = doit(n / 42, inconst);
/* ... */
return magicResult;
}
任何 constexpr
评估都有它开始的地方,如果我没记错的话。将 inconst
传递到那里
enum foo { bar = f(256, true) }
如果您在运行时世界中,只需调用 f
就像调用其他任何东西一样
int main() { std::cout << "test-case: " << f(256); }
需要注意的是,这对运算符不起作用,因为你不能在那里添加布尔参数。相反,您可以以某种不同的方式传递值,如果这适合您(对于像 int
和 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; }
运算符函数然后可以检查 input.isconst
并使用 input.t
作为实际值。