Unterschied zwischen "constexpr" und "const"

593

Was ist der Unterschied zwischen constexprund const?

  • Wann kann ich nur einen davon verwenden?
  • Wann kann ich beide verwenden und wie soll ich eine auswählen?
MBZ
quelle
71
constexprerstellt eine Kompilierungszeitkonstante; constbedeutet einfach, dass der Wert nicht geändert werden kann.
0x499602D2
boost/hanaMöglicherweise kann dieser Artikel aus der Bibliothek einige constexprProbleme aufzeigen, die Sie verwenden können constexprund wo Sie nicht können: boost.org/doc/libs/1_69_0/libs/hana/doc/html/…
Andry
@ 0x499602D2 " bedeutet einfach, dass der Wert nicht geändert werden kann " Für einen mit einem Literal initialisierten Skalar ist ein Wert, der nicht geändert werden kann, auch eine Kompilierungszeitkonstante.
Neugieriger
@curiousguy Ja, mein Kommentar war sehr stark vereinfacht. Zugegeben, ich war constexprdamals auch neu :)
0x499602D2

Antworten:

587

Grundlegende Bedeutung und Syntax

Beide Schlüsselwörter können sowohl bei der Deklaration von Objekten als auch bei Funktionen verwendet werden. Der grundlegende Unterschied bei der Anwendung auf Objekte ist folgender:

  • constdeklariert ein Objekt als konstant . Dies impliziert eine Garantie, dass sich der Wert dieses Objekts nach der Initialisierung nicht ändert und der Compiler diese Tatsache für Optimierungen nutzen kann. Es hilft auch zu verhindern, dass der Programmierer Code schreibt, der Objekte ändert, die nach der Initialisierung nicht geändert werden sollten.

  • constexprdeklariert ein Objekt als zur Verwendung in den vom Standard als konstant bezeichneten Ausdrücken geeignet . Beachten Sie jedoch, dass dies constexprnicht der einzige Weg ist, dies zu tun.

Bei der Anwendung auf Funktionen ist der grundlegende Unterschied folgender:

  • constkann nur für nicht statische Elementfunktionen verwendet werden, nicht für Funktionen im Allgemeinen. Es gibt eine Garantie dafür, dass die Elementfunktion keines der nicht statischen Datenelemente ändert.

  • constexprkann sowohl mit Member- als auch Nicht-Member-Funktionen sowie mit Konstruktoren verwendet werden. Es deklariert die Funktion für die Verwendung in konstanten Ausdrücken . Der Compiler akzeptiert es nur, wenn die Funktion bestimmte Kriterien erfüllt (7.1.5 / 3,4), vor allem (†) :

    • Der Funktionskörper muss nicht virtuell und äußerst einfach sein: Abgesehen von typedefs und statischen Asserts ist nur eine einzige returnAnweisung zulässig. Im Fall eines Konstruktors sind nur eine Initialisierungsliste, typedefs und statische Zusicherungen zulässig. ( = defaultund = deletesind aber auch erlaubt.)
    • Ab C ++ 14 sind die Regeln entspannter, was seitdem in einer constexpr-Funktion zulässig ist: asmDeklaration, eine gotoAnweisung, eine Anweisung mit einer anderen Bezeichnung als caseund default, try-block, Definition einer Variablen vom nicht-wörtlichen Typ, Definition einer Variablen mit statischer oder Thread-Speicherdauer, Definition einer Variablen, für die keine Initialisierung durchgeführt wird.
    • Die Argumente und der Rückgabetyp müssen Literaltypen sein (dh im Allgemeinen sehr einfache Typen, typischerweise Skalare oder Aggregate).

Konstante Ausdrücke

