https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89610
Bug ID: 89610 Summary: Move-assigning a pmr container sometimes copies the elements instead of moving them Product: gcc Version: unknown Status: UNCONFIRMED Severity: normal Priority: P3 Component: libstdc++ Assignee: unassigned at gcc dot gnu.org Reporter: arthur.j.odwyer at gmail dot com Target Milestone: --- When we move-assign an allocator-aware container, and the allocator does POCMA, and the allocators of the left-hand and right-hand sides are not equal, then we must request a new batch of memory from the left-hand allocator and transfer the elements over from the right-hand heap to the left-hand heap. libstdc++ does this mostly correctly. But, for the actual transfer of the elements' values, it appears to use "move_if_noexcept" rather than vanilla "move". So if the element type's move-constructor is non-nothrow, MOVE-ASSIGNING a pmr::container will COPY-CONSTRUCT each of its elements! MSVC's containers will unconditionally use the move-constructor in this situation. libc++'s containers will unconditionally use the move-constructor in this situation. libstdc++ is the odd one out (and the inefficient one). Casey Carter says, "The IS says it should move the Widgets: http://eel.is/c++draft/container.requirements.general#16.sentence-41". I'm not sure it's *required* to move them, but I certainly think it would be *preferable* to move them, for reasons of efficiency and portability-of-user-code-between-vendors. Here's my test case. You can replace `deque` with `vector` or `list` (or with appropriate tweaks `forward_list` or `set`) and observe libstdc++'s inefficient "COPY widget" behavior in each case. ===== // https://wandbox.org/permlink/SXi0LYt5D6QN1BBJ #include <memory_resource> #include <stdio.h> #include <deque> struct Widget { const char *data_ = "uninitialized"; Widget(const char *s) : data_(s) {} Widget(const Widget& rhs) : data_(rhs.data_) { puts("COPY widget"); } Widget(Widget&& rhs) /*NOT NOEXCEPT*/ : data_(rhs.data_) { puts("MOVE widget"); rhs.data_ = "moved-from"; } Widget& operator=(const Widget& rhs) { data_ = rhs.data_; puts("COPY-ASSIGN widget"); return *this; } Widget& operator=(Widget&& rhs) { data_ = rhs.data_; rhs.data_ = "moved-from"; puts("MOVE-ASSIGN widget"); return *this; } }; int main() { std::pmr::monotonic_buffer_resource mr1; std::pmr::monotonic_buffer_resource mr2; std::pmr::deque<Widget> v1(&mr1); std::pmr::deque<Widget> v2(&mr2); v1.emplace_back("foo"); v1.emplace_back("bar"); v2 = std::move(v1); // libstdc++ prints "COPY widget" twice // everyone else prints "MOVE widget" twice } =====