const vs constexpr na zmiennych

303

Czy istnieje różnica między następującymi definicjami?

const     double PI = 3.141592653589793;
constexpr double PI = 3.141592653589793;

Jeśli nie, jaki styl jest preferowany w C ++ 11?

fredoverflow
źródło
2
Superset: stackoverflow.com/questions/14116003/…
Ciro Santilli 15 冠状 病 六四 事件 法轮功
Oba są stałe czas kompilacji. Ale możesz zrobić const_cast pierwszego i napisać do niego. Ale zostanie zoptymalizowany przez dowolny kompilator, ponieważ nie wpływa to na „odczyty”, tak jak dzieje się to w czasie kompilacji.
Bonita Montero,

Odpowiedzi:

347

Wierzę, że jest różnica. Zmieńmy ich nazwy, abyśmy mogli łatwiej o nich mówić:

const     double PI1 = 3.141592653589793;
constexpr double PI2 = 3.141592653589793;

Zarówno PI1i PI2są stałe, co oznacza, że nie można ich modyfikować. Jednak tylko PI2 stała czasowa kompilacji. To powinno być inicjowane w czasie kompilacji. PI1może być zainicjowany w czasie kompilacji lub w czasie wykonywania. Ponadto, tylko PI2 mogą być stosowane w kontekście, który wymaga stałego kompilacji. Na przykład:

constexpr double PI3 = PI1;  // error

ale:

constexpr double PI3 = PI2;  // ok

i:

static_assert(PI1 == 3.141592653589793, "");  // error

ale:

static_assert(PI2 == 3.141592653589793, "");  // ok

Do czego powinieneś użyć? Używaj w zależności od potrzeb. Czy chcesz mieć pewność, że masz stałą czasową kompilacji, której można używać w kontekstach, w których wymagana jest stała czasowa kompilacji? Czy chcesz mieć możliwość zainicjowania go za pomocą obliczeń wykonanych w czasie wykonywania? Itp.

Howard Hinnant
źródło
60
Jesteś pewny? Ponieważ const int N = 10; char a[N];działa, a granice tablic muszą być stałymi w czasie kompilacji.
fredoverflow
10
Jestem pewien, że jeśli chodzi o przykłady, które napisałem, przetestowałem każdy z nich przed opublikowaniem. Jednak mój kompilator pozwala mi przekonwertować PI1na stałą całkowania w czasie kompilacji do użycia w tablicy, ale nie do użycia jako parametr szablonu całki nietypowej. Tak więc PI1wydaje mi się, że konwertowanie na typ całkowy w czasie jest hitem.
Howard Hinnant,
34
@FredOverflow: Nieprzetworzone indeksy tablicowe „działały” przez około dekadę (istnieje na przykład rozszerzenie g ++), ale to nie znaczy, że jest to całkowicie legalne C ++ (chociaż niektóre nowsze standardy C lub C ++ sprawiły, że jest to legalne , ja zapomniałem który). Jeśli chodzi o różnice w stałych kompilacji, parametry szablonu i użycie jako enuminicjalizatora są jedynymi dwiema znaczącymi różnicami pomiędzy consti constexpr(i żadna z nich i tak nie działa double).
Damon,
17
Paragraf 4 5.19 Wyrażenia stałe [wyrażenie normalne] jest również (nienormatywną) uwagą, która słynie, że implementacja może wykonywać arytmetykę zmiennoprzecinkową inaczej (np. W odniesieniu do dokładności) w czasie kompilacji niż w czasie wykonywania. Więc 1 / PI1i 1 / PI2mogą dawać różne wyniki. Nie sądzę jednak, aby ta technika była tak ważna jak rada w tej odpowiedzi.
Luc Danton
4
Ale constexpr double PI3 = PI1;działa dla mnie poprawnie. (MSVS2013 CTP). Co ja robię źle?
NuPagadi
77

Nie ma tutaj różnicy, ale ma to znaczenie, gdy masz typ z konstruktorem.

struct S {
    constexpr S(int);
};

const S s0(0);
constexpr S s1(1);

s0jest stały, ale nie obiecuje, że zostanie zainicjowany w czasie kompilacji. s1jest oznaczony constexpr, więc jest stałą, a ponieważ Skonstruktor jest również oznaczony constexpr, zostanie zainicjowany w czasie kompilacji.

