Boost logo

Boost :

From: Fernando Luis Cacciola Carballal (fcacciola_at_[hidden])
Date: 2000-06-08 08:43:12


Hello boosters!

I'm working in the generalization of algorithms for arbitrary numeric
types.
As such, I got interested in numeric_cast<>.

I had a couple of problems which I solved with a little fix. These
problems arised because I'm using some number classes that I borrowed
which are not optimized for copy operations. That is, passing those
numbers by value is a problem because of the unnecesary copying
involved. The same applies to returning by value.

To illustrate this assume we have a class integer_t, which uses a
dynamic buffer to allocate an integer of arbitrary length, and which
copies the entire buffer each time an integer_t is copied.

  1) The first problem is that the signature of numeric_cast<> is:

       template<typename Target, typename Source>
       inline Target numeric_cast(Source arg)

     This way, in:

       integer_t a(0);
       int b = numeric_cast<int>(a);

     a temporary integer_t will be used as the cast argument.

     I solved this using call_traits<> (explicitely speciallized for
integer_t). I changed the signature for:

       template<typename Target, typename Source>
       inline Target numeric_cast( call_traits<Source>::param_type
arg)

  2) The second problem was that I found that some algorithms use
numeric_cast<> to
     deal with 2 parametrized numeric types.
     Take the following example:

       scalar_type length ( call_trais<coord_type>::param_type x ,
                            call_trais<coord_type>::param_type y
                          )
       {
         return sqrt ( numeric_cast<scalar_type>(x)
*numeric_cast<scalar_type>(x)
                       + numeric_cast<scalar_type>(y)
*numeric_cast<scalar_type>(y)
                     ) ;
       }

     A numeric_cast<> is needed because, for example, if scalar_type
is double and coord_type is int (we want the distance between two
integer coordinate points) we have to convert to double to perform an
accurate calculation. This way, length(1,1) is 1.41 and not 1.

     Now, what if both scalar_type and coord_type are of the same
type? Futhermore, what if they are both integer_t?

     What I really need here is a numeric_cast<> smart enought to
actually bypass the convertion if the types are the same.

     With this in mind, I've rewritten numeric_cast<>.

     I believe that its interface remains EXACTLY the same (that is,
no user code will notice the difference).

     The rewritten version will expand to 'nothing' when Source and
Target are the same type. The trick is to move the implementation to
an auxiliary template class which is specialized for <Source,Source>

     Here it is:

       // numeric_cast_imp__ is used to allow a convertion between
the same
       // type to do nothing.
       template<typename Target,typename Source>
       struct numeric_cast_imp__
       {
         // ret_type is a COPY when Target!=Source.
         typedef typename Target ret_type ;

         // Actual convertion is done here when Target!=Source.
         static ret_type convert ( call_traits<Source>::param_type
arg )
         {
           // typedefs abbreviating respective trait classes
           typedef std::numeric_limits<Source> arg_traits;
           typedef std::numeric_limits<Target> result_traits;

           // typedefs that act as compile time assertions
           // (to be replaced by boost compile time assertions
           // as and when they become available and are stable)
           typedef bool argument_must_be_numeric
[arg_traits::is_specialized];
           typedef bool result_must_be_numeric
[result_traits::is_specialized];

           if( (arg < 0 && !result_traits::is_signed) || // loss of
negative range
                 (arg_traits::is_signed &&
                    arg < result_traits::min()) || // underflow
                  arg > result_traits::max() ) // overflow
               throw bad_numeric_cast();
           return static_cast<Target>(arg);
         }
       };

       // This partial specialization allows the compiler to optimize
the entire call
       // when Target=Source, so no convertion is actually performed.
       template<typename Source> struct
numeric_cast_imp__<Source,Source>
       {
         // ret_type is exactly of the same type of arg. (normally a
const&)
         typedef typename call_traits<Source>::param_type ret_type ;
         static ret_type convert ( call_traits<Source>::param_type
arg )
           { return arg ; }
       };

       template<typename Target, typename Source>
       inline
       numeric_cast_imp__<Target,Source>::ret_type
         numeric_cast( call_traits<Source>::param_type arg)
       {
         return numeric_cast_imp__<Target,Source>::convert(arg);
       }

P.S.: I think that a static_cast<> is no better than an implicit
cast, so I think that a simple: return arg; should suffix.
(instead of return static_cast<Target>(arg); )

Thanks.
Hope this new cast helps as much as it helped me.

Fernando Cacciola.
fcacciola_at_[hidden]


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk