Ist eine statische constexpr-Variable innerhalb einer Funktion sinnvoll?

192

Wenn ich eine Variable in einer Funktion habe (z. B. ein großes Array), ist es sinnvoll, beide staticund zu deklarieren constexpr? constexprgarantiert, dass das Array zur Kompilierungszeit erstellt wird, wäre das staticalso nutzlos?

void f() {
    static constexpr int x [] = {
        // a few thousand elements
    };
    // do something with the array
}

Ist das statictatsächlich etwas zu tun gibt es in Bezug auf die generierten Code oder Semantik?

David Stone
quelle

Antworten:

230

Die kurze Antwort lautet: Es ist nicht nur staticnützlich, es wird auch immer erwünscht sein.

Beachten Sie zunächst, dass staticund constexprvöllig unabhängig voneinander sind. staticdefiniert die Lebensdauer des Objekts während der Ausführung; constexprGibt an, dass das Objekt während der Kompilierung verfügbar sein soll. Kompilierung und Ausführung sind zeitlich und räumlich unzusammenhängend und nicht zusammenhängend. Sobald das Programm kompiliert ist, constexprist es nicht mehr relevant.

Jede Variable deklariert constexprist implizit constaber constund staticsind fast orthogonal ( mit Ausnahme der Interaktion mit static constganzen Zahlen.)

Das C++Objektmodell (§1.9) erfordert, dass alle Objekte außer Bitfeldern mindestens ein Byte Speicher belegen und Adressen haben; Darüber hinaus müssen alle in einem Programm zu einem bestimmten Zeitpunkt beobachtbaren Objekte unterschiedliche Adressen haben (Absatz 6). Dies erfordert nicht ganz, dass der Compiler für jeden Aufruf einer Funktion mit einem lokalen nicht statischen const-Array ein neues Array auf dem Stapel erstellt, da der Compiler in das as-ifPrinzip flüchten könnte, vorausgesetzt, er kann beweisen, dass kein anderes solches Objekt sein kann beobachtete.

Dies wird leider nicht leicht zu beweisen sein, es sei denn, die Funktion ist trivial (z. B. ruft sie keine andere Funktion auf, deren Körper in der Übersetzungseinheit nicht sichtbar ist), da Arrays mehr oder weniger per Definition Adressen sind. In den meisten Fällen muss das nicht statische const(expr)Array bei jedem Aufruf auf dem Stapel neu erstellt werden, was den Punkt zunichte macht, dass es zur Kompilierungszeit berechnet werden kann.

Andererseits wird ein lokales static constObjekt von allen Beobachtern gemeinsam genutzt und kann darüber hinaus initialisiert werden, selbst wenn die Funktion, in der es definiert ist, niemals aufgerufen wird. Keiner der oben genannten Punkte trifft also zu, und ein Compiler kann nicht nur eine einzige Instanz davon generieren. Es ist kostenlos, eine einzelne Instanz davon im Nur-Lese-Speicher zu generieren.

Sie sollten es also unbedingt static constexprin Ihrem Beispiel verwenden.

Es gibt jedoch einen Fall, in dem Sie nicht verwenden möchten static constexpr. Sofern ein constexprdeklariertes Objekt nicht ODR-verwendet oder deklariert ist static, kann der Compiler es überhaupt nicht einschließen. Das ist ziemlich nützlich, da temporäre constexprArrays zur Kompilierungszeit verwendet werden können, ohne das kompilierte Programm mit unnötigen Bytes zu belasten. In diesem Fall möchten Sie eindeutig nicht verwenden static, da staticdas Objekt wahrscheinlich zur Laufzeit erzwungen wird.

Rici
quelle
2
@ AndrewLazarus, du kannst nicht constvon einem constObjekt wegwerfen, nur von einem, const X*das auf ein zeigt X. Aber darum geht es nicht; Der Punkt ist, dass automatische Objekte keine statischen Adressen haben können. Wie gesagt, constexprhört nach Abschluss der Kompilierung auf, aussagekräftig zu sein, sodass nichts wegzuwerfen ist (und möglicherweise gar nichts, da nicht einmal garantiert wird, dass das Objekt zur Laufzeit existiert.)
Rici
17
Ich habe das Gefühl, dass diese Antwort nicht nur unglaublich verwirrend, sondern auch selbst widersprüchlich ist. Zum Beispiel sagen Sie, dass Sie fast immer wollen staticund constexprerklären, dass sie orthogonal und unabhängig sind und verschiedene Dinge tun. Sie erwähnen dann einen Grund, die beiden NICHT zu kombinieren, da dies die ODR-Verwendung ignorieren würde (was nützlich erscheint). Oh und ich verstehe immer noch nicht, warum statisch mit constexpr verwendet werden sollte, da statisch für Laufzeitsachen ist. Sie haben nie erklärt, warum statisch mit constexpr wichtig ist.
void.pointer
2
@ void.pointer: Du hast Recht mit dem letzten Absatz. Ich habe das Intro geändert. Ich dachte, ich hätte die Wichtigkeit von erklärt static constexpr(es verhindert, dass das konstante Array bei jedem Funktionsaufruf neu erstellt werden muss), aber ich habe einige Wörter optimiert, die es klarer machen könnten. Vielen Dank.
Rici
8
Könnte auch nützlich sein, um Kompilierungszeitkonstanten gegenüber Laufzeitkonstanten zu erwähnen. Mit anderen Worten, wenn eine constexprkonstante Variable nur in Kontexten zur Kompilierungszeit verwendet wird und zur Laufzeit nie benötigt wird, staticmacht dies keinen Sinn, da der Wert zum Zeitpunkt der Laufzeit effektiv "inline" ist. Wenn constexpres jedoch in Laufzeitkontexten verwendet wird (mit anderen Worten, das constexprmüsste constimplizit konvertiert werden und mit einer physischen Adresse für Laufzeitcode verfügbar sein), möchte statices die ODR-Konformität usw. sicherstellen. Das ist zumindest mein Verständnis.
void.pointer
3
Ein Beispiel für meinen letzten Kommentar : static constexpr int foo = 100;. Es gibt keinen Grund, warum der Compiler die Verwendung von fooüberall nicht durch Literal ersetzen könnte 100, es sei denn, Code würde so etwas tun &foo. Daher ist staticon fooin diesem Fall nicht nützlich, da fooes zur Laufzeit nicht vorhanden ist. Wieder alles bis zum Compiler.
void.pointer
10

