std::vector::emplace() 是否真的在面对投掷移动 constructor/assignment 运算符时提供强大的异常保证?

Does std::vector::emplace() really offer the strong exception guarantee in the face of a throwing move constructor/assignment operator?

According to cppreference.com,std::vector::emplace()无条件提供强异常保证:

If an exception is thrown (e.g. by the constructor), the container is left unmodified, as if this function was never called (strong exception guarantee).

但是,在 GCC 7.1.1 的实践中似乎并非如此。以下程序:

#include <iostream>
#include <vector>

struct ugly
{
  int i;

  ugly(int i) : i{i} { }

  ugly(const ugly& other) = default;

  ugly& operator=(ugly&& other) {
    if (other.i == 3) {
      throw other.i;
    }
    i = other.i;
    return *this;
  }

  ugly& operator=(const ugly& other) = default;
};

int main() {
  std::vector<ugly> vec;
  vec.reserve(6);
  vec.emplace_back(0);
  vec.emplace_back(1);
  vec.emplace_back(2);
  vec.emplace_back(4);
  vec.emplace_back(5);

  try {
    vec.emplace(vec.begin() + 3, 3);
  } catch (int i) {
  }

  for (const auto& u : vec) {
    std::cout << u.i << "\n";
  }

  return 0;
}

打印

0
1
2
4
4
5

事实上,如果允许 copying/moving 抛出,我很难看出 emplace() 如何提供强有力的保证。要放置在中间,我们必须先将一堆元素移开,然后在其位置构建新元素。如果其中任何一个抛出,我们必须将所有其他元素移回它们原来的位置,但这些移动也可以抛出!

那么谁错了,是 cppreference 还是 gcc?

根据 C++14 标准,强异常保证仅在您插入的类型本身具有强异常保证时才有效。

这里:

23.3.6.5 vector modifiers [ vector.modifiers ]

iterator insert(const_iterator position, const T& x);
iterator insert(const_iterator position, T&& x);
iterator insert(const_iterator position, size_type n, const T& x);
template <class InputIterator>
iterator insert(const_iterator position, InputIterator first, InputIterator last);
iterator insert(const_iterator position, initializer_list<T>);
template <class... Args> void emplace_back(Args&&... args);
template <class... Args> iterator emplace(const_iterator position, Args&&... args);
void push_back(const T& x);
void push_back(T&& x);

1 Remarks: Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid. If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move assignment operator of T or by any InputIterator operation there are no effects. If an exception is thrown while inserting a single element at the end and T is CopyInsertable or is_nothrow_move_constructible::value is true, there are no effects. Otherwise, if an exception is thrown by the move constructor of a non-CopyInsertable T, the effects are unspecified.

所以看起来 cppreference.com 是错误的。