【问题标题】:Is there a way to specialize a function between compile time and run time?有没有办法在编译时间和运行时间之间专门化一个函数?
【发布时间】:2013-01-18 07:55:41
【问题描述】:

使用constexpr,可以根据参数在编译时或运行时评估函数。但通常,编译时和运行时的算法必须不同。例如。考虑阶乘的 constexpr 版本。

constexpr int fact(int n)
{
return (n)?n*fact(n-1):1;
}

如果n 发生在运行时,该函数会不会比一个forloop 效率低?是否有一些模板魔术来确定函数是在编译时执行还是在运行时执行并使用不同的算法?

更新
阶乘只是一个例子。如果在没有constexpr 限制的情况下进行编码,所有constexpr 函数是否都像它们一样高效? 例如:

constexpr int combinations(int n, int k)
{
//Assume all error conditions and edge conditions are taken care with ternary operator ?:
return fact(n)/(fact(k)*fact(n-k);
}

如果函数是在运行时编写的,它可以从Memoization 中受益。即使这是可能的,我想也很难将函数表达为既是constexpr,又是在运行时尽可能高效。

【问题讨论】:

  • 它不会回答你的问题,但是如果你的编译器针对尾递归进行了优化,这个函数就不会低效。
  • @Eduardo 由于函数不是尾递归,因此编译器需要在这里做更多的事情。但是,现代编译器可以在足够简单的情况下对此进行优化(OP 的示例将被优化)。
  • 不,当前语言无法检查 constexpr 函数何时执行 - 编译器可能会根据 rand() % 2 == 0 突发奇想决定是否在编译时评估函数 -或运行时(假设存在实际在编译时对其进行评估的先决条件)。
  • @Xeo:好吧,如果结果需要是编译时常量(模板参数,static <built-in> const,...),那么您可以确保在编译时完成计算;显然这并不能回答这个问题。
  • @KonradRudolph:你是对的;它必须在调用后将结果乘以 n。谢谢指正。

标签: c++ c++11 constexpr


【解决方案1】:

不,据我所知,您无法检测到编译器如何在给定调用中使用该函数,或者根据 constness 指示编译器使用不同的实现。

但首先,constexpr 函数仅限于单个 return 语句,这意味着编译器可以(大多数情况下)轻松应用尾递归优化,将递归调用变成循环。因此,这个问题是关于如何进行过早优化,这不是一个好主意。低级优化是编译器的工作:顺其自然。

其次,如果你真的想做编译器的工作,那么你可以为函数命名,而不是试图毫无意义地将两个不同的函数实现塞进一个函数中。那是为了什么目的?只是默默无闻。


对于给出的特定示例,

constexpr int fact(int n)
{
    return (n)?n*fact(n-1):1;
}

编译器必须认识到它可以被重写为尾递归。正如我从我对早先关于它的 SO 问题的测试中回忆的那样,即使是 Visual C++ 编译器也能做到这一点。尽管出于某种莫名其妙的原因(可能与最初的 x86 处理器设计有关),它被浮点类型的使用所困扰:相同的高级逻辑,不同的低级结果。

作为一项稍微不那么激烈的帮助编译器的工作,在测量并发现该功能是使您的应用程序慢得无法接受的功能之后,并且在检查了机器代码并发现编译器无法识别函数的尾递归,可以改写如下:

constexpr int fact( int multiplier, int n )
{
    return (n != 0? fact( multiplier*n, n-1 ) : multiplier);
}

constexpr int fact( int n )
{
    return fact( 1, n );
}

免责声明:编译器的脏手未触及代码。

【讨论】:

  • 除非你有一个非常宽松的浮点模式,否则浮点版本不能自动重写,因为它改变了浮点陷阱的语义。原始的可以在递归之后产生溢出陷阱,几乎等效的可能只能在递归之前触发它。此外,浮点乘法不是可交换/分配的,几乎等价的乘法是 n*(n-1)*(n-2)*...*2,而原始乘法是 2*3*4*...n
  • @BenVoigt:对于普通的 SO 读者,我认为最好还提到,对于可以由浮点类型精确表示的整数范围内的结果,大约 51 位普通的 64 位 IEEE 754,浮点乘法是精确的和可交换的(和分配的)。不管怎样,谢谢!我没有想到你提到的任何一种可能性,只是关于浮点处理的机器代码级同步。
  • @Alf:是的,差异可能只影响计算精度的最后几位,在 x86 CPU 上为 80 位或更多位。所以四舍五入到双精度后应该没有变化。 (在一般情况下,固定精度浮点数学在任何精度下都不是可交换/可分配的,但阶乘乘积的行为比一般情况要好得多。编译器是否可以推断出这是一个表现良好的情况值得怀疑。)
  • 另外,我认为这个答案中声称重载函数名是毫无意义的部分是非常荒谬的。
  • @BenVoigt:对不起,你所说的我“声称重载函数名是毫无意义的”是不正确的,完全是捏造的,你知道的。你试图让读者相信它。因此,你在撒谎。另外,您的“也”暗示您的主张是对不正确/荒谬的观察,这是对早期此类观察的补充。好吧,这不是一个观察结果(这只是您的误导性声明),并且它不是任何早期观察结果的补充。你也知道。因此,这也是一个谎言。很抱歉钉了这两个谎言。
猜你喜欢
  • 2010-12-29
  • 1970-01-01
  • 1970-01-01
  • 2011-12-22
  • 1970-01-01
  • 2020-03-10
  • 2021-11-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多