Juddy
Juddy

Reputation: 329

Checking for self-assignment when overloading operator= for template class of generic type

Usually, when overloading and assign operator, one should check for self-assignment. In a simple non-templated class it would like the following:

MyClass& MyClass::operator=(const MyClass& rhs) {
    if (this != &rhs) {
        // do the assignment
    }

    return *this;
}

However, suppose that MyClass is templated, and that we want to generalize the assignment operator overloading for generic types that that the class can have:

template<class T>
class MyClass
{
    template<class U>
    friend class MyClass;
    T value;

    template<class U>
    MyClass<T>& operator=(const MyClass<U>& rhs)

    //... other stuff
}

template<class T>
template<class U>
MyClass<T>& MyClass<T>::operator=(const MyClass<U>& rhs)
{
    if (this != &rhs) { //this line gives an error
        value = (T)rhs.value;
    }
}

In the above case, the line if (this != &rhs) would give a compiler error. In MS Visual Studio 2015, it is the error C2446:

'==': no conversion from 'const MyClas< T > *' to 'MyClass< U > *const '

Therefore, how could I implement the checking for self-assignment when working with an assignment operator that can take at the right hand side an instance of MyClass of generic templated type?

Upvotes: 1

Views: 414

Answers (2)

R Sahu
R Sahu

Reputation: 206577

I would recommend overloading operator=.

template<class T>
class MyClass
{
    template<class U>
    friend class MyClass;
    T value;

    template<class U>
    MyClass& operator=(const MyClass<U>& rhs) { ... }

    // Overload for MyClass<T>
    MyClass& operator=(const MyClass& rhs) { ... }
};

and check for self-assignment only in the second overload.


I want to point out that the above logic breaks if specializations of MyClass are not simple. For example, if you use:

template<> class MyClass<int>:public MyClass<long> { ... };

the code to check for self assignment doesn't get invoked. See http://ideone.com/AqCsa3.

I would definitely have liked to have seen

Came to MyClass<T>::operator=(const MyClass& rhs)

as output of that program.

Upvotes: 3

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

same_object is a function that takes two references, and returns true if they both refer to the same object; not the same address, but the same object.

template<class T, class U, class=void>
struct same_object_t {
  constexpr bool operator()(T const volatile&, U const volatile&)const{return false;}
};
template<class T>
struct same_object_t<T,T,void> {
  bool operator()(T const volatile& lhs, T const volatile& rhs)const{
    return std::addressof(lhs) == std::addressof(rhs);
  }
};
template<class T, class U>
struct same_object_t<T,U,
  typename std::enable_if<
    std::is_base_of<T, U>::value && !std::is_same<T,U>::value
  >::type
>:
  same_object_t<T,T>
{};
template<class T, class U>
struct same_object_t<T,U,
  typename std::enable_if<
    std::is_base_of<U, T>::value && !std::is_same<T,U>::value
  >::type
>:
  same_object_t<U,U>
{};
template<class T, class U>
constexpr bool same_object(T const volatile& t, U const volatile& u) {
  return same_object_t<T,U>{}(t, u);
}

template<class T>
template<class U>
MyClass<T>& MyClass<T>::operator=(const MyClass<U>& rhs)
{
  if (!same_object(*this, rhs)) {
    value = static_cast<T>(rhs.value);
  }
  return *this;
}

Live example.

Two distinct objects can share an address due to unions and standard layout "first member" address sharing, as well as an array and the first element of the array. Those cases return false from same_object.

private/protected inheritance can break this, as can a type U that inherits from a type T through more than one path.

Upvotes: 2

Related Questions