@ 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 expressionint 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(){constint 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>classlist{};constexprint sqr1(int arg){return arg * arg;}int sqr2(int arg){return arg * arg;}int main(){constint X =2;list<sqr1(X)> mylist1;// OK: sqr1 is constexprlist<sqr2(X)> mylist2;// wrong: sqr2 is not constexpr}
Kiedy mogę / powinienem używać obu consti constexprrazem?
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.
constexprconstint N =5;
jest taki sam jak
constexprint 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:
staticconstexprint N =3;int main(){constexprconstint*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
constexprvoid f();
musi zostać zadeklarowany jako
constexprvoid f()const;
w C ++ 14, aby nadal można go było używać jako constfunkcji.
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.
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.
constexprint max(){return INT_MAX;}// OKconstexprlong long_max(){return2147483647;}// OKconstexprbool get_val(){bool res =false;return res;}// error: body is not just a return statementconstexprint square(int x){return x * x;}// OK: compile-time evaluation only if x is a constant expressionconstint 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
{constexprint two();// constant-expression functionprivate:staticconstexprint sz;// constant-expression object};constexprint S::sz =256;enumDataPacket{Small= S::two(),// error: S::two() called before it was definedBig=1024};constexprint 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 constructorconstexpr complex(double r,double i): re(r), im(i){}// OK: empty body// constant-expression functionsconstexprdouble real(){return re;}constexprdouble imag(){return im;}private:double re;double im;};constexpr complex COMP(0.0,1.0);// creates a literal complexdouble x =1.0;constexpr complex cx1(x,0);// error: x is not a constant expressionconst complex cx2(x,1);// OK: runtime initializationconstexprdouble xx = COMP.real();// OK: compile-time initializationconstexprdouble 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.
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:
constint dmv =17;// dmv is a named constantint var =17;// var is not a constantconstexprdouble max1 =1.4*square(dmv);// OK if square(17) is a constant expressionconstexprdouble max2 =1.4∗square(var);// error : var is not a constant expressionconstdouble max3 =1.4∗square(var);//OK, may be evaluated at run timedouble sum(constvector<double>&);// sum will not modify its argument (§2.2.5)vector<double> v {1.2,3.4,4.5};// v is not a constantconstdouble s1 = sum(v);// OK: evaluated at run timeconstexprdouble 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:
constexprdouble square(double x){return x∗x;}
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).
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:
constint x1=10;constexprint 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.constint x1=10;// OK - known at compile time.constint x2=temp;// OK - known only at runtime.constexprint x3=10;// OK - known at compile time.constexprint 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:
constint size1=10;// OK - value known at compile time.constint size2=temp;// OK - value known only at runtime.constexprint 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ć.
constexprint 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.constexprint 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
{staticconstexprint 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.constexprint getvalue()const{return(value);}constexpr test3(intValue): value(Value){}};constexpr test3 x(100);// OK. Constructor is constexpr.intarray[x.getvalue()];// OK. x.getvalue() is constexpr and can be evaluated at compile time.
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;constint myAge{age};// worksconstexprint someAge{age};// error: age can only be resolved at runtime
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[]){constint 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 constexprint 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.
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>constexprint multiply (int x,int y){return x * y;}externconstint val = multiply(10,10);int main (){
std::cout << val;}
kompiluje się do
val:.long100//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>constint multiply (int x,int y){return x * y;}constint val = multiply(10,10);//constexpr is an errorint 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.
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 ++.
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.
constexpr
tworzy stałą czasową kompilacji;const
oznacza po prostu, że wartości nie można zmienić.boost/hana
biblioteki może ujawnić niektóreconstexpr
kwestie, w których można używać,constexpr
a gdzie nie: boost.org/doc/libs/1_69_0/libs/hana/doc/html/…constexpr
wtedy też byłem nowy :)Odpowiedzi:
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:
const
deklaruje 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.constexpr
deklaruje obiekt jako odpowiedni do użycia w tym, co Standard nazywa stałymi wyrażeniami . Pamiętaj jednak, żeconstexpr
nie jest to jedyny sposób, aby to zrobić.Kiedy stosuje się do funkcji podstawowa różnica jest taka:
const
moż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.constexpr
moż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 (†) :return
dozwolona jest tylko jedna instrukcja. W przypadku konstruktora dozwolona jest tylko lista inicjalizacji, typedefs i static assert. (= default
I= delete
są dozwolone, zbyt, choć.)asm
deklaracja,goto
oświadczenie, oświadczenie z etykietą inny niżcase
adefault
, 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.Wyrażenia stałe
Jak wspomniano powyżej,
constexpr
deklaruje 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:
Ale uwaga:
Zadeklarowanie czegoś jako
constexpr
niekoniecznie 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:Jest to możliwe, ponieważ
N
będąc stałym i inicjalizowanym w czasie deklaracji dosłownym, spełnia kryteria stałego wyrażenia, nawet jeśli nie jest zadeklarowaneconstexpr
.Kiedy więc muszę użyć
constexpr
?Przedmiot jak
N
powyżej, mogą być używane jako stałej ekspresji bez zadeklarowaneconstexpr
. Dotyczy to wszystkich obiektów, które są:const
[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:Kiedy mogę / powinienem używać obu
const
iconstexpr
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.
constexpr
implikujeconst
.jest taki sam jak
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:
Tutaj
NP
jest 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, jakconstexpr
iconst
wymagane są:constexpr
zawsze odnosi się do wypowiedzi zadeklarowane (tutajNP
), natomiastconst
odnosi się doint
(nie deklaruje pointer- to-const). Usunięcieconst
spowoduje, że wyrażenie będzie nielegalne (ponieważ (a) wskaźnik do obiektu niestałego nie może być wyrażeniem stałym, a (b)&N
jest w rzeczywistości wskaźnikiem do stałej).B. W deklaracjach funkcji składowych. W C ++ 11
constexpr
oznacza toconst
, podczas gdy w C ++ 14 i C ++ 17 tak nie jest. Funkcja składowa zadeklarowana w C ++ 11 jakomusi zostać zadeklarowany jako
w C ++ 14, aby nadal można go było używać jako
const
funkcji.źródło
constexpr
funkcję 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 jakoconstexpr
zawsze będzie oceniana w czasie kompilacji, można ją interpretować w niewłaściwy sposób.constexpr
przedmiotach, a nie funkcjach. Lubię myśleć oconstexpr
obiektach jako o wymuszaniu oceny wartości w czasie kompilacji, aconstexpr
o funkcjach jako o umożliwieniu oceny funkcji w czasie kompilacji lub w czasie wykonywania.mutable
mogą również być modyfikowani przezconst
funkcje członków.const
stosuje się do zmiennych i zapobiega ich modyfikacji w kodzie.constexpr
informuje 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 doconst
zmiennych 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.
źródło
Przegląd
const
gwarantuje, że program nie zmieni wartości obiektu . Jednakconst
nie gwarantuje, jakiego typu inicjalizacji obiekt zostanie poddany.Rozważać:
Funkcja
max()
zwraca jedynie wartość literalną. Ponieważ jednak inicjalizator jest wywołaniem funkcji,mx
podlega inicjalizacji w czasie wykonywania. Dlatego nie można używać go jako stałego wyrażenia :constexpr
to 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 ,constexpr
pozwala 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.
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.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.
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;constexpr
obiekty i funkcje mogą być używane w szerszym zakresie kontekstów niżconstexpr
obiekty 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 ++ .
źródło
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:
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:
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).
źródło
Zarówno
const
iconstexpr
może być stosowany do zmiennych i funkcji. Mimo że są do siebie podobne, w rzeczywistości są to bardzo różne koncepcje.Zarówno
const
iconstexpr
oznacza to, że ich wartości nie można zmienić po ich inicjalizacji. Na przykład:Główną różnicą pomiędzy
const
iconstexpr
jest czas, w którym ich wartości inicjalizacji są znane (oceniane). Chociaż wartościconst
zmiennych można oceniać zarówno w czasie kompilacji, jak i w środowisku wykonawczym,constexpr
zawsze są one oceniane w czasie kompilacji. Na przykład: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.
Oznacza to, że:
const
Zmienne mogą więc definiować obie stałe czasowe kompilacji podobnesize1
, które mogą być użyte do określenia rozmiaru tablicy i stałych uruchomieniowe jaksize2
, które są znane tylko w środowisku wykonawczym i nie może być używany do określenia rozmiaru tablicy. Z drugiej stronyconstexpr
zawsze definiuj stałe czasowe kompilacji, które mogą określać rozmiary tablic.Zarówno
const
iconstexpr
mogą być stosowane również do funkcji.const
Funkcja musi być funkcją członkiem (metoda, operator), gdzie stosowanieconst
ś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.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ć.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.The
constexpr
Może być również stosowany do funkcji składowych (metody), operatorów i nawet konstruktorów. Na przykład.Bardziej „szalona” próbka.
źródło
constexpr int
istnieje, ale jest napisaneconst int
Jak już wskazano @ 0x499602d2,
const
zapewnia tylko, że wartości nie można zmienić po inicjalizacji, gdzie asconstexpr
(wprowadzony w C ++ 11) gwarantuje, że zmienna jest stałą czasową kompilacji.Rozważ następujący przykład (z LearnCpp.com):
źródło
const int var
Wartość A można dynamicznie ustawić na wartość w czasie wykonywania, a po jej ustawieniu nie można jej już zmienić.ZA
constexpr int var
Nie 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:
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 time
irun 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.źródło
Nie sądzę, aby żadna z odpowiedzi naprawdę wyjaśniała dokładnie, jakie ma skutki uboczne, a nawet co to jest.
constexpr
iconst
w przestrzeni nazw / zakresie plików są identyczne, gdy są inicjowane literałem lub wyrażeniem; ale z funkcjąconst
może być zainicjalizowany przez dowolną funkcję, aleconstexpr
zainicjowany przez non-constexpr (funkcja, która nie jest oznaczona constexpr lub wyrażeniem innym niż constexpr) wygeneruje błąd kompilatora. Zarównoconstexpr
iconst
są niejawnie wewnętrzne powiązanie zmiennych (no faktycznie, nie przetrwać dostać się do etapu łączącej jeśli kompilacji -O1 i silniejszy, astatic
nie zmusić kompilator, aby emitować wewnętrznego (lokalnego) na symbol łącznikaconst
lubconstexpr
gdy na -O1 lub silniejszy; dzieje się tak tylko wtedy, gdy weźmiesz adres zmiennejconst
iconstexpr
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 funkcjiconstexpr
powoduje , że funkcja na stałe nigdy nie osiągnie etapu łączenia (niezależnie odextern
lubinline
w definicji albo -O0 lub -Ofast), podczas gdyconst
nigdy tego nie robistatic
iinline
ma taki wpływ tylko na -O1 i wyżej. Kiedy zmiennaconst
/constexpr
jest inicjalizowana przezconstexpr
funkcję, obciążenie jest zawsze optymalizowane za pomocą dowolnej flagi optymalizacji, ale nigdy nie jest optymalizowane, jeśli funkcja jest tylkostatic
lubinline
, lub jeśli zmienna nie jestconst
/constexpr
.Kompilacja standardowa (-O0)
kompiluje się do
jednak
Kompiluje do
To wyraźnie pokazuje, że
constexpr
powoduje to inicjalizacjęconst/constexpr
zmiennej 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
constexpr
funkcje mogą być również wywoływane z innychconstexpr
funkcji dla tego samego wyniku.constexpr
na 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
.constexpr
zakres 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ćinline
na C; funkcje przypisywania zmiennych o zasięgu blokowym można wprowadzić po prostu za pomocą optymalizacji -O1 bezconstexpr
C i C ++.źródło
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.
źródło