Różnica między „constexpr” i „const”

593

Jaka jest różnica między constexpri const?

  • Kiedy można używać tylko jednego z nich?
  • Kiedy mogę korzystać z obu tych opcji i jak wybrać jedną z nich?
MBZ
źródło
71
constexprtworzy stałą czasową kompilacji; constoznacza po prostu, że wartości nie można zmienić.
0x499602D2
Być może ten artykuł z boost/hanabiblioteki może ujawnić niektóre constexprkwestie, w których można używać, constexpra gdzie nie: boost.org/doc/libs/1_69_0/libs/hana/doc/html/…
Andry
@ 0x499602D2 „ oznacza po prostu, że wartości nie można zmienić ” W przypadku skalara zainicjowanego literałem wartość, której nie można zmienić, jest również stałą czasową kompilacji.
ciekawy
@curiousguy Tak, mój komentarz był bardzo uproszczony. Trzeba przyznać, że constexprwtedy też byłem nowy :)
0x499602D2

Odpowiedzi:

587

Podstawowe znaczenie i składnia

Oba słowa kluczowe mogą być używane w deklaracji obiektów oraz funkcji. Podstawowa różnica po zastosowaniu do obiektów jest następująca:

  • constdeklaruje obiekt jako stały . Oznacza to gwarancję, że po zainicjowaniu wartość tego obiektu nie zmieni się, a kompilator może wykorzystać ten fakt do optymalizacji. Pomaga również zapobiegać programistę od konieczności pisania kodu, który modyfikuje obiekty, które nie miały być zmodyfikowany po inicjalizacji.

  • constexprdeklaruje obiekt jako odpowiedni do użycia w tym, co Standard nazywa stałymi wyrażeniami . Pamiętaj jednak, że constexprnie jest to jedyny sposób, aby to zrobić.

Kiedy stosuje się do funkcji podstawowa różnica jest taka:

  • constmoże być używany tylko w przypadku niestatycznych funkcji składowych, a nie w ogóle funkcji. Daje gwarancję, że funkcja członka nie modyfikuje żadnego z niestatycznych elementów danych.

  • constexprmoże być używany zarówno z funkcjami składowymi, jak i innymi, a także z konstruktorami. Deklaruje, że funkcja nadaje się do użycia w wyrażeniach stałych . Kompilator zaakceptuje to tylko wtedy, gdy funkcja spełnia określone kryteria (7.1.5 / 3,4), co najważniejsze (†) :

    • Ciało funkcji musi być nie-wirtualne i niezwykle proste: oprócz typedefs i twierdzeń statycznych returndozwolona jest tylko jedna instrukcja. W przypadku konstruktora dozwolona jest tylko lista inicjalizacji, typedefs i static assert. ( = defaultI = deletesą dozwolone, zbyt, choć.)
    • Jak C ++ 14 reguły są bardziej rozluźnione, co jest dozwolone, ponieważ wtedy wewnątrz funkcji constexpr: asmdeklaracja, gotooświadczenie, oświadczenie z etykietą inny niż casea default, spróbuj blok, definicja zmiennej typu non-dosłownym, definicja zmiennej o czasie przechowywania statycznego lub wątku, definicja zmiennej, dla której nie jest inicjowana.
    • Argumenty i typ zwracany muszą być dosłowne (tzn. Ogólnie bardzo proste typy, zwykle skalary lub agregacje)

Wyrażenia stałe

Jak wspomniano powyżej, constexprdeklaruje zarówno obiekty, jak i funkcje, jako odpowiednie do użycia w wyrażeniach stałych. Stałe wyrażenie jest czymś więcej niż tylko stałym:

  • Może być stosowany w miejscach wymagających oceny czasu kompilacji, na przykład w parametrach szablonu i specyfikatorach rozmiaru tablicy:

    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
  • Ale uwaga:

    • Zadeklarowanie czegoś jako constexprniekoniecznie gwarantuje, że zostanie to ocenione w czasie kompilacji. To może być używana do takich, ale może być używany w innych miejscach, które są oceniane w czasie wykonywania, jak również.

    • Obiekt może nadawać się do użycia w wyrażeniach stałych bez deklaracji constexpr. Przykład:

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

    Jest to możliwe, ponieważ Nbędąc stałym i inicjalizowanym w czasie deklaracji dosłownym, spełnia kryteria stałego wyrażenia, nawet jeśli nie jest zadeklarowane constexpr.

