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
}

=====

Reply via email to