llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
28.08k stars 11.59k forks source link

Default constructors of node-based containers don't construct allocators #48676

Open miyuki opened 3 years ago

miyuki commented 3 years ago
Bugzilla Link 49332
Version 11.0
OS Linux
CC @mclow

Extended Description

Consider the following code:

https://godbolt.org/z/Tn73Me

#include <forward_list>
#include <list>
#include <vector>
#include <deque>
#include <set>
#include <unordered_set>
#include <stdio.h>

int n_alloc = 0;
struct alloc : public std::allocator<char> {
    alloc() { n_alloc++; };
};

int main(void)
{
    std::vector<char, alloc> c1;
    printf("n_alloc: %d\n", n_alloc);
    std::deque<char, alloc> c2;
    printf("n_alloc: %d\n", n_alloc);

    std::forward_list<char, alloc> c3;
    printf("n_alloc: %d\n", n_alloc);
    std::list<char, alloc> c4;
    printf("n_alloc: %d\n", n_alloc);
    std::set<char, std::less<char>, alloc> c5;
    printf("n_alloc: %d\n", n_alloc);
    std::unordered_set<char, std::hash<char>, std::equal_to<char>, alloc> c6;
    printf("n_alloc: %d\n", n_alloc);
}

In [forwardlist.overview], the default constructor is specified as follows: forward_list() : forward_list(Allocator()) { }

Similarly for other containers.

So, the expected output of the above program is:

1
2
3
4
5
6

But with libc++ (recent trunk version) the output is:

1
2
2
2
2
2
cneumann commented 2 years ago

This is similar to #51816 but this issue more generic (covers all node based containers). Similar to my comment there I think the problem boils down to the custom allocator not having rebind_alloc nor there being a allocator_traits specialization for it. Modifying the repro to contain:

template <typename T>
struct alloc : public std::allocator<T> {
    // re-use ctors
    using std::allocator<T>::allocator;

    alloc() { n_alloc++; };
};

namespace std
{
    template <typename T>
    struct allocator_traits<alloc<T>> : public allocator_traits<std::allocator<T>>
    {
        template <typename U>
        using rebind_alloc = alloc<U>;
    };
} // namespace std

I get results that are closer to your expectations, but with libstdc++ interestingly depend on the selected standard mode (all with godbolt's clang trunk):

-std=c++17 -stdlib=libc++ and -std=c++20 -stdlib=libc++

n_alloc: 1
n_alloc: 3
n_alloc: 4
n_alloc: 5
n_alloc: 6
n_alloc: 8

-std=c++17 -stdlib=libstdc++

n_alloc: 1
n_alloc: 2
n_alloc: 2
n_alloc: 3
n_alloc: 4
n_alloc: 4

-std=c++20 -stdlib=libstdc++

n_alloc: 1
n_alloc: 2
n_alloc: 3
n_alloc: 4
n_alloc: 5
n_alloc: 6