是什么导致不调用移动赋值运算符?

Ker*_*g73 2 c++ move move-semantics c++17

我正在研究自己的智能指针,遇到了一些奇怪的问题.未调用移动赋值运算符.所以我写了一个测试类,并能够重现这个问题.不调用移动赋值运算符,但会发生复制赋值(即使没有复制赋值运算符).

这是我的测试课

#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;
};
Run Code Online (Sandbox Code Playgroud)

这些是测试

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;
}
Run Code Online (Sandbox Code Playgroud)

运行测试后,我得到了这些结果

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
Run Code Online (Sandbox Code Playgroud)

令我感到困惑的是第一次测试的结果.不调用移动赋值运算符,但会进行复制赋值.这导致5被摧毁两次.当你想要制作一个智能指针时,这并不理想!

我正在使用Apple Clang编译并禁用优化.有人可以解释我的观察吗?另外,如何确保在第一次测试中调用移动赋值运算符?

Yak*_*ont 5

template <typename U>
Foo &operator=(Foo<U> &&other) noexcept;
Run Code Online (Sandbox Code Playgroud)

这不能被称为={ }.

相反,Foo& operator=(Foo&&)noexcept被称为.

模板方法永远不是特殊的成员函数.明确默认,删除或实现它们.