Kiedy więc muszę użyć constexpr?

  • Przedmiot jak Npowyżej, mogą być używane jako stałej ekspresji bez zadeklarowane constexpr. Dotyczy to wszystkich obiektów, które są:

    • const
    • typu całkowego lub numeracyjnego oraz
    • inicjowany w momencie zgłoszenia o ekspresji, który sam jest stała ekspresja

    [Wynika to z §5.19 / 2: Stałe wyrażenie nie może zawierać podwyrażeń obejmujących „modyfikację wartości do wartości, chyba że […] glvalue typu całkowego lub wyliczeniowego […]” Dzięki Richard Smith za poprawienie mojego wcześniej twierdzono, że dotyczy to wszystkich literałów.]

  • Aby funkcja była zdolna do użycia w wyrażeniach stałych, musi być jawnie zadeklarowana constexpr; nie wystarczy, że spełnia on jedynie kryteria funkcji stałej ekspresji. Przykład:

    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
    }

Kiedy mogę / powinienem używać obu consti constexpr razem?

A. W deklaracjach obiektowych. Nie jest to nigdy konieczne, gdy oba słowa kluczowe odnoszą się do tego samego obiektu, który ma zostać zadeklarowany. constexprimplikuje const.

constexpr const int N = 5;

jest taki sam jak

constexpr int N = 5;

Należy jednak pamiętać, że mogą wystąpić sytuacje, w których każde ze słów kluczowych odnosi się do różnych części deklaracji:

static constexpr int N = 3;

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

Tutaj NPjest zadeklarowany jako stały adres wyrażenia, tzn. Wskaźnik, który sam jest stałym wyrażeniem. (Jest to możliwe, gdy adres jest generowany przez zastosowanie operatora adresu statycznego / globalnej stałej ekspresji.) Oto, jak constexpri constwymagane są: constexprzawsze odnosi się do wypowiedzi zadeklarowane (tutaj NP), natomiast constodnosi się do int(nie deklaruje pointer- to-const). Usunięcie constspowoduje, że wyrażenie będzie nielegalne (ponieważ (a) wskaźnik do obiektu niestałego nie może być wyrażeniem stałym, a (b) &Njest w rzeczywistości wskaźnikiem do stałej).

B. W deklaracjach funkcji składowych. W C ++ 11 constexproznacza to const, podczas gdy w C ++ 14 i C ++ 17 tak nie jest. Funkcja składowa zadeklarowana w C ++ 11 jako

constexpr void f();

musi zostać zadeklarowany jako

constexpr void f() const;

w C ++ 14, aby nadal można go było używać jako constfunkcji.

jogojapan
źródło
3
IMO „niekoniecznie oceniane w czasie kompilacji” jest mniej pomocne niż myślenie o nich jako „ocenianych w czasie kompilacji”. Ograniczenia wyrażenia stałego oznaczają, że kompilator byłby stosunkowo łatwy do jego oceny. Kompilator musi narzekać, jeśli ograniczenia te nie są spełnione. Ponieważ nie ma żadnych skutków ubocznych, nigdy nie można odróżnić, czy kompilator to „ocenił”, czy nie.
aschepler
10
@aschepler Sure. Chodzi mi przede wszystkim o to, że jeśli wywołasz constexprfunkcję na niestałym wyrażeniu, np. Zwykłej zmiennej, jest to całkowicie legalne i funkcja będzie używana jak każda inna funkcja. Nie będzie oceniany w czasie kompilacji (ponieważ nie może). Być może uważasz, że to oczywiste - ale jeśli stwierdzę, że funkcja zadeklarowana jako constexprzawsze będzie oceniana w czasie kompilacji, można ją interpretować w niewłaściwy sposób.
jogojapan
5
Tak, mówiłem o constexprprzedmiotach, a nie funkcjach. Lubię myśleć o constexprobiektach jako o wymuszaniu oceny wartości w czasie kompilacji, a constexpro funkcjach jako o umożliwieniu oceny funkcji w czasie kompilacji lub w czasie wykonywania.
aschepler
2
Korekta: „const” jest jedynie ograniczeniem, którego NIE MOŻESZ zmienić wartości zmiennej; nie obiecuje, że wartość się nie zmieni (tj. przez kogoś innego). To właściwość zapisu, a nie własność odczytu.
Jared Grubb,
3
To zdanie: daje gwarancję, że funkcja członka nie modyfikuje żadnego z niestatycznych elementów danych. brakuje jednego ważnego szczegółu. Członkowie oznaczeni jako mutablemogą również być modyfikowani przez constfunkcje członków.
Wszechobecny
119

