Wann sollten Sie die constexpr-Funktion in C ++ 11 verwenden?

337

Es scheint mir, dass eine "Funktion, die immer 5 zurückgibt" die Bedeutung von "Aufrufen einer Funktion" bricht oder verwässert. Es muss einen Grund oder eine Notwendigkeit für diese Funktion geben, sonst wäre es nicht in C ++ 11. Warum ist es dort?

// preprocessor.
#define MEANING_OF_LIFE 42

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }

Es scheint mir, dass wenn ich eine Funktion schreibe, die einen Literalwert zurückgibt, und ich zu einer Codeüberprüfung komme, mir jemand sagt, ich sollte dann einen konstanten Wert deklarieren, anstatt return 5 zu schreiben.

Warren P.
quelle
28
Können Sie eine rekursive Funktion definieren, die a zurückgibt constexpr? Wenn ja, kann ich eine Verwendung sehen.
am
20
Ich glaube, dass die Frage lauten sollte: "Warum ein neues Schlüsselwort (!) Einführen, wenn der Compiler selbst ableiten kann, ob eine Funktion in der Kompilierungszeit ausgewertet werden kann oder nicht?" Es klingt gut, wenn es "durch ein Schlüsselwort garantiert" ist, aber ich denke, ich würde es vorziehen, wenn es garantiert ist, wann immer es möglich ist, ohne dass ein Schlüsselwort erforderlich ist.
Kos
6
@Kos: Jemand, der mit C ++ - Interna besser vertraut ist, würde Ihre Frage wahrscheinlich bevorzugen, aber meine Frage stammt aus der Perspektive einer Person, die zuvor C-Code geschrieben hat, aber mit C ++ 2011-Schlüsselwörtern und den Details der C ++ - Compiler-Implementierung weder vertraut ist . Über Compileroptimierung und Konstantausdrucksableitung nachdenken zu können, ist ein Thema für eine fortgeschrittenere Benutzerfrage als diese.
Warren P
8
@Kos Ich habe in die gleiche Richtung gedacht wie Sie, und die Antwort, die ich gefunden habe, war, ohne zu wissen, wie würden Sie (leicht) wissen, dass der Compiler die Funktion tatsächlich für Sie kompiliert hat? Ich nehme an, Sie könnten die Assembly-Ausgabe überprüfen, um zu sehen, was sie getan hat, aber es ist einfacher, dem Compiler zu sagen, dass Sie diese Optimierung benötigen, und wenn dies aus irgendeinem Grund nicht für Sie möglich ist, erhalten Sie eine schöne Kompilierung. Fehler, anstatt stillschweigend nicht zu optimieren, wo Sie es erwartet haben.
Jeremy Friesner
3
@ Kos: Man könnte das Gleiche sagen const. In der Tat beauftragt Absicht ist nützlich ! Array-Dimensionen sind das kanonische Beispiel.
Leichtigkeitsrennen im Orbit

Antworten:

302

Angenommen, es macht etwas etwas komplizierter.

constexpr int MeaningOfLife ( int a, int b ) { return a * b; }

const int meaningOfLife = MeaningOfLife( 6, 7 );

Jetzt haben Sie etwas, das bis auf eine Konstante ausgewertet werden kann, während die Lesbarkeit erhalten bleibt und eine etwas komplexere Verarbeitung möglich ist, als nur eine Konstante auf eine Zahl zu setzen.

Grundsätzlich bietet es eine gute Hilfe für die Wartbarkeit, da klarer wird, was Sie tun. Nehmen Sie max( a, b )zum Beispiel:

template< typename Type > constexpr Type max( Type a, Type b ) { return a < b ? b : a; }

Dort ist es eine ziemlich einfache Wahl, aber es bedeutet, dass wenn Sie maxmit konstanten Werten aufrufen , diese explizit zur Kompilierungszeit und nicht zur Laufzeit berechnet wird.

Ein weiteres gutes Beispiel wäre eine DegreesToRadiansFunktion. Jeder findet Abschlüsse leichter zu lesen als Bogenmaß. Während Sie vielleicht wissen, dass 180 Grad im Bogenmaß sind, ist es viel klarer wie folgt geschrieben:

const float oneeighty = DegreesToRadians( 180.0f );

Viele gute Infos hier:

