MSVC 2017 中 STL 容器的移动构造函数未标记为 noexcept

我正在将我的项目从 VS2015 转移到 VS2017,这当然并不顺利.

I am moving my project from VS2015 to VS2017, which of course does not go smoothly.

我看到可以通过以下代码重现的奇怪编译器错误:

I am seeing strange compiler error that can be reproduced by the following code:

struct MoveOnly
{
    MoveOnly() {}
    MoveOnly(const MoveOnly&) = delete;
    MoveOnly& operator = (const MoveOnly&) = delete;
    MoveOnly(MoveOnly&&) = default;
    MoveOnly& operator = (MoveOnly&&) = default;
    bool operator == (const MoveOnly& rhs)const{return false;}
};
struct Hasher
{
    size_t operator()(const MoveOnly&)const{return 0;}
};
std::vector < std::unordered_map<MoveOnly, int, Hasher> > test;
test.emplace_back();

我可以使用所有编译器(gcc 7.2、clang 5.0.0、icc 18 以及 MSVC 2015)成功编译此代码.请点击此链接查看测试:https://godbolt.org/g/uSqwDJ.但是,在 MSVC 2017 (19.10.25017) 上,编译器尝试引用 MoveOnly 类型的已删除复制构造函数会导致错误.这个错误对我来说没有多大意义,因为没有理由在这里复制任何东西而不是移动./std:c++14/std:c++17/std:c++latest 没有帮助.gcc 和 clang 正确处理代码的事实也让我怀疑 msvc 2017 编译器.

I can successfully compile this code with all compilers (gcc 7.2, clang 5.0.0, icc 18, as well as MSVC 2015). Please follow this link to see the test: https://godbolt.org/g/uSqwDJ. On MSVC 2017 (19.10.25017), however there is an error that is caused by the compiler trying to reference deleted copy constructor of MoveOnly type. This error does not make much sense to me, because there is no reason to copy anything here instead of moving. /std:c++14, /std:c++17, /std:c++latest do not help. Also the fact the gcc and clang handle the code correctly makes me suspicious about msvc 2017 compiler.

更新:

Yakk 发现问题所在后,我尝试使用其他容器代替 unordered_map 并且代码只能用 vector 编译.

After Yakk found what the problem is, I tried using other containers in place of unordered_map and the code only compiles with vector.

推荐答案

所以问题好像是这样的:

So the problem seems to be this:

static_assert( noexcept(std::unordered_map<MoveOnly, int, Hasher>( std::declval<std::unordered_map<MoveOnly, int, Hasher>&&>())), "");

您的编译器不认为 std::unordered_map<MoveOnly, int, Hasher> 可以是 noexcept 移动构造的.

Your compiler doesn't think std::unordered_map<MoveOnly, int, Hasher> can be noexcept move-constructed.

然后,MSVC 2017 附带的偏执 std::vector 实现依赖于复制元素,以在矢量调整大小时生成强大的异常保证.

Then, the paranoid std::vector implementation that MSVC 2017 ships with falls back on copying elements to generate a strong exception guarantee on vector resize.

复制显然是不可能的.

现在 std::unordered_map<int, int> 也是如此――MSVC 认为移动它也有引发异常的风险;我相信您对 key 或 hash 类型所做的任何事情都不可能使 unordered_map 的移动构造函数异常安全.

Now the same is true of std::unordered_map<int, int> -- MSVC thinks that moving it also risks throwing exceptions; I believe nothing you can do with the key or hash type can possibly make the move constructor of unordered_map exception-safe.

unordered_map(unordered_map&&) 没有充分的理由不 是 noexcept.我不确定标准是否允许或强制它,或者它是否是编译器中的错误;如果标准要求它是noexcept(false),那么标准就有缺陷.

There is no good reason for unordered_map(unordered_map&&) to not be noexcept. I am uncertain if the standard permits it or mandates it, or if it is a bug in the compiler; if the standard mandates it to be noexcept(false), then the standard has a defect.

您可以通过存储 unique_ptr 的向量来解决此问题.或者编写一个吃异常的 value_ptr,对代码的更改较少:

You can work around this by storing a vector of unique_ptrs. Or write an exception-eating value_ptr with fewer changes to your code:

template<class T>
struct value_ptr {
    std::unique_ptr<T> raw;
    value_ptr() noexcept(true)
    {
      try {
        raw = std::make_unique<T>();
      } catch (...) {}
    }
    template<class T0, class...Ts,
        std::enable_if_t<!std::is_same<value_ptr, std::decay_t<T0>>{}, bool> =true
    >
    value_ptr(T0&& t0, Ts&&...ts) noexcept(true)
    {
      try {
        raw=std::make_unique<T>( std::forward<T0>(t0), std::forward<Ts>(ts)... )
      } catch(...) {}
    }
    value_ptr(value_ptr&& o)noexcept(true)=default;
    value_ptr(value_ptr const& o)noexcept(true)
    { try {
      if (o.raw)
        raw = std::make_unique<T>(*o.raw);
      }catch(...){}
    }
    value_ptr& operator=(value_ptr&& o)noexcept(true)=default;
    value_ptr& operator=(value_ptr const& o)noexcept(true)
    { try {
      if (o.raw)
        raw = std::make_unique<T>(*o.raw);
      }catch(...){}
      return *this;
    }
    T* operator->() const { return raw.get(); }
    T& operator*() const { return *raw; }
    explicit operator bool() const { return (bool)raw; }
};

这是我的测试工具:

template<class M>
void test_M() {
  static_assert( noexcept(M( std::declval<M&&>())), "");
  std::vector < M > test;
  test.emplace_back();

}
void foo()
{
  using M0=value_ptr<std::unordered_map<MoveOnly, int, Hasher>>;
  using M1=std::unique_ptr<std::unordered_map<MoveOnly, int, Hasher>>;
  test_M<M0>();
  test_M<M1>();
}

相关文章