Suppose you need to write a generic function to convert angles from degrees to radians. The usual way is to declare a constant to hold the value of one radian in degrees [1]:
#define RADIAN (180/M_PI)Then you write your conversion function something like this:
template<typename T> T toRAD (T const& deg) { return deg / T(RADIAN); }Now suppose the user of your code has a library with a bigfloat_t number type, and that this bigfloat_t can be constructed from a double. The user might call your toRad function as follows:
bigfloat_t alfa (45.0); bigfloat_t beta = toRAD (alfa);This code will cause the compiler to instantiate the function toRAD<bigfloat_t>, which is equivalent to:
bigfloat_t toRAD (bigfloat_t const& deg) { return deg / bigfloat_t(RADIAN); }What's the problem here? Remember that in expressions of the form T(CONSTANT) or const T & C = CONSTANT that the T object is constructed (or copy initialized) using a constructor that takes the same type as the literal CONSTANT [2]. No matter how CONSTANT is declared, its type is unique and will not necessarily be of the same type as T. In the above example, the expression bigfloat_t(RADIAN) will use the bigfloat_t constructor that takes a double value. As a result, the newly constructed bigfloat_t will have no more precision than a double.
Math libraries often provide definitions for commonly used numbers, such as pi. For example, the bigfloat library might provide an external symbol, BigFloatRadian_, that represents this number with a precision suitable for bigfloat_t. The user would naturally want toRad to use the full-precision BigFloatRadian_ in its calculation of degrees to radians. This tip shows how to preserve such precision in generic calculations involving constants.
When writing generic code involving constants such as RADIAN, M_PI, etc., don't use expressions of the form T(CONSTANT). Instead, define and use a template function as shown in the following snippet:
template<typename T> inline T cRadian() {return 180 / M_PI;} template<typename T> T toRAD ( T const& deg ) { return deg / cRadian<T>() ; }The user can now add an explicit specialization of the template function cRadian for type bigfloat_t:
template<> inline bigfloat_t cRadian<bigfloat_t>() {return BigFloatRadian_;}When the compiler sees a call to the function cRadian<bigfloat_t>, it will substitute this special definition for the more general one.
For the specialization for bigfloat_t to work, it must return a reasonable bigfloat_t representation of one radian. If the library does not provide such a representation, it may be possible to use a helper function to compute the value at run time.
[Fernando also showed how to return a reference to a generic constant instead of a copy, but the solution is rather involved. We may run it as a separate tip. mb]
Notes
Thanks to Fernando Cacciola for this tip. If you have a C/C++ programming tip and can write it up in 400 words or less, send it to mbriand@cmp.com. If we print your tip, we'll send you a nice CUJ shirt and a copy of the CUJ CD-ROM.