Zusätzlich zur gegebenen Antwort ist zu beachten, dass der Compiler die constexprVariable zum Zeitpunkt der Kompilierung nicht initialisieren muss , da Sie wissen, dass der Unterschied zwischen constexprund darin static constexprbesteht, dass static constexprSie sicherstellen, dass die Variable nur einmal initialisiert wird.

Der folgende Code zeigt, wie eine constexprVariable mehrmals initialisiert wird (allerdings mit demselben Wert), während sie static constexprsicherlich nur einmal initialisiert wird.

Zusätzlich vergleicht der Code den Vorteil von constexprgegen constin Kombination mit static.

#include <iostream>
#include <string>
#include <cassert>
#include <sstream>

const short const_short = 0;
constexpr short constexpr_short = 0;

// print only last 3 address value numbers
const short addr_offset = 3;

// This function will print name, value and address for given parameter
void print_properties(std::string ref_name, const short* param, short offset)
{
    // determine initial size of strings
    std::string title = "value \\ address of ";
    const size_t ref_size = ref_name.size();
    const size_t title_size = title.size();
    assert(title_size > ref_size);

    // create title (resize)
    title.append(ref_name);
    title.append(" is ");
    title.append(title_size - ref_size, ' ');

    // extract last 'offset' values from address
    std::stringstream addr;
    addr << param;
    const std::string addr_str = addr.str();
    const size_t addr_size = addr_str.size();
    assert(addr_size - offset > 0);

    // print title / ref value / address at offset
    std::cout << title << *param << " " << addr_str.substr(addr_size - offset) << std::endl;
}

// here we test initialization of const variable (runtime)
void const_value(const short counter)
{
    static short temp = const_short;
    const short const_var = ++temp;
    print_properties("const", &const_var, addr_offset);

    if (counter)
        const_value(counter - 1);
}

// here we test initialization of static variable (runtime)
void static_value(const short counter)
{
    static short temp = const_short;
    static short static_var = ++temp;
    print_properties("static", &static_var, addr_offset);

    if (counter)
        static_value(counter - 1);
}

// here we test initialization of static const variable (runtime)
void static_const_value(const short counter)
{
    static short temp = const_short;
    static const short static_var = ++temp;
    print_properties("static const", &static_var, addr_offset);

    if (counter)
        static_const_value(counter - 1);
}

// here we test initialization of constexpr variable (compile time)
void constexpr_value(const short counter)
{
    constexpr short constexpr_var = constexpr_short;
    print_properties("constexpr", &constexpr_var, addr_offset);

    if (counter)
        constexpr_value(counter - 1);
}

// here we test initialization of static constexpr variable (compile time)
void static_constexpr_value(const short counter)
{
    static constexpr short static_constexpr_var = constexpr_short;
    print_properties("static constexpr", &static_constexpr_var, addr_offset);

    if (counter)
        static_constexpr_value(counter - 1);
}

// final test call this method from main()
void test_static_const()
{
    constexpr short counter = 2;

    const_value(counter);
    std::cout << std::endl;

    static_value(counter);
    std::cout << std::endl;

    static_const_value(counter);
    std::cout << std::endl;

    constexpr_value(counter);
    std::cout << std::endl;

    static_constexpr_value(counter);
    std::cout << std::endl;
}

Mögliche Programmausgabe:

value \ address of const is               1 564
value \ address of const is               2 3D4
value \ address of const is               3 244

value \ address of static is              1 C58
value \ address of static is              1 C58
value \ address of static is              1 C58

value \ address of static const is        1 C64
value \ address of static const is        1 C64
value \ address of static const is        1 C64

value \ address of constexpr is           0 564
value \ address of constexpr is           0 3D4
value \ address of constexpr is           0 244

value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0

Wie Sie sehen, constexprwird Sie mehrfach staticinitialisiert (Adresse ist nicht dieselbe), während das Schlüsselwort sicherstellt, dass die Initialisierung nur einmal durchgeführt wird.

Metablaster
quelle
können wir nicht verwenden, constexpr const short constexpr_shortum Fehler zu geben, wenn constexpr_short erneut initialisiert wird
akhileshzmishra
Ihre Syntax constexpr constmacht keinen Sinn , weil constexprschon ist const, das Hinzufügen consteinmal oder mehrmals durch Compiler ignoriert. Sie versuchen, einen Fehler abzufangen, aber dies ist kein Fehler. So funktionieren die meisten Compiler.
Metablaster