Reputation: 329
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
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
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;
}
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