numeric_cast
Korzystając z wzorca numeric_limits, o którym wspomniałem w poprzedniej notce i informacjach o min/max wartości danego typu numerycznego, można w bardzo łatwy sposób stworzyć sobie bezpieczny, odporny na underflow/overflow, „operator” – wzorzec do konwersji wartości liczbowych pomiędzy poszczególnymi typami numerycznymi.
template <typename TargetType, typename SourceType> inline TargetType numeric_cast(SourceType arg) { if (arg < std::numeric_limits<TargetType>::min()) throw std::underflow_error("bad numeric cast"); if (arg > std::numeric_limits<TargetType>::max()) throw std::overflow_error("bad numeric cast"); return static_cast<TargetType>(arg); } |
Drugi typ argumentu wzorca można pominąć, zostanie on wydedukowany przez kompilator na podstawie typu argumentu przekazanego do funkcji, więc konstrukcja ta przyjmuje prostą postać podobną do wbudowanych operatorów rzutowania:
try { char c = 100; int i = -129; unsigned int ui = 12345; long long l = 453232; c = numeric_cast<char>(i); // overflow_error i = numeric_cast<int>(c); i = numeric_cast<int>(ui); // underflow_error l = numeric_cast<long long>(i); } catch (const std::underflow_error& e) { std::cout << "underflow_error: " << e.what() << endl; } catch (const std::overflow_error& e) { std::cout << "overflow_error: " << e.what() << endl; } |
W rzeczywistości konstrukcja takiego „operatora” nie jest taka prosta jak się może wydawać i powyższy program nie zadziała tak jak można byłoby się spodziewać (według podanych komentarzy w przykładzie), a dlaczego nie to zostawiam do przemyślenia ;)
Trzeba rozważyć wiele przypadków, mozliwości i problemów, o czym można się przekonać oddając się lekturze topicu Rzutowanie sprawdzające zakresy z pl.comp.lang.c.
Swoja drogą boost zawiera w swoich zbiorach numerycznych konwerterów własny, bezpieczny numeric_cast i chyba najrozsądniej, jeśli już trzeba to jego używać. Jego konstrukcja opiera się na podobnej do przedstawionej wyżej koncepcji.