常量整数和常量求值

38

考虑以下程序:

#include <iostream>
#include <type_traits>

constexpr int f() {
  if (std::is_constant_evaluated())
    return -1;
  else return 1;
}

int main() {
  int const i = f();
  std::cout << i;
}

当运行时,它打印-1(wandbox)。

然而,如果我在编译时评估时使函数throw

#include <iostream>
#include <type_traits>

constexpr int f() {
  if (std::is_constant_evaluated())
    throw -1; // <----------------------- Changed line
  else return 1;
}

int main() {
  int const i = f();
  std::cout << i;
}

在wandbox上,it 编译成功并输出1。为什么我没有得到编译失败的结果呢?


1
据我所知,在constexpr函数中抛出异常会导致第二个程序未定义行为。 - NathanOliver
@FrançoisAndrieux 噢,是啊。我忘了提到!是的,如果 i 被标注为 constexpr,则无法编译。 - Pilar Latiesa
3
我知道。我见过这种技巧,可以有意地使编译失败。你可以使用 throw,但不能在常量求值的代码路径中使用。 - Pilar Latiesa
没错。我又仔细看了一下,只要不是走这条路线就可以了。 - NathanOliver
1个回答

38

不断地评估难道不很有趣吗?

在语言中,有一些地方我们尝试进行常量评估,如果失败了,我们就退回到非常量评估。静态初始化是其中之一,在初始化常量整数时也是如此。

如下会发生什么:

int const i = f();

这是一个可能是常量评估的例子,但不一定是。因为(非constexpr)常量整数仍然可以作为常量表达式使用,如果它们满足所有其他条件,我们就必须尝试。例如:

const int n = 42;       // const, not constexpr
std::array<int, n> arr; // n is a constant expression, this is ok
尽管我们尝试调用f()作为常量表达式,但是由于std::is_constant_evaluated()在这种情况下是true,所以我们进入了带有throw的分支并最终失败了。无法在常量求值期间抛出异常,因此我们的常量求值失败。
但随后我们回退,再次尝试,这次将f()作为非常量表达式调用(即std::is_constant_evaluated()false)。这条路径成功了,给了我们1,所以i被初始化为1。但需要注意的是,此时i不是一个常量表达式。后续的static_assert(i == 1)将是非法的,因为i的初始值不是常量表达式!即使非常量初始化路径(否则)完全满足常量表达式的要求。
请注意,如果我们尝试:
constexpr int i = f();

由于我们无法回退到非常量初始化,因此这将失败。


3
不使用 const 作为编译时常量的另一个原因。 - Guillaume Racicot
8
C++03试图使用const来模仿它没有的constexpr;现在当然应该使用真正的东西。 - Davis Herring
有趣的是,如果f()不被评估为常量表达式,它就是一个核心常量表达式。这就是为什么我们必须使用“明显常量评估”的语言。 - Davis Herring
虽然不太相关,但我对实现非常好奇。如果它是神奇的,那么为什么它不是一个关键词呢? - Red.Wave
我觉得这里有趣的是第二个 f() 也是一个理论上的优化机会,如果编译器在逻辑推理方面足够熟练。 "如果 std::is_constant_evaluated(),那么 f() 会抛出异常。这在常量表达式中是无效的,因此如果 std::is_constant_evaluated(),它就不是一个常量表达式。f() 因此永远不是一个常量表达式,因此 std::is_constant_evaluated() 必须始终为 false。" 理论上它可以优化为 constexpr int f() { return 1; },因为可证明无法使用真分支(但在技术上是可达的)... - Justin Time - Reinstate Monica
然而,具有讽刺意味的是,该函数最终只能作为一个“可在编译时求值”的函数存在于非常量表达式的情况下。(聪明的编译器可以将其内联并用1替换f(),但由于抛出f()不能是有效的常量表达式,因此它只允许在内联不会导致常量表达式的情况下执行。) [当然,所有这些都是严格理论上的。尽管如此,这仍然是一个有趣的怪癖。] - Justin Time - Reinstate Monica

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