More C++ Idioms/Coercion by Member Template

From Wikibooks, the open-content textbooks collection

Jump to: navigation, search

Contents

[edit]

Coercion by Member Template

[edit] Intent

To increase flexibility of the interface of a class template by allowing the template class to participate in the same implicit type conversions (coercion) that are otherwise possible only on the type parameters of the class template.

[edit] Also Known As

[edit] Motivation

It is often useful to 'extend' the relationship between two types to their respective template class types when they are instantiated. For example, suppose that class D derives from class B. So a pointer to an object of type D can be assigned to a pointer of type B. These semantics are implicitly supported by C++ programming language. Unfortunately, types that are composed of one of these types do not share the relationship of their composed types. The rule is applicable to templates as well and therefore, Helper<D> can not be assigned to Helper<B> in general.

class B {};
class D : public B {};
template <class T>
class Helper {};
 
B *bptr;
D *dptr;
bptr = dptr; // This is allowed by the language.
 
Helper<B> hb;
Helper<D> hd; 
hb = hd; // This is not allowed but could be very useful.

There are cases where exceptions to this rule yield more benefits and flexibility than not allowing it at all. For example, we would like to allow conversion from auto_ptr<D> to auto_ptr<B>. It is quite intuitive but not supported by the language without using coercion by member template idiom.

[edit] Solution and Sample Code

Define independent member template functions inside a class template and use the implicit type conversion supported by the parameter type in the implementation of the member template.

template <class T>
class Ptr
{
  public:
    Ptr () {}
 
    Ptr (Ptr const & p)
      : ptr (p.ptr)
    {
      std::cout << "This is copy-constructor\n";
    }
 
    // Supporting coercion using member template constructor.
    template <class U>
    Ptr (Ptr <U> const & p)
      : ptr (p.ptr) // Implicit conversion from U to T must be allowed.
    {
      std::cout << "conversion friendly member template "
                   "constructor is not a copy-constructor\n";
    }
 
    // Member copy-assignment operator.
    Ptr & operator = (Ptr const & p)
    {
      ptr = p.ptr;
      std::cout << "member copy-assignment operator\n";
      return *this;
    }
 
    // Supporting coercion using member template assignment operator.
    template <class U>
    Ptr & operator = (Ptr <U> const & p)
    {
      ptr = p.ptr; // Implicit conversion from U to T must be allowed.
      std::cout << "conversion friendly member template assignment "
                   "operator is not a copy-assignment operator\n";
      return *this;
    } 
 
    T *ptr;
};
int main (void)
{
   Ptr <D> d_ptr;
   Ptr <B> b_ptr (d_ptr); // Now allowed
   b_ptr = d_ptr;         // This is also allowed
 
}

Even though class D is-a class B, an array of D objects is-not-an array of B objects. This is prohibited in C++ language because of slicing issues. Quite often relaxing this rule for array of pointers can yield significant flexibility. For example, an array of pointers to D should be assignable to an array of pointers to B. This idiom can achieve that. Extra care should be taken to disallow copying of array of objects of derived type to base. Partial specialization of the member template functions can be used to achieve that. Following example uses a partial specialization of U * to only allow copy of array of pointers.

template <class T>
class Array
{
  public:
    Array () {}
    Array (Array const & a)
    {
      std::copy (a.array_, a.array_ + SIZE, array_);
    }
 
    template <class U>
    Array (Array <U *> const & a)
    {
      std::copy (a.array_, a.array_ + SIZE, array_);
    }
 
    template <class U>
    Array & operator = (Array <U *> const & a)
    {
      std::copy (a.array_, a.array_ + SIZE, array_);
    }
 
    enum {  SIZE = 10 };
    T array_[SIZE];
};

Many smart pointers such as standard auto_ptr, boost shared_ptr employ this idiom.

[edit] Caveats

A typical mistake in implementation of the Coercion by Member Template idiom is an attempt to provide template copy-constructor or template assignment operator without providing their non-template versions.

A compiler will automatically generate default copy-constructor and/or copy-assignment operator if non-template versions are not provided, and it may cause hidden and non-obvious faults.

[edit] Known Uses

  • std::auto_ptr
  • boost::shared_ptr

[edit] Related Idioms

[edit] References

In other languages