Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allocators: how are the standard containers expected to work internally?

As an example for this question, I'll use std::vector.
Its definition from the documentation follows:

template<class T, class Allocator = std::allocator<T>>
class vector;

As expected, if T is its type, the allocator should be biased towards T.
Anyway, the code below compiles with no errors (at least, using GCC) and runs:

#include<vector>
#include<memory>
#include<string>

struct S {
    int i;
    double d;
    std::string s;
};

int main() {
    std::allocator<int> alloc;
    std::vector<S, std::allocator<int>> v{alloc};
    v.push_back(S{});
}

Here, I'm creating a vector of S by using an allocator focused on int.

Is it legal code? Should I expect undefined behavior? What else?
I don't fully understand what the magic behind that and why the STL lets the users do that.
On the other side, things like rebind are mentioned only in the documentation of std::list and I don't know if they are meant also for such a case.

In other therms, if it works I'd like to know why it works (is it rebind behind that?), otherwise I'd like to know why it is allowed.

like image 403
skypjack Avatar asked Jan 06 '23 16:01

skypjack


2 Answers

Table 98 -- Allocator-aware container requirements says in its first row:

Requires: allocator_type::value_type is the same as X::value_type

Violation of a Requires clause by the client code results in undefined behavior.

gcc and VS do not detect this error. They allow the code to work by rebinding the allocator to the appropriate type. libc++ actively detects this error with a static_assert.

http://melpon.org/wandbox/permlink/LVYEHfVIGoyZsii8

All three implementations of vector are valid in this regard.

The rationale for the libc++ active detection of this error is to find your errors for you faster (and at compile time). It would be a bad thing if in a large program you had two vectors: vector<int, some_allocator<int>> and vector<int, some_allocator<long>> and the program logic assumed these were the same type.

like image 171
Howard Hinnant Avatar answered Jan 20 '23 01:01

Howard Hinnant


It seems that it works, which kind of surprises me, but:

You're breaking a contract. allocator must fulfill the requirements for allocator<T>, and to cite cppreference, this includes:

a.allocate(n) | allocates storage suitable for n objects of type T, but does not construct them. May throw exceptions.

Which std::allocator<int> for S doesn't do, as sizeof(int)!= sizeof(S).

So,

I'd like to know why it is allowed.

It's not allowed. It's just nothing your compiler can detect.

@AlbertoM adds:

To add a quote from cppreference, in the std::vector page: " the behavior is undefined if Allocator::value_type is not the same as T.", T being the value type of the vector.

Ie. in your case, the behaviour is undefined.

like image 21
Marcus Müller Avatar answered Jan 19 '23 23:01

Marcus Müller