为什么在增加 -fconstexpr-步数后无法解析常量表达式?
Why can't I resolve a constant expression after increasing -fconstexpr-steps?
以以下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^45
或35184372088832
的数量级。如果你在-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
,但我怀疑这会达到一些内部编译器的限制。
- 不能在初始值设定项列表中将非常量表达式从类型 'int' 缩小到'unsigned long long'
- 当一个值是非常量但用常量表达式初始化时使用constexpr
- 使用自动推导的 lambda 参数作为常量表达式
- 生成提升::hana::set 的常量表达式问题
- 为什么不能用常量表达式声明数组?
- 不是 lambda 函数中的常量表达式
- 函数调用在常量表达式中必须具有常量值
- 错误:constexpr 变量'struct2Var'必须由常量表达式初始化
- 关于在需要常量表达式的上下文中使用的glvalue常量表达式的问题
- 生成 constexpr 字符串表,不能产生常量表达式
- 整体模板参数。错误:在常量表达式中使用'this'
- 如何在满足常量表达式的同时将整数传递给指针,传递给 std::array<double、integer>?
- 编译器错误:函数调用在常量表达式中必须有一个常量值
- 错误:'new'不能出现在常量表达式中
- 我可以写出小于 -0.5 两个 ulps 的常量表达式双精度吗?
- 编译器在传递 const 变量时返回错误:模板参数不是常量表达式
- 为什么我不能在非常量表达式上使用此模板阶乘函数?
- C++ 使用变量而不是常量表达式初始化数组
- 使用函数参数作为常量表达式的一部分 - gcc vs clang
- 片段着色器中的"错误:在 GLSL 1.30 及更高版本中禁止使用非常量表达式索引的采样器数组"