http://en.cppreference.com/w/cpp/language/constexpr

Goz
quelle
18
Hervorragender Punkt, der den Compiler auffordert, den Wert zur Kompilierungszeit zu berechnen. Ich bin gespannt, warum const diese Funktionalität nicht bietet, wenn bestimmte Optimierungen angegeben werden. Oder doch?
TamusJRoyce
11
@ Tamus: Oft wird es aber nicht dazu verpflichtet. constexpr verpflichtet den Compiler und spuckt einen Fehler aus, wenn dies nicht möglich ist.
Goz
20
Ich sehe es jetzt. Sünde (0,5) ist eine andere. Dies ersetzt C-Makros ordentlich.
Warren P
10
Ich kann dies als neue Interviewfrage sehen: Erklären Sie die Unterschiede zwischen dem Schlüsselwort const und constexpr.
Warren P
2
Um diesen Punkt für mich selbst zu dokumentieren, habe ich einen ähnlichen Code wie oben und wieder geschrieben, wobei die Funktion "const" und nicht "constexpr" war. Da ich Clang3.3, -pedantic-error und -std = c ++ 11 verwende, habe ich erwartet, dass letzteres nicht kompiliert wird. Es wurde wie im Fall "constexpr" kompiliert und ausgeführt. Nehmen Sie an, dass dies eine Clang-Erweiterung ist, oder wurde die C ++ 11-Spezifikation geändert, seit dieser Beitrag beantwortet wurde?
Arbalest
144

Einführung

constexprwurde nicht eingeführt, um der Implementierung mitzuteilen, dass etwas in einem Kontext bewertet werden kann, der einen konstanten Ausdruck erfordert ; Konforme Implementierungen konnten dies vor C ++ 11 beweisen.

Was eine Implementierung nicht beweisen kann, ist die Absicht eines bestimmten Codes:

  • Was möchte der Entwickler mit dieser Entität ausdrücken?
  • Sollten wir blind zulassen, dass Code in einem konstanten Ausdruck verwendet wird , nur weil er zufällig funktioniert?

Was wäre die Welt ohne constexpr?

Angenommen, Sie entwickeln eine Bibliothek und stellen fest, dass Sie die Summe jeder Ganzzahl im Intervall berechnen möchten (0,N].

int f (int n) {
  return n > 0 ? n + f (n-1) : n;
}

Der Mangel an Absicht

Ein Compiler kann leicht beweisen, dass die obige Funktion in einem Konstantenausdruck aufrufbar ist, wenn das übergebene Argument während der Übersetzung bekannt ist. Aber Sie haben dies nicht als Absicht deklariert - es war einfach so.

Jetzt kommt jemand anderes, liest Ihre Funktion und führt die gleiche Analyse durch wie der Compiler. " Oh, diese Funktion kann in einem konstanten Ausdruck verwendet werden!" und schreibt den folgenden Code.

T arr[f(10)]; // freakin' magic

Die Optimierung

Sie als "großartiger" Bibliotheksentwickler entscheiden, dass fdas Ergebnis beim Aufrufen zwischengespeichert werden soll. Wer möchte immer wieder die gleichen Werte berechnen?

int func (int n) { 
  static std::map<int, int> _cached;

  if (_cached.find (n) == _cached.end ()) 
    _cached[n] = n > 0 ? n + func (n-1) : n;

  return _cached[n];
}

Das Ergebnis

Durch die Einführung Ihrer albernen Optimierung haben Sie einfach jede Verwendung Ihrer Funktion unterbrochen, die sich zufällig in einem Kontext befand, in dem ein konstanter Ausdruck erforderlich war.

Sie haben nie versprochen, dass die Funktion in einem konstanten Ausdruck verwendet werden kann , und ohne sie constexprgäbe es keine Möglichkeit, ein solches Versprechen abzugeben.


Also, warum brauchen wir constexpr?

Die Hauptverwendung von constexpr besteht darin, die Absicht zu erklären .

Wenn eine Entität nicht als gekennzeichnet ist, sollte constexprsie niemals in einem konstanten Ausdruck verwendet werden . und selbst wenn dies der Fall ist, verlassen wir uns darauf, dass der Compiler einen solchen Kontext diagnostiziert (weil er unsere Absicht missachtet).

