Boost logo

Boost :

Subject: [boost] [conversion] Motivation for two NEW generic conver_to and assign_to functions
From: vicente.botet (vicente.botet_at_[hidden])
Date: 2009-10-23 07:17:10


Hi,

I would like to share with you what motivated me to add two new free template functions convert_to<> and assign_to<> on the Boost.Conversion.

I've needed recently to convert from boost::chrono::time_point<Clock, Duration> to boost::posix_time::ptime and from boost::chrono::duration<Rep, Period> to boost::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<T1, const U&> && Constructible<T2, const V&>
        std::pair(const pair<U, V>& p);

        template<class U , class V>
        //requires HasAssign<T1, const U&> && HasAssign<T2, const V&>
        std::pair& operator=(const std::pair<U , V>& p);
        ...
    };

But here the types Target and Source are not 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);

Thus `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

    template <>
    Target convert_to<Target, Source>(Source& v) {return ConvertToTarget(v);}

In my case I need

    template <typename Rep, typename Period>
    boost::posix_time::time_duration
    convert_to<posix_time_time_duration, boost::chrono::duration<Rep, Period> >
            (const boost::chrono::duration<Rep, Period>& from)
    {
        return convert_to_posix_time_time_duration(from);
    }
    template <typename Clock, typename Duration>
    boost::posix_time::ptime
    convert_to<posix_time_ptime, chrono::time_point<Clock, Duration> >
            (const boost::chrono::time_point<Clock, Duration>& from)
    {
        return convert_to_posix_time_ptime(from);
    }

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<Target1,T2>, std::pair<S1,S2> >(
    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);
    }

With compilers supporting partial specialization of function templates there is no major problem. For the others, we need to use a trick; as it allows partial specialization of classes we can define convert_to by as relying to a specific function of a class, as follows:

    namespace partial_specialization_workaround {
        template < typename Target, typename Source >
        struct convert_to {
            static Target apply(const Source& val);
        };
    }

    template < typename Target, typename Source >
    Target convert_to(const Source& val) {
        return partial_specialization_workaround::convert_to<Target,Source>::apply(val);
    }

So now we can specialize `partial_specialization_workaround::convert_to` for pairs as follows:

    namespace partial_specialization_workaround {
        template <typename Target1, typename Target2, typename Source1, typename Source2)
        struct convert_to< std::pair<Target1,Target2>, std::pair<Source1,Source2> > {
            static std::pair<Target1,Target2> apply(std::pair<Source1,Source2>& v) {
            {
                return std::pair<T1,T2>(convert_to<T1>(from.first), convert_to<T2>(from.second));
            }
        };
    }

Conclusion
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)
require
    convert_to<Target>(from)

The same applies to classes or algorithms relying on the assignment operator. So instead of requiring
    to = from
require
    assign_to(to, from);

The default implementation of assign_to relies on the assignment operator

    template < typename Target, typename Source >
    To& assign_to(Target& to, const Source& from)
    {
        to = from;
        return to;
    }

and with the workaround

    namespace partial_specialization_workaround {
        template < typename Target, typename Source >
        struct assign_to {
            static To& apply(Target& to, const Source& from)
            {
                to = from;
                return to;
            }
        };
    }
    template < typename Target, typename Source >
    To& assign_to(Target& to, const Source& from) {
        return partial_specialization_workaround::assign_to<Target,Source>::apply(to, from);
    }

For classes that are explicitly convertible and having a self assignment operator it is easy to make a specialization of assign_to as follows.

    to = convert_to<Target>(from);

The rationale is that if there was not a copy constructor from a Source seems reasonable to think that there will not be an assignment operator. So in most of the cases, once we have specialized the convert_to function we recover a reasonable implementation for the assign_to function.

When doing multiple assignments we use to do

    a = b = c;

With assign_to we could do

    assign_to(a, assign_to(b, c));

and if we find this not really readable we can try with

    tie(a) = tie(b) = c;

The behavior of tie recall the tie function of Boost.Tuple, but instead assigning multiple variable at once, assign only one by calling assign_to.

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.

We can even generalize this, so classes or algorithms relying on a member function, can be made more generic by relaying on a free function. The default function implementation could just call to the member function with the equivalent prototype, but this is out of the scope of this library. Note that this is already the case of the free functions as swap(), begin(), next(), end(), ...

What about changing the C++ standard?

C++1x 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<T1, const U&> && HasConvertTo<T2, const V&>
    std::pair& operator=(const std::pair<U , V>& p) {
        return std::make_pair(convert_to<T1>(p.first), convert_to<T1>(p.second));
    }

But this is not possible. We can not add operators to a class from outside.

A more generic alternative could be to make an evolution to the standard, so the convertible concept takes car of extrinsic conversions. We could be able to implicitly or explicitly add extrinsic conversion operators between unrelated types. Assignment operators could also be specialized.

    template < typename To, typename From >
    operator To(const From& val);

    template < typename To, typename From >
    To& operator=(To& to, const From& from);

For example we could specialize the conversion from as chrono::time_point<Clock, Duration> to posix_time::ptime follows

    template < class Clock, class Duration>
    operator boost::posix_time::ptime(const boost::chrono::time_point<Clock, Duration>& from) {
        typedef boost::chrono::time_point<Clock, Duration> time_point_t;
        typedef boost::chrono::nanoseconds duration_t;
        typedef boost::duration_t::rep rep_t;
        rep_t d = boost::chrono::duration_cast<duration_t>(from.time_since_epoch()).count();
        rep_t sec = d/1000000000;
        rep_t nsec = d%1000000000;
        return boost::posix_time::from_time_t(0)+
                    boost::posix_time::seconds(static_cast<long>(sec))+
                    boost::posix_time::nanoseconds(nsec);
    }

The convert_to and the assign_to functions preted to be a library solution to the convertibility of unrelated types problem.

Best regards,
_____________________
Vicente Juan Botet Escribá


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