Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
401 views
in Technique[技术] by (71.8m points)

c++ - Move constructors of STL containers in MSVC 2017 are not marked as noexcept

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();

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.

Update:

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

So the problem seems to be this:

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

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

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.

Copying is obviously not possible.

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.

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.

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; }
};

Here is my test harness:

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>();
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...