W większości przypadków ma to znaczenie, gdy inicjalizacja w środowisku wykonawczym byłaby czasochłonna i chcesz przekazać tę pracę kompilatorowi, gdzie jest to również czasochłonne, ale nie spowalnia czasu wykonywania skompilowanego programu

Pete Becker
źródło
3
Zgadzam się: doszedłem do wniosku, constexprże doprowadziłoby to do diagnozy, gdyby obliczenie obiektu w czasie kompilacji było niemożliwe. Mniej jasne jest to, czy funkcja oczekująca stałego parametru może być wykonana w czasie kompilacji, czy parametr powinien być zadeklarowany jako, consta nie jako constexpr: tzn. Czy constexpr int foo(S)byłby wykonywany w czasie kompilacji, jeśli wywołam foo(s0)?
Matthieu M.,
4
@MatthieuM: Wątpię, czy foo(s0)byłby wykonywany w czasie kompilacji, ale nigdy nie wiadomo: kompilator może wykonywać takie optymalizacje. Z pewnością ani gcc 4.7.2, ani clang 3.2 nie pozwalają mi na kompilacjęconstexpr a = foo(s0);
rici
50

constexpr wskazuje wartość, która jest stała i znana podczas kompilacji.
const wskazuje wartość, która jest tylko stała; wiedza nie jest obowiązkowa podczas kompilacji.

int sz;
constexpr auto arraySize1 = sz;    // error! sz's value unknown at compilation
std::array<int, sz> data1;         // error! same problem

constexpr auto arraySize2 = 10;    // fine, 10 is a compile-time constant
std::array<int, arraySize2> data2; // fine, arraySize2 is constexpr

Zauważ, że const nie daje takiej samej gwarancji jak constexpr, ponieważ obiekty const nie muszą być inicjowane wartościami znanymi podczas kompilacji.

int sz;
const auto arraySize = sz;       // fine, arraySize is const copy of sz
std::array<int, arraySize> data; // error! arraySize's value unknown at compilation

Wszystkie obiekty constexpr są const, ale nie wszystkie obiekty constexpr są constexpr.

Jeśli chcesz, aby kompilatory gwarantowały, że zmienna ma wartość, której można użyć w kontekstach wymagających stałych czasowych kompilacji, narzędziem, do którego należy sięgnąć, jest constexpr, a nie const.

Ajay Yadav
źródło
2
Bardzo podobało mi się twoje wyjaśnienie. Czy mógłbyś skomentować więcej gdzie są przypadki, w których możemy potrzebować użyć stałych czasowych kompilacji w rzeczywistych scenariuszach
Mayukh Sarkar
1
@MayukhSarkar Wystarczy Google C ++ dlaczego constexpr np stackoverflow.com/questions/4748083/...
underscore_d
18

Constexpr stałe symboliczne należy podać wartość, która jest znany w czasie kompilacji. Na przykład:

constexpr int max = 100; 
void use(int n)
{
    constexpr int c1 = max+7; // OK: c1 is 107
    constexpr int c2 = n+7;   // Error: we don’t know the value of c2
    // ...
}

Aby obsłużyć przypadki, w których wartość „zmiennej”, która jest inicjowana wartością nieznaną w czasie kompilacji, ale nigdy się nie zmienia po inicjalizacji, C ++ oferuje drugą formę stałej ( const ). Na przykład:

constexpr int max = 100; 
void use(int n)
{
    constexpr int c1 = max+7; // OK: c1 is 107
    const int c2 = n+7; // OK, but don’t try to change the value of c2
    // ...
    c2 = 7; // error: c2 is a const
}

Takie „ stałe zmienne” są bardzo powszechne z dwóch powodów:

  1. C ++ 98 nie miał constexpr, więc ludzie używali const .
  2. Element listy „Zmienne”, które nie są stałymi wyrażeniami (ich wartość nie jest znana w czasie kompilacji), ale nie zmieniają wartości po inicjalizacji, same w sobie są bardzo przydatne.

Odniesienie: „Programowanie: zasady i praktyka przy użyciu C ++” Stroustrup

Jnana
źródło
25
Być może powinieneś wspomnieć, że tekst w twojej odpowiedzi pochodzi dosłownie z „Programowania: zasady i praktyka przy użyciu C ++” Stroustrupa
Aky