Mechanizm promocji w C++

18.05.2010 @ 11:33:28 by Rafał Kozik | C++ szablony programowanie

W standardzie języka C (i C++) opisany jest mechanizm promocji, który polega na automatycznej konwersji z mniejszego typu na większy (np. short na int). Istnieją dwa rodzaje promocji:

  • promocja całkowita (ang. integral promotion) -- konwersja liczb całkowitych
  • promocja zmiennoprzecinkowa (ang. floating point promotion) -- konwersja liczb zmiennoprzecinkowych

Zachowanie to jest bardzo intuicyjne. Dodatkowo, w przypadku użycia funkcji o zmiennej ilości argumentów (tzn. użycia ...), małe typy całkowitoliczbowe ulegają promocji do int / unsigned int, a float do double. To jest już trochę mniej naturalne, ale ta wiedza może czasami się przydać (choćby do tego, żeby tego nie używać ;)).

W C++, z mechanizmem promocji wiąże się dodatkowy problem przy pisaniu funkcji szablonowych. Typowa implementacja max wygląda tak:

template <typename T>
const T& max(const T& a, const T& b)
{
    return (a > b) ? a : b;
}

Wszystko byłoby fajnie, gdyby nie fakt, że wywołanie max(1.0f, 2.0) się nie skompiluje. Da się to rozwiązać bawiąc się trochę szablonami -- o tym w dalszej części.



Możemy napisać szablon max tak, żeby przyjmował parametry dwóch różnych typów. Problemem jest to, co powinna taka funkcja szablonowa zwrócić. Wartość pierwszego typu? Wartość drugiego typu? Najlepiej byłoby po części skorzystać z mechanizmu promocji i po prostu zwrócić wartość większego typu. Można zrobić to tak:

// szablon, który pozwoli nam wybrać większy typ
template <typename T, typename R, bool first>
struct type_selector;

template <typename T, typename R>
struct type_selector<T, R, true>
{
	typedef T type;
};
 
template <typename T, typename R>
struct type_selector<T, R, false>
{
	typedef R type;
};

// nasz mechanizm promocji
template <typename T, typename R>
struct promotion
{
	typedef typename type_selector<T, R, (sizeof(T) > sizeof(R))>::type type;
};

// zmodyfikowana wersja max
template <typename T, typename R>
typename const promotion<T, R>::type& max(const T& t, const R& r)
{
	typedef typename promotion<T, R>::type RT;
	RT pt = t;
	RT pr = r;
	return (pt > pr) ? pt : pr;
}

Prawda, że proste? ;) Przypisanie do zmiennych typu RT jest możliwe tylko wtedy gdy zadziała mechanizm promocji. Do kompletu wypadałoby dopisać jeszcze wersję max dla jednego typu parametru.

Wybór zwracanego typu przez funkcję jest prostym przykładem tego jak można podejmować decyzje w trakcie kompilacji.


Komentarze

Reg
2010-05-23 @ 19:35:18

Mnie to jednak nie wygląda na proste :P Dlatego w sytuacji takiej jak tu opisałeś z std::max - która mnie też często się zdarza - piszę po prostu jawnie parametr szablonu, np. std::max<float>(v1, v2);
Komentowanie zostało tymczasowo wyłączone.