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

            Bug ID: 86127
           Summary: STL containers do not satisfy
                    container.requirements.general clause 8
           Product: gcc
           Version: 7.3.1
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: fidget324 at gmail dot com
  Target Milestone: ---

Clause 23.2.1.8 of n4140 states that for containers which obtain memory using
an allocator, "Move constructors obtain an allocator by move construction from
the allocator belonging to the container being copied." At least several STL
containers in libstdc++ do not satisfy this requirement, and in fact perform
several unnecessary copies. I recently reported a similar bug in libc++, which
has since been fixed: https://bugs.llvm.org/show_bug.cgi?id=37694

// test.cpp
#include <memory>
#include <forward_list>
#include <vector>
#include <map>
#include <iostream>

template <typename T>
class my_allocator {
  std::allocator<T> __alloc;

public:
  using value_type = T;
  using propagate_on_container_move_assignment = std::true_type;

  template <typename U> friend class my_allocator;

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

  my_allocator() = default;
  template <typename U>
  my_allocator(const my_allocator<U> &other) : __alloc(other.__alloc) {
    std::cout << "template copy constructor\n";
  }
  my_allocator(const my_allocator &other) : __alloc(other.__alloc) {
    std::cout << "copy constructor\n";
  }
  template <typename U>
  my_allocator(my_allocator<U> &&other) noexcept : __alloc(other.__alloc) {
    std::cout << "template move constructor\n";
  }
  my_allocator(my_allocator &&other) noexcept : __alloc(other.__alloc) {
    std::cout << "move constructor\n";
  }

  value_type *allocate(std::size_t n) {
    return __alloc.allocate(n);
  }
  void deallocate(value_type *p, std::size_t n) {
    __alloc.deallocate(p, n);
  }
};

int main() {
  std::cout << "\nforward_list test\n==================\n";
  std::forward_list<int, my_allocator<int>> l = {1, 2, 3, 4};
  auto l1 = std::move(l);
  std::cout << "\nmap test\n==================\n";
  std::map<int, int, std::less<int>, my_allocator<std::pair<const int, int>>> m
= {{1, 2}, {3, 4}};
  auto m1 = std::move(m);
  std::cout << "\nvector test\n==================\n";
  std::vector<int, my_allocator<int>> v = {1, 2, 3, 4};
  auto v1 = std::move(v);
}

Compiling with libstdc++ 7.3.1:

$ clang++ -std=c++14 test.cpp
$ ./a.out

forward_list test
==================
template copy constructor
move constructor
template copy constructor
template copy constructor
template copy constructor
template copy constructor
move constructor

map test
==================
copy constructor
template copy constructor
move constructor
move constructor

vector test
==================
move constructor
template copy constructor
template copy constructor
template copy constructor
template copy constructor

The same test using the latest 'master' of libc++:
$ clang++ -stdlib=libc++ -std=c++14 test.cpp
$ ./a.out

forward_list test
==================
move constructor

map test
==================
move constructor

vector test
==================
move constructor

Reply via email to