【问题标题】:When does a constexpr function get evaluated at compile time?constexpr 函数何时在编译时进行评估?
【发布时间】:2012-12-24 06:26:46
【问题描述】:

既然声明为 constexpr 的函数可以在运行时调用,那么编译器根据什么标准决定是在编译时还是在运行时计算它?

template<typename base_t, typename expo_t>
constexpr base_t POW(base_t base, expo_t expo)
{
    return (expo != 0 )? base * POW(base, expo -1) : 1;
}

int main(int argc, char** argv)
{
    int i = 0;
    std::cin >> i;

    std::cout << POW(i, 2) << std::endl;
    return 0;
}

在这种情况下,i 在编译时是未知的,这可能是编译器将 POW() 视为在运行时调用的常规函数​​的原因。然而,这种动态,尽管看起来很方便,但有一些不切实际的含义。例如,是否存在我希望编译器在编译时计算 constexpr 函数的情况,而编译器决定将其视为普通函数,而它在编译时也可以工作?是否有任何已知的常见陷阱?

【问题讨论】:

  • AFAIK,当所有参数都是常量表达式时。
  • @chris 如果我写 POW((unsigned __int64)2, 63) 会怎样。这仍然算作常量表达式吗?
  • @chris:实际上,它比我想象的要复杂。我认为 constexpr 仅在其结果用作模板参数、数组绑定或其他整数常量时才需要进行评估。任何其他时间都是优化。事实上,即使给定常量表达式参数,也可能需要在运行时执行。 constexpr int func(int p) { return !p ? 1 : throw std::exception("HI");} 必须在给定非零输入时在运行时进行评估。
  • 作为常量表达式的初始化器构成静态初始化阶段的一部分,例如constexpr int a = POW(5, 4);。这基本上是在编译时计算的。但是你当然仍然可以在其他地方使用POW
  • @MooingDuck:除非你前面提到的常量表达式“requirerers”中使用了函数的结果,否则它会因为异常而产生编译时错误。

标签: c++ c++11 runtime compile-time constexpr


【解决方案1】:

当需要常量表达式时,必须在编译时计算函数。

保证这一点的最简单方法是使用constexpr 值或std::integral_constant

constexpr auto result = POW(i, 2); // this should not compile since i is not a constant expression
std::cout << result << std::endl;

或:

std::cout << std::integral_constant<int, POW(i, 2)>::value << std::endl;

#define POW_C(base, power) (std::integral_constant<decltype(POW((base), (power)), POW((base), (power))>::value)

std::cout << POW_C(63, 2) << std::endl;

template<int base, int power>
struct POW_C {
  static constexpr int value = POW(base, power);
};

std::cout << POW_C<2, 63>::value << std::endl;

【讨论】:

  • 这是否意味着 std::cout &lt;&lt; POW(2, 63) &lt;&lt; std::endl 在编译时可能最终不会被评估,因为 cout 不需要常量表达式值?
  • @cyberpunk_ 是的。事实上,你应该假设它会在运行时被调用,就像当前编译器所做的那样。
  • @cyberpunk_ constexpr 函数的参数不是常量表达式,因此无济于事。
  • @cyberpunk_:您应该为此使用参考资料。 (并不是说复制编译时值会花费任何成本......)
  • @balki 这是我想出的,但我不确定这是否是最好的解决方案。我也不确定返回右值引用是否真的以这种方式工作。 #define FORCE_CT_EVAL(func) [](){constexpr auto ___expr = func; return std::move(___expr);}()
【解决方案2】:

constexpr 函数在其所有参数都是常量表达式并且结果也用于常量表达式时在编译时进行评估。常量表达式可以是文字(如 42),非类型模板参数(如 template&lt;class T, size_t N&gt; class array; 中的 N),enum 元素声明(如 enum Color { Red, Blue, Green }; 中的 Blue,另一个变量声明constexpr,等等。

当所有参数都是常量表达式并且结果在常量表达式中使用时,它们可能会被计算,但这取决于实现。

【讨论】:

  • @mcmcc 尝试将结果用作非类型模板参数。这应该涵盖积分 constexpr。对于浮动,你需要别的东西。
  • 如果所有参数都是常量表达式,标准中哪里可以保证 constexpr 函数在编译时被评估?我相信标准唯一说的是constexpr 函数可以在必须在编译时评估的上下文中使用,例如模板参数。其他任何事情都由编译器决定。至少到目前为止我是这么认为的。
  • @jogojapan:根据 Bjarne 和 Herb 在 ISO C++ 帖子中的说法,“正确的答案 - 正如 Herb 所说 - 是根据标准constexpr 函数可以在编译器时或运行时求值,除非它用作常量表达式,在这种情况下,它必须在编译时求值。”。这意味着您的示例中的a必须在编译时进行评估,并且不会使注释完全错误。我不确定此时标准是否真的保证了这一点......
  • @mcmcc: 对于你的“别的东西”,为什么不写一个constexpr 函数,它接受一个浮点值(或者实际上你喜欢的任何类型)并返回一个整数,然后使用它在模板非类型参数内。 constexpr size_t check_nonintegral_constexpr(float v) { return sizeof v; } std::array&lt;check_nonintegral_constexpr(pow(2.0, 4))&gt; assertion;
  • “结果也用于常量表达式”。我为此苦苦挣扎。谢谢。
猜你喜欢
  • 2021-02-13
  • 1970-01-01
  • 1970-01-01
  • 2017-05-26
  • 2019-04-24
  • 2020-10-01
  • 2014-04-06
  • 2022-08-17
  • 1970-01-01
相关资源
最近更新 更多