it-swarm.com.de

Unterschied zwischen "constexpr" und "const"

Was ist der Unterschied zwischen constexpr und const?

  • Wann kann ich nur einen von ihnen verwenden?
  • Wann kann ich beides verwenden und wie soll ich eines auswählen?
511
MBZ

Grundbedeutung und Syntax

Beide Schlüsselwörter können sowohl in der Deklaration von Objekten als auch in Funktionen verwendet werden. Der grundlegende Unterschied, wenn er auf Objekte angewendet wird, ist folgender:

  • const deklariert ein Objekt als Konstante. Dies impliziert die 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.

  • constexpr deklariert ein Objekt als verwendbar für das, was der Standard aufruft konstante Ausdrücke. Beachten Sie jedoch, dass constexpr nicht die einzige Möglichkeit ist, dies zu tun.

Bei Anwendung auf Funktionen ist der grundlegende Unterschied:

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

  • constexpr kann sowohl mit Member- als auch mit Non-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 (7.1.5/3,4) erfüllt, was am wichtigsten ist (†):

    • Der Funktionskörper muss nicht virtuell und extrem einfach sein: Abgesehen von typedefs und statischen Zusicherungen ist nur eine einzelne return -Anweisung zulässig. Im Fall eines Konstruktors sind nur eine Initialisierungsliste, Typedefs und statische Zusicherungen zulässig. (= default und = delete sind jedoch ebenfalls zulässig.)
    • Ab C++ 14 sind die Regeln entspannter, was seitdem in einer constexpr-Funktion zulässig ist: asm Deklaration, goto Anweisung, eine Anweisung mit einer anderen Bezeichnung als case und default, try-block, Definition einer nicht-literalen Variablen, Definition einer statischen Variablen oder einer Thread-Speicherdauer, Definition einer Variablen, für die keine Initialisierung durchgeführt wird.
    • Die Argumente und der Rückgabetyp müssen Literaltypen sein (d. H. Im Allgemeinen sehr einfache Typen, typischerweise Skalare oder Aggregate).

Konstante Ausdrücke

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

  • Es kann an Stellen verwendet werden, die eine Auswertung zur Kompilierungszeit erfordern, z. B. Vorlagenparameter und Arraygröß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 als constexpr garantiert nicht unbedingt, dass es zur Kompilierungszeit ausgewertet wird. Es kann verwendet werden für solche, aber es kann auch an anderen Orten verwendet werden, die zur Laufzeit ausgewertet werden.

    • Ein Objekt kann für die Verwendung in konstanten Ausdrücken geeignet sein ohne als constexpr deklariert zu sein. Beispiel:

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

    Dies ist möglich, weil N, das zum Zeitpunkt der Deklaration mit einem Literal konstant und initialisiert ist, die Kriterien für einen konstanten Ausdruck erfüllt, auch wenn es nicht als constexpr deklariert ist.

Also, wann muss ich tatsächlich constexpr verwenden?

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

    • const
    • vom Integral- oder Aufzählungstyp nd
    • 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 Typ Integral oder Aufzählung […]". Vielen Dank an Richard Smith für die Korrektur von my frühere Behauptung, dass dies für alle wörtlichen Typen zutraf.]

  • Damit eine Funktion für die Verwendung in konstanten Ausdrücken geeignet ist, muss sie muss explizit als constexpr deklariert werden. es reicht nicht aus, nur die Kriterien für Funktionen des konstanten Ausdrucks 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 verwenden, const und constexprzusammen?

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

constexpr const int N = 5;

ist das gleiche wie

constexpr int N = 5;

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

static constexpr int N = 3;

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

Hier wird NP als Adresskonstantenausdruck deklariert, d. H. Als Zeiger, der selbst ein konstanter Ausdruck ist. (Dies ist möglich, wenn die Adresse durch Anwenden des Adressoperators auf einen statischen/globalen konstanten Ausdruck generiert wird.) Hier sind sowohl constexpr als auch const erforderlich: constexpr bezieht sich immer auf den Ausdruck wird deklariert (hier NP), während const auf int verweist (es deklariert einen Zeiger auf const). Das Entfernen von const würde den Ausdruck ungültig machen (da (a) ein Zeiger auf ein Nicht-Konstanten-Objekt kein konstanter Ausdruck sein kann und (b) &N tatsächlich ein Zeiger auf eine Konstante ist ).

