Służy do wnioskowania typu na podstawie wyrażenia. Przykłady:
// && zachowuje się tutaj podobnie jak & albo * const int&& fx() { return 5; } class A { public: double x; static double sx; double xx() const { return 5; } static double sxx() { return 5; } }; void test1() { // zadeklarujmy trochę zmiennych. int i1 = 3; const int i2 = 5; double f1 = 5; const double f2 = 6; const A* a = new A(); // // decltype z zmiennej to typ zmiennej // decltype(i1) x41 = i1; // int x41 = i1; decltype(i1) x42 = i2; // int x42 = i2; decltype(i2) x21 = i1; // const int //x21 = i1; błąd decltype(i2) x22 = i2; // const int //x22 = i2; błąd // decltype z wywołania funkcji to rezultat tej funkcji decltype(fx()) x11 = fx(); // const int&&, funkcja nie // zostanie wywołana, typ ustalany // jest podczas kompilacji. const int&& x12 = fx(); // decltype z wskaźnika na funkcje to wskaźnik na funkcje. decltype(fx) x2; // const int&&() const int&& (*x3)() = fx; decltype(a->x) x33 = f1; // double x33 = f1; decltype(a->x) x34 = f2; // double x34 = f2; decltype((a->x)) x35 = f1; // const double& //x35 = f1; błąd decltype((a->x)) x36 = f2; // const double& //x36 = f1; błąd // decltype może nam pomóc w definiowaniu skomplikowanych // wskaźników na funkcję. decltype(A::sx) x51; // double decltype(&A::x) x52 = &A::x; // double A::* x51 = 5; double x53 = a->*x52; // decltype można wykorzystać do definiowania typów. typedef decltype(A::sx) XX; XX aa = 5; decltype(&A::sxx) x61 = &A::sxx; // double (A::*)() decltype(&A::xx) x62 = &A::xx; // double A::*)() x61(); (a->*x62)(); } // Robimy dziwne rzeczy i oczekujemy normalnych od kompilatora. void test_stupid() { decltype(1/0) x71; // int, wyrażenie nie jest wyliczane. // Typ jest ustalany podczas kompilacji. decltype(exit(2)) x76(); // funkcja nie zostanie wywołana. decltype(1.0/0.0) x72; // double decltype((float)1.0/0.0) x73; decltype(1.0f/0) x74; // float //decltype(float) x75; // błąd, musi być wyrażenie // jakkolwiek auto wydaje się bardziej naturalne do // deklarowania zmiennych, decltype pozwala na // stworzenie zmiennej bez jej inicjalizacji. decltype(5) x81; // x81 jest typu int x81 = 5; //auto x82; błąd, musi zostać zainicjalizowane } template <class R, class X, class Y> R add_old(X x, Y y) { return x + y; } void test_template_old() { auto x1 = add_old<int>(5, 5); // int auto x2 = add_old<double>(5, 5.0); // double auto x3 = add_old<double>(5.0, 5); // double auto x4 = add_old<double>(5.0, 5.0); // double std::string str1 = "1"; std::string str2 = "1"; auto str3 = add_old<std::string>(str1, str2); } // Tutaj ujawnia się prawdziwa zaleta decltype, // zwracany typ zależy od wyniku operacji, // pośrednio od typu parametrów szablonu. template <class X, class Y> auto add_new(X x, Y y) -> decltype(x+y) { return x + y; } void test_template_new() { auto x1 = add_new(5, 5); // int auto x2 = add_new(5, 5.0); // double auto x3 = add_new(5.0, 5); // double auto x4 = add_new(5.0, 5.0); // double std::string str1 = "1"; std::string str2 = "1"; auto str3 = add_new(str1, str2); } int main(int argc, char* argv[]) { test1(); test_template_old(); test_template_new(); test_stupid(); return 0; }
W funkcji
test1
widzimy pierwszy rodzaj zastosowania. Zamiast definiować typ możemy zrzucić na kompilator by sam go określił na podstawie wyrażenia. Tak zdefiniowany za pomocą decltype
typ możemy wykorzystać do określenia typu zmiennej albo do stworzenia nowego typu (linia 78). Oczywiście do deklarowania typu zmiennej lepiej nadaje się auto
. Znowu auto
nie wykorzystamy do tworzenia nowego typu. W przeciwieństwie do decltype
słowo kluczowe auto
wymaga od nas zainicjalizowania zmiennej aby zgadnąć typ (linia 103). Słowo decltype
może być szczególnie ważne w przypadku korzystania ze skompilowanej struktury szablonów albo z wyrażeń lambda, gdzie ręczne podanie typu może być nie lada wyzwaniem. W linii 96 widzimy, że
delctype
nie zadziała dla typu. Jest to błąd kompilacji.W linii 64 widzimy zastosowanie nawiasów które pełnią tutaj rolę specyficznego operatora 'referencja do'. Ponieważ obiekt jest zadeklarowany jako
const
referencja jest też stała.Jeśli wyrażeniem jest wywołanie funkcji to typem jest wynik funkcji (linia 49). Jeśli podamy adres funkcji typem będzie wskaźnik na funkcję.
Drugi ważny rodzaj zastosowania widzimy w
test_template_new
. Dla porównania mamy jakby to wyglądało po staremu test_template_old
. Tutaj słowo kluczowe decltype
służy do określania typu rezultatu funkcji na podstawie wyrażenia zawierającego typy z szablonu. Porównując obie wersje wydaję się, że po co tyle zachodu - o jeden dodatkowy argument do podania. Tak naprawdę siła decltype
ujawnia się w skomplikowanym systemie klas szablonowych, kiedy odgadnięcie typu musi być zastosowane gdzieś głęboko. Wtedy nawet możemy nie rozumieć sensu tego dodatkowego parametru podawanego w starej wersji. W klasach szablonowych słowo kluczowe decltype
pozwala nam zredukować ilość klas szablonowych, a także sprawić, że już napisane klasy będą mogły łatwiej współpracować z naszymi typami. Rozpatrzmy np. zestaw klas które służą nam do implementowania wyrażeń arytmetycznych. Jedna klasa Add
jest w stanie dodawać dowolne typy, jeśli tylko mają one zaimplementowany operator +
. Podsumowując:
- łatwiejsze tworzenie nowych typów
- łatwiejsze deklarowanie zmiennych (z
auto
jeszcze łatwiejsze) - uproszczone korzystania z i tworzenie szablonów
Brak komentarzy:
Prześlij komentarz