Szukaj…


Wprowadzenie

constexpr jest słowem kluczowym, które może być użyte do oznaczenia wartości zmiennej jako wyrażenia stałego, funkcji potencjalnie użytecznej w wyrażeniach stałych lub (od C ++ 17) instrukcji if jako posiadającej tylko jedną z gałęzi wybranych do kompilacji.

Uwagi

constexpr kluczowe constexpr zostało dodane w C ++ 11, ale przez kilka lat od opublikowania standardu C ++ 11 nie wszystkie główne kompilatory go obsługiwały. w momencie opublikowania standardu C ++ 11. W momencie publikacji C ++ 14 wszystkie główne kompilatory obsługują constexpr .

zmienne constexpr

Zmienna deklarowana constexpr jest domyślnie const a jej wartość może być używana jako wyrażenie stałe.

Porównanie z #define

constexpr jest bezpiecznym zamiennikiem typów dla wyrażeń czasu kompilacji opartych na #define . W constexpr wyrażenie wyrażone w czasie kompilacji jest zastępowane wynikiem. Na przykład:

C ++ 11
int main()
{
   constexpr int N = 10 + 2;
   cout << N;
}

wygeneruje następujący kod:

cout << 12;

Makro czasu kompilacji oparte na procesorze wstępnym byłoby inne. Rozważać:

#define N 10 + 2

int main()
{
    cout << N;
}

będzie produkować:

cout << 10 + 2;

które oczywiście zostaną przekonwertowane na cout << 10 + 2; . Jednak kompilator musiałby wykonać więcej pracy. Ponadto stwarza problem, jeśli nie jest używany prawidłowo.

