Berechnen der Länge eines C-Strings zur Kompilierungszeit. Ist das wirklich ein constexpr?

94

Ich versuche, die Länge eines String-Literals zur Kompilierungszeit zu berechnen. Dazu verwende ich folgenden Code:

#include <cstdio>

int constexpr length(const char* str)
{
    return *str ? 1 + length(str + 1) : 0;
}

int main()
{
    printf("%d %d", length("abcd"), length("abcdefgh"));
}

Alles funktioniert wie erwartet, das Programm druckt 4 und 8. Der durch clang generierte Assembler-Code zeigt, dass die Ergebnisse zur Kompilierungszeit berechnet werden:

0x100000f5e:  leaq   0x35(%rip), %rdi          ; "%d %d"
0x100000f65:  movl   $0x4, %esi
0x100000f6a:  movl   $0x8, %edx
0x100000f6f:  xorl   %eax, %eax
0x100000f71:  callq  0x100000f7a               ; symbol stub for: printf

Meine Frage: lengthWird durch den Standard garantiert, dass die Kompilierungszeit der Funktion ausgewertet wird?

Wenn dies zutrifft, hat sich mir gerade die Tür für Berechnungen von Zeichenfolgenliteralen zur Kompilierungszeit geöffnet ... zum Beispiel kann ich Hashes zur Kompilierungszeit berechnen und vieles mehr ...

Mircea Ispas
quelle
3
Solange der Parameter ein konstanter Ausdruck ist, muss es sein.
Chris
1
@chris Gibt es eine Garantie dafür, dass etwas, das ein konstanter Ausdruck sein kann, zur Kompilierungszeit ausgewertet werden muss , wenn es in einem Kontext verwendet wird, für den kein konstanter Ausdruck erforderlich ist?
TC
12
Übrigens, einschließlich <cstdio>und anschließender Anrufe, ::printfist nicht portabel. Der Standard muss nur <cstdio>bereitstellen std::printf.
Ben Voigt
1
@ BenVoigt Ok, danke für den Hinweis :) Anfangs habe ich std :: cout verwendet, aber der generierte Code war ziemlich groß, um die tatsächlichen Werte zu finden :)
Mircea Ispas
3
@Felics Ich benutze Godbolt oft, wenn ich Fragen zur Optimierung und Verwendung beantworte, printfwas dazu führen kann, dass deutlich weniger Code bearbeitet werden muss .
Shafik Yaghmour

Antworten:

76

Konstante Ausdrücke sind nicht garantiert bei der Kompilierung ausgewertet werden, haben wir nur ein nicht-normatives Zitat aus Entwurf C ++ Standard Abschnitt 5.19 Konstante Ausdrücke , der dies sagt aber:

[...]> [Hinweis: Konstante Ausdrücke können während der Übersetzung ausgewertet werden. - Endnote]

Sie können das Ergebnis einer constexprVariablen zuweisen , um sicherzustellen, dass es zur Kompilierungszeit ausgewertet wird. Dies geht aus der C ++ 11-Referenz von Bjarne Stroustrup hervor, in der es heißt ( Hervorhebung von mir ):

Um Ausdrücke zur Kompilierungszeit auswerten zu können, möchten wir auch verlangen , dass Ausdrücke zur Kompilierungszeit ausgewertet werden. constexpr vor einer Variablendefinition macht das (und impliziert const):

Beispielsweise:

constexpr int len1 = length("abcd") ;

Bjarne Stroustrup gibt in diesem isocpp-Blogeintrag eine Zusammenfassung darüber, wann wir die Bewertung der Kompilierungszeit sicherstellen können, und sagt:

[...] Die richtige Antwort - wie von Herb angegeben - ist, dass eine constexpr-Funktion gemäß dem Standard zur Compiler- oder Laufzeit ausgewertet werden kann, sofern sie nicht als konstanter Ausdruck verwendet wird. In diesem Fall muss sie beim Kompilieren ausgewertet werden -Zeit. Um eine Auswertung zur Kompilierungszeit zu gewährleisten, müssen wir sie entweder verwenden, wenn ein konstanter Ausdruck erforderlich ist (z. B. als Array-Bindung oder als Fallbezeichnung), oder sie zum Initialisieren eines Constexpr verwenden. Ich würde hoffen, dass kein Compiler mit Selbstachtung die Optimierungsmöglichkeit verpasst, um das zu tun, was ich ursprünglich gesagt habe: "Eine constexpr-Funktion wird zur Kompilierungszeit ausgewertet, wenn alle ihre Argumente konstante Ausdrücke sind."

Dies beschreibt also zwei Fälle, in denen es zur Kompilierungszeit ausgewertet werden sollte:

  1. Verwenden Sie es, wenn ein konstanter Ausdruck erforderlich ist. Dies scheint an einer beliebigen Stelle im Standardentwurf zu sein, an der die Phrase shall be ... converted constant expressionoder shall be ... constant expressionverwendet wird, z. B. an ein gebundenes Array.
  2. Verwenden Sie es, um a constexprwie oben beschrieben zu initialisieren .