B. In Member-Funktionsdeklarationen. In C++ 11 impliziert constexprconst, während dies in C++ 14 und C++ 17 nicht der Fall ist. Eine Member-Funktion, die unter C++ 11 als deklariert wurde

constexpr void f();

muss als deklariert werden

constexpr void f() const;

unter C++ 14, um weiterhin als const -Funktion verwendbar zu sein.

511
jogojapan

const gilt für Variablen und verhindert, dass sie in Ihrem Code geändert werden .

constexpr teilt dem Compiler mit, dass dieser Ausdruck zu einem Wert für die Kompilierzeitkonstante führt, sodass er an bestimmten Stellen verwendet werden kann wie Arraylängen, Zuweisen zu const Variablen usw. Das link von Oli hat viele hervorragende Beispiele.

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

105
Karthik T

Überblick

  • const garantiert, dass ein Programm den Wert eines Objekts nicht ändert . const garantiert jedoch nicht, welche Art von Initialisierung das Objekt durchläuft.

    Erwägen:

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

    Die Funktion max() liefert lediglich einen Literalwert. Da der Initialisierer jedoch ein Funktionsaufruf ist, wird mx zur Laufzeit initialisiert. Daher können Sie ihn nicht als konstanten Ausdruck verwenden:

    int arr[mx];  // error: “constant expression required”
    
  • constexpr ist ein neues C++ 11-Schlüsselwort, mit dem Sie keine Makros und hartcodierten Literale mehr erstellen müssen. Außerdem wird unter bestimmten Bedingungen garantiert, dass Objekte einer statischen Initialisierung unterzogen werden . Es steuert die Auswertungszeit eines Ausdrucks. Durch Erzwingen der Kompilierzeitauswertung seines Ausdrucks können Sie mit constexpr wahre konstante Ausdrücke definieren , die für zeitkritische Anwendungen, Systemprogrammierung, Vorlagen und generell für jeden Code, der auf Konstanten für die Kompilierungszeit beruht, von entscheidender Bedeutung sind.

Funktionen für konstante Ausdrücke

Eine Konstantenausdrucksfunktion ist eine als constexpr deklarierte Funktion. Sein Hauptteil darf nicht virtuell sein und nur aus einer einzigen return-Anweisung bestehen, abgesehen von typedefs und statischen Zusicherungen. Die Argumente und der Rückgabewert müssen Literaltypen haben. Es kann mit Argumenten verwendet werden, die keine konstanten Ausdrücke sind. In diesem Fall ist das Ergebnis jedoch 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 Konstantenausdruck-Objekt ist ein Objekt, das als constexpr deklariert wurde. Es muss mit einem konstanten Ausdruck oder einem Wert initialisiert werden, der von einem Konstruktor für konstante Ausdrücke mit Argumenten für konstante Ausdrücke erstellt wurde.

Ein Objekt mit konstantem Ausdruck verhält sich so, als ob es als const deklariert worden wäre, 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 konstantem Ausdruck

Ein Konstruktor mit konstantem Ausdruck ist ein Konstruktor, der als constexpr deklariert ist. Es kann eine Elementinitialisierungsliste haben, aber sein Hauptteil muss leer sein, abgesehen von typedefs und statischen Zusicherungen. Die Argumente müssen wörtliche Typen haben.

Mit einem Konstruktor für konstante Ausdrücke kann der Compiler das Objekt zur Kompilierungszeit 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.
  • constexpr -Objekte und -Funktionen können in einem breiteren Bereich von Kontexten verwendet werden als nicht-constexpr -Objekte und -Funktionen;
  • constexpr ist Teil der Schnittstelle eines Objekts oder einer Funktion.

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

58
zangw

