5

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
Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
  • I think the depth is important. Does `std::array arr;` work as well? fib(45) takes about 6 seconds to run on my machine. It's not being evaluated at compile-time. – Trevor Hickey May 31 '16 at 04:12
  • I posted an answer about the possible issue, but I think it may actually be a bug in clang. See [constexpr depth limit with clang (fconstexpr-depth doesnt seem to work)](https://stackoverflow.com/questions/24591466/constexpr-depth-limit-with-clang-fconstexpr-depth-doesnt-seem-to-work) – uh oh somebody needs a pupper May 31 '16 at 04:22
  • @sleeptightpupper I don't see a bug. A missing feature (to memoize `constexpr` functions), perhaps. – T.C. Jun 01 '16 at 03:04
  • Have you considered creating an alias of `clang` to `g++`? – Yakk - Adam Nevraumont Jun 02 '16 at 17:59
  • @sleeptightpupper I am tempted to close this question as a duplicate of that one. It even tells you how to make it work, but I strongly suspect the depth limit required will hit other problems at depth 47. So the answer is "add memoization to clang constexpr evaluation". – Yakk - Adam Nevraumont Jun 02 '16 at 18:02

4 Answers4

5

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 2^45 is a loose bound, not a tight bound. A tight bound should be much smaller. The `-fconstexpr-steps` setting is stored using a 32-bit signed integer, but converted to `unsigned` in the code, so I think the max setting is actually `-1`. Trying it now... – T.C. Jun 02 '16 at 18:41
  • 1
    @T.c. ["execution expired"](http://coliru.stacked-crooked.com/a/166eb69df1449c7e). And phi^45 still takes ~36 bits to store. – Yakk - Adam Nevraumont Jun 02 '16 at 19:06
  • Yep (I ran it locally, so no "expired" but still step exceeded). – T.C. Jun 02 '16 at 19:10
2

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.

  • 1
    The more pertinent one is probably "Full-expressions evaluated within a core constant expression [1 048 576]." – T.C. Jun 01 '16 at 03:03
2

When evaluating a constexpr you are not allowed to have undefined behavior according to 5.20 [expr.const] paragraph 2.6:

an operation that would have undefined behavior as specified in Clauses 1 through 16 of this International Standard [Note: including, for example, signed integer overflow (Clause 5) ... ]

Overflowing a signed integer object is undefined behavior and fib(45) is a pretty large value (I would have expected overflows earlier than that...). I would imagine that the code compiles OK (but, of course, eventually the results are wrong) if you used

constexpr unsigned int fib(unsigned int i) { ... }
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
1

Given that the complexity of naive Fibonacci is O(2^N), 99999999 is much less than 2^45. So you can try putting in -fconstexpr-steps=35184372088832, but I suspect that will hit some internal compiler limits.

MSN
  • 53,214
  • 7
  • 75
  • 105
  • The recursion *depth* is O(N). – Yakk - Adam Nevraumont Jun 02 '16 at 17:53
  • The complexity of naïve Fibonacci is O(2^N). A decently implemented one that caches the results of internal calculations is O(N). (Based on the compiler overhead of fib(45), I suspect it is not doing that.) – MSN Jun 02 '16 at 17:57
  • I said *depth*. You are talking about steps. I do now see that clang has `fconstexpr-steps=99999999`, maybe that is what you are suggesting to set to `35184372088832`? Well, you are right in that it hits another limit: "error: invalid integral value '35184372088832' in '-fconstexpr-steps 35184372088832'" – Yakk - Adam Nevraumont Jun 02 '16 at 18:03
  • Err, yes. I meant steps. – MSN Jun 02 '16 at 18:05
  • so the value is parsed as an `int`, which fails before the "internal limits" can be reached. passing in `-1` (apparently internally it is an unsigned int) gives the max possible value that clang can handle. That (2^32-1) isn't big enough for fib(47). – Yakk - Adam Nevraumont Jun 02 '16 at 21:02