Filip Roséen - refp
quelle
25
Dies ist wahrscheinlich die richtige Antwort, da die jüngsten Änderungen in C ++ 14 und C ++ 17 die Verwendung eines viel größeren Bereichs der Sprache in constexprAusdrücken ermöglichen. Mit anderen Worten, so ziemlich alles kann mit Anmerkungen versehen werden constexpr(vielleicht verschwindet es eines Tages einfach?), Und wenn man kein Kriterium dafür hat, wann man es verwendet constexproder nicht, wird so ziemlich der gesamte Code als solcher geschrieben .
Alecov
4
@alecov Definitiv nicht alles ... I/O, syscallund dynamic memory allocationdefinitly cann't als markiert constexprAußerdem nicht alles sollte sein constexpr.
JiaHao Xu
1
@alecov Einige Funktionen sollen zur Laufzeit ausgeführt werden und sind zur Kompilierungszeit sinnlos.
JiaHao Xu
1
Diese Antwort gefällt mir auch am besten. Die Auswertung der Kompilierungszeit ist eine nette Optimierung, aber was Sie wirklich erhalten, constexprist eine Garantie für ein Verhalten. Genau wie const.
Tomáš Zato - Wiedereinsetzung Monica
Welcher Compiler erlaubt diese constexpr-freie Version von int f (int n) { return n > 0 ? n + f (n-1) : n;} T arr[f(10)]; Ich kann sie nirgendwo kompilieren lassen?
Jer
91

Nehmen Sie std::numeric_limits<T>::max(): aus welchem ​​Grund auch immer, dies ist eine Methode. constexprwäre hier von Vorteil.

Ein weiteres Beispiel: Sie möchten ein C-Array (oder a std::array) deklarieren , das so groß ist wie ein anderes Array. Der Weg, dies im Moment zu tun, ist wie folgt:

int x[10];
int y[sizeof x / sizeof x[0]];

Aber wäre es nicht besser schreiben zu können:

int y[size_of(x)];

Dank constexprkönnen Sie:

template <typename T, size_t N>
constexpr size_t size_of(T (&)[N]) {
    return N;
}
Konrad Rudolph
quelle
1
+1 für eine nette Vorlagenverwendung, aber ohne constexpr würde es genauso funktionieren, nicht wahr?
Kos
21
@Kos: Nein. Es würde einen Laufzeitwert zurückgeben. constexprzwingt den Complier, die Funktion einen Wert zur Kompilierungszeit zurückgeben zu lassen (falls möglich).
Deft_code
14
@Kos: Ohne das constexprkann es weder in einer Array-Größendeklaration noch als Vorlagenargument verwendet werden, unabhängig davon, ob das Ergebnis des Funktionsaufrufs eine Konstante zur Kompilierungszeit ist oder nicht. Diese beiden sind im Grunde die einzigen Anwendungsfälle, constexpraber zumindest der Anwendungsfall des Vorlagenarguments ist irgendwie wichtig.
Konrad Rudolph
2
"Aus welchem ​​Grund auch immer, dies ist eine Methode": Der Grund dafür ist, dass es in C ++ 03 nur Ganzzahlen zur Kompilierungszeit gibt, aber keine anderen Kompilierungszeittypen, sodass nur eine Methode für beliebige Typen vor C ++ 11 funktionieren kann.
Sebastian Mach
5
@LwCui Nein, es ist nicht „ok“: GCC ist in bestimmten Dingen standardmäßig nur lasch. Verwenden Sie die -pedanticOption und sie wird als Fehler gekennzeichnet.
Konrad Rudolph
19

constexprFunktionen sind wirklich nett und eine großartige Ergänzung zu C ++. Sie haben jedoch Recht, dass die meisten Probleme, die es löst, unelegant mit Makros umgangen werden können.

Eine der Verwendungen von constexprhat jedoch keine C ++ 03-äquivalenten typisierten Konstanten.

// This is bad for obvious reasons.
#define ONE 1;

// This works most of the time but isn't fully typed.
enum { TWO = 2 };

// This doesn't compile
enum { pi = 3.1415f };

// This is a file local lvalue masquerading as a global
// rvalue.  It works most of the time.  But May subtly break
// with static initialization order issues, eg pi = 0 for some files.
static const float pi = 3.1415f;

// This is a true constant rvalue
constexpr float pi = 3.1415f;