Wie oben erwähnt, werden constexprsowohl Objekte als auch Funktionen als für die Verwendung in konstanten Ausdrücken geeignet deklariert. Ein konstanter Ausdruck ist mehr als nur konstant:

  • Es kann an Orten verwendet werden, an denen eine Auswertung zur Kompilierungszeit erforderlich ist, z. B. bei Vorlagenparametern und Array-Größenangaben:

    template<int N>
    class fixed_size_list
    { /*...*/ };
    
    fixed_size_list<X> mylist;  // X must be an integer constant expression
    
    int numbers[X];  // X must be an integer constant expression
  • Aber beachten Sie:

    • Das Deklarieren von etwas constexprgarantiert nicht unbedingt, dass es beim Kompilieren ausgewertet wird. Es kann für solche verwendet werden, aber es kann auch an anderen Orten verwendet werden, die zur Laufzeit ausgewertet werden.

    • Ein Objekt kann zur Verwendung in konstanten Ausdrücken geeignet sein, ohne deklariert zu werden constexpr. Beispiel:

      int main()
      {
        const int N = 3;
        int numbers[N] = {1, 2, 3};  // N is constant expression
      }

    Dies ist möglich, weil Nes konstant ist und zur Deklarationszeit mit einem Literal initialisiert wird und die Kriterien für einen konstanten Ausdruck erfüllt, auch wenn er nicht deklariert ist constexpr.

Also wann muss ich eigentlich verwenden constexpr?

  • Ein Objekt wie Noben kann als konstanter Ausdruck verwendet werden, ohne deklariert zu werden constexpr. Dies gilt für alle Objekte, die:

    • const
    • vom Integral- oder Aufzählungstyp und
    • Wird zur Deklarationszeit mit einem Ausdruck initialisiert, der selbst ein konstanter Ausdruck ist

    [Dies liegt an §5.19 / 2: Ein konstanter Ausdruck darf keine Unterausdrücke enthalten, die "eine Änderung von lWert zu rWert" beinhalten, es sei denn, […] ein GlWert vom Integral- oder Aufzählungstyp […] "Dank an Richard Smith für die Korrektur von my frühere Behauptung, dass dies für alle wörtlichen Typen gilt.]

  • Damit eine Funktion für die Verwendung in konstanten Ausdrücken geeignet ist, muss sie explizit deklariert werden constexpr. es reicht nicht aus, nur die Kriterien für Konstantausdrucksfunktionen zu erfüllen. Beispiel:

    template<int N>
    class list
    { };
    
    constexpr int sqr1(int arg)
    { return arg * arg; }
    
    int sqr2(int arg)
    { return arg * arg; }
    
    int main()
    {
      const int X = 2;
      list<sqr1(X)> mylist1;  // OK: sqr1 is constexpr
      list<sqr2(X)> mylist2;  // wrong: sqr2 is not constexpr
    }

Wann kann / soll ich beide constund constexpr zusammen verwenden?

A. In Objektdeklarationen. Dies ist niemals erforderlich, wenn beide Schlüsselwörter auf dasselbe zu deklarierende Objekt verweisen. constexprimpliziert const.

constexpr const int N = 5;

ist das gleiche wie

constexpr int N = 5;

Beachten Sie jedoch, dass es Situationen geben kann, in denen sich die Schlüsselwörter jeweils auf verschiedene Teile der Deklaration beziehen:

static constexpr int N = 3;

int main()
{
  constexpr const int *NP = &N;
}

Hier NPwird als Adresskonstantenausdruck deklariert, dh als Zeiger, der selbst ein konstanter Ausdruck ist. (Dies ist möglich, wenn die Adresse durch Anwenden des Adressoperators auf einen statischen / globalen Konstantenausdruck generiert wird.) Hier sind beide constexprund consterforderlich: constexprbezieht sich immer auf den deklarierten Ausdruck (hier NP), während constauf int(er deklariert einen Zeiger-) to-const). Das Entfernen von constwürde den Ausdruck unzulässig machen (weil (a) ein Zeiger auf ein nicht konstantes Objekt kein konstanter Ausdruck sein kann und (b) &Ntatsächlich ein Zeiger auf eine Konstante ist).

B. In Mitgliedsfunktionsdeklarationen. In C ++ 11 constexprbedeutet dies const, dass dies in C ++ 14 und C ++ 17 nicht der Fall ist. Eine unter C ++ 11 deklarierte Mitgliedsfunktion als

constexpr void f();

muss deklariert werden als

constexpr void f() const;

unter C ++ 14, um weiterhin als constFunktion verwendbar zu sein .