Shafik Yaghmour
quelle
4
Im Prinzip ist ein Compiler jedoch berechtigt, ein Objekt mit interner oder keiner Verknüpfung zu sehen constexpr int x = 5;. Beachten Sie, dass der Wert zur Kompilierungszeit nicht erforderlich ist (vorausgesetzt, er wird nicht als Vorlagenparameter verwendet oder so weiter) und tatsächlich emittiert Code, der den Anfangswert zur Laufzeit unter Verwendung von 5 Sofortwerten von 1 und 4 Additionsoperationen berechnet. Ein realistischeres Beispiel: Der Compiler erreicht möglicherweise ein Rekursionslimit und verschiebt die Berechnung bis zur Laufzeit. Sofern Sie nichts tun, was den Compiler dazu zwingt, den Wert tatsächlich zu verwenden, ist "garantiert zur Kompilierungszeit ausgewertet" ein QOI-Problem.
Steve Jessop
@SteveJessop Bjarne scheint ein Konzept zu verwenden, das kein Analogon enthält, das ich im Standardentwurf finden kann und das als konstantes Ausdrucksmittel verwendet wird , das bei der Übersetzung bewertet wird. Es scheint also, dass der Standard nicht explizit angibt, was er sagt, daher würde ich Ihnen eher zustimmen. Obwohl sowohl Bjarne als auch Herb sich darin einig zu sein scheinen, könnte dies darauf hinweisen, dass es nur unterbestimmt ist.
Shafik Yaghmour
2
Ich denke, beide ziehen nur "Compiler mit Selbstachtung" in Betracht, im Gegensatz zu dem standardkonformen, aber absichtlich obstruktiven Compiler, den ich vermute. Es ist nützlich, um darüber nachzudenken, was der Standard tatsächlich garantiert , und nicht viel anderes ;-)
Steve Jessop
3
@SteveJessop Vorsätzlich obstruktive Compiler wie die berüchtigte (und leider nicht existierende) Hell ++. So etwas wäre tatsächlich großartig, um Konformität / Portabilität zu testen.
Angew ist nicht mehr stolz auf SO
Nach der Als-ob-Regel reicht es nicht aus, den Wert als scheinbare Kompilierungszeitkonstante zu verwenden: Der Compiler kann eine Kopie Ihrer Quelle versenden und zur Laufzeit neu kompilieren oder eine Berechnung durchführen, um den Typ von a zu bestimmen variabel, oder führen Sie Ihre constexprBerechnung einfach sinnlos aus dem Bösen heraus. Es ist sogar kostenlos, 1 Sekunde pro Charakter in einer bestimmten Quellzeile zu warten oder eine bestimmte Quellzeile zu nehmen und damit eine Schachposition zu setzen und dann beide Seiten zu spielen, um festzustellen, wer gewonnen hat.
Yakk - Adam Nevraumont
27

Es ist wirklich einfach herauszufinden, ob ein Aufruf einer constexprFunktion zu einem konstanten Kernausdruck führt oder lediglich optimiert wird:

Verwenden Sie es in einem Kontext, in dem ein konstanter Ausdruck erforderlich ist.

int main()
{
    constexpr int test_const = length("abcd");
    std::array<char,length("abcdefgh")> test_const2;
}
Ben Voigt
quelle
4
... und kompilieren mit -pedantic, wenn Sie gcc verwenden. Andernfalls erhalten Sie keine Warnungen und Fehler
BЈовић
@ BЈовић Oder verwenden Sie es in einem Kontext, in dem GCC keine potenziell störenden Erweiterungen aufweist, z. B. ein Vorlagenargument.
Angew ist nicht mehr stolz auf SO
Wäre ein Enum-Hack nicht zuverlässiger? Wie zum Beispiel enum { Whatever = length("str") }?
Scharfzahn
18
Erwähnenswert iststatic_assert(length("str") == 3, "");
Chris
8
constexpr auto test = /*...*/;ist wahrscheinlich die allgemeinste und einfachste.
TC
19

Nur eine Anmerkung, die moderne Compiler (wie gcc-4.x) strlenzur Kompilierungszeit für String-Literale verwenden, da sie normalerweise als intrinsische Funktion definiert sind . Ohne aktivierte Optimierungen. Obwohl das Ergebnis keine Kompilierungszeitkonstante ist.

Z.B:

printf("%zu\n", strlen("abc"));

Ergebnisse in:

