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:
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:
int main()
{
constexpr int size = 10;
int arr[size];
}
Wyrażenie constexpr
musi oceniać na wartość czasu kompilacji. Dlatego nie możesz użyć:
constexpr int size = abs(10);
Chyba że sama funkcja ( abs
) sama zwraca constexpr
.
Wszystkie podstawowe typy mogą być inicjowane przy pomocy constexpr
.
constexpr bool FailFatal = true;
constexpr float PI = 3.14f;
constexpr char* site= "StackOverflow";
Co ciekawe i wygodnie, możesz także użyć auto
:
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:
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
:
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:
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:
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:
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:
constexpr int Sum(int a, int b)
{
constexpr int a1 = a; // ERROR
..
Ponadto następujące kompilacje również nie będą się kompilować:
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?
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
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
}