Quuxplusone / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
https://p1144.godbolt.org/z/jf67zx5hq
Other
0 stars 2 forks source link

Noexcept-spec for `deque::operator=(deque&&)` is wrongly weakened #38

Open Quuxplusone opened 2 weeks ago

Quuxplusone commented 2 weeks ago

https://godbolt.org/z/MGsPzenbo

template<class T>
struct A {
    using value_type = T;
    static T *allocate(size_t n) { return std::allocator<T>().allocate(n); }
    static void deallocate(T *p, size_t n) { return std::allocator<T>().deallocate(p, n); }
};
static_assert(!std::allocator_traits<A<int>>::propagate_on_container_move_assignment::value);
static_assert(std::allocator_traits<A<int>>::is_always_equal::value);

static_assert(std::is_nothrow_move_assignable_v<std::deque<int, A<int>>>); // fails on libc++

[deque] requires that the noexcept-spec here be:

  deque& operator=(deque&& x)
   noexcept(allocator_traits<Allocator>::is_always_equal::value);

But libc++ has instead:

  deque& operator=(deque&& __c)
    noexcept(__alloc_traits::propagate_on_container_move_assignment::value &&
             is_nothrow_move_assignable<allocator_type>::value);

So if you have an allocator that is always equal, but does not propagate on container move assignment (e.g. because it is empty), then deque's move-assignment becomes noexcept(false), which is forbidden by [deque].

It seems to be controversial whether [res.on.exception.handling]/5 actually permits libc++ to strengthen [deque]'s noexcept-spec, for example to:

  deque& operator=(deque&& __c)
    noexcept(__alloc_traits::is_always_equal::value ||
             __alloc_traits::propagate_on_container_move_assignment::value);

(because it is not obvious that that would count as "adding [vs. modifying] a non-throwing [vs. conditional] exception specification"). But it's quite certain that libc++ is forbidden to weaken the noexcept-spec!