jogojapan
quelle
3
IMO ist das "nicht unbedingt zur Kompilierungszeit ausgewertet" weniger hilfreich, als sie als "zur Kompilierungszeit ausgewertet" zu betrachten. Die Einschränkungen für einen konstanten Ausdruck bedeuten, dass es für einen Compiler relativ einfach wäre, ihn auszuwerten. Ein Compiler muss sich beschweren, wenn diese Einschränkungen nicht erfüllt sind. Da es keine Nebenwirkungen gibt, können Sie nie einen Unterschied feststellen, ob ein Compiler dies "bewertet" hat oder nicht.
Aschepler
10
@aschepler Sicher. Mein Hauptpunkt dort ist, dass, wenn Sie eine constexprFunktion für einen nicht konstanten Ausdruck aufrufen , z. B. eine gewöhnliche Variable, dies vollkommen legal ist und die Funktion wie jede andere Funktion verwendet wird. Es wird zur Kompilierungszeit nicht ausgewertet (da dies nicht möglich ist). Vielleicht denken Sie, dass dies offensichtlich ist - aber wenn ich feststelle, dass eine als deklarierte Funktion constexprimmer zur Kompilierungszeit ausgewertet wird, könnte sie falsch interpretiert werden.
Jogojapan
5
Ja, ich habe über constexprObjekte gesprochen, nicht über Funktionen. Ich stelle mir constexprObjekte gerne so vor, dass sie die Auswertung der Werte zur Kompilierungszeit erzwingen, und constexprFunktionen, bei denen die Funktion je nach Bedarf zur Kompilierungs- oder Laufzeit ausgewertet werden kann.
Aschepler
2
Eine Korrektur: 'const' ist nur eine Einschränkung, dass SIE den Wert einer Variablen nicht ändern können. Es gibt kein Versprechen, dass sich der Wert nicht ändert (dh von jemand anderem). Es ist eine Schreibeigenschaft, keine Leseeigenschaft.
Jared Grubb
3
Dieser Satz: Es gibt eine Garantie dafür, dass die Elementfunktion keines der nicht statischen Datenelemente ändert. vermisst ein wichtiges Detail. Als markiert gekennzeichnete Mitglieder mutablekönnen auch durch constMitgliederfunktionen geändert werden .
Omnifarious
119

constgilt für Variablen und verhindert , dass diese in Ihrem Code geändert werden.

constexprteilt dem Compiler mit, dass dieser Ausdruck zu einem Wert für die Kompilierungszeitkonstante führt , sodass er an Stellen wie Array-Längen, Zuweisen zu constVariablen usw. verwendet werden kann. Der von Oli angegebene Link enthält viele hervorragende Beispiele.

Grundsätzlich handelt es sich um zwei verschiedene Konzepte, die zusammen verwendet werden können (und sollten).

Karthik T.
quelle
2
const & constexpr Verwendung, zB: en.cppreference.com/w/cpp/container/array/get
Manohar Reddy Poreddy
64

Überblick

  • constgarantiert, dass ein Programm den Wert eines Objekts nicht ändert . Jedoch,const garantiert nicht , welche Art der Initialisierung das Objekt erfährt.

    Erwägen:

    const int mx = numeric_limits<int>::max();  // OK: runtime initialization

    Die Funktion gibt max()lediglich einen Literalwert zurück. Da der Initialisierer jedoch ein Funktionsaufruf ist, mxwird er zur Laufzeit initialisiert. Daher können Sie es nicht als konstanten Ausdruck verwenden :

    int arr[mx];  // error: “constant expression required”
  • constexprist ein neues C ++ 11-Schlüsselwort, mit dem Sie keine Makros und fest codierten Literale erstellen müssen. Unter bestimmten Bedingungen wird auch garantiert, dass Objekte einer statischen Initialisierung unterzogen werden . Es steuert die Auswertungszeit eines Ausdrucks. Durch die Durchsetzung der Kompilierung Zeitauswertung seines Ausdrucks , constexprkönnen Sie wahre definieren konstante Ausdrücke , die für zeitkritische Anwendungen von entscheidender Bedeutung sind, Systemprogrammierung, Vorlagen und allgemein gesprochen, in jedem Code, der auf der Kompilierung Zeitkonstanten beruht.

Funktionen mit konstantem Ausdruck

