为什么在增加 -fconstexpr-步数后无法解析常量表达式?

Why can't I resolve a constant expression after increasing -fconstexpr-steps?

本文关键字:常量 表达式 增加 -fconstexpr- 为什么      更新时间:2023-10-16

以以下constexpr为例:

#include <iostream>
constexpr int fib(const int i)
{
if (i == 0) return 0;
if (i == 1) return 1;
return fib(i-1) + fib(i-2);
}
int main(){
std::cout << fib(45) << 'n';
}

尽管它是constexpr,但在编译时它不是求值的
我学到的强制执行编译时评估的技巧如下:

#include <iostream>
#include <type_traits>
#define COMPILATION_EVAL(e) (std::integral_constant<decltype(e), e>::value)
constexpr int fib(const int i)
{
if (i == 0) return 0;
if (i == 1) return 1;
return fib(i-1) + fib(i-2);
}
int main(){
std::cout << COMPILATION_EVAL(fib(45)) << 'n';
}

这是g++,但是我在clang++中得到以下错误:

clang++-3.9 --std=c++1z -o main main.cpp 
main.cpp:14:33: error: non-type template argument is not a constant expression
std::cout << COMPILATION_EVAL(fib(45)) << 'n';
^~~~~~~
main.cpp:4:66: note: expanded from macro 'COMPILATION_EVAL'
#define COMPILATION_EVAL(e) (std::integral_constant<decltype(e), e>::value)
^
main.cpp:9:3: note: constexpr evaluation hit maximum step limit; possible infinite loop?
if (i == 1) return 1;
^
main.cpp:10:21: note: in call to 'fib(7)'
return fib(i-1) + fib(i-2);
^
main.cpp:10:21: note: in call to 'fib(9)'
main.cpp:10:10: note: in call to 'fib(11)'
return fib(i-1) + fib(i-2);
^
main.cpp:10:10: note: in call to 'fib(12)'
main.cpp:10:10: note: in call to 'fib(13)'
main.cpp:10:21: note: (skipping 23 calls in backtrace; use -fconstexpr-backtrace-limit=0 to see all)
return fib(i-1) + fib(i-2);
^
main.cpp:10:10: note: in call to 'fib(41)'
return fib(i-1) + fib(i-2);
^
main.cpp:10:10: note: in call to 'fib(42)'
main.cpp:10:10: note: in call to 'fib(43)'
main.cpp:10:10: note: in call to 'fib(44)'
main.cpp:14:33: note: in call to 'fib(45)'
std::cout << COMPILATION_EVAL(fib(45)) << 'n';
^
1 error generated.  

我已经尝试增加constexpr步骤,但clang仍然不会编译代码:

clang++-3.9 -fconstexpr-depth=99999999 -fconstexpr-backtrace-limit=9999999 -fconstexpr-steps=99999999 --std=c++1z -o main main.cpp

我必须为clang做些什么才能按原样编译此代码?

叮当++:

clang version 3.9.0-svn267343-1~exp1 (trunk)

g++:

g++ (Ubuntu 5.1.0-0ubuntu11~14.04.1) 5.1.0

clang不存储constexpr函数调用。

有人也遇到了类似的问题。

fib(47)中的步骤数是2^4535184372088832的数量级。如果你在-fconstexpr-steps=发送这么多步骤,你会得到::

error: invalid integral value '35184372088832' in '-fconstexpr-steps 35184372088832'

基本上,价值太大了。即使不是,由于缺乏记忆,编译器也可能在运行那么多步骤之前就爆炸了。(好吧,phi^47更接近递归步骤的数量,但这仍然是36位的大小,clang将constexpr-steps存储在32位无符号int中,因此没有骰子)

记忆化是一种跟踪什么值映射到什么结果的方法。所以g++通过首先评估fib(46),然后一直到fib(1)和fib(0)来评估fib。然后它评估fib(45),但它在计算fib(46)时已经这样做了,所以它只是查找结果并使用它

g++运行O(N+1)个步骤来计算fib(47)。Clang不存储和跟踪以前调用fib的结果,因此它探索递归调用的二进制树。这需要比任何合理数量的步骤都多的步骤,而且它没有达到深度限制或递归限制,而是达到了步骤限制。

记忆的代价是它使用了更多的内存。

为了使clang按原样编译程序,您必须修改clang编译器源代码本身,以便为其constexpr评估引擎添加内存。

您遇到的问题似乎超出了实现定义的限制,这将使对fib的调用不是一个常量表达式:

条件表达式e是核心常量表达式,除非e的评估,遵循抽象机的规则([intro.execution]),将评估以下表达式之一:

  • 一个将超过实施定义限制的表达式(见附件[implimits])

特别是:

  • 递归constexpr函数调用[512]

可能还有:

  • 对象的大小[262 144]

指标是clang认为int arr[fib(3)];很好,但抱怨int arr[fib(45)];,给出了一个相当误导性的诊断。

为了解决这个问题,我会使用fibonacci的迭代算法,它会更快,并解决递归深度问题。

根据5.20[expr.const]第2.6段:,评估constexpr时,不允许有未定义的行为

本国际标准第1条至第16条中规定的具有未定义行为的操作[注:例如,包括有符号整数溢出(第5条)…]

溢出一个有符号整数对象是未定义的行为,fib(45)是一个相当大的值(我预计会提前溢出…)

constexpr unsigned int fib(unsigned int i) { ... }

假定朴素斐波那契的复杂度为O(2^N),则99999999远小于2^45。所以您可以尝试放入-fconstexpr-steps=35184372088832,但我怀疑这会达到一些内部编译器的限制。