Uogólnione stałe wyrażenia - constexpr
¶
C++11 wprowadza dwa znaczenia dla „stałej”:
constexpr
- stała ewaluowana na etapie kompilacjiconst
- stała, której wartość nie może ulec zmianie
Stałe wyrażenie (constant expression) jest wyrażeniem ewaluowanym przez kompilator na etapie kompilacji. Nie może zawierać wartości, które nie są znane na etapie kompilacji i nie może mieć efektów ubocznych.
Jeśli wyrażenie inicjalizujące dla constexpr
nie będzie mogło być
wyliczone na etapie kompilacji kompilator zgłosi błąd:
int x1 = 7; // variable
constexpr int x2 = 7; // constant at compile-time
constexpr int x3 = x1; // error: initializer is not a contant expression
constexpr auto x4 = x2; // Ok
W wyrażeniu contexpr
można użyć:
- Wartości typów całkowitych, zmiennoprzecinkowych oraz wyliczeniowych
- Operatorów nie modyfikujących stanu (np. +, ? i [] ale nie = lub ++)
- Funkcji
constexpr
- Typów literalnych
- Stałych
const
zainicjowanych stałym wyrażeniem
Stałe wartości constexpr
¶
W C++11 constexpr
przed definicją zmiennej definiuje ją jako stałą,
która musi zostać zainicjowana wyrażeniem stałym.
Stała const
w odróżnieniu od stałej constexpr
nie musi być
zainicjowana wyrażeniem stałym.
constexpr int x = 7;
constexpr auto prefix = "Data";
constexpr double pi = 3.1415;
constexpr double pi_2 = pi / 2;
(const double) 1.570750
Funkcje constexpr
¶
W C++11 funkcje mogą zostać zadeklarowane jako constexpr
jeśli spełniają dwa wymagania:
- Ciało funkcji zawiera tylko jedną instrukcję
return
zwracającą wartość, która nie jest typuvoid
- Typ wartości zwracanej oraz typy parametrów powinny być typami dozwolonymi dla wyrażeń
constexpr
C++14 znacznie poluzowuje wymagania stawiane przed funkcjami constexpr
. Funkcją constexpr
może zostać dowolna funkcja o ile:
- nie jest wirtualna
- typ wartości zwracanej oraz typy parametrów są typami literalnymi
- zmienne użyte wewnątrz funkcji są zmiennymi typów literalnych
- nie zawiera instrukcji
asm
,goto
, etykiet oraz blokówtry-catch
- zmienne użyte wewnątrz funkcji nie są statyczne oraz nie są
thread-local
- zmienne użyte wewnątrz funkcji są zainicjowane
Funkcje ``constexpr`` nie mogą mieć żadnych efektów ubocznych. Zapisywanie stanu do nielokalnych zmiennych jest błędem kompilacji.
Przykład rekurencyjnej funkcji constexpr
:
constexpr int factorial(int n)
{
return (n == 0) ? 1 : n * factorial(n-1);
}
Funkcja constexpr
może zostać użyta w kontekście, w którym wymagana
jest stała ewaluowana na etapie kompilacji (np. rozmiar tablicy natywnej
lub stała będąca parametrem szablonu):
#include <array>
const int size = 2;
int arr1[factorial(1)];
int arr2[factorial(size)];
std::array<int, factorial(3)> arr3;
template <typename T, size_t N>
constexpr size_t size_of_array(T(&)[N])
{
return N;
}
int arr4[factorial(size_of_array(arr2))];
(int [2]) { 0, 0 }
Instrukcje warunkowe w funkcjach constexpr
¶
Pominięty blok kodu w instrukcji warunkowej nie jest ewaluowany na etapie kompilacji.
constexpr int low = 0;
constexpr int high = 99;
#include <stdexcept>
constexpr int check(int i)
{
return (low <= i && i < high) ? i : throw std::out_of_range("range error");
}
constexpr int val0 = check(50); // Ok
constexpr int val2 = check(200); // Error
Typy literalne¶
C++11 wprowadza pojęcie typu literalnego (literal type), który
może być użyty w stałym wyrażeniu constexpr
:
Typem literalnym jest:
- Typ arytmetyczny (całkowity, zmiennoprzecinkowy, znakowy lub logiczny)
- Typ referencyjny do typu literalnego (np:
int&
,double&
) - Tablica typów literalnych
- Klasa, która:
- ma trywialny destruktor (może być
default
) - wszystkie niestatyczne składowe i typy bazowe są typami literalnymi
- jest agregatem lub ma przynajmniej jeden konstruktor
contexpr
, który nie jest konstruktorem kopiującym lub przenoszącym (konstruktor musi mieć pustą implementację, ale umożliwia inicjalizację składowych na liście inicjalizującej)
- ma trywialny destruktor (może być
class Complex
{
double real_, imaginary_;
public:
constexpr Complex(const double& real, const double& imaginary)
: real_ {real}, imaginary_ {imaginary}
{}
constexpr double real() const { return real_; };
constexpr double imaginary() const { return imaginary_; }
};
constexpr Complex c1 {1, 2};
Przykłady zastosowań wyrażeń i funkcji stałych (constexpr
)¶
Operacje na polach bitowych¶
Interesującym zastosowaniem funkcji constexpr
jest implementacja
operatorów bitowych dla wyliczeń.
namespace Constexpr
{
enum class Bitmask { b0 = 0x1, b1 = 0x2, b2 = 0x4 };
constexpr Bitmask operator|(Bitmask left, Bitmask right)
{
return Bitmask( static_cast<int>(left) | static_cast<int>(right) );
}
}
Umożliwia to, zastosowanie czytelnych wyrażeń bitowych np. w etykietach
instrukcji switch
:
#include <iostream>
using namespace std;
using namespace Constexpr;
Bitmask b = Bitmask::b0 | Bitmask::b1;
switch (b)
{
case Bitmask::b0 | Bitmask::b1:
cout << "b0 | b1 - " << static_cast<int>(b) << endl;
break;
default:
cout << "Other value...";
}
b0 | b1 - 3