// Haven't you always wanted to do this?
// constexpr std::string awesome = "oh yeah!!!";
// UPDATE: sadly std::string lacks a constexpr ctor

struct A
{
   static const int four = 4;
   static const int five = 5;
   constexpr int six = 6;
};

int main()
{
   &A::four; // linker error
   &A::six; // compiler error

   // EXTREMELY subtle linker error
   int i = rand()? A::four: A::five;
   // It not safe use static const class variables with the ternary operator!
}

//Adding this to any cpp file would fix the linker error.
//int A::four;
//int A::six;
deft_code
quelle
12
Könnten Sie bitte klarstellen, dass "EXTREM subtiler Linker-Fehler"? Oder zumindest einen Hinweis auf eine Klarstellung geben?
Enobayram
4
@enobayram, Der ternäre Operator übernimmt die Adresse der Operanden. Das geht aus dem Code nicht hervor. Alles wird gut kompiliert, aber der Link schlägt fehl, weil die Adresse von fournicht aufgelöst wird. Ich musste wirklich graben, um herauszufinden, wer die Adresse meiner static constVariablen nahm.
Deft_code
23
"Das ist aus offensichtlichen Gründen schlecht": Der offensichtlichste Grund ist das Semikolon, oder?
TonyK
4
Der "EXTREM subtile Linker-Fehler" hat mich völlig verwirrt. Weder fournoch fiveim Geltungsbereich.
Steven Lu
3
Siehe auch den neuen enum classTyp, der einige der Enum-Probleme behebt.
NinMonkey
14

Nach dem, was ich gelesen habe, ergibt sich die Notwendigkeit von constexpr aus einem Problem bei der Metaprogrammierung. Für Merkmalsklassen können Konstanten als Funktionen dargestellt werden. Denken Sie daran: numeric_limits :: max (). Mit constexpr können diese Arten von Funktionen in der Metaprogrammierung oder als Array-Grenzen usw. usw. verwendet werden.

Ein weiteres Beispiel wäre, dass Sie für Klassenschnittstellen möglicherweise möchten, dass abgeleitete Typen für einige Operationen ihre eigenen Konstanten definieren.

Bearbeiten:

Nach Stossen auf SO herum, sieht es aus wie andere kommen mit haben einige Beispiele von dem, was mit constexprs möglich sein könnte.

Luke
quelle
"Um Teil einer Schnittstelle zu sein, muss man eine Funktion sein"?
Daniel Earwicker
Jetzt, da ich die Nützlichkeit davon sehen kann, bin ich etwas aufgeregter über C ++ 0x. Es scheint eine gut durchdachte Sache zu sein. Ich wusste, dass sie es sein müssen. Diese Sprachstandard-Überfreaks machen selten zufällige Dinge.
Warren P
Ich bin viel aufgeregter über Lambdas, das Threading-Modell, initializer_list, rvalue-Referenzen, verschiedene Vorlagen, die neuen Bindungsüberladungen ... es gibt einiges, worauf man sich freuen kann.
Luke
1
Oh ja, aber ich verstehe Lambdas / Verschlüsse bereits in mehreren anderen Sprachen. constexprist insbesondere in einem Compiler mit einem leistungsstarken Bewertungssystem für Ausdrücke zur Kompilierungszeit nützlich. C ++ hat wirklich keine Peers in dieser Domäne. (das ist ein starkes Lob für C ++ 11, IMHO)
Warren P
11

Aus Stroustrups Rede bei "Going Native 2012":

template<int M, int K, int S> struct Unit { // a unit in the MKS system
       enum { m=M, kg=K, s=S };
};

template<typename Unit> // a magnitude with a unit 
struct Value {
       double val;   // the magnitude 
       explicit Value(double d) : val(d) {} // construct a Value from a double 
};

using Speed = Value<Unit<1,0,-1>>;  // meters/second type
using Acceleration = Value<Unit<1,0,-2>>;  // meters/second/second type
using Second = Unit<0,0,1>;  // unit: sec
using Second2 = Unit<0,0,2>; // unit: second*second 

constexpr Value<Second> operator"" s(long double d)
   // a f-p literal suffixed by ‘s’
{
  return Value<Second> (d);  
}   

