Use an Asymmetric Assignment Operator to Assign Containers of Different Types

++ implicitly converts the operands’ types in expressions that involve different datatypes. Here’s a few examples:

int n=5;double d=n; //5 implicitly converted to 5.0if (n==d) //same here

Many programmers don’t even know that these implicit conversions take place because they are so trivial and intuitive. However, when templates are involved, it’s a different story. Consider the following:

Array ai;Array ad;ai.push_back(5);ad=ai;//compilation error

The assignment of Array to Array fails because there is no standard conversion between these two independent types.


How do you simulate the implicit conversion of built-in datatypes with different specializations of the same template?


Add an asymmetric assignment operator to the class template.

Notes on Terminology: The terms “template member” and “member template” sound like synonyms. They aren’t. A template member is a member of a class template. For example:

template  class Array{ T * p; // p is a template member void func(T &) const; // so is func};

By contrast, a member template is a template declared within a class or class template. This 10-Minute Solution uses a member template to overcome the lack of implicit conversions between different specializations of the same class template.

Presenting the Problem
Suppose you’ve designed a class template that represents a collection of some sort, for instance, an array:

template  class Array{  std::vector v;public:  explicit Array(size_t n=0) : v(n) {}  ~Array(); //accessors  const T& operator[] (int idx) const;  size_t size() const;//mutators  T& operator[] (int idx);  void add_element(const T& t);  void clear();  //..};

Assigning two Array objects works fine so long as they have exactly the same type:

Array a1, a2;a1.add_element(5);a2=a1; //OK, using implicit assignment operator

Notice that Array doesn’t even declare an assignment operator?the compiler generated assignment operator is called in this case. Now, suppose you want to assign two different Array objects, one containing int and the other one containing double:

Array a; Array d;a.add_element(5);d=a; //compilation error

Although C++ allows you to assign plain int to double, it won’t let you assign Array to Array. Intuitively, you know what such an assignment should do: copy every a[n] to its matching d[n].

Declaring an Asymmetrical Assignment Operator
In C++ 101, you learned that a canonical assignment operator of class X takes const X& as its parameter and returns X&. However, to enable the assignment of different specializations, you need to use a different type of an assignment operator. Such an assignment operator has no special name in the C++ literature, so I will refer to it as an asymmetrical assignment operator. The asymmetry is accomplished by using two different template parameters within the same member template. Let’s call these template parameters T and T2.

First, add a declaration of a member template operator= to Array (highlighted):

template  class Array{  std::vector v;public:  explicit Array(size_t n=0) : v(n) {}  ~Array(); //accessors  const T& operator[] (int idx) const;  size_t size() const;//mutators  T& operator[] (int idx);  void add_element(const T& t);  void clear();  //..  template    Array& operator=(const Array &);};

Notice that the assignment operator’s return type and its parameter aren’t the same.

Definition
The precise implementation details of the assignment operator depend, of course, on how your template stores its elements. However, the general idea is to break down the collection to its individual elements, assigning every Array[n] to its matching Array[n]. Here is a complete definition of the assignment operator you’ve just declared:

template  template Array& Array::operator=(const Array& t2){ if (this == (void*) &t2) //avoid self assignment  return *this;  clear(); //remove existing elements    for (size_t i=0; i

Pay attention to the first two lines of the definition. This part:

template  template 

indicates a definition of a member template of a class template.

Now, you can assign different Array specializations:

Array a;Array d;a.add_element(10);a.add_element(20);d=a; //OK, d contains the elements 10.0 and 20.0

The asymmetric assignment operator doesn't disable type safety. If you try to assign the following objects:

Array  words;Array  num;num=words; //compilation error

Your compiler will complain because there is no implicit string to int conversion.

Design Refinements
The compiler-generated assignment operator, not the asymmetric one, is still used when you assign objects of the same type:

Array a1, a2;a2=a1; //calls compiler-generated operator=

Remember: the asymmetric assignment operator is called only when the operands have different types. This discovery leads to a picking question. If the two operands have different types, don't they always have different addresses as well? In other words, isn't the code that checks for self-assignment redundant? In the overwhelming majority of cases, it is, indeed, redundant. Yet it's still possible to write code?however uncommon?that assigns the same object to itself using the asymmetric assignment operator. Here's an example:

a=*(Array*)&a;

You wouldn't write such code. However, third-party libraries might contain gems like this. Therefore, the self-assignment test protects you from unpleasant runtime surprises.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: