Constexpr funktioniert nicht so.
Einfach ausgedrückt müssen constexpr-Funktionen auch als Laufzeitfunktionen verfügbar sein. Stellen Sie sich vor, Sie würden das constexpr von der Funktion entfernen. Denken Sie dann darüber nach, warum es unmöglich funktionieren kann.
Der Grund ist, dass der Compiler den Körper der Funktion vollständig instantiieren muss; er kann nicht basierend auf der Bedingung im ?:
entscheiden, eine Seite nicht zu instanziieren. Deshalb muss er den rekursiven Aufruf immer instantiieren, was zu einer unendlichen Rekursion führt.
Auf jeden Fall verwenden Sie constexpr falsch. Sie verwenden die alte Technik der Template-Metaprogrammierung (Übergeben von Sachen als Template-Parameter), wenn constexpr gedacht war, dies zu ersetzen. Verwenden Sie einfach normale Parameter.
constexpr unsigned Log2(unsigned n, unsigned p = 0) {
return (n <= 1) ? p : Log2(n / 2, p + 1);
}
std::cout << "Log2(8) = " << Log2(8) << std::endl;
Bearbeitung: Ich werde versuchen, ausführlicher darauf einzugehen, wie dies funktioniert.
Wenn der Compiler Ihren Code erreicht, analysiert er die Template-Funktion und speichert sie in Template-Form. (Wie dies funktioniert, unterscheidet sich zwischen Compilern.) Bisher ist alles in Ordnung.
Als nächstes sieht der Compiler in main
den Aufruf Log2<8>()
. Er sieht, dass er das Template instantiieren muss, also macht er genau das: er instantiiert Log2<8, 0>
. Der Körper des Funktions-Templates lautet wie folgt:
return (N <= 1) ? P : Log2();
OK, der Compiler sieht das, versucht es aber nicht auszuwerten. Warum sollte er das tun? Er instantiiert gerade ein Template, berechnet also keinen Wert. Er setzt einfach die bereitgestellten Werte ein:
return (8 <= 1) ? 0 : Log2<8/2,0+1>();
Hm, hier gibt es eine weitere Template-Instantiierung. Es spielt keine Rolle, dass es sich um einen bedingten Ausdruck handelt oder dass die linke Seite bekannt sein könnte. Die Template-Instantiierung muss vollständig sein. Also berechnet er die Werte für die neue Instantiierung und instantiiert dann Log2<4, 1>
:
return (4 <= 1) ? 1 : Log2<4/2,1+1>();
Und das Spiel beginnt von vorne. Es gibt eine Template-Instantiierung darin, und es ist Log2<2, 2>
:
return (2 <= 1) ? 2 : Log2<2/2,2+1>();
Und wieder Log2<1,3>()
:
return (1 <= 1) ? 3 : Log2<1/2,3+1>();
Habe ich erwähnt, dass dem Compiler die semantische Bedeutung dieser Dinge egal ist? Es ist einfach ein weiteres Template, das instantiiert werden soll: Log2<0,4>
:
return (0 <= 1) ? 4 : Log2<0/2,4+1>();
Und dann Log2<0,5>
:
return (0 <= 1) ? 5 : Log2<0/2,5+1>();
Und so weiter, und so weiter. Irgendwann erkennt der Compiler, dass die Rekrusion nie endet, und gibt auf. Aber zu keinem Zeitpunkt sagt er: "Moment, die Bedingung dieses ternären Operators ist falsch, ich muss die rechte Seite nicht instantiieren." Denn das erlaubt ihm der C++-Standard nicht. Der Funktionskörper muss vollständig instantiiert werden.
Schauen Sie sich jetzt meine Lösung an. Hier gibt es kein Template. Es gibt nur eine Funktion. Der Compiler sieht sie und denkt: "Hey, hier ist eine Funktion. Klasse, lass mich hier einen Aufruf zu dieser Funktion einfügen." Und dann denkt er irgendwann (es kann sofort sein, es kann viel später sein, je nach Compiler): "Moment mal, diese Funktion ist `constexpr` und ich kenne die Parameterwerte, lass mich das auswerten." Jetzt bewertet er also vielleicht (aber in diesem Fall nicht gezwungenermaßen) `Log2(8, 0)`. Denken Sie an den Körper:
return (n <= 1) ? p : Log2(n / 2, p + 1);
"OK", sagt der Compiler, "ich möchte nur wissen, was diese Funktion zurückgibt. Mal sehen, `8 <= 1` ist falsch, also schaue ich auf die rechte Seite. `Log2(4, 1)`, huh? Lass mich das anschauen. OK, `4 <= 1` ist auch falsch, also muss es `Log2(2, 2)` sein. Was ist das, `2 <= 1`? Auch falsch, also ist es `Log2(1, 3)`. Hey, `1 <= 1` ist wahr, also nehme ich das `3` und gebe es zurück. Den ganzen Weg den Aufrufstapel hoch."
Also kommt er auf die Antwort 3. Es gerät nicht in eine endlose Rekursion, weil es die Funktion mit vollem Wissen über Werte und Semantik auswertet, und nicht nur stupide ASTs aufbaut.
Ich hoffe, das hilft.