conststosuje się do zmiennych i zapobiega ich modyfikacji w kodzie.

constexprinformuje kompilator, że to wyrażenie daje stałą czasową kompilacji , więc można go używać w miejscach takich jak długości tablic, przypisywanie do constzmiennych itp. Łącze podane przez Oli ma wiele doskonałych przykładów.

Zasadniczo są to 2 różne koncepcje i mogą (i powinny) być używane razem.

Karthik T.
źródło
2
użycie const i constexpr, np .: en.cppreference.com/w/cpp/container/array/get
Manohar Reddy Poreddy
64

Przegląd

  • constgwarantuje, że program nie zmieni wartości obiektu . Jednak constnie gwarantuje, jakiego typu inicjalizacji obiekt zostanie poddany.

    Rozważać:

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

    Funkcja max()zwraca jedynie wartość literalną. Ponieważ jednak inicjalizator jest wywołaniem funkcji, mxpodlega inicjalizacji w czasie wykonywania. Dlatego nie można używać go jako stałego wyrażenia :

    int arr[mx];  // error: “constant expression required”
  • constexprto nowe słowo kluczowe C ++ 11, które eliminuje potrzebę tworzenia makr i literałów zakodowanych na stałe. Gwarantuje również, pod pewnymi warunkami, że obiekty zostaną poddane statycznej inicjalizacji . Kontroluje czas oceny wyrażenia. Wymuszając ocenę wyrażenia w czasie kompilacji , constexprpozwala zdefiniować prawdziwe stałe wyrażenia, które są kluczowe dla aplikacji o krytycznym czasie, programowania systemu, szablonów i ogólnie w każdym kodzie, który opiera się na stałych czasu kompilacji.

Funkcje wyrażeń stałych

Funkcja stałego wyrażenia jest funkcją zadeklarowaną constexpr. Jego treść musi być nie-wirtualna i składać się tylko z jednej instrukcji return, oprócz typedefs i twierdzeń statycznych. Argumenty i zwracana wartość muszą mieć dosłowne typy. Można go używać z argumentami o wyrażeniu innym niż stały, ale po wykonaniu tej czynności wynik nie jest wyrażeniem stałym.

Funkcja stałej ekspresji ma na celu zastąpienie makr i literałów zakodowanych na stałe bez poświęcania wydajności lub bezpieczeństwa typu.

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)

Obiekty o stałym wyrażeniu

Obiekt o stałym wyrażeniu to obiekt zadeklarowany constexpr. Musi być zainicjowany stałym wyrażeniem lub wartością skonstruowaną przez konstruktora stałego wyrażenia z argumentami stałego wyrażenia.

Obiekt o stałym wyrażeniu zachowuje się tak, jakby został zadeklarowany const, z tym wyjątkiem, że wymaga użycia inicjalizacji przed użyciem, a jego inicjator musi być wyrażeniem stałym. W związku z tym obiekt o stałej ekspresji może zawsze być używany jako część innego stałego wyrażenia.

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

Konstruktory o stałej ekspresji

Konstruktor o stałym wyrażeniu jest deklarowanym konstruktorem constexpr. Może mieć listę inicjującą członka, ale jej treść musi być pusta, oprócz typedefs i twierdzeń statycznych. Argumenty muszą mieć dosłowne typy.

