Search code examples
c++c++17movemove-semantics

What causes a move assignment operator to not be called?


I'm working on my own smart pointer and I ran into some weird problems. The move assignment operator was not being called. So I wrote a test class and was able to reproduce the issue. The move assignment operator is not called but a copy assignment occurs (even when there is no copy assignment operator).

This is my test class

#include <utility>
#include <iostream>

struct tag_t {};
constexpr tag_t tag {};

template <typename T>
struct Foo {
  Foo() noexcept
    : val{} {
    std::cout << "Default construct\n";
  }
  template <typename U>
  Foo(tag_t, const U &val) noexcept
    : val{val} {
    std::cout << "Construct " << val << '\n';
  }
  ~Foo() noexcept {
    std::cout << "Destruct " << val << '\n';
  }

  template <typename U>
  Foo(Foo<U> &&other) noexcept
    : val{std::exchange(other.val, U{})} {
    std::cout << "Move construct " << val << '\n';
  }
  template <typename U>
  Foo &operator=(Foo<U> &&other) noexcept {
    std::cout << "Move assign " << other.val << '\n';
    val = std::exchange(other.val, U{});
    return *this;
  }

  T val;
};

These are the tests

int main() {
  {
    Foo<int> num;
    std::cout << "Value " << num.val << '\n';
    num = {tag, 5};
    std::cout << "Value " << num.val << '\n';
  }
  std::cout << '\n';
  {
    Foo<int> num;
    std::cout << "Value " << num.val << '\n';
    num = Foo<int>{tag, 5};
    std::cout << "Value " << num.val << '\n';
  }
  return 0;
}

After running the tests, I get these results

Default construct
Value 0
Construct 5
Destruct 5
Value 5
Destruct 5

Default construct
Value 0
Construct 5
Move assign 5
Destruct 0
Value 5
Destruct 5

What baffles me is the output of the first test. The move assignment operator is not called but a copy assignment takes place. This results in 5 being destroyed twice. Not ideal when you're trying to make a smart pointer!

I'm compiling with Apple Clang with optimizations disabled. Can someone explain my observations? Also, how do I ensure that the move assignment operator is called in the first test?


Solution

  • template <typename U>
    Foo &operator=(Foo<U> &&other) noexcept;
    

    this cannot be called by ={ }.

    Instead, Foo& operator=(Foo&&)noexcept is called.

    Template methods are never special member functions. Explicitly default, delete or implement them.