Eine Funktion mit konstantem Ausdruck ist eine deklarierte Funktion constexpr. Sein Body darf nicht virtuell sein und nur aus einer einzigen return-Anweisung bestehen, abgesehen von typedefs und statischen Asserts. Die Argumente und der Rückgabewert müssen Literaltypen haben. Es kann mit Argumenten ohne konstanten Ausdruck verwendet werden, aber wenn dies erledigt ist, ist das Ergebnis kein konstanter Ausdruck.

Eine Funktion mit konstantem Ausdruck soll Makros und fest codierte Literale ersetzen, ohne die Leistung oder die Typensicherheit zu beeinträchtigen .

constexpr int max() { return INT_MAX; }           // OK
constexpr long long_max() { return 2147483647; }  // OK
constexpr bool get_val()
{
    bool res = false;
    return res;
}  // error: body is not just a return statement

constexpr int square(int x)
{ return x * x; }  // OK: compile-time evaluation only if x is a constant expression
const int res = square(5);  // OK: compile-time evaluation of square(5)
int y = getval();
int n = square(y);          // OK: runtime evaluation of square(y)

Objekte mit konstantem Ausdruck

Ein Objekt mit konstantem Ausdruck ist ein deklariertes Objekt constexpr. Es muss mit einem konstanten Ausdruck oder einem Wert initialisiert werden, der von einem Konstruktor mit konstantem Ausdruck mit Argumenten mit konstantem Ausdruck erstellt wurde.

Ein Objekt mit konstantem Ausdruck verhält sich so, als ob es deklariert worden wäre const, außer dass es vor der Verwendung initialisiert werden muss und sein Initialisierer ein konstanter Ausdruck sein muss. Folglich kann ein Objekt mit konstantem Ausdruck immer als Teil eines anderen konstanten Ausdrucks verwendet werden.

struct S
{
    constexpr int two();      // constant-expression function
private:
    static constexpr int sz;  // constant-expression object
};
constexpr int S::sz = 256;
enum DataPacket
{
    Small = S::two(),  // error: S::two() called before it was defined
    Big = 1024
};
constexpr int S::two() { return sz*2; }
constexpr S s;
int arr[s.two()];  // OK: s.two() called after its definition

Konstruktoren mit konstanter Expression

Ein Konstruktor mit konstantem Ausdruck ist ein deklarierter Konstruktor constexpr. Es kann eine Mitgliederinitialisierungsliste haben, aber sein Text muss leer sein, abgesehen von typedefs und statischen Asserts. Seine Argumente müssen Literaltypen haben.

Ein Konstruktor mit konstantem Ausdruck ermöglicht es dem Compiler, das Objekt zur Kompilierungszeit zu initialisieren, vorausgesetzt, die Argumente des Konstruktors sind alle konstante Ausdrücke.

struct complex
{
    // constant-expression constructor
    constexpr complex(double r, double i) : re(r), im(i) { }  // OK: empty body
    // constant-expression functions
    constexpr double real() { return re; }
    constexpr double imag() { return im; }
private:
    double re;
    double im;
};
constexpr complex COMP(0.0, 1.0);         // creates a literal complex
double x = 1.0;
constexpr complex cx1(x, 0);              // error: x is not a constant expression
const complex cx2(x, 1);                  // OK: runtime initialization
constexpr double xx = COMP.real();        // OK: compile-time initialization
constexpr double imaglval = COMP.imag();  // OK: compile-time initialization
complex cx3(2, 4.6);                      // OK: runtime initialization

Tipps aus dem Buch Effective Modern C ++ von Scott Meyers über constexpr:

  • constexpr Objekte sind const und werden mit Werten initialisiert, die während der Kompilierung bekannt sind.
  • constexpr Funktionen erzeugen Ergebnisse zur Kompilierungszeit, wenn sie mit Argumenten aufgerufen werden, deren Werte während der Kompilierung bekannt sind.
  • constexprObjekte und Funktionen können in einem größeren Bereich von Kontexten verwendet werden als Nichtobjekte constexprund Funktionen.
  • constexpr ist Teil der Schnittstelle eines Objekts oder einer Funktion.

Quelle: Verwenden von constexpr zur Verbesserung von Sicherheit, Leistung und Kapselung in C ++ .