Konstruktor o stałym wyrażeniu umożliwia kompilatorowi zainicjowanie obiektu w czasie kompilacji, pod warunkiem, że wszystkie argumenty konstruktora są wyrażeniami stałymi.

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

Wskazówki z książki Effective Modern C ++ autorstwa Scotta Meyersa na temat constexpr:

  • constexpr obiekty są stałe i są inicjowane wartościami znanymi podczas kompilacji;
  • constexpr funkcje generują wyniki czasu kompilacji, gdy są wywoływane z argumentami, których wartości są znane podczas kompilacji;
  • constexprobiekty i funkcje mogą być używane w szerszym zakresie kontekstów niż constexprobiekty niebędące obiektami i funkcjami;
  • constexpr jest częścią interfejsu obiektu lub funkcji.

Źródło: Korzystanie z constexpr w celu poprawy bezpieczeństwa, wydajności i enkapsulacji w C ++ .

zangw
źródło
Dzięki za świetny przykładowy kod pokazujący różne sytuacje. Choć niektóre z innych wyjaśnień są świetne, uważam, że zobaczenie kodu w działaniu jest o wiele bardziej przydatne i zrozumiałe. To naprawdę pomogło umocnić moje zrozumienie tego, co się dzieje.
RTHarston
35

Według książki Bjarne Stroustrup „The C ++ Programming Language 4th Editon”
const : co oznacza mniej więcej „Obiecuję nie zmieniać tej wartości” (§ 7.5). Służy to przede wszystkim do określania interfejsów, dzięki czemu dane mogą być przekazywane do funkcji bez obawy, że zostaną zmodyfikowane.
Kompilator egzekwuje obietnicę złożoną przez const.
constexpr : co oznacza mniej więcej „do oceny w czasie kompilacji” (§ 10.4). Służy to przede wszystkim do określenia stałych, aby umożliwić
na przykład:

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

Aby funkcja była użyteczna w stałym wyrażeniu, to znaczy w wyrażeniu, które zostanie ocenione przez kompilator, należy zdefiniować constexpr .
Na przykład:

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


Aby być constexpr, funkcja musi być raczej prosta: po prostu instrukcja return obliczająca wartość. Funkcja constexpr może być używana do nietrwałych argumentów, ale kiedy to zostanie zrobione, wynik nie jest stałym wyrażeniem. Umożliwiamy wywoływanie funkcji constexpr z argumentami o niestałym wyrażeniu w kontekstach, które nie wymagają stałych wyrażeń, dzięki czemu nie musimy definiować zasadniczo tej samej funkcji dwa razy: raz dla wyrażeń stałych i raz dla zmiennych.
W kilku miejscach stałe wyrażenia są wymagane przez reguły językowe (np. Granice tablic (§2.2.5, §7.3), etykiety przypadków (§2.2.4, §9.4.2), niektóre argumenty szablonu (§25.2) i stałe zadeklarowane przy użyciu constexpr). W innych przypadkach ocena czasu kompilacji jest ważna dla wydajności. Niezależnie od problemów z wydajnością, pojęcie niezmienności (obiektu o niezmiennym stanie) jest istotną kwestią projektową (§ 10.4).

Mustafa Ekici
źródło
nadal występują problemy z wydajnością. Wydaje się, że funkcja constexpr, jeśli jest oceniana w czasie wykonywania, może być wolniejsza niż wersja funkcji nie constexpr. Także jeśli mamy stałą wartość, czy powinniśmy preferować „const” czy „constexpr”? (więcej zestaw generowany przez pytanie stylowe wygląda tak samo)
CoffeDeveloper
31

Zarówno consti constexprmoże być stosowany do zmiennych i funkcji. Mimo że są do siebie podobne, w rzeczywistości są to bardzo różne koncepcje.

Zarówno consti constexproznacza to, że ich wartości nie można zmienić po ich inicjalizacji. Na przykład:

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.

