ambiguous overload for ‘operator=’ with c++11 std::move and copy and swap idiom
Asked Answered
D

1

9

I am getting the following error:

[matt ~] g++ -std=c++11 main.cpp -DCOPY_AND_SWAP && ./a.out
main.cpp: In function ‘int main(int, const char* const*)’:
main.cpp:101:24: error: ambiguous overload for ‘operator=’ in ‘move = std::move<Test&>((* & copy))’
main.cpp:101:24: note: candidates are:
main.cpp:39:7: note: Test& Test::operator=(Test)
main.cpp:52:7: note: Test& Test::operator=(Test&&)

When the following code is compiled:

#include <iostream>
#include <unordered_map>
class Test final {
public:
  typedef std::unordered_map<std::string, std::string> Map;
public:
  Test();
  explicit Test(Map&& map);
  ~Test();
  Test(const Test& other);
  Test(Test&& test);
#ifdef COPY_AND_SWAP
  Test& operator=(Test other);
#else
  Test& operator=(const Test& other);
#endif
  Test& operator=(Test&& other);
  size_t Size() const noexcept;
  friend void swap(Test& lhs, Test& rhs);
private:
  friend std::ostream& operator<<(std::ostream& stream, const Test& test);
private:
  Map map_;
};
Test::Test() : map_() {
  std::cerr << "Default constructor called" << std::endl;
};
Test::Test(const Test& other) : map_(other.map_) {
  std::cerr << "Copy constructor called" << std::endl;
};
Test::Test(Test&& other) : map_(std::move(other.map_)) {
  std::cerr << "Move constructor called" << std::endl;
};
Test::Test(Map&& map) : map_(std::move(map)) {
  std::cerr << "Map constructor called" << std::endl;
};
Test::~Test() {};
#ifdef COPY_AND_SWAP
Test& Test::operator=(Test other) {
  std::cerr << "Copy and swap assignment called" << std::endl;
  using std::swap;
  swap(this->map_, other.map_);
  return *this;
}
#else
Test& Test::operator=(const Test& other) {
  std::cerr << "Copy assignment called" << std::endl;
  this->map_ = other.map_;
  return *this;
}
#endif
Test& Test::operator=(Test&& other) {
  std::cerr << "Move assignment called" << std::endl;
  this->map_ = other.map_;
  other.map_.clear();
  return *this;
}
size_t Test::Size() const noexcept {
  return map_.size();
}
void swap(Test& lhs, Test& rhs) {
  using std::swap;
  swap(lhs.map_, rhs.map_);
}
std::ostream& operator<<(std::ostream& stream, const Test& test) {
  return stream << test.map_.size();
}
int main (const int argc, const char * const * const argv) {
  using std::swap;
  Test::Map map {
    {"some", "dummy"},
    {"data", "to"},
    {"fill", "up"},
    {"the", "map"}
  };
  std::cout << " map size(): " << map.size() << std::endl;
  std::cout << "Constructing" << std::endl;
  Test test(std::move(map));
  std::cout << " map.size(): " << map.size() << std::endl;
  std::cout << "test.Size(): " << test.Size() << std::endl;
  std::cout << "Copy construction" << std::endl;
  Test copy(test);
  std::cout << "copy.Size(): " << copy.Size() << std::endl;
  std::cout << "Move construction" << std::endl;
  Test move(std::move(copy));
  std::cout << "move.Size(): " << move.Size() << std::endl;
  std::cout << "copy.Size(): " << copy.Size() << std::endl;
  std::cout << "Swapping" << std::endl;
  swap(move, copy);
  std::cout << "move.Size(): " << move.Size() << std::endl;
  std::cout << "copy.Size(): " << copy.Size() << std::endl;
  std::cout << "Swapping back" << std::endl;
  swap(move, copy);
  std::cout << "move.Size(): " << move.Size() << std::endl;
  std::cout << "copy.Size(): " << copy.Size() << std::endl;
  std::cout << "Copy assignment" << std::endl;
  copy = test;
  std::cout << "test.Size(): " << test.Size() << std::endl;
  std::cout << "copy.Size(): " << copy.Size() << std::endl;
  std::cout << "Move assignment" << std::endl;
  move = std::move(copy);
  std::cout << "move.Size(): " << move.Size() << std::endl;
  std::cout << "copy.Size(): " << copy.Size() << std::endl;
 return 0;
}

When compiled with g++ -std=c++11 main.cpp && ./a.out:

[matt ~] g++ -std=c++11 main.cpp && ./a.out
 map size(): 4
Constructing
Map constructor called
 map.size(): 0
test.Size(): 4
Copy construction
Copy constructor called
copy.Size(): 4
Move construction
Move constructor called
move.Size(): 4
copy.Size(): 0
Swapping
move.Size(): 0
copy.Size(): 4
Swapping back
move.Size(): 4
copy.Size(): 0
Copy assignment
Copy assignment called
test.Size(): 4
copy.Size(): 4
Move assignment
Move assignment called
move.Size(): 4
copy.Size(): 0

Could someone help me understand why the ambiguity occurs when using the copy and swap idiom in this case?

Discoverer answered 24/1, 2013 at 14:40 Comment(0)
A
12

for overload resolution purposes the functions

Test& operator=(Test other);
Test& operator=(Test&& other);

are equal, because the implicit conversion sequences used to convert to Test and Test&&, respectively, are equal. The former is not better, because the direct reference binding is also considered an identity conversion.

When confronted with the ambiguity of 2 equally good matches, the compiler gives an error. You probably want this:

#ifdef COPY_AND_SWAP
  Test& operator=(Test other);
#else
  Test& operator=(const Test& other);
  Test& operator=(Test&& other);
#endif
Agler answered 24/1, 2013 at 14:50 Comment(2)
+1. And the solution to the problem is to get rid of the move assignment operator, the copy-and-swap one handles rvalues and lvalues, that's the pointAnnabell
@rhalbersma Thanks for the answer. I obviously have more to understand about why the implicit conversion sequences are same by understanding rvalues moreDiscoverer

© 2022 - 2024 — McMap. All rights reserved.