Wednesday, January 28, 2015

Type conversion between C++ template instantiations

I ran into a frustrating C++ problem the other day.

Say you have a template class like such:

template<typename T>
class foo
{

}

Now you try to do something like this:

foo<int> bob();
foo<const int> sally = bob;

Intuitively, people who are new to the C++ type system would say that it seems obvious that foo<int> can be converted to foo<const int>.

Consider, though, that the implementations of foo<int> and foo<const int> might be very different.

template<>
class foo<int> : public GUI_related_class
{
}

template<>
class foo<const int> : public Network_socket_related_class
{
}
That implementation of the two classes makes it very clear that they are not trivially convertible. As such the c++ type system treats them as two completely separate types with no relationship to each other, and for good reason. In C++ template's aren't types. Templates are instructions for the compiler to create new types. They are more like the molds used to stamp out plastic parts, where you can fill in the type of plastic that you want the mold to use, than they are like types themselves.

But what if your template doesn't have any specializations like that? What if you know, without a shadow of a doubt, that you should be allowed to do this?

foo<const int> bob = foo<int>();
To accomplish this you'll need to define a few custom type conversion operators.
 Lets redefine our template foo so that it knows how to do this conversion.

You might first try this implementation
template<typename T>
struct foo
{
    foo()
    {}
    foo(const foo<const T> &)
    {}
};
 But that wont work, as it's actually accomplishing the opposite and allowing what amounts to a const-cast. Generally you want to avoid permitting going from const to non-const like this example.
    foo<const int> sally;
    foo<int> bob(sally);
If you use a version of C++ with TR1, or C++11, you can use the remove_const template feature like so:
template<typename T>
struct foo
{
    foo()
    {}
    foo(const foo<typename std::tr1::remove_const<T>::type> &)
    {}
};
 foo<int> sally;
 foo<const int> bob(sally);
 foo<const int> alice(bob);

If you don't need a user defined copy constructor, then this accomplishes the goal and you're done! Notice that in the case that T isn't const to begin with, the second constructor becomes a copy constructor and overrides the default compiler-generated one. I suspect that for a lot of people this is the end of the road and they don't need to do anything extra.


 For sake of argument though, what if you need to define your own copy constructor?

Well, now any time you have foo with a non const template parameter, your remove_const ends up generating a duplicate copy constructor, and thus this breaks.

We can use the template metaprogramming technique called SFINAE (Substitution failure is not an error), to define a constructor that takes the non-const version of our template if and only if our current template paramater is const. 

If you're using C++11, then you can use the version of these found in the standard library, but if you're on an older implementation you'll probably want to check out the BOOST libraries for an implementation, like such:
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/type_traits/is_const.hpp>

Now, the trick is that we need to have a template function inside of our template class, such that the template function encounters a substitution failure in all cases except for the specific parameters that we want.

Keep in mind that this only works with template functions. If you try it with only a regular function inside of a template class, you'll end up with classes that can't be properly declared because of type resolution problems, and it's just not fun. But inside of a class (whether a template class or not), you can declare template functions that, if they were instantiated with specific template parameters, would result in a compiler error.

This is allowed because there are many different function resolution paths to take when determining which function you, as the programmar, mean for the compiler to call, and not all of the possible paths would result in a perfect compile. So the compiler simply discards any that would result in an error and continues searching. If it can find a template instantiation that can work, even after dozens of failed versions, it'll use that and continue on its way.

We can use this to our advantage to explicitly forbid the compiler from successfully creating certain instantiations of template functions, allowing for what is effectively compile-time-generated decision making. Template metaprogramming.

So a first pass at using enable_if might look like this.
template<typename T>
struct foo
{
    foo()
    {}
    foo(const foo<T> &)
    {}
    template<typename C>
    foo(const foo<C> &,
          typename boost::enable_if_c<       boost::is_const<T>::value
                                                       && !boost::is_const<C>::value)::type*=0);
    {}
}; 

This'll allow the template function to match if and only if the left hand side is const and the right hand side is non-const. That accomplishes part of our goal, but it also allows for any right-hand-side type, so long as the right-hand-side type is non-const!
template<typename T>
struct foo
{
    foo()
    {}
    foo(const foo<T> &)
    {}
    template<typename C>
    foo(const foo<C> &,
          typename boost::enable_if_c<     !boost::is_const<C>::value
                                                      &&  boost::is_const<T>::value
                                                      &&  boost::is_same<typename std::tr1::remove_const<T>::type,
                                                                                     typename std::tr1::remove_const<C>::type>::value>::type*=0);
{}
}; 
This version is much better! Now we'll only properly match the template if the left hand side is const while the right hand side is the const version of our template parameter! That should actually fully accomplish our goal!

