3

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?

2
  • 3
    You accidentally made a forwarding reference. Commented Nov 29, 2019 at 19:16
  • @HolyBlackCat: Thanks, I'm not familiar with forwarding references so perhaps I should read about it. At the moment, I don't understand why this is wrong. Commented Nov 29, 2019 at 19:23

2 Answers 2

3
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.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the explanation. Now I understand the basics. I know how I have to change my code. I will discard the unused forwardings and replace them by some other methodes.
3

&& in the context of an argument to a template function has a different meaning then in other situations.

It's called a forwarding reference and it will be either an rvalue-reference or a non-const lvalue reference depending on what you pass in.

That means your template operator= is the best match for C1 = C2 since both the copy assignements take const&, and C1 is not const.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.