I have some class with copy and move assignment, but move seems to be wrong in my example and leads to unecpected behavior. Why is move called and how can I avoid this? C1 is assigned to C2 and used afterwards, but move is called and C1 is empty then.
#include <iostream>
class CSomeClass
{
protected:
size_t m_uiSize = 0u;
public:
CSomeClass() {}
~CSomeClass() {}
size_t size() const { return m_uiSize; }
void resize( size_t p_uiNewSize ) { m_uiSize = p_uiNewSize; }
/* This operator I was expected to be called in all cases. */
CSomeClass& operator=( const CSomeClass& p_rzOther )
{
std::wcout << "Copy explicit" << std::endl;
m_uiSize = p_rzOther.size();
return *this;
}
CSomeClass& operator=( CSomeClass&& p_rzOther )
{
std::wcout << "Move explicit" << std::endl;
m_uiSize = p_rzOther.size();
p_rzOther.resize( 0u );
return *this;
}
#if 1
template<typename M> CSomeClass& operator=( const M& p_rzOther )
{
std::wcout << "Copy UNDEF" << std::endl;
m_uiSize = p_rzOther.size();
return *this;
}
template<typename M> CSomeClass& operator=( M&& p_rzOther )
{
std::wcout << "Move UNDEF" << std::endl;
p_rzOther.resize( 0u );
return *this;
}
#endif
};
int main()
{
CSomeClass C1;
CSomeClass C2;
C1.resize( 1u );
std::wcout << L"C1 size before: " << C2.size() << std::endl;
C2 = C1;
std::wcout << L"C1 size after: " << C2.size() << std::endl;
return 0;
}
This results in the following output:
C1 size before: 1
Move UNDEF
C1 size after: 0
My real problem is a bit more complicated (with more templates and a large range of assignment variants).
If the #if 1
is changed to #if 0
, the correct copy assignment operator is called, but in my real code, there are cases where non of the assignment operators are called (instead there is done a plain copy which is wrong, too).
I hope you can explain the mechanism to me. What am I missing?
template<typename M> CSomeClass& operator=( M&& p_rzOther )
Here, M&& p_rzOther
is a forwarding reference. You can pass both lvalues and rvalues to it, both const
and non-const
.
In your case, M
gets deduced as CSomeClass &
, which, due to the reference collapsing turns the assignment operator into:
CSomeClass &operator=(CSomeClass &p_rzOther)
Because in C2 = C1;
, C1
is not const
, the operator above is a better match than two other assignment operators that take a const CSomeClass &
.
You can solve this with SFINAE, by preventing M
from being CSomeClass
(possibly cv-qualified, possibly a reference to one):
template <
typename M,
std::enable_if_t<
!std::is_same_v<
CSomeClass,
std::remove_cv_t<std::remove_reference_t<M>>
>,
decltype(nullptr)
> = nullptr
>
CSomeClass &operator=(M &&p_rzOther)
And since this operator=
can handle both value categories with and without const
, you don't need the other one. I suggest removing
template<typename M> CSomeClass& operator=( const M& p_rzOther )
to prevent it from conflicting with the other operators.