A more generic way to handle this, if you have c++11 support might be:
template<typename T>
struct foo
{
    foo()
    {}
    foo(const foo<T> &)
    {}
    template<typename C>
    foo(const foo<C> &,
          typename std::enable_if_c<      !std::is_const<C>::value
                                                    &&  std::is_const<T>::value
                                                    &&  std ::is_convertable<C, T>::value>::type*=0);
    {}
}; 
 I'm not convinced that this is necessarily the best, or only way, to handle it. But if I understand correctly, the is_convertable operator will allow for A=const A, which isn't what we want. So I put in the needed constness relationships directly using std::is_const.


One thing I should note is that even with all of the above, this operation will never be allowed by the compiler without a reinterpret_cast<>().

foo<int> bar;
foo<const  int> * baz = &bar;
This is because, fundamentally, foo<T> and foo<C>, for any T and C where T != C, are completely unrelated types as far as the C++ type system is concerned. It's a shame, but that's how it is!.

And finally I leave you with an example class with all of the appropriate operators implemented. I've not tested this, and the compiler I'm using today doesn't support half of the syntax, so use at your own risk!

template<typename T>
struct foo
{
//Default constructor.
    foo()
    {}
//Default copy constructor.
    foo(const foo<T> &)
    {}

    template<typename C>
    foo(const foo<C> &,
        typename boost::enable_if_c<   !boost::is_const<C>::value
                                    &&  boost::is_const<T>::value
                                    &&  boost::is_same<typename std::tr1::remove_const<T>::type,
                                                       typename std::tr1::remove_const<C>::type >::value >::type* = 0)
    {}
 
//This version doesn't work, I don't know why. 
//    template<typename C>
//    foo(typename boost::enable_if_c<   !boost::is_const<C>::value
//                                    &&  boost::is_const<T>::value
//                                    &&  boost::is_same<typename std::tr1::remove_const<T>::type,
//                                                       typename std::tr1::remove_const<C>::type >::value,
//                                    const foo<C> & >::type)
//    {}

// C++11 version.   
//    template<typename C, typename = typename boost::enable_if_c<   !boost::is_const<C>::value
//                                                                &&  boost::is_const<T>::value
//                                                                &&  boost::is_same<typename std::tr1::remove_const<T>::type,
//                                                                                   typename std::tr1::remove_const<C>::type >::value >::type>
//    foo(const foo<C> &)
//    {}
 
//Only functions in C++11, don't believe this can be done in C++98 or C++03
//    template<typename C, typename = typename boost::enable_if_c<    std::is_const<C>::value
//                                                                && !std::is_const<T>::value
//                                                                &&  std::is_same<typename std::remove_const<T>::type,
//                                                                                 typename std::remove_const<C>::type >::value>::type>
//    operator foo<C>&()
//    { return *static_cast< foo<C>* >(static_cast<void *>(this)); }
                                      
//Normal copy assignment operator.                                      
    foo<T> & operator=(const foo<T> &)
    {}
   
    template<typename C>
    typename boost::enable_if_c<   !boost::is_const<C>::value
                                                   &&  boost::is_const<T>::value
                                                   &&  boost::is_same<typename std::tr1::remove_const<T>::type,
                                                                      typename std::tr1::remove_const<C>::type >::value,
                                                   foo<T> >::type & operator=(const foo<C> &)
    { return *this; }

//C++11 version
//    template<typename C, typename = typename boost::enable_if_c<   !std::is_const<C>::value
//                                                                &&  std::is_const<T>::value
//                                                                &&  std::is_same<typename std::remove_const<T>::type,
//                                                                                 typename std::remove_const<C>::type >::value>::type>
//    foo<T> & operator=(const foo<C> &)
//    { return *this; }
};
static void test()
{

//Tests constructor
    foo<int> sally;
    foo<const int> bob(sally);
    foo<const int> alice(bob);

//Tests operator foo<T>(), doesn't work in C++98 or C++03 
//    foo<int> bar;
//    foo<const int> baz;
//    baz = bar.operator foo<const int>();

//Tests operator=
    foo<int> goo;
    foo<const int> boo;
    boo = goo;
}