https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92124

            Bug ID: 92124
           Summary: std::vector copy-assigning when it should move-assign.
           Product: gcc
           Version: 9.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: cassio.neri at gmail dot com
  Target Milestone: ---

Consider two vectors a and rv. In the situation below a = std::move(rv)
copy-assigns elements of rv into a, violating
[container.requirements.general]/4, Table 83: "All existing elements of a are
either move assigned to or destroyed" (See [1].)

It happens with std::vector<X, A<X>> such that:

1) X's move-constructor might throw (though I'm assigning and not
constructing);
2) A<X> does not propagate on move-assignment and allocators used by source and
target vectors do not compare equal.

The following MCVE contains some boiler plate and the most important parts are
indicated by comments.

#include <cstdio>
#include <vector>
#include <memory>
#include <type_traits>

struct X {
    X() = default;
    X(const X&) = default;

    // Move constructor might throw
    X(X&&) noexcept(false) {} // "= default" changes reported behaviour

    // Tracking calls to assignment functions
    X& operator=(const X&) {
        putchar('c'); return *this;
    }
    X& operator=(X&&) noexcept(true) {
        putchar('m'); return *this;
    }
};

unsigned counter = 0;

template <typename T>
struct A : std::allocator<T> {

    template <typename U>
    struct rebind { using other = A<U>; };

    A() : std::allocator<T>(), id(++counter) {}

    // Does not propagate
    using propagate_on_container_move_assignment = std::false_type;

    // Does not always compare equal
    using is_always_equal = std::false_type;
    bool operator ==(const A& o) { return id == o.id; }
    bool operator !=(const A& o) { return id != o.id; }

    unsigned id;
};

int main() {
    std::vector<X, A<X>> a(2), rv(2);
    a = std::move(rv);
}

Running the code above outputs "cc" (instead of "mm") confirming the two
elements of rv are copy-assigned into a.

See relevant discussion in [2] (with link to possible culprit lines of code in
libstdc++) and life example above in [3]

[1]
https://timsong-cpp.github.io/cppwp/n4659/container.requirements#tab:containers.container.requirements
[2]
https://stackoverflow.com/questions/58378051/issue-when-compiling-libstdc-with-clang?noredirect=1#comment103136248_58378051
[3] https://godbolt.org/z/EgkPrP

Reply via email to