zangw
quelle
Vielen Dank für den großartigen Beispielcode, der die verschiedenen Situationen zeigt. So großartig einige der anderen Erklärungen auch sind, ich fand es viel nützlicher und verständlicher, den Code in Aktion zu sehen. Es hat mir wirklich geholfen, mein Verständnis für das, was vor sich geht, zu festigen.
RTHarston
35

Laut dem Buch "The C ++ Programming Language 4th Editon" von Bjarne Stroustrup
const : bedeutet ungefähr "Ich verspreche, diesen Wert nicht zu ändern" (§7.5). Dies wird hauptsächlich verwendet, um Schnittstellen anzugeben, damit Daten an Funktionen übergeben werden können, ohne befürchten zu müssen, dass sie geändert werden.
Der Compiler erzwingt das Versprechen von const.
constexpr : bedeutet ungefähr '' zur Kompilierungszeit auszuwerten '' (§10.4). Dies wird hauptsächlich verwendet, um Konstanten anzugeben, um
beispielsweise Folgendes zu ermöglichen :

const int dmv = 17; // dmv is a named constant
int var = 17; // var is not a constant
constexpr double max1 = 1.4*square(dmv); // OK if square(17) is a constant expression
constexpr double max2 = 1.4square(var); // error : var is not a constant expression
const double max3 = 1.4square(var); //OK, may be evaluated at run time
double sum(const vector<double>&); // sum will not modify its argument (§2.2.5)
vector<double> v {1.2, 3.4, 4.5}; // v is not a constant
const double s1 = sum(v); // OK: evaluated at run time
constexpr double s2 = sum(v); // error : sum(v) not constant expression

Damit eine Funktion in einem konstanten Ausdruck verwendet werden kann, dh in einem Ausdruck, der vom Compiler ausgewertet wird, muss sie constexpr definiert werden .
Zum Beispiel:

constexpr double square(double x) { return xx; }


Um constexpr zu sein, muss eine Funktion ziemlich einfach sein: nur eine return-Anweisung, die einen Wert berechnet. Eine constexpr-Funktion kann für nicht konstante Argumente verwendet werden. In diesem Fall ist das Ergebnis jedoch kein konstanter Ausdruck. Wir erlauben den Aufruf einer constexpr-Funktion mit Argumenten für nicht konstante Ausdrücke in Kontexten, die keine konstanten Ausdrücke erfordern, so dass wir nicht im Wesentlichen dieselbe Funktion zweimal definieren müssen: einmal für konstante Ausdrücke und einmal für Variablen.
An einigen Stellen sind konstante Ausdrücke für Sprachregeln (z. B. Array-Grenzen (§2.2.5, §7.3), Fallbezeichnungen (§2.2.4, §9.4.2), einige Vorlagenargumente (§25.2) und erforderlich Konstanten, die mit constexpr deklariert wurden). In anderen Fällen ist die Auswertung zur Kompilierungszeit wichtig für die Leistung. Unabhängig von Leistungsproblemen ist der Begriff der Unveränderlichkeit (eines Objekts mit unveränderlichem Zustand) ein wichtiges Designanliegen (§ 10.4).

Mustafa Ekici
quelle
Es gibt immer noch Leistungsprobleme. Scheint, dass die constexpr-Funktion, wenn sie zur Laufzeit ausgewertet wird, langsamer sein kann als die Nicht-constexpr-Version der Funktion. Auch wenn wir einen konstanten Wert haben, sollten wir "const" oder "constexpr" bevorzugen? (mehr eine Stil Frage generiert Assembly sieht gleich aus)
CoffeDeveloper
31

Beides constund constexprkann auf Variablen und Funktionen angewendet werden. Obwohl sie einander ähnlich sind, handelt es sich tatsächlich um sehr unterschiedliche Konzepte.

Beides constund constexprbedeuten, dass ihre Werte nach ihrer Initialisierung nicht mehr geändert werden können. Also zum Beispiel:

const int x1=10;
constexpr int x2=10;

x1=20; // ERROR. Variable 'x1' can't be changed.
x2=20; // ERROR. Variable 'x2' can't be changed.

