Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 19.  Casts


19.6. literal_cast

Although the use of literal integer constants in our code is not the done thing, we nevertheless use named constants quite often. Sometimes it is possible to use a constant in an inappropriate way, and risk loss of information through truncation. Although most good compilers warn of constant truncation, several do not, or do not in their commonly used configurations. In any case it is still only a warning. I know that none of you gentle readers would let such a warning slip through your build/release process, but there are people who are less diligent, or who are forced to suppress some warnings, or who "inherit" such warning suppressions from poorly written third-party libraries. It would be nice to have a construct that detected truncation and forced a compile-time error.

Just such a construct is the template function literal_cast, shown in Listing 19.8.

Listing 19.8.


#ifdef ACMELIB_64BIT_INT_SUPPORT


typedef int64_t   literal_cast_int_t;


#else /* ? ACMELIB_64BIT_INT_SUPPORT */


typedef int32_t   literal_cast_int_t;


#endif /* ACMELIB_64BIT_INT_SUPPORT */





template< typename T


        , literal_cast_int_t V


        >


inline T literal_cast()


{


  const int literal_cast_value_too_large =


                        V <= limit_traits<T>::maximum_value;


  const int literal_cast_value_too_small =


                        V >= limit_traits<T>::minimum_value;


  STATIC_ASSERT(literal_cast_value_too_large);


  STATIC_ASSERT(literal_cast_value_too_small);


  return T(V);


}



It uses a limits_traits template class that provides the member constants (see section 15.5.3) minimum_value and maximum_value. It works by treating the constant to be tested as a signed integer of the maximum size for the compiler—literal_cast_int_t; for example, 64 bits for 32-bit compilers. This is then tested against the cast type's minimum and maximum values in the static assertions (see section 1.4.8).

In the comparisons, all values are promoted to literal_cast_int_t, so for all types smaller than this, the case provides a comprehensive evaluation of the type's value. Assuming literal_cast_int_t is 64-bits, consider the following code:



const int I   = 200;


sint8_t   i8  = literal_cast<sint8_t, I>();  // Compile error


uint8_t   ui8 = literal_cast<uint8_t, I>();  // Compiles ok


sint16_t  i16 = literal_cast<sint16_t, I>(); // Compiles ok



There's a limitation in its application to casts to 64-bit types, by virtue of the fact that it uses the signed 64-bit type for the constant. If you specify uint64_t as the cast type, and pass in a value larger than the maximum positive signed value, you will get truncation or sign conversion. This is a flaw in the technique, to which the language can provide no remedy; we cannot escape the truth that there's no recourse to a larger type than the largest type available.

The only answer is to prevent the cast being used with the largest unsigned integer type. This is easy to do using partial specialization, but it requires that we transform the cast from a function to a class, as shown in Listing 19.9.

Listing 19.9.


#ifdef ACMELIB_64BIT_INT_SUPPORT


typedef int64_t   literal_cast_int_t;


typedef uint64_t  invalid_int_t;


#else /* ? ACMELIB_64BIT_INT_SUPPORT */


typedef int32_t   literal_cast_int_t;


typedef uint32_t  invalid_int_t;


#endif /* ACMELIB_64BIT_INT_SUPPORT */





template< . . . >


class literal_cast


{


public:


  operator T () const


  {


    . . . // the rest of the previous implementation


  }


};





template<literal_cast_int_t V>


class literal_cast<invalid_int_t, V>


{


private:


  operator invalid_int_t () const


  {


    const int cannot_literal_cast_to_largest_unsigned_integer = 0;


    STATIC_ASSERT(cannot_literal_cast_to_largest_unsigned_integer);


    return 0;


  }


};



The specialization for the new invalid_int_t type, representing the largest unsigned integer type, hides the conversion operator, thereby preventing use of the cast with this type. For good measure, the operator contains a static assertion (see section 12.4.8), which will aid the unsuspecting developer a little better than the "operator is inaccessible" compile errors they would otherwise only receive.

Now you can cast literals to any type (except the largest unsigned integer type) in your code with complete confidence against truncation.

Before I complete this section, I'd like to mention that the Boost libraries contains a run time analog of this cast, called numeric_cast, written by Kevlin Henney. Whether you want compile time or run time detection of truncation, your bases are covered.


      Previous section   Next section