movl    $3, %esi    # strlen("abc")
movl    $.LC0, %edi # "%zu\n"
movl    $0, %eax
call    printf
Maxim Egorushkin
quelle
Beachten Sie, dass dies funktioniert, da strlenes sich um eine integrierte Funktion handelt. Wenn wir sie verwenden -fno-builtins, um sie zur Laufzeit aufzurufen, sehen Sie sie live
Shafik Yaghmour,
strlenist constexprfür mich auch mit -fno-nonansi-builtins(scheint -fno-builtinsin g ++ nicht mehr zu existieren). Ich sage "constexpr", weil ich dies tun kann template<int> void foo();und foo<strlen("hi")>(); g ++ - 4.8.4
Aaron McDaid
17

Lassen Sie mich eine andere Funktion vorschlagen, die die Länge eines Strings zur Kompilierungszeit berechnet, ohne rekursiv zu sein.

template< size_t N >
constexpr size_t length( char const (&)[N] )
{
  return N-1;
}

Schauen Sie sich diesen Beispielcode bei ideone an .

user2436830
quelle
4
Es kann nicht gleich strlen sein, da '\ 0' eingebettet ist: strlen ("hi \ 0there")! = Länge ("hi \ 0there")
unkulunkulu
Dies ist der richtige Weg, dies ist ein Beispiel in Effective Modern C ++ (wenn ich mich richtig erinnere). Es gibt jedoch eine nette String-Klasse, die völlig konstexpr ist, siehe diese Antwort: Scott Schurrs str_const , vielleicht ist dies nützlicher (und weniger C-Stil).
QuantumKarl
@ MikeWeir Ops, das ist seltsam. Hier sind verschiedene Links: Link zur Frage , Link zum Papier , Link zur Quelle auf Git
QuantumKarl
Jetzt tun Sie es: char temp[256]; sprintf(temp, "%u", 2); if(1 != length(temp)) printf("Your solution doesn't work"); ideone.com/IfKUHV
Pablo Ariel
7

Es gibt keine Garantie dafür, dass eine constexprFunktion zur Kompilierungszeit ausgewertet wird, obwohl jeder vernünftige Compiler dies bei entsprechenden aktivierten Optimierungsstufen tut. Auf der anderen Seite, Template - Parameter müssen zur Compile-Zeit ausgewertet werden.

Ich habe den folgenden Trick verwendet, um die Auswertung zur Kompilierungszeit zu erzwingen. Leider funktioniert es nur mit Integralwerten (dh nicht mit Gleitkommawerten).

template<typename T, T V>
struct static_eval
{
  static constexpr T value = V;
};

Nun, wenn Sie schreiben

if (static_eval<int, length("hello, world")>::value > 7) { ... }

Sie können sicher sein, dass die ifAnweisung eine Konstante zur Kompilierungszeit ohne Laufzeitaufwand ist.

5gon12eder
quelle
8
oder verwenden Sie einfach std :: integrale_konstante <int, Länge (...)> :: Wert
Mircea Ispas
1
Das Beispiel ist ein bisschen eines sinnloser Gebrauch da lensein constexprMittel lengthmuss sowieso bei der Kompilierung ausgewertet werden.
Chris
@ Chris Ich wusste nicht , es muss sein, obwohl ich beobachtet habe , dass es ist mit meinem Compiler.
5gon12eder
Ok, nach den meisten anderen Antworten muss es so sein, also habe ich das Beispiel so geändert, dass es weniger sinnlos ist. Tatsächlich war es eine ifBedingung (bei der es wichtig war, dass der Compiler die Beseitigung von totem Code durchführte), für die ich ursprünglich den Trick verwendet habe.
5gon12eder
1

Eine kurze Erklärung aus dem Wikipedia-Eintrag zu verallgemeinerten konstanten Ausdrücken :

Die Verwendung von constexpr für eine Funktion führt zu Einschränkungen bei der Funktionsweise dieser Funktion. Erstens muss die Funktion einen nicht ungültigen Rückgabetyp haben. Zweitens kann der Funktionskörper keine Variablen deklarieren oder neue Typen definieren. Drittens darf der Body nur Deklarationen, Null-Anweisungen und eine einzelne return-Anweisung enthalten. Es müssen Argumentwerte vorhanden sein, sodass der Ausdruck in der return-Anweisung nach der Argumentersetzung einen konstanten Ausdruck erzeugt.

Wenn das constexprSchlüsselwort vor einer Funktionsdefinition steht, wird der Compiler angewiesen, zu überprüfen, ob diese Einschränkungen erfüllt sind. Wenn ja und die Funktion mit einer Konstanten aufgerufen wird, ist der zurückgegebene Wert garantiert konstant und kann daher überall dort verwendet werden, wo ein konstanter Ausdruck erforderlich ist.

Kaedinger
quelle
Diese Bedingungen garantieren nicht, dass der zurückgegebene Wert konstant ist . Beispielsweise kann die Funktion mit anderen Argumentwerten aufgerufen werden.
Ben Voigt
Richtig, @BenVoigt. Ich habe es so bearbeitet, dass es mit einem konstanten Ausdruck aufgerufen wird.
Kaedinger