Template assignment operator overloading mystery
Why does assigning
d
toc
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
toa
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
What Does ## in a #Define Mean
How to Read Input When Debugging in C++ in Visual Studio Code
Why Doesn't Gcc Support Naked Functions
Why Is '"Literal"' Encouraged to Decay to 'Const Char*' in C++ Argument Type Match
Why Is Operator""S Hidden in a Namespace
Can You Make a Computed Goto in C++
0Xc0000005: Access Violation Reading Location 0X00000000
Interactive Console Programming for C/C++
Optimal Buffer Size for Write(2)
How to Check That I Didn't Break Anything When Refactoring
C++ Back End Call the Python Level Defined Callbacks with Swig Wrapper
Using C++ Aggregate Initialization in Std::Make_Shared
Getting Actual File Name (With Proper Casing) on Windows
Getting Errors While Compiling
Rotating a 2D Pixel Array by 90 Degrees
Linux C++: Does a Return from Main() Cause a Multithreaded App to Terminate