constexpr Value<Second2> operator"" s2(long double d)
  // a f-p literal  suffixed by ‘s2’ 
{
  return Value<Second2> (d); 
}

Speed sp1 = 100m/9.8s; // very fast for a human 
Speed sp2 = 100m/9.8s2; // error (m/s2 is acceleration)  
Speed sp3 = 100/9.8s; // error (speed is m/s and 100 has no unit) 
Acceleration acc = sp1/0.5s; // too fast for a human

quelle
2
Dieses Beispiel finden Sie auch in Stroustrups Artikel Software Development for Infrastructure .
Matthieu Poullet
clang-3.3: Fehler: Der Rückgabetyp 'Wert <Sekunde>' der constexpr-Funktion ist kein Literaltyp
Mitja
Das ist schön, aber wer setzt Literale in Code wie diesen. Wenn Sie einen interaktiven Taschenrechner schreiben, ist es sinnvoll, wenn Ihr Compiler "Ihre Einheiten überprüft".
Bobobobo
5
@ Bobobobo oder wenn Sie Navigationssoftware für den Mars Climate Orbiter schreiben, vielleicht :)
Jeremy Friesner
1
So kompilieren Sie es: 1. Verwenden Sie in den Literal-Suffixen einen Unterstrich. 2. Fügen Sie den Operator "" _m für 100_m hinzu. 3. Verwenden Sie 100.0_m oder fügen Sie eine Überladung hinzu, die unsigned long long akzeptiert. 4. Deklarieren Sie den Wertekonstruktor constexpr. 5. Fügen Sie der Wertklasse den entsprechenden Operator / wie folgt hinzu: constexpr auto operator / (const Wert <Y> & andere) const {Rückgabewert <Einheit <TheUnit :: m - Wert <Y> :: TheUnit :: m, TheUnit :: kg - Wert <Y> :: TheUnit :: kg, TheUnit :: s - Wert <Y> :: TheUnit :: s >> (val / other.val); }. Wobei TheUnit für Unit typedef ist, das innerhalb der Value-Klasse hinzugefügt wurde.
0kcats
8

Eine andere Verwendung (noch nicht erwähnt) sind constexprKonstruktoren. Auf diese Weise können Konstanten für die Kompilierungszeit erstellt werden, die zur Laufzeit nicht initialisiert werden müssen.

const std::complex<double> meaning_of_imagination(0, 42); 

Wenn Sie dies mit benutzerdefinierten Literalen kombinieren, haben Sie volle Unterstützung für benutzerdefinierte Literalklassen.

3.14D + 42_i;
Motti
quelle
6

Früher gab es ein Muster mit Metaprogrammierung:

template<unsigned T>
struct Fact {
    enum Enum {
        VALUE = Fact<T-1>*T;
    };
};

template<>
struct Fact<1u> {
    enum Enum {
        VALUE = 1;
    };
};

// Fact<10>::VALUE is known be a compile-time constant

Ich glaube, es constexprwurde eingeführt, damit Sie solche Konstrukte schreiben können, ohne Vorlagen und seltsame Konstrukte mit Spezialisierung, SFINAE und anderen Dingen zu benötigen - aber genau so, als würden Sie eine Laufzeitfunktion schreiben, aber mit der Garantie, dass das Ergebnis beim Kompilieren ermittelt wird -Zeit.

Beachten Sie jedoch Folgendes:

int fact(unsigned n) {
    if (n==1) return 1;
    return fact(n-1)*n;
}

int main() {
    return fact(10);
}

Kompilieren Sie dies mit g++ -O3und Sie werden sehen, dass dies fact(10)tatsächlich zur Kompilierungszeit ausgewertet wird!

Ein VLA-fähiger Compiler (also ein C-Compiler im C99-Modus oder ein C ++ - Compiler mit C99-Erweiterungen) kann Ihnen sogar Folgendes ermöglichen:

int main() {
    int tab[fact(10)];
    int tab2[std::max(20,30)];
}

Aber dass es sich im Moment nicht um Standard-C ++ handelt, constexprscheint eine Möglichkeit zu sein, dies zu bekämpfen (im obigen Fall auch ohne VLA). Und es gibt immer noch das Problem, dass "formale" konstante Ausdrücke als Vorlagenargumente erforderlich sind.

