Sunday, February 1, 2015

Pointer conversion between C++ template instantiations

A follow up to my prior entry :

That entry detailed template meta-programming techniques to allow for two otherwise not related template classes to be converted from one to another. Sadly, that technique doesn't allow for the C++ type system to convert from, say, Foo<T>* to Foo<U>*. Despite having the appropriate conversion functions to construct new instances of Foo<T> out of Foo<U>, this doesn't give us the ability to treat an existing pointer to Foo<T> as a pointer to Foo<U>, the two classes might have completely different implementations, different v-tables, different memory layouts, and in general be incompatible. Thus, trying to convert a pointer to Foo<T> to a pointer to Foo<U> is impossible, as the C++ type system simply isn't designed to make that a possibility, no matter how many conversion functions you try to apply.

But there is one trick that can help in some situations, by having a template class inherit from a different instantiation of itself!

A quick example, which uses some template metaprogramming techniques.
template<typename T>
class Foo : public boost::conditional<  boost::is_const<T>::value
                                                            , boost::mpl::empty_base
                                                            , Foo<const T>    >::type
boost::mpl::empty_base is simply a POD struct that contains nothing, making inheriting from it a non-issue.

Any instantiation of Foo, Foo<T> where T is non const will be legal, and the resulting class will contain all of the members of both instantiations (with the appropriate inheritance rules), just as if the public foo<const T> were instead bar<const T>.

Any instantiation of Foo, Foo<T> where T is const will be legal, but will inherit from boost::mpl::empty_base, which contains no data or member functions, and will not inherit from any other instantation of Foo.

This technique provides us with a mechanism to allow in-place conversion of pointers between the two template instantiation.

In other words

Foo<T> * bar = new Foo<T>();
Foo<const T> * baz = bar;
Is fully legal, because any Foo<T> IS-A Foo<const T> by virtue of inheritance!

Of course, this doesn't then permit
Foo<const T> * bar = new Foo<T>();
Foo<T> * baz = bar; // WRONG
Without a cast, just like we normally need when going from a base-class to a derived-class.

If you're willing to introduce a few specializations, we can get some much more interesting behavior, such as the non-const version delegating to the const-version's methods if and only if we have both.

template<typename T, typename = void>
class Foo
    bar   function1(T) = 0;
    void function2(baz) = 0;
template<typename T, typename boost::enable_if<  boost::is_const<T>::value >::type >
class Foo
    bar   function1(T) = 0;
    T function2(baz) = 0;
template<typename T, typename boost::disable_if<  boost::is_const<T>::value >::type >
class Foo : public Foo<const T>
    bar   function1(T)
    { return Foo<const T>::function1(T); }
    T function2(baz)
    { return Foo<const T>::function2(baz); }
With that pattern, pointer conversion from non-const->const will work properly, and classes that use the non-const version of functions will delegate to the const version of functions

No comments:

Post a Comment