Główną różnicą pomiędzy consti constexprjest czas, w którym ich wartości inicjalizacji są znane (oceniane). Chociaż wartości constzmiennych można oceniać zarówno w czasie kompilacji, jak i w środowisku wykonawczym, constexprzawsze są one oceniane w czasie kompilacji. Na przykład:

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.

Kluczową zaletą wiedzieć, czy wartość jest znana w czasie kompilacji, czy w czasie wykonywania, jest fakt, że stałych czasowych kompilacji można używać zawsze, gdy potrzebne są stałe czasowe kompilacji. Na przykład C ++ nie pozwala na określenie macierzy C o zmiennej długości.

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

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

Oznacza to, że:

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.

constZmienne mogą więc definiować obie stałe czasowe kompilacji podobne size1, które mogą być użyte do określenia rozmiaru tablicy i stałych uruchomieniowe jak size2, które są znane tylko w środowisku wykonawczym i nie może być używany do określenia rozmiaru tablicy. Z drugiej strony constexprzawsze definiuj stałe czasowe kompilacji, które mogą określać rozmiary tablic.

Zarówno consticonstexpr mogą być stosowane również do funkcji. constFunkcja musi być funkcją członkiem (metoda, operator), gdzie stosowanie constśrodków słów kluczowych, że metoda ta nie może zmienić wartości w swoim państwie (nie-statyczne) pól. Na przykład.

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.
   }
};

ZA constexpr to inna koncepcja. Oznacza funkcję (element członkowski lub element inny niż element członkowski) jako funkcję, która może być oceniana w czasie kompilacji, jeśli jako argumenty zostaną przekazane stałe czasu kompilacji . Na przykład możesz to napisać.

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.

Przy okazji constexpr funkcje są zwykłymi funkcjami C ++, które można wywoływać, nawet jeśli przekazane zostaną niestałe argumenty. Ale w takim przypadku otrzymujesz wartości inne niż constexpr.

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.

The constexprMoże być również stosowany do funkcji składowych (metody), operatorów i nawet konstruktorów. Na przykład.

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

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


    }
};

Bardziej „szalona” próbka.

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
źródło
Również w C constexpr intistnieje, ale jest napisaneconst int
ciekawy
8

Jak już wskazano @ 0x499602d2, constzapewnia tylko, że wartości nie można zmienić po inicjalizacji, gdzie as constexpr(wprowadzony w C ++ 11) gwarantuje, że zmienna jest stałą czasową kompilacji.
Rozważ następujący przykład (z 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
źródło
5

const int varWartość A można dynamicznie ustawić na wartość w czasie wykonywania, a po jej ustawieniu nie można jej już zmienić.

ZA constexpr int varNie można ustawić dynamicznie w czasie wykonywania, ale raczej w czasie kompilacji. Po ustawieniu tej wartości nie można jej już zmienić.

Oto solidny przykład:

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
}

Powyższy fragment kodu dobrze się kompiluje, a ja skomentowałem te, które powodują jego błąd.

Kluczowymi pojęciami, na które należy zwrócić uwagę, są pojęcia compile timei run time. W C ++ wprowadzono nowe innowacje mające na celu jak najwięcej ** know **pewnych rzeczy w czasie kompilacji w celu poprawy wydajności w czasie wykonywania.

trueadjustr
źródło
3

Nie sądzę, aby żadna z odpowiedzi naprawdę wyjaśniała dokładnie, jakie ma skutki uboczne, a nawet co to jest.