Kos
quelle
Die Faktfunktion wird zur Kompilierungszeit nicht ausgewertet. Es muss constexpr sein und nur eine return-Anweisung haben.
Sumant
1
@Sumant: Sie haben Recht, dass es nicht zur Kompilierungszeit ausgewertet werden muss, aber es ist! Ich bezog mich auf das, was wirklich in Compilern passiert. Kompilieren Sie es auf dem letzten GCC, sehen Sie sich das Ergebnis an und prüfen Sie selbst, ob Sie mir nicht glauben!
Kos
Versuchen Sie hinzuzufügen, std::array<int, fact(2)>und Sie werden sehen, dass fact () beim Kompilieren nicht ausgewertet wird. Es ist nur der GCC-Optimierer, der gute Arbeit leistet.
1
Das habe ich gesagt ... bin ich wirklich so unklar? Siehe den letzten Absatz
Kos
5

Ich habe gerade angefangen, ein Projekt auf c ++ 11 umzustellen, und bin auf eine vollkommen gute Situation für constexpr gestoßen, die alternative Methoden zur Ausführung derselben Operation bereinigt. Der entscheidende Punkt hierbei ist, dass Sie die Funktion nur dann in die Arraygrößendeklaration einfügen können, wenn sie als constexpr deklariert ist. Es gibt eine Reihe von Situationen, in denen ich sehe, dass dies in Bezug auf den Codebereich, an dem ich beteiligt bin, sehr nützlich ist.

constexpr size_t GetMaxIPV4StringLength()
{
    return ( sizeof( "255.255.255.255" ) );
}

void SomeIPFunction()
{
    char szIPAddress[ GetMaxIPV4StringLength() ];
    SomeIPGetFunction( szIPAddress );
}
jgibbs
quelle
4
Dies könnte ebenso geschrieben werden: const size_t MaxIPV4StringLength = sizeof ("255.255.255.255");
Superfly Jon
static inline constexpr const autowahrscheinlich ist besser.
JiaHao Xu
3

Alle anderen Antworten sind großartig. Ich möchte nur ein cooles Beispiel für eine Sache geben, die Sie mit constexpr machen können, die erstaunlich ist. See-Phit ( https://github.com/rep-movsd/see-phit/blob/master/seephit.h ) ist eine HTML -Parser- und Vorlagen-Engine zur Kompilierungszeit. Dies bedeutet, dass Sie HTML einfügen und einen Baum herausholen können, der bearbeitet werden kann. Wenn Sie das Parsen zur Kompilierungszeit durchführen, erhalten Sie zusätzliche Leistung.

Aus dem Beispiel der Github-Seite:

#include <iostream>
#include "seephit.h"
using namespace std;



int main()
{
  constexpr auto parser =
    R"*(
    <span >
    <p  color="red" height='10' >{{name}} is a {{profession}} in {{city}}</p  >
    </span>
    )*"_html;

  spt::tree spt_tree(parser);

  spt::template_dict dct;
  dct["name"] = "Mary";
  dct["profession"] = "doctor";
  dct["city"] = "London";

  spt_tree.root.render(cerr, dct);
  cerr << endl;

  dct["city"] = "New York";
  dct["name"] = "John";
  dct["profession"] = "janitor";

  spt_tree.root.render(cerr, dct);
  cerr << endl;
}
Halcyon
quelle
1

Ihr grundlegendes Beispiel dient demselben Argument wie das der Konstanten selbst. Warum verwenden

static const int x = 5;
int arr[x];

Über

int arr[5];

Weil es viel wartbarer ist. Die Verwendung von constexpr ist viel, viel schneller zu schreiben und zu lesen als die vorhandenen Metaprogrammiertechniken.

Hündchen
quelle
0

Es kann einige neue Optimierungen ermöglichen. constTraditionell ist ein Hinweis für das Typsystem, und nicht für die Optimierung verwendet werden kann (zB ein constMember - Funktion kann const_castund das Objekt ohnehin ändern, rechtlich, so constkann nicht für die Optimierung vertraut werden).

constexprbedeutet, dass der Ausdruck wirklich konstant ist, vorausgesetzt, die Eingaben für die Funktion sind const. Erwägen:

class MyInterface {
public:
    int GetNumber() const = 0;
};

