c++movemove-semanticsmove-assignment-operator

Ambiguous overload for ‘operator=’ when trying to invoke the move assignment operator


I am trying to clarify-understand move semantics and, for that, I wrote the following code. I used a raw pointer as a data member only to practice in finding all the dangerous spots and also apply idioms like copy & swap.

#include <iostream>
#include <utility>

class Example {
protected:
  int* intPtr;
  
public:
  Example() : intPtr{nullptr} {
    allocate();
    std::cout << "Example() called" << "\n";
  }

  Example(int value) : intPtr{nullptr} {
    allocate();
    assign(value);
    std::cout << "Example(int) called" << "\n";
  }

  ~Example() {
    deallocate();
    std::cout << "~Example called" << "\n";
  }

  Example(const Example& anExample) :
    intPtr{anExample.intPtr ? new int(*anExample.intPtr) : nullptr} {
    std::cout << "Example(const Example&) called" << "\n";
  }

  Example(Example&& anExample) noexcept {
    intPtr = anExample.intPtr;
    anExample.intPtr = nullptr;
    std::cout << "Example(Example&&) called" << "\n";
  }

  Example& operator=(Example&& anExample) noexcept {
    if(this != &anExample){
      intPtr = anExample.intPtr;
      anExample.intPtr = nullptr;
      std::cout << "Move assignment op called" << "\n";
    }
    return *this;
  }
  
  Example& operator=(Example anExample) {
    std::swap(intPtr, anExample.intPtr);
    std::cout << "Copy assignment op called \n";
    return *this;
  }

  void assign(int value) {
    if (intPtr != nullptr && *intPtr!=value) 
      *intPtr = value;
    else {
      std::cout << __FUNCTION__ << ": intPtr is either null, or its memory contains already the value you want.\n";
    }
  }
  
private:
  void allocate() {intPtr = new int{};}

  void deallocate() {delete intPtr;}
 
  friend std::ostream& operator<<(std::ostream& strm, const Example& anExample) {
    return strm << *anExample.intPtr;
  }
  
};

int main() {

  Example ex1;
  std::cout << "----------------" <<std::endl;
  Example ex2{ex1};
  std::cout << "----------------" <<std::endl;
  Example ex3 = ex2; // invokes copy constructor
  std::cout << "----------------" <<std::endl;
  Example ex4;
  std::cout << "----------------" <<std::endl;
  ex4 = ex3; // invokes copy assignement operator
  std::cout << "----------------" <<std::endl;
  Example ex5 = std::move(ex1); // invokes move constructor
  std::cout << "----------------" <<std::endl;
  ex4 = std::move(ex5); // compilation error!
  return 0;
}

The last line gives the following error

main.cxx:116:22: error: ambiguous overload for ‘operator=’ (operand types are ‘Example’ and ‘std::remove_reference<Example&>::type’ {aka ‘Example’})
  116 |   ex4 = std::move(ex5);
      |                      ^
main.cxx:36:12: note: candidate: ‘Example& Example::operator=(Example&&)’
   36 |   Example& operator=(Example&& anExample) noexcept {
      |            ^~~~~~~~
main.cxx:45:12: note: candidate: ‘Example& Example::operator=(Example)’
   45 |   Example& operator=(Example anExample) {
      |            ^~~~~~~~

Why is the right hand side's type std::remove_reference<Example&>::type. What am I doing wrong here and how to invoke the move-assignement operator properly? Every other comment is welcome.


Solution

  • Example& operator=(Example anExample) is a general assignment operator. It copies lvalues and moves rvalues.

    If you want to distinguish copy from move assignment you need Example& operator=(const Example & anExample).

    Alternatively you could remove Example& operator=(Example&& anExample).