Laut Buch von "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 zur Angabe von Schnittstellen verwendet, damit Daten an Funktionen übergeben werden können, ohne dass Änderungen befürchtet werden.
Der Compiler setzt das Versprechen von const.
constexpr: Bedeutet ungefähr "zur Kompilierungszeit auszuwerten" (§10.4). Dies wird hauptsächlich verwendet, um Konstanten anzugeben, um zuzulassen
Zum Beispiel:

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.4∗square(var); // error : var is not a constant expression
const double max3 = 1.4∗square(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 constexpr definiert werden.
Zum Beispiel:

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


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. In Kontexten, in denen keine konstanten Ausdrücke erforderlich sind, können wir eine constexpr-Funktion mit nicht konstanten Ausdrucksargumenten aufrufen, damit wir nicht im Wesentlichen dieselbe Funktion zweimal definieren müssen: einmal für konstante Ausdrücke und einmal für Variablen.
An einigen Stellen werden konstante Ausdrücke durch Sprachregeln (z. B. Array-Grenzen (§2.2.5, §7.3), Fallbezeichnungen (§2.2.4, §9.4.2), einige Vorlagenargumente ( §25.2) und mit constexpr) deklarierte Konstanten. In anderen Fällen ist die Auswertung zur Kompilierungszeit wichtig für die Leistung. Unabhängig von Leistungsaspekten ist der Begriff der Unveränderlichkeit (eines Objekts mit einem unveränderlichen Zustand) ein wichtiges Konstruktionsproblem (§10.4).

29
Mustafa Ekici

Sowohl const als auch constexpr können auf Variablen und Funktionen angewendet werden. Auch wenn sie einander ähnlich sind, handelt es sich tatsächlich um sehr unterschiedliche Konzepte.

Sowohl const als auch constexpr bedeuten, 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 const und constexpr ist der Zeitpunkt, zu dem die Initialisierungswerte bekannt sind (ausgewertet). Während die Werte von const Variablen sowohl zur Kompilierungszeit als auch zur Laufzeit ausgewertet werden können, werden constexpr 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.

const Variablen können also beide definieren zeitkonstanten kompilieren wie size1, mit dem Arraygrößen und angegeben werden können laufzeitkonstanten wie size2, die nur zur Laufzeit bekannt sind und nicht zum Definieren von Array-Größen verwendet werden können. Auf der anderen Seite definieren constexpr immer Kompilierungszeitkonstanten, mit denen Array-Größen angegeben werden können.

Sowohl const als auch constexpr können auch auf Funktionen angewendet werden. Eine const -Funktion muss eine Mitgliedsfunktion (Methode, Operator) sein, wobei die Anwendung des Schlüsselworts const bedeutet, dass die Methode die Werte ihrer Mitgliedsfelder (nicht statische Felder) 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.
   }
};

Ein constexpr ist ein anderes Konzept. Es markiert eine Funktion (Mitglied oder Nichtmitglied) als die Funktion, die beim Kompilieren ausgewertet werden kann wenn Kompilierzeitkonstanten 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 constexpr -Funktionen die regulären C++ - Funktionen, die auch dann aufgerufen werden können, 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.

constexpr kann auch auf Member-Funktionen (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 eher "verrückte" 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.
24
Timmy_A

Wie bereits in @ 0x499602d2 erwähnt, stellt const nur sicher, dass ein Wert nach der Initialisierung nicht geändert werden kann, wobei constexpr (eingeführt in C++ 11) sicherstellt, 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
7
Lokesh Meher

Ein const int var kann zur Laufzeit dynamisch auf einen Wert gesetzt werden, und sobald er auf diesen Wert gesetzt ist, kann er nicht mehr geändert werden.

Ein constexpr int var kann nicht zur Laufzeit dynamisch gesetzt werden, sondern zur Kompilierzeit. 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 problemlos kompilieren, und ich habe diejenigen auskommentiert, die einen Fehler verursachen.

3
typelogic

Zunächst einmal sind beide Qualifikationsmerkmale in c ++. Eine als const deklarierte Variable 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.

0