Wenn dies in einem anderen Modul verfügbar gemacht wird, kann der Compiler nicht darauf vertrauen, dass GetNumber()bei jedem Aufruf keine anderen Werte zurückgegeben werden - auch nicht nacheinander ohne dazwischen liegende Nicht-Konstanten-Aufrufe -, da constdies in der Implementierung möglicherweise verworfen wurde. (Natürlich sollte jeder Programmierer, der dies getan hat, erschossen werden, aber die Sprache erlaubt es, daher muss der Compiler die Regeln einhalten.)

Hinzufügen constexpr:

class MyInterface {
public:
    constexpr int GetNumber() const = 0;
};

Der Compiler kann jetzt eine Optimierung anwenden, bei der der Rückgabewert von GetNumber()zwischengespeichert wird, und zusätzliche Aufrufe von eliminieren GetNumber(), da dies constexpreine stärkere Garantie dafür ist, dass sich der Rückgabewert nicht ändert.

AshleysBrain
quelle
Tatsächlich const kann in der Optimierung verwendet werden ... Es ist nicht definiertes Verhalten Wert ändern const definierte auch nach einem const_castIIRC. Ich würde erwarten, dass es für constMitgliedsfunktionen konsistent ist, aber ich müsste das mit dem Standard überprüfen. Dies würde bedeuten, dass der Compiler dort sicher Optimierungen vornehmen kann.
Kos
1
@ Warren: Es ist egal, ob die Optimierung tatsächlich durchgeführt wird, es ist nur erlaubt. @Kos: Es ist eine wenig bekannte Subtilität, dass, wenn das ursprüngliche Objekt nicht als const ( int xvs. const int x) deklariert wurde , es sicher geändert werden kann, indem const_castconst auf einem Zeiger / Verweis darauf entfernt wird. Andernfalls const_castwürde immer undefiniertes Verhalten aufgerufen und wäre nutzlos :) In diesem Fall hat der Compiler keine Informationen über die Konstanz des ursprünglichen Objekts, sodass er dies nicht erkennen kann.
AshleysBrain
@Kos Ich glaube nicht, dass const_cast das einzige Problem hier ist. Die const-Methode darf eine globale Variable lesen und sogar ändern. Umgekehrt könnte jemand aus einem anderen Thread auch das const-Objekt zwischen den Aufrufen ändern.
Enobayram
1
Das "= 0" ist hier nicht gültig und sollte entfernt werden. Ich würde es selbst tun, aber ich bin nicht sicher, ob dies mit dem SO-Protokoll übereinstimmt.
KnowItAllWannabe
Beide Beispiele sind ungültig: Das erste ( int GetNumber() const = 0;) sollte die GetNumber()Methode als virtuell deklarieren . Das zweite ( constexpr int GetNumber() const = 0;) ist nicht gültig, da der reine Spezifizierer ( = 0) impliziert, dass die Methode virtuell ist, aber constexprs dürfen nicht virtuell sein (ref: en.cppreference.com/w/cpp/language/constexpr )
stj
-1

Wann zu verwenden constexpr:

  1. wann immer es eine Kompilierungszeitkonstante gibt.
BreakBadSP
quelle
Obwohl ich Ihnen zustimme, erklärt diese Antwort nicht, warum constexpr Präprozessor-Makros oder bevorzugt werden sollten const.
Sneftel
-3

Es ist nützlich für so etwas

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }

int some_arr[MeaningOfLife()];

Binden Sie dies mit einer Merkmalsklasse oder ähnlichem zusammen und es wird ziemlich nützlich.

plivesey
quelle
4
In Ihrem Beispiel bietet es keinen Vorteil gegenüber einer einfachen Konstante, sodass die Frage nicht wirklich beantwortet wird.
Jalf
Dies ist ein erfundenes Beispiel. Stellen Sie sich vor, MeaningOfLife () bezieht seinen Wert von einer anderen Stelle, z. B. einer anderen Funktion oder einem #define oder einer Reihe davon. Möglicherweise wissen Sie nicht, was zurückgegeben wird. Möglicherweise handelt es sich um Bibliothekscode. Stellen Sie sich in anderen Beispielen einen unveränderlichen Container mit der Methode constexpr size () vor. Sie können jetzt int arr [container.size ()] ausführen.
Plivesey
2
@plivesey können Sie bitte Ihre Antwort mit einem besseren Beispiel als bearbeiten.
Mukesh