Monday, April 19, 2010

Extrinsic Conversions

I've needed recently to convert from chrono::time_point to posix_time::ptime and from chrono::duration to posix_time::time_duration.
This kind of conversions are needed quite often when you use code from two different libraries that have implemented the same concept using of course different representations and have hard coded the library interface to its own implementation. Well this is a normal situation we can't avoid. Life is life.
Quite often we need to convert unrelated types Source and Target. As these classes are unrelated, neither of them offers conversion operators to the other. Usually we get it by defining a specific function such as
Target ConvertToTarget(Source& v);
In my case I started by defining
template <typename Rep, typename Period>
boost::posix_time::time_duration
convert_to_posix_time_time_duration(
  const boost::chrono::duration<Rep, Period>& from);

template <typename Clock, typename Duration>
posix_time::ptime
convert_to_posix_time_ptime(
  const chrono::time_point<Clock, Duration>& from);
Imagine now that you need to convert a std::pair<Source, Source> to a std::pair<Target, Target>. The standard defines conversions of pairs if the related types are C++ convertible:
template <typename T1, typename T2>
struct pair {
  ...
  template<class U, class V>
  //requires Constructible 
  // && Constructible
  std::pair(const pair<U, V>& p);

  template<class U , class V>
  //requires HasAssign 
  // && HasAssign
  std::pair& operator=(const std::pair<U , V>& p);
  ...
};
But as the types Target and Source are not C++ convertible other than using a specific function.
Well we can again define a specific function
std::pair<Target,Target>
ConvertToPairOfTarget(std::pair<Source,Source>& v)
{
  return std::make_pair(
  ConvertToTarget(v.fisrt),
  ConvertToTarget(v.second));
}
While the ConvertToTarget could be specific, the ConvertToPairOfTarget should be generic
template <typename Target1, typename Target2
, typename Source1, typename Source2>
std::pair<Target1,Target2>
ConvertToPair(std::pair<Source1,Source2>& v);

In order to do that we need that the pair template parameters define a common function, let it call convert_to
template <typename Target, typename Source>
Target convert_to(Source& v);
so ConvertToPair can be defined as

template <typename Target1, typename Target2,
typename Source1, typename Source2>
std::pair<Target1,Target2>
ConvertToPair(std::pair<Source1,Source2>& v)
{
  return std::make_pair(
  convert_to<Target1>(v.fisrt),
  convert_to<Target2>(v.second));
}
We need to specialize the convert_to function for the specific classes Source and Target. We can do it as follows
Target  convert_to(Source& v) {
  return ConvertToTarget(v);
}
So now I can convert std::pair<chrono::time_point<Clock, Duration>, boost::chrono::duration<Rep, Period> > to std::pair<boost::posix_time::ptime, boost::posix_time::time_duration> using the ConvertToPair function.
What about converting std::pair<Source,std::pair<Source,Source> > to std::pair<Target,std::pair<Target,Target> >? The issue now is that convert_to(std::make_pair<to, std::make_pair<to,to> >) do not compiles because the conversion of std::pair is named ConvertToPair. So we need to specialize the function convert_to for pairs.
template <typename T1, typename T2,
typename S1, typename S2>
static std::pair<T1,T2>
convert_to(std::pair<Source1,Source2>& from) {
{
  return std::pair<T1,T2>(
  convert_to<T1>(from.first),
  convert_to<T2>(from.second));
}
There is still a last point. The preceding design works well with unrelated classes, but what about classes that already define some kind of conversion, using a constructor or a conversion operator. Do we need to make specialization for these conversion? The answer is no. We need just to define the default implementation of convert_to function to just return the explicit conversion.
template < typename Target, typename Source>
Target convert_to(const Source& from)
{
  return Target(from);
}
Classes or algorithms relying on a conversion by copy-construction or by the conversion operator can be made more generic by relaying in a function that explicitly states this conversion. Thus, instead of requiring
Target(from)
requires
convert_to<Target>(from)
So one of the advantages of using this common functions is uniformity. The other is that now we are able to find all the explicit conversions to one type, as we can do with explicit casts.

C++ Evolution?
C++0x has added explicit conversion operators, but they must always be defined in the Source class. The same applies to the assignment operator, it must be defined on the Target class.
What it will interesting is to be able to add constructors and assignments operators to the class std::pair, so we can say that two pairs are convertible if the parameters are explicitly convertible using a convert_to function
template<class U , class V>
  //requires HasConvertTo 
  // && HasConvertTo
  std::pair& operator=(const std::pair<U , V>& p) {
  return std::make_pair(
  convert_to<T1>(p.first),
  convert_to<T2>(p.second));
}
But this is not currently possible, we can not add operations to a class.

Another possibility could be to make an evolution to the C++ standard, so the convertible concept takes care of extrinsic conversions. We could be able to implicitly or explicitly add extrinsic conversion operators between unrelated types.
template < typename To, typename From >
operator To(const From& val);
For example we could specialize the conversion from chrono::time_point to posix_time::ptime as follows
template < class Clock, class Duration>
operator boost::posix_time::ptime(
const boost::chrono::time_point<Clock, Duration>& from)
{
  using namespace boost;
  typedef chrono::time_point<Clock, Duration> time_point_t;
  typedef chrono::nanoseconds duration_t;
  typedef duration_t::rep rep_t;
  rep_t d = chrono::duration_cast<duration_t>(
  from.time_since_epoch()).count();
  rep_t sec = d/1000000000;
  rep_t nsec = d%1000000000;
  return  posix_time::from_time_t(0)+
    posix_time::seconds(static_cast<long>(sec))+
    posix_time::nanoseconds(nsec);
}

No comments:

Post a Comment