Der Hauptunterschied zwischen constund constexprist der Zeitpunkt, zu dem ihre Initialisierungswerte bekannt sind (ausgewertet werden). Während die Werte von constVariablen sowohl zur Kompilierungszeit als auch zur Laufzeit constexprausgewertet werden können, werden sie immer zur Kompilierungszeit ausgewertet. Zum Beispiel:

int temp=rand(); // temp is generated by the the random generator at runtime.

const int x1=10; // OK - known at compile time.
const int x2=temp; // OK - known only at runtime.
constexpr int x3=10; // OK - known at compile time.
constexpr int x4=temp; // ERROR. Compiler can't figure out the value of 'temp' variable at compile time so `constexpr` can't be applied here.

Der Hauptvorteil zu wissen, ob der Wert zur Kompilierungszeit oder zur Laufzeit bekannt ist, ist die Tatsache, dass Kompilierungszeitkonstanten immer dann verwendet werden können, wenn Kompilierungszeitkonstanten benötigt werden. In C ++ können Sie beispielsweise keine C-Arrays mit variablen Längen angeben.

int temp=rand(); // temp is generated by the the random generator at runtime.

int array1[10]; // OK.
int array2[temp]; // ERROR.

Das heißt also:

const int size1=10; // OK - value known at compile time.
const int size2=temp; // OK - value known only at runtime.
constexpr int size3=10; // OK - value known at compile time.


int array3[size1]; // OK - size is known at compile time.
int array4[size2]; // ERROR - size is known only at runtime time.
int array5[size3]; // OK - size is known at compile time.

So constVariablen beide definieren kann Kompilierung Konstanten wie size1verwendet werden können , dass Feldgrößen und zu spezifizieren Laufzeitkonstanten wie size2nur zur Laufzeit, die bekannt ist und nicht verwendet werden können , Feldgrößen zu definieren. constexprDefinieren Sie andererseits immer Kompilierungszeitkonstanten, die Arraygrößen angeben können.

Beides constund constexprkann auch auf Funktionen angewendet werden. Eine constFunktion muss eine Elementfunktion (Methode, Operator) sein, bei der die Anwendung eines constSchlüsselworts bedeutet, dass die Methode die Werte ihrer Elementfelder (nicht statisch) nicht ändern kann. Zum Beispiel.

class test
{
   int x;

   void function1()
   {
      x=100; // OK.
   }

   void function2() const
   {
      x=100; // ERROR. The const methods can't change the values of object fields.
   }
};

A constexprist ein anderes Konzept. Es markiert eine Funktion (Mitglied oder Nichtmitglied) als die Funktion, die zur Kompilierungszeit ausgewertet werden kann, wenn Konstanten zur Kompilierungszeit als Argumente übergeben werden . Zum Beispiel können Sie dies schreiben.

constexpr int func_constexpr(int X, int Y)
{
    return(X*Y);
}

int func(int X, int Y)
{
    return(X*Y);
}

int array1[func_constexpr(10,20)]; // OK - func_constexpr() can be evaluated at compile time.
int array2[func(10,20)]; // ERROR - func() is not a constexpr function.

int array3[func_constexpr(10,rand())]; // ERROR - even though func_constexpr() is the 'constexpr' function, the expression 'constexpr(10,rand())' can't be evaluated at compile time.

Übrigens sind die constexprFunktionen die regulären C ++ - Funktionen, die aufgerufen werden können, auch wenn nicht konstante Argumente übergeben werden. In diesem Fall erhalten Sie jedoch die Nicht-Constexpr-Werte.

int value1=func_constexpr(10,rand()); // OK. value1 is non-constexpr value that is evaluated in runtime.
constexpr int value2=func_constexpr(10,rand()); // ERROR. value2 is constexpr and the expression func_constexpr(10,rand()) can't be evaluated at compile time.

Das constexprkann auch auf die Elementfunktionen (Methoden), Operatoren und sogar Konstruktoren angewendet werden. Zum Beispiel.

class test2
{
    static constexpr int function(int value)
    {
        return(value+1);
    }

    void f()
    {
        int x[function(10)];


    }
};

Eine "verrücktere" Probe.

class test3
{
    public:

    int value;

