Template Assignment Operator Overloading Mystery

Template assignment operator overloading mystery

Why does assigning d to c not use the const overloaded assignment operator provided?

The implicitly-declared copy assignment operator, which is declared as follows, is still generated:

Wrapper& operator=(const Wrapper&);

An operator template does not suppress generation of the implicitly-declared copy assignment operator. Since the argument (a const-qualified Wrapper) is an exact match for the parameter of this operator (const Wrapper&), it is selected during overload resolution.

The operator template is not selected and there is no ambiguity because--all other things being equal--a nontemplate is a better match during overload resolution than a template.

Why does assigning b to a not use the default copy assignment operator?

The argument (a non-const-qualified Wrapper) is a better match for the operator template that takes a Wrapper<U>& than for the implicitly-declared copy assignment operator (which takes a const Wrapper<U>&.

Assignment operator to reference template type requires non-const overload

template <class U>
vec2<T>& operator=(const vec2<U>& v)

within this method, v is a name for a const view of the right hand side. If U is int, then v.x is a const int.

If T is int&, then this->x is a int&.

this->x = static_cast<int&>(v.x);

this is obviously illegal: you cannot static cast a const int to a non const reference.

A general solution basically requires rebuilding the std::tuple or std::pair machinery. SFINAE can be used to bootstrap it. But in general, structs containing references and those containing values are usually quite different beasts; using one template for both is questionable.

template <class T>
struct vec2 final {
template<class Self,
std::enable_if_t<std::is_same<std::decay_t<Self>, vec2>>{}, bool> =true
>
friend auto as_tuple( Self&& self ){
return std::forward_as_tuple( std::forward<Self>(self).x, std::forward<Self>(self).y );
}

then we can do SFINAE tests to determine if as_tuple(LHS)=as_tuple(RHS) is valid.

Doing this for construction is another pain, as LHS's tuple type needs massage before the constructibility test can work.


The more generic you make your code, the more work it takes. Consider actual use cases before writing infinitely generic code.

Overload assignment operator and rule of zero

According to my understanding, adding a operator= overload will not prevent the compiler from generating the default one according to the rule of 0.

I base this understanding on the fact that your operator= overload is not in fact a copy assignment, nor a move assignment.

Therefore the rules about generaing default constructors and assignment operators are not relevant.

I verified it with MSVC.

You can use the code below to verify with your compiler:

#include <iostream>

template <typename T>
struct B
{
B(T const & n) : bn(n) {}
T bn{ 0 };
};

template <typename T>
struct A
{
A(T const & n) : an(n) {}
A<T>& operator=(const B<T>& rhs)
{
an = rhs.bn;
return *this;
}
T an{ 0 };
};

int main()
{
A<int> a1{ 5 };
A<int> a2{ 6 };
std::cout << a2.an << ",";
a2 = a1; // Use default assinment
std::cout << a2.an << ",";
B<int> b{ 3 };
a2 = b; // Use custom assignment
std::cout << a2.an << std::endl;
return 0;
}

The output should be: 6,5,3:

6 is the value A<int> a2 is constructed with, 5 is the value assigned from A<int> a1, and 3 is the value assigned from B<int> b.

Note: an alternative would be to use a user-defined conversion function, as @LouisGo commented (see above).

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

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.

C++ - Copy Assignment Operator in Template

It gives me infinite output as


Why is that?

Because you've defined the function in terms of itself, see the following code comment.

xpair& operator= (const xpair& that)
{
cout << "*this = " << *this << " " << "that = " << that << endl;
cout << "use operator = " << endl;

// Here you're asking for `this` (i.e., an `xpair` type) to be assigned
// a `that` (i.e., another `xpair` type) using the `operator=` which is
// the function currently being implemented/defined. A function calling
// itself is recursion and there is no stopping condition so it will
// continue infinitely.
*this = that;
return *this;
}

Instead your operation should set the data members of this instance using the data members of that instance.

xpair& operator= (const xpair& that)
{
cout << "*this = " << *this << " " << "that = " << that << endl;
cout << "use operator = " << endl;

first = that.first;
second = that.second;
return *this;
}

C++ template class copy-constructor and assignment-operator

You should probably not have const references as members since you can't (in general) know that the objects lifetime will outlast the lifetime of your object, a, b and c should almost certainly be of type Tx and not Tx const&.

If you do know this (be sure that you do, it's more probable that you don't understand the implications unless you're an expert C++ developer), then you can have a copy constructor using an initialization list.

Triple(const Triple& other) {
: a(other.a)
, b(other.b)
, c(other.c)
{ }

You can't have assignment operator since assigning to a reference changes the referred to object not the reference, you could simulate references with pointers but since I think this is not what you want I won't spell it out.

In any case the real thing you should be doing is using std::tuple and not reinventing The wheel.

Assignment-Operator for templated class

This

template<typename T>
Wrapper& Wrapper<T>::operator=(const Wrapper&)

which is really just shorthand for

template<typename T>
Wrapper<T>& Wrapper<T>::operator=(const Wrapper<T>&)

The other version would apply to a non-template class named Wrapper, it has no effect on your templates.



Related Topics



Leave a reply



Submit