Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Take the following constexpr example:

#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';
}

Despite being constexpr, it is not evaluated at compile time.
The trick I've learned to enforce the compile time evaluation, is as followed:

#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';
}

This works is g++, however I get the following error in 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.  

I've tried increasing the constexpr-steps, but clang will still not compile the code:

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

What must I do for clang to compile this code as is?

clang++:

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

g++:

g++ (Ubuntu 5.1.0-0ubuntu11~14.04.1) 5.1.0
like image 496
Trevor Hickey Avatar asked May 31 '16 03:05

Trevor Hickey


People also ask

How do you solve a constant expression required error in C++?

For example, if a program requires a “const”, but you're feeding it a “variable value”, which can be changed in the program or overwritten. So, use the keyword “const” before that variable to make it a constant and resolve the error.

How do you define a constant expression in Java?

To make any variable a constant, we must use 'static' and 'final' modifiers in the following manner: Syntax to assign a constant value in java: static final datatype identifier_name = constant; The static modifier causes the variable to be available without an instance of it's defining class being loaded.

What is a constant expression?

A constant expression is an expression that can be evaluated at compile time. Constants of integral or enumerated type are required in several different situations, such as array bounds, enumerator values, and case labels. Null pointer constants are a special case of integral constants.

What is a constant integer expression?

An integer constant is a value that is determined at compile time and cannot be changed at run time. An integer constant expression is an expression that is composed of constants and evaluated to a constant at compile time.


2 Answers

clang does not memoize constexpr function invocations.

Here is someone strugging with a similar problem.

The number of steps in fib(47) is on the order of 2^45, or 35184372088832. If you send this many steps at -fconstexpr-steps=, you get::

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

basically, value too big. Even if it wasn't, the compiler would probably blow up before it ran that many steps, due to lack of memoization. (well, phi^47 is closer to the number of recursive steps, but that is still 36 bits of size, and clang stores constexpr-steps in a 32 bit unsigned int, so no dice)

Memoization is the thing where you keep track of what values map to what results. So g++ evaluates fib(47) by first evaluating fib(46) then all the way down to fib(1) and fib(0). Then it evaluates fib(45), but it already did so when it calculated fib(46), so it just looks up the result and uses it.

g++ runs O(N+1) steps to calculate fib(47). Clang does not memoize and keep track of the result of previous calls to fib, so it explores the binary tree of recursive calls. This takes more than any reasonable number of steps, and it hits not a depth limit or a recursion limit, but rather a step limit.

The cost to memoizing is that it uses more memory.

In order to make clang compile the program as is, you'll have to modify the clang compiler source code itself to add memoization to its constexpr evaluation engine.

like image 111
Yakk - Adam Nevraumont Avatar answered Oct 17 '22 06:10

Yakk - Adam Nevraumont


The problem you're encountering seems to be exceeding the implementation-defined limits, which would then make invocations to fib not a constant expression:

A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following expressions:

  • an expression that would exceed the implementation-defined limits (see Annex [implimits]);

In particular:

  • Recursive constexpr function invocations [512].

And possibly:

  • Size of an object [262 144].

as well.

The indicator would be that clang considers int arr[fib(3)]; fine but complains about int arr[fib(45)];, giving a rather misleading diagnostic.

To get around this problem, I would use an iterative algorithm for fibonacci which would be faster and get around your recursive depth issue.

like image 2
uh oh somebody needs a pupper Avatar answered Oct 17 '22 04:10

uh oh somebody needs a pupper