Na przykład (z #define ):

cout << N * 2;

formy:

cout << 10 + 2 * 2; // 14

Ale wstępnie oceniany constexpr poprawnie dałby 24 .

Porównanie z const

Zmienna const to zmienna, która potrzebuje pamięci do przechowywania. constexpr nie. constexpr tworzy stałą czasową kompilacji, której nie można zmienić. Możesz argumentować, że const również nie może zostać zmieniony. Ale zastanów się:

int main()
{
   const int size1 = 10;
   const int size2 = abs(10);

   int arr_one[size1]; 
   int arr_two[size2]; 
}

W przypadku większości kompilatorów druga instrukcja zawiedzie (może na przykład działać z GCC). Rozmiar dowolnej tablicy, jak być może wiesz, musi być stałym wyrażeniem (tzn. Skutkuje wartością czasu kompilacji). Druga zmienna size2 ma przypisaną wartość, która jest ustalana w czasie wykonywania (nawet jeśli wiesz, że jest to 10 , dla kompilatora nie jest to czas kompilacji).

Oznacza to, że const może, ale nie musi być prawdziwą stałą czasową kompilacji. Nie można zagwarantować ani wyegzekwować, że określona const wartość jest absolutnie czasem kompilacji. Możesz użyć #define ale ma swoje własne pułapki.

Dlatego po prostu użyj:

C ++ 11
int main()
{
    constexpr int size = 10;

    int arr[size];
}

Wyrażenie constexpr musi oceniać na wartość czasu kompilacji. Dlatego nie możesz użyć:

C ++ 11
constexpr int size = abs(10);

Chyba że sama funkcja ( abs ) sama zwraca constexpr .

Wszystkie podstawowe typy mogą być inicjowane przy pomocy constexpr .

C ++ 11
constexpr bool FailFatal = true;
constexpr float PI = 3.14f;
constexpr char* site= "StackOverflow";

Co ciekawe i wygodnie, możesz także użyć auto :

C ++ 11
constexpr auto domain = ".COM";  // const char * const domain = ".COM"
constexpr auto PI = 3.14;        // constexpr double

funkcje constexpr

Funkcja, która jest zadeklarowana jako constexpr jest domyślnie wbudowana i wywołania takiej funkcji potencjalnie dają stałe wyrażenia. Na przykład następująca funkcja, jeśli zostanie wywołana ze stałymi argumentami wyrażenia, również daje wyrażenie stałe:

C ++ 11
constexpr int Sum(int a, int b)
{
    return a + b;
}

Zatem wynik wywołania funkcji może być użyty jako argument związany z tablicą lub jako szablon lub do zainicjowania zmiennej constexpr :

C ++ 11
int main()
{
    constexpr int S = Sum(10,20);
   
    int Array[S];
    int Array2[Sum(20,30)]; // 50 array size, compile time
}

Zauważ, że jeśli usuniesz constexpr ze specyfikacji typu zwracanej funkcji, przypisanie do S nie będzie działać, ponieważ S jest zmienną constexpr i musi zostać przypisana constexpr kompilacji. Podobnie, rozmiar tablicy również nie będzie wyrażeniem stałym, jeśli funkcja Sum nie jest constexpr .

Interesującą rzeczą w funkcjach constexpr jest to, że możesz ich również używać jak zwykłych funkcji:

C ++ 11
int a = 20;
auto sum = Sum(a, abs(-20));

Sum nie będzie teraz funkcją constexpr , zostanie skompilowana jako zwykła funkcja, przyjmująca zmienne (niestałe) argumenty i zwracająca constexpr wartość. Nie musisz pisać dwóch funkcji.

Oznacza to również, że jeśli spróbujesz przypisać takie wywołanie do zmiennej innej niż const, nie zostanie ona skompilowana:

C ++ 11
int a = 20;
constexpr auto sum = Sum(a, abs(-20));

Powód jest prosty: constexpr musi mieć tylko stałą czasową kompilacji. Jednak powyższe wywołanie funkcji powoduje, że Sum nie jest constexpr (wartość R jest non-const, ale wartość L deklaruje się jako constexpr ).


Funkcja constexpr musi również zwracać stałą czasową kompilacji. Następujące nie zostaną skompilowane:

C ++ 11
constexpr int Sum(int a, int b)
{
    int a1 = a;     // ERROR
    return a + b;
}

Ponieważ a1 jest zmienną inną niż constexpr i zabrania funkcji bycia prawdziwą funkcją constexpr . Czyni go constexpr i przypisanie go nie będzie również praca - ponieważ wartość a a (parametr przychodzące) wciąż nie jest jeszcze znana:

C ++ 11
constexpr int Sum(int a, int b)
{
   constexpr int a1 = a;     // ERROR
   ..

Ponadto następujące kompilacje również nie będą się kompilować:

C ++ 11
constexpr int Sum(int a, int b)
{
   return abs(a) + b; // or abs(a) + abs(b)
}

Ponieważ abs(a) nie jest ciągłym wyrażeniem (nawet abs(10) nie będzie działać, ponieważ abs nie zwraca constexpr int !

A co z tym?

C ++ 11
constexpr int Abs(int v)
{
    return v >= 0 ? v : -v;
}

constexpr int Sum(int a, int b)
{
    return Abs(a) + b;
}

Stworzyliśmy własną funkcję Abs która jest constexpr , a ciało Abs również nie łamie żadnej reguły. Ponadto w witrynie wywołującej (w Sum ) wyrażenie przyjmuje wartość constexpr . W związku z tym wywołanie Sum(-10, 20) będzie stałym wyrażeniem czasu kompilacji, którego wynikiem będzie 30 .

Instrukcja statyczna if

C ++ 17

Do warunkowego kompilowania kodu można if constexpr instrukcji if constexpr . Warunek musi być ciągłym wyrażeniem. Nie wybrana gałąź jest odrzucana. Odrzucone oświadczenie w szablonie nie jest tworzone. Na przykład:

template<class T, class ... Rest>
void g(T &&p, Rest &&...rs)
{
  // ... handle p
  if constexpr (sizeof...(rs) > 0)
    g(rs...);  // never instantiated with an empty argument list
}

Ponadto zmienne i funkcje używane odr tylko w odrzuconych instrukcjach nie muszą być definiowane, a odrzucone instrukcje return nie są wykorzystywane do odliczania typu zwracanej funkcji.

if constexpr różni się od #ifdef . #ifdef warunkowo kompiluje kod, ale tylko na podstawie warunków, które można ocenić w czasie przetwarzania wstępnego. Na przykład #ifdef nie mógł zostać użyty do warunkowego skompilowania kodu w zależności od wartości parametru szablonu. Z drugiej strony, if constexpr nie może być użyte do odrzucenia kodu niepoprawnego pod względem składniowym, podczas gdy #ifdef może.

if constexpr(false) {
    foobar;  // error; foobar has not been declared
    std::vector<int> v("hello, world");  // error; no matching constructor
}


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow