2016-05-31 17 views
4

Nehmen Sie das folgende Beispiel constexpr:Warum kann ich nach dem Erhöhen von -fconstexpr-steps keinen konstanten Ausdruck auflösen?

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

Trotz constexpr zu sein, ist es nicht bei der Kompilierung ausgewertet.
Der Trick, den ich gelernt habe, die Kompilierung Auswertung zu erzwingen ist, wie folgt:

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

Dies funktioniert ist g ++, jedoch bekomme ich den folgenden Fehler in Klirren ++:

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. 

Ich habe versucht, die constexpr-Schritten zu erhöhen, aber Klappern noch den Code nicht kompilieren:

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

Was muss ich für Klirren tun, um diesen Code zu kompilieren wie?

Klirren ++:

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

g ++:

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

Ich denke, die Tiefe ist wichtig. Funktioniert 'std :: array arr;' auch? fib (45) benötigt ungefähr 6 Sekunden, um auf meiner Maschine zu laufen. Es wird zur Kompilierungszeit nicht ausgewertet. –

+0

Ich habe eine Antwort auf das mögliche Problem gepostet, aber ich denke, es könnte tatsächlich ein Bug im Klirren sein. Siehe [constexpr-Tiefenbegrenzung mit clang (fconstexpr-depth scheint nicht zu funktionieren)] (https://stackoverflow.com/questions/24591466/constexpr-depth-limit-with-clang-fconstexpr-depth-doesnt-seem-to- Arbeit) –

+0

@sleeptightpupper Ich sehe keinen Fehler. Ein fehlendes Feature (um conetexpr-Funktionen zu memotisieren), vielleicht. –

Antwort

2

Klirren nicht Funktion Anrufungen memoize constexpr.

Here is someone strugging with a similar problem. Die Anzahl der Schritte in fib (47) liegt in der Größenordnung von 2^45 oder 35184372088832. Wenn Sie diese vielen Schritte bei -fconstexpr-steps= senden, you get::

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

grundsätzlich Wert zu groß. Selbst wenn dies nicht der Fall wäre, würde der Compiler vermutlich aufgrund von fehlender Memoisierung in die Luft gehen, bevor er so viele Schritte ausgeführt hat. (Nun, Phi^47 ist näher an der Anzahl der rekursiven Schritte, aber das ist immer noch 36 Bits Größe und Clang speichert constexpr-steps in einem 32 Bit unsigned Int, also keine Würfel)

Memoization ist die Sache, wo Sie behalten verfolgen, welche Werte zu welchen Ergebnissen zugeordnet werden. So wertet g ++ fib (47) aus, indem es zuerst fib (46) und dann bis fib (1) und fib (0) auswertet. Dann wertet es fib (45) aus, aber hat es schon getan, als es fib (46) berechnet hat, also sieht es nur das Ergebnis nach und benutzt es.

g ++ läuft O (N + 1) Schritte zur Berechnung von fib (47). Clang merkt sich nicht und verfolgt das Ergebnis früherer Aufrufe von fib, sodass es den Binärbaum von rekursiven Aufrufen untersucht. Dies erfordert mehr als jede vernünftige Anzahl von Schritten und es trifft keine Tiefenbegrenzung oder Rekursionsgrenze, sondern eine Stufengrenze.

Die Kosten für das Memo sind, dass es mehr Speicher verwendet.

Damit call das Programm so kompiliert, wie es ist, müssen Sie den Quellcode des clang-Compilers selbst ändern, um seiner consExpr-Evaluierungs-Engine eine Memoisierung hinzuzufügen.

+0

2^45 ist eine lose gebundene, keine enge Bindung. Eine enge Bindung sollte viel kleiner sein. Die Einstellung "-fconstexpr-steps" wird unter Verwendung einer 32-Bit-Ganzzahl mit Vorzeichen gespeichert, aber im Code in "unsigned" umgewandelt, so dass ich denke, dass die maximale Einstellung tatsächlich "-1" ist. Versucht es jetzt ... –

+0

@ T.c. ["Ausführung abgelaufen"] (http://coliru.stacked-crooked.com/a/166eb69df1449c7e). Und phi^45 benötigt immer noch ~ 36 Bits zum Speichern. – Yakk

+0

Yep (ich habe es lokal ausgeführt, also nicht "abgelaufen" aber trotzdem Schritt überschritten). –

2

Das Problem scheint Sie stoßen exceeding the implementation-defined limits zu sein, die dann Anrufungen fib kein constant expression machen würde:

A conditional-expressione 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]);

Insbesondere:

  • Recursive constexpr function invocations [512].

Und vielleicht:

  • Size of an object [262 144].

auch.

Der Indikator wäre, dass clang int arr[fib(3)]; in Ordnung hält, aber über int arr[fib(45)]; klagt, was eine eher irreführende Diagnose gibt.

Um dieses Problem zu umgehen, würde ich einen iterativen Algorithmus für Fibonacci verwenden, der schneller wäre und Ihr rekursives Tiefenproblem umgehen würde.

+1

Der passendere ist wahrscheinlich "Full-Ausdrücke ausgewertet innerhalb einer Kernkonstante Ausdruck [1 048 576]." –

1

Da die Komplexität der naiven Fibonacci O(2^N) ist, 99999999 als 2^45 viel geringer ist. So können Sie versuchen, -fconstexpr-steps=35184372088832 zu setzen, aber ich vermute, dass einige interne Compiler-Grenzen treffen werden.

+0

Die Rekursion * Tiefe * ist O (N). – Yakk

+0

Die Komplexität des naiven Fibonacci ist O (2^N). Ein anständig implementiertes, das die Ergebnisse von internen Berechnungen zwischenspeichert, ist O (N). (Basierend auf dem Compiler-Overhead von fib (45), vermute ich, dass es das nicht tut.) – MSN

+0

Ich sagte * Tiefe *. Sie sprechen über Schritte. Ich sehe jetzt, dass clang 'fconstexpr-steps = 99999999' hat, vielleicht ist es das, was Sie vorschlagen, auf' 35184372088832' zu setzen? Nun, Sie haben recht, dass es ein anderes Limit trifft: "Fehler: ungültiger Integralwert '35184372088832' in '-fconstexpr-steps 35184372088832'" – Yakk

2

Wenn eine constexpr Auswertung Sie nicht zu haben, nicht definiertes Verhalten nach 5.20 [expr.const] Ziffer 2.6 erlaubt: Objekt

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 eine signierte ganze Zahl ist nicht definiertes Verhalten und fib(45) ist ein ziemlich großer Wert (Ich hätte früher Überläufe erwartet ...).Ich könnte mir vorstellen, dass der Code kompiliert OK (aber natürlich, schließlich die Ergebnisse falsch sind), wenn Sie

verwendet
constexpr unsigned int fib(unsigned int i) { ... } 
+0

[nein] (http://coliru.stacked-crooked.com/a/bb2af55813cf88d9) – Yakk