constexpr函数何时在编译时进行评估?

72

既然可能会在运行时调用声明为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函数,但编译器决定将其视为普通函数,即使它也可以在编译时工作?有没有已知的常见陷阱?

据我所知,当所有参数都是常量表达式时。 - chris
10
@chris: 实际上,我认为情况比那更加复杂。我认为 constexpr 只有在其结果被用作模板参数、数组边界或其他整型常量时才需要求值。任何其他时间都是一种优化。事实上,即使给定常量表达式参数,它也可能需要在运行时执行。当给定非零输入时,constexpr int func(int p) { return !p ? 1 : throw std::exception("HI");} 必须在运行时求值。 - Mooing Duck
@MooingDuck:除非函数的结果在你上述的常量表达式“requirerers”中被使用,否则由于异常它将会在编译时出错。 - GManNickG
@GManNickG:我没有留下足够的评论空间,但是没错 :D - Mooing Duck
相关:https://dev59.com/fWYq5IYBdhLWcg3wrSas - WorldSEnder
显示剩余2条评论
2个回答

108
constexpr函数只有在所有参数都是常量表达式且结果也用于常量表达式时,才会在编译时进行计算。 常量表达式可以是字面量(如“42”),非类型模板参数(例如在template<class T, size_t N> class array;中的N),枚举元素声明(例如在enum Color { Red, Blue, Green };中的Blue),另一个声明为constexpr的变量等等。

如果所有参数都是常量表达式且结果未用于常量表达式中,则constexpr函数可能会进行计算,但这取决于实现方式。


2
@mcmcc 尝试将结果用作非类型模板参数。这应该涵盖整数constexpr。对于浮点数,您需要其他东西。 - TemplateRex
4
标准中有没有保证如果所有参数都是常量表达式,则constexpr函数在编译时被评估的保证?我认为标准只是说constexpr函数可以在必须在编译时评估的上下文中使用,例如模板参数。其他任何事情都取决于编译器决定。至少这是我目前相信的。 - jogojapan
1
@jogojapan:......这正是我的答案所说的。您可以查看ISO C++链接以获取更多关于该主题的讨论。 - K-ballo
1
根据Bjarne和Herb在_ISO C++_帖子中的说法,“正确的答案 - 如Herb所述 - 是根据标准,constexpr函数可以在编译时或运行时进行评估,除非它被用作常量表达式,在这种情况下,它必须在编译时进行评估。”这意味着您示例中的a必须在编译时进行评估,并且并不使注释完全错误。我不确定这实际上是否有保证。 - K-ballo
6
针对您的“其他事情”,为什么不编写一个constexpr函数,接受浮点值(或实际上您喜欢的任何类型),并返回整数,然后在模板非类型参数中使用它。 constexpr size_t check_nonintegral_constexpr(float v) { return sizeof v; } std :: array <check_nonintegral_constexpr(pow(2.0, 4))> assertion; - Ben Voigt
显示剩余10条评论

23
函数必须在编译时进行评估,当需要常量表达式时。
要保证这一点的最简单方法是使用 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 << POW(2, 63) << std::endl 可能不会在编译时被评估,因为 cout 不需要常量表达式值? - Byzantian
1
@cyberpunk_ constexpr函数的参数不是常量表达式,因此这并没有帮助。 - Pubby
1
@cyberpunk_: 你应该使用引用来解决这个问题。(虽然复制编译时的值并不会有什么成本...) - GManNickG
5
@balki 这是我想到的解决方案,但我不确定它是否是最佳解决方案。我也不确定返回一个 rvalue 引用是否会按照这种方式工作。#define FORCE_CT_EVAL(func) [](){constexpr auto ___expr = func; return std::move(___expr);}() - Byzantian
@cyberpunk_ 编译器为什么不能调用lambda对象的operator()?这正是我在gdb中看到的情况。不仅如此,operator()本身可能不是constexpr,你可能会强制进行运行时评估。 - user1095108
显示剩余13条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接