【问题标题】:constexpr vs template for compile-time maths functions?用于编译时数学函数的 constexpr vs 模板?
【发布时间】:2012-08-27 08:05:12
【问题描述】:

我对 C++2011 的新关键字 constexpr 感到很困惑。当我编写编译时函数(尤其是数学函数)时,我想知道在哪里使用 constexpr 以及在哪里使用模板元编程。例如,如果我们采用整数 pow 函数:

// 1 : 
template <int N> inline double tpow(double x)
{
    return x*tpow<N-1>(x);
}
template <> inline double tpow<0>(double x)
{
    return 1.0;
}

// 2 :
constexpr double cpow(double x, int N)
{
    return (N>0) ? (x*cpow(x, N-1)) : (1.0);
}

// 3 :
template <int N> constexpr double tcpow(double x)
{
    return x*tcpow<N-1>(x);
}
template <> constexpr double tcpow<0>(double x)
{
    return 1.0;
}

第二个和第三个函数是等价的吗? 什么是最好的解决方案?是否产生相同的结果:

  • 如果 x 在编译时已知
  • 如果 x 在编译时未知

何时使用 constexpr 以及何时使用模板元编程?

编辑 1:修改代码以包含模板的特化

【问题讨论】:

  • 即使模板函数的值在编译时是已知的,它也不是一个常量,也不能在需要常量的地方使用。但是,如果参数是常量,则 constexpr 函数可以保证给您一个常量。

标签: c++ templates c++11 metaprogramming constexpr


【解决方案1】:

我可能不应该这么晚才回答模板元编程问题。但是,我走了。

首先,constexpr 没有在 Visual Studio 2012 中实现。如果你想为 windows 开发,那就算了。我知道,这很糟糕,我讨厌微软不包括它。

除此之外,您可以将很多东西声明为常量,但就“您可以在编译时使用它们”而言,它们并不是真正的“常量”。例如:

const int foo[5] = { 2, 5, 1, 9, 4 };
const int bar = foo[foo[2]]; // Fail!

您认为您可以在编译时从中读取,对吗?没有。但是如果你把它变成一个 constexpr 就可以了。

constexpr int foo[5] = { 2, 5, 1, 9, 4 };
constexpr int bar = foo[foo[2]]; // Woohoo!

Consexpr 非常适合“恒定传播”优化。这意味着如果您有一个变量 X,它是在编译时根据某些条件(可能是元编程)声明的,如果它是一个 constexpr,那么编译器知道它可以在进行优化时“安全地”使用它,例如,删除像 a = (X * y); 这样的指令并将它们替换为 a = 0;如果 X 评估为 0(并且满足其他条件)。

显然这很好,因为对于许多数学函数,常量传播可以为您提供简单(使用)过早优化。

它们的主要用途,而不是深奥的东西(例如使我能够更容易地编写编译时字节码解释器),是能够制作“函数”或可以同时调用和使用的类在编译时和运行时。

基本上,它们只是填补了 C++03 中的一个漏洞,并帮助编译器进行优化。

那么你的三个中哪个是“最好的”?

2 可以在运行时调用,而其他的只能在编译时调用。好甜啊。

还有更多内容。 Wikipedia 为您提供了“constexpr 允许这样做”的非常基本的摘要,但模板元编程可能很复杂。 Consexpr 使它的某些部分变得容易得多。我希望我有一个明确的例子给你,而不是说从数组中读取。

我想,如果你想实现一个用户定义的复数类,一个很好的数学例子。仅使用模板元编程而没有 constexpr 进行编码会复杂一个数量级。

那么什么时候不应该使用 constexpr?老实说,constexpr 基本上是“除 MORE CONST 之外的 const”。您通常可以在使用 const 的任何地方使用它,但有一些注意事项,例如在运行时调用时,如果函数的输入不是 const,函数将如何表现非 const。

嗯。好的,这就是现在的全部。我太累了,不能多说。我希望我对您有所帮助,如果我不是,请随时对我投反对票,我会删除它。

【讨论】:

  • 2 can be called at run-time, whereas the others are compile-time only. -- 这是不正确的。所有函数都可以在运行时调用。在编译时,只计算递归深度,并且 N 应该是第 1 和第 3 的 constexpr。对于第二个 - N 可以是运行时。
  • 对不起,#1 和#3 是编译时模板。您不能在运行时调用此类元模板,它们在编译时被实例化。
  • 被调用和实例化是不同的东西。所有函数模板都在编译时实例化。模板函数的特定实例在运行时被调用。正如我上面所说,所有 OP 函数都可以在运行时调用,如果给定 constexpr 参数,则可以在编译时评估 #2。如果它们是编译时,则不适用于非 constexpr x
【解决方案2】:

第一个和第三个不正确。编译器将在评估(N&gt;0) ? 之前尝试实例化tpow&lt;N-1&gt;,您将获得无限的模板递归。您需要对N==1(或==0)进行专业化才能使其发挥作用。 2nd 将在编译时和运行时为 xknown 工作。

在您专攻==0 编辑后添加。现在所有函数都将在编译时或运行时工作x。 1st 将始终返回非 constexpr 值。如果 xN 是 constexpr,则 2nd 和 3rd 将返回 constexpr。如果 N 不是 constexpr,则第二个甚至可以工作,其他需要 constexpr N (因此,第二个和第三个不等价)。

constexpr 用于两种情况。当您编写int N=10; 时,N 的值在编译时是已知的,但它不是 constexpr 并且不能用作例如模板参数。关键字 constexpr 明确告诉编译器 N 可以安全地用作编译时值。 第二个用途是作为 constexpr 函数。他们使用 C++ 的子集有条件地生成 constexpr 值,并且可以显着简化等效的模板函数。 constexpr 函数的一个缺点是您无法保证编译时评估——编译器可以选择在运行时进行评估。使用模板实现可以保证编译时评估。

【讨论】:

    猜你喜欢
    • 2013-11-18
    • 1970-01-01
    • 2022-01-19
    • 1970-01-01
    • 2018-07-10
    • 1970-01-01
    • 2014-04-06
    • 2013-08-29
    • 2017-08-21
    相关资源
    最近更新 更多