constexpri constw przestrzeni nazw / zakresie plików są identyczne, gdy są inicjowane literałem lub wyrażeniem; ale z funkcją constmoże być zainicjalizowany przez dowolną funkcję, ale constexprzainicjowany przez non-constexpr (funkcja, która nie jest oznaczona constexpr lub wyrażeniem innym niż constexpr) wygeneruje błąd kompilatora. Zarówno constexpri constsą niejawnie wewnętrzne powiązanie zmiennych (no faktycznie, nie przetrwać dostać się do etapu łączącej jeśli kompilacji -O1 i silniejszy, a staticnie zmusić kompilator, aby emitować wewnętrznego (lokalnego) na symbol łącznika constlub constexprgdy na -O1 lub silniejszy; dzieje się tak tylko wtedy, gdy weźmiesz adres zmiennej consticonstexpr będzie to symbol wewnętrzny, chyba że zostanie wyrażony za pomocąextern np.extern constexpr/const int i = 3;należy użyć). W przypadku funkcji constexprpowoduje , że funkcja na stałe nigdy nie osiągnie etapu łączenia (niezależnie od externlub inlinew definicji albo -O0 lub -Ofast), podczas gdy constnigdy tego nie robi statici inlinema taki wpływ tylko na -O1 i wyżej. Kiedy zmienna const/ constexprjest inicjalizowana przez constexprfunkcję, obciążenie jest zawsze optymalizowane za pomocą dowolnej flagi optymalizacji, ale nigdy nie jest optymalizowane, jeśli funkcja jest tylko staticlub inline, lub jeśli zmienna nie jest const/ constexpr.

Kompilacja standardowa (-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;
} 

kompiluje się do

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):
        . 
        . 
        . 

jednak

#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;
}

Kompiluje do

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

To wyraźnie pokazuje, że constexprpowoduje to inicjalizację const/constexprzmiennej o zasięgu pliku w czasie kompilacji i nie generuje żadnego globalnego symbolu, natomiast jej niestosowanie powoduje, że inicjalizacja ma miejsce wcześniejmain w czasie wykonywania.

Kompilowanie przy użyciu -Ofast

Nawet -Ofast nie optymalizuje obciążenia! https://godbolt.org/z/r-mhif , więc potrzebujesz constexpr


constexprfunkcje mogą być również wywoływane z innych constexprfunkcji dla tego samego wyniku. constexprna funkcji zapobiega także użyciu czegokolwiek, czego nie można zrobić w czasie kompilacji w funkcji; na przykład połączenie z <<operatorem włączonestd::cout .

constexprzakres bloku zachowuje się tak samo, ponieważ powoduje błąd, jeśli jest inicjowany przez funkcję inną niż constexpr; wartość jest również podstawiana natychmiast.

Ostatecznie jego głównym celem jest jak wbudowana funkcja C, ale jest skuteczna tylko wtedy, gdy funkcja jest używana do inicjalizacji zmiennych o zasięgu pliku (które to funkcje nie mogą wykonać w C, ale mogą to zrobić w C ++, ponieważ umożliwia dynamiczną inicjalizację pliku zmienne zasięgu), z tą różnicą, że funkcja nie może również eksportować globalnego / lokalnego symbolu do linkera, nawet używając extern/static, co można by zrobić inlinena C; funkcje przypisywania zmiennych o zasięgu blokowym można wprowadzić po prostu za pomocą optymalizacji -O1 bez constexprC i C ++.

Lewis Kelsey
źródło
Niezły punkt na linkerze. Czy ogólnie można uznać za bezpieczniejsze stosowanie constexpr, ponieważ powoduje to mniej przecieków symboli?
Neil McGill
1
@NeilMcGill nie jest tak naprawdę, ponieważ wbudowany i statyczny spowoduje, że kompilator nie wyemituje lokalnego symbolu mnożenia, jeśli kompiluje się przy użyciu -O1 lub silniejszego. Constexpr jest jedynym, który optymalizuje obciążenie dla val, ale poza tym jest identyczny z umieszczeniem statycznego lub wstawionego przed funkcją. Zapomniałem też czegoś innego. Constexpr jest jedynym słowem kluczowym, które nie emituje symbolu funkcji na -O0, static i inline do
Lewis Kelsey
1

Przede wszystkim oba są kwalifikatorami w c ++. Zmienna zadeklarowana const musi zostać zainicjowana i nie może być zmieniona w przyszłości. Stąd ogólnie zmienna zadeklarowana jako const będzie miała wartość nawet przed kompilacją.

Ale dla constexpr jest nieco inaczej.

W przypadku constexpr możesz podać wyrażenie, które może być ocenione podczas kompilacji programu.

Oczywiście zmiennej zadeklarowanej jako constexper nie można w przyszłości zmienić tak jak const.

Subhash Malireddy
źródło