    // constexpr const method - can't chanage the values of object fields and can be evaluated at compile time.
    constexpr int getvalue() const
    {
        return(value);
    }

    constexpr test3(int Value)
        : value(Value)
    {
    }
};


constexpr test3 x(100); // OK. Constructor is constexpr.

int array[x.getvalue()]; // OK. x.getvalue() is constexpr and can be evaluated at compile time.
Timmy_A
quelle
Auch in C constexpr intexistiert, aber es ist geschriebenconst int
neugieriger Kerl
8

Wie bereits in @ 0x499602d2 erwähnt, wird constnur sichergestellt, dass ein Wert nach der Initialisierung nicht geändert werden kann, wenn as constexpr(eingeführt in C ++ 11) garantiert, dass die Variable eine Kompilierungszeitkonstante ist.
Betrachten Sie das folgende Beispiel (von LearnCpp.com):

cout << "Enter your age: ";
int age;
cin >> age;

const int myAge{age};        // works
constexpr int someAge{age};  // error: age can only be resolved at runtime
Lokesh Meher
quelle
5

A const int varkann zur Laufzeit dynamisch auf einen Wert gesetzt werden, und sobald es auf diesen Wert gesetzt ist, kann es nicht mehr geändert werden.

A constexpr int varkann nicht zur Laufzeit dynamisch festgelegt werden, sondern zur Kompilierungszeit. Sobald dieser Wert eingestellt ist, kann er nicht mehr geändert werden.

Hier ist ein gutes Beispiel:

int main(int argc, char*argv[]) {
    const int p = argc; 
    // p = 69; // cannot change p because it is a const
    // constexpr int q = argc; // cannot be, bcoz argc cannot be computed at compile time 
    constexpr int r = 2^3; // this works!
    // r = 42; // same as const too, it cannot be changed
}

Das obige Snippet lässt sich gut kompilieren und ich habe diejenigen auskommentiert, die einen Fehler verursachen.

Die Schlüsselbegriffe, die hier zur Kenntnis genommen werden müssen, sind die Begriffe von compile timeund run time. In C ++ wurden neue Innovationen eingeführt, mit denen ** know **bestimmte Dinge zur Kompilierungszeit so weit wie möglich verbessert werden sollen, um die Leistung zur Laufzeit zu verbessern.

trueadjustr
quelle
3

Ich glaube nicht, dass eine der Antworten wirklich klar macht, welche Nebenwirkungen es hat oder was es tatsächlich ist.

constexprund constim Namespace / Dateibereich sind identisch, wenn sie mit einem Literal oder Ausdruck initialisiert werden; Aber mit einer Funktion, constdie von jeder Funktion initialisiert werden kann, aber constexprvon einem Nicht-Constexpr (einer Funktion, die nicht mit Constexpr oder einem Nicht-Constexpr-Ausdruck markiert ist) initialisiert wird, wird ein Compilerfehler generiert. Beide constexprund constsind implizit interne Verknüpfungen für Variablen (tatsächlich überleben sie nicht, um die Verknüpfungsstufe zu erreichen, wenn sie -O1 und stärker kompilieren, und staticzwingen den Compiler nicht, ein internes (lokales) Linkersymbol für constoder constexprbei at auszugeben -O1 oder stärker, das einzige Mal , es dies tut , ist , wenn Sie die Adresse der variablen nehmen. constund constexprwird ein internes Symbol sein , es sei denn mit ausgedrückt externdhextern constexpr/const int i = 3;muss verwendet werden). Bei einer Funktion erreicht constexprdie Funktion dauerhaft nie die Verknüpfungsstufe (unabhängig von externoder inlinein der Definition oder -O0 oder -Ofast), wohingegen dies constniemals der Fall ist staticund inlinenur diese Auswirkung auf -O1 und höher hat. Wenn eine const/ constexpr-Variable von einer constexprFunktion initialisiert wird , wird die Last immer mit einem Optimierungsflag optimiert, aber niemals, wenn die Funktion nur staticoder inlineist oder wenn die Variable kein const/ ist constexpr.

Standardzusammenstellung (-O0)

#include<iostream>
constexpr int multiply (int x, int y)
{

  return x * y;
}

extern const int val = multiply(10,10);
int main () {
  std::cout << val;
} 

