Why Is Allocator::Rebind Necessary When We Have Template Template Parameters

what does (template) rebind do?

The _Alloc template is used to obtain objects of some type. The container may have an internal need to allocate objects of a different type. For example, when you have a std::list<T, A>, the allocator A is meant to allocate objects of type T but the std::list<T, A> actually needs to allocate objects of some node type. Calling the node type _Ty, the std::list<T, A> needs to get hold of an allocator for _Ty objects which is using the allocation mechanism provided by A. Using

typename _A::template rebind<_Ty>::other

specifies the corresponding type. Now, there are a few syntactic annoyances in this declaration:

  1. Since rebind is a member template of _A and _A is a template argument, the rebind becomes a dependent name. To indicate that a dependent name is a template, it needs to be prefixed by template. Without the template keyword the < would be considered to be the less-than operator.
  2. The name other also depends on a template argument, i.e., it is also a dependent name. To indicate that a dependent name is a type, the typename keyword is needed.

Why are allocators to containers passed as template parameters?

Because this would work with only one allocator - standard one. But what if you want to allocate memory differently? For example, you might want to use shared memory, or file-backed memory, or anything else.

This is the whole point of having allocators - to allow user to customize the way memory is going to be allocated and freed.

A query regarding allocator rebind

The standard library always accesses Allocators through the std::allocator_traits template. This template provides a default definition of rebind if it conforms to the form Alloc<U, Args>

rebind_alloc<T> Alloc::rebind<T>::other if present, otherwise Alloc<T, Args> if this Alloc is Alloc<U, Args> - cppreference.com : std::allocator_traits

Why do container allocators need to specify the type they're allocating?

It's a good question, and your suggestion is one possible alternative to the standard scheme. Another would have been to use template template parameters:

template<typename T>
class AnAllocator
{ ... };

template<typename T, template <typename> class Alloc = std::allocator>
class Vector
{
typedef Alloc<T> allocator_type;
...
};

Vector<int, AnAllocator> v;

The allocator interface was designed before template template parameters were part of the language, so that wasn't an option.

There are lots of things that would be done differently if the allocator API were to be designed today, unfortunately we're stuck with the one we have (and the continuing complexity caused by trying to extend it in semi-compatible ways).

How to avoid rebind in allocator T, N c++17

Only std::allocator's rebind member template is deprecated. If you are using your own class, you can still define rebind.

Do it through std::allocator_traits, like:

using AllocatorForU = std::allocator_traits<AllocatorForT>::template rebind_alloc<U>;

The default for rebind_alloc for AllocatorTemplate<T, OtherTypes...> is AllocatorTemplate<U, OtherTypes...>, which works for std::allocator, which is why std::allocator<T>::rebind is deprecated. You have to define it for your class since it has a non-type template parameter.

C++ custom allocator size argument as template parameter throws compiler error

To get an allocator for some type U from an allocator for type T, member alias template std::allocator_traits::rebind_alloc<U> is used [allocator.traits.types]:

Alloc::rebind<T>::other if Alloc::rebind<T>::other is valid and
denotes a type; otherwise, Alloc<T, Args> if Alloc is a class template instantiation of the form Alloc<U, Args>, where Args is zero or more type arguments; otherwise, the instantiation of rebind_alloc is ill-formed.

Note that Args are type template parameters. In your allocators there is no rebind. In the first case Alloc<U> is used, Args is empty. But in the second case, the second template parameter is a non-type one, it cannot be matched by Args.

You need to manually add rebind member struct:

template<typename T, std::size_t sz>
class StaticAllocator {
// ...

template<class U>
struct rebind {
using other = StaticAllocator<U, sz>;
};
};

Also note that your allocator is broken for some general type S. First, buf will be initialized by default constructing sz objects S. Then, it will be overwritten by newly constructing Ss at the same location before destructing the existing ones. The similar thing happens upon reallocation. This can lead to undefined behaviour. See this and this questions for some details.


The following solution was proposed in the now-deleted answer: inherit from std::allocator<T>:

template<typename T, std::size_t sz> class StaticAllocator : 
public std::allocator<T> {
// ...
};

The code compiles and runs, but... StaticAllocator::allocate and StaticAllocator::deallocate are not called (at least in libstdc++). The reason is that internally std::vector always uses rebind to get the allocator type:

using Tp_alloc_type = 
typename gnu_cxx::alloc_traits<Alloc>::template rebind<Tp>::other;

rebind is inherited from std::allocator<T> and it returns std::allocator instead of StaticAllocator. That's why you still have to provide your own rebind in StaticAllocator.

Purpose of rebind in the following container

It is not a data member. It is a template member, that allows code (which is probably itself a template) to get a different container type with a similar allocator.

e.g. applying a function to each element to get a new container of the results

template <typename Container, typename Function, typename Result = typename Container::template Rebind<std::invoke_result_t<Function, typename Container::const_reference>>::Other>
Result transform(const Container & container, Function function)
{
Result result(container.get_allocator());
for (auto & element : container) { result.push_back(function(element); }
return result;
}

Can I use allocator specified for some type to allocate objects of another type in C++?

std::allocator has a member type rebind for exactly that purpose:

std::allocator<Node> alloc;

std::allocator<Node>::rebind<char>::other char_alloc;

char * mem = char_alloc.allocate(string_len);

In allocator-aware STL classes, why are the allocators not template template arguments?

This would introduce a requirement that the allocator type be a class template with exactly one template argument, specialized with the container's value_type. Your proposal would eliminate

template<typename T, unsigned int PoolNumber = 0>
class my_allocator;

as a valid allocator.

At the same time, I can simply use the typedef I already have for my allocator type, and don't need to take it apart or repeat its template name:

template<typename T> class my_allocator;

typedef my_allocator<int> int_allocator;

std::list<int, int_allocator> ... // valid currently, difficult to express with your proposal


Related Topics



Leave a reply



Submit