kompiliert zu

val:
        .long   100  //extra external definition supplied due to extern

main:
        push    rbp
        mov     rbp, rsp
        mov     esi, 100 //substituted in as an immediate
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        mov     eax, 0
        pop     rbp
        ret

__static_initialization_and_destruction_0(int, int):
        . 
        . 
        . 

jedoch

#include<iostream>
const int multiply (int x, int y)
{

  return x * y;
}

const int val = multiply(10,10); //constexpr is an error
int main () {
  std::cout << val;
}

Kompiliert zu

multiply(int, int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        mov     eax, DWORD PTR [rbp-4]
        imul    eax, DWORD PTR [rbp-8]
        pop     rbp
        ret

main:
        push    rbp
        mov     rbp, rsp
        mov     eax, DWORD PTR val[rip]
        mov     esi, eax
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        mov     eax, 0
        pop     rbp
        ret

__static_initialization_and_destruction_0(int, int):
        . 
        . 
        . 
        mov     esi, 10
        mov     edi, 10
        call    multiply(int, int)
        mov     DWORD PTR val[rip], eax

Dies zeigt deutlich, dass constexprdie Initialisierung der const/constexprDateibereichsvariablen zur Kompilierungszeit erfolgt und kein globales Symbol erzeugt, während bei Nichtverwendung die Initialisierung vor mainder Laufzeit erfolgt.

Kompilieren mit -Ofast

Auch -Ofast optimiert die Last nicht! https://godbolt.org/z/r-mhif , also brauchen Sie constexpr


constexprFunktionen können constexprfür das gleiche Ergebnis auch aus anderen Funktionen heraus aufgerufen werden . constexpron a function verhindert auch die Verwendung von Elementen, die zur Kompilierungszeit in der Funktion nicht ausgeführt werden können. Zum Beispiel ein Anruf beim <<Bediener auf std::cout.

constexprat block scope verhält sich insofern gleich, als es einen Fehler erzeugt, wenn es von einer Nicht-Constexpr-Funktion initialisiert wird. Der Wert wird ebenfalls sofort ersetzt.

Letztendlich ist sein Hauptzweck wie die Inline-Funktion von C, aber sie ist nur dann wirksam, wenn die Funktion zum Initialisieren von Variablen für den Dateibereich verwendet wird (welche Funktionen in C nicht möglich sind, in C ++ jedoch, da sie die dynamische Initialisierung von Datei- ermöglichen). Bereichsvariablen), außer dass die Funktion auch kein globales / lokales Symbol in den Linker exportieren kann, selbst wenn extern/staticSie dies inlineauf C verwenden könnten ; Zuweisungsfunktionen für Block-Scope-Variablen können einfach mithilfe einer -O1-Optimierung ohne constexprC und C ++ eingefügt werden .

Lewis Kelsey
quelle
Schöner Punkt auf dem Linker. Könnte es im Allgemeinen als sicherer angesehen werden, constexpr zu verwenden, da dies zu weniger Symbollecks führt?
Neil McGill
1
@NeilMcGill nicht wirklich, da Inline und Statik dazu führen, dass der Compiler kein lokales Symbol zum Multiplizieren ausgibt, wenn er mit -O1 oder höher kompiliert. Constexpr ist das einzige, das die Last für val optimiert, aber ansonsten ist es identisch damit, statische oder Inline-Werte vor die Funktion zu stellen. Ich habe noch etwas anderes vergessen. Constexpr ist das einzige Schlüsselwort, das kein Symbol für die Funktion auf -O0, statisch und inline do ausgibt
Lewis Kelsey
1

Zunächst einmal sind beide Qualifikationsmerkmale in c ++. Eine deklarierte Variable const muss initialisiert werden und kann in Zukunft nicht mehr geändert werden. Daher hat eine als const deklarierte Variable im Allgemeinen bereits vor dem Kompilieren einen Wert.

Aber für constexpr ist es ein bisschen anders.

Für constexpr können Sie einen Ausdruck angeben, der während der Kompilierung des Programms ausgewertet werden kann.

Offensichtlich kann die als constexper deklarierte Variable in Zukunft nicht mehr wie const geändert werden.

Subhash Malireddy
quelle