Search code examples
c++templatesallocator

How can I pass a state to the rebind_alloc constructor in STL?


Suppose I have a custom stateful allocator that I want to use for a STL container. The container is going to use the rebind_alloc trait to allocate its internal elements, thus by default (or by defining rebind<T>::other), I can ask it to use my allocator class for the internals as well.

However, I would like to pass the state of my allocator.

This related question asks why

  template<class U> allocator( const allocator<U> & o ) throw()

exists and the answer suggests that what I want to do is possible.

However, from my experiments (basically tracing calls to Allocator() and template <class U> Allocator(const Allocator<U> &other) in a custom allocator), I couldn't get the rebind_alloc conversion constructor to be called instead of the default constructor.


Solution

  • It was actually quite simple, the conversion constructor will be called if the container's constructor was given an allocator instance as argument. It is logical that the container's constructor has to know the allocator instance.

    For example, if the container is not given any argument, the default constructor of the allocator will be used.

    #include <memory>
    #include <set>
    #include <iostream>
    #include <type_traits>
    
    void *default_allocator;
    
    template <typename T>
    struct Allocator
    {
        using value_type = T;
        using pointer = T *;
        using size_type = size_t;
    
        int state;
    
        Allocator(int s) : state(s)
        {
            std::cout << "default with arg " << typeid(T).name() << std::endl;
            std::cout << "state " << state << std::endl;
        }
    
        Allocator() : Allocator(*static_cast<Allocator *>(default_allocator)) // state(static_cast<Allocator *>(default_allocator)->state)
        {
            std::cout << "default without arg " << typeid(T).name() << std::endl;
            std::cout << "state " << state << std::endl;
        }
    
        template <class U>
        Allocator(const Allocator<U> &other)
        {
            state = other.state;
            std::cout
                << "conversion " << typeid(T).name() << " from " << typeid(U).name() << std::endl;
            std::cout << "state " << state << std::endl;
        }
    
        pointer allocate(size_type n)
        {
            std::allocator<T> a;
            return a.allocate(n);
        }
    
        void deallocate(pointer p, size_type n)
        {
            std::allocator<T> a;
            a.deallocate(p, n);
        }
    };
    
    template <class T, class Compare = std::less<T>>
    using set = std::set<T, Compare, Allocator<T>>;
    
    int main()
    {
        std::cout << "new Allocator<int>(10)" << std::endl;
        default_allocator = static_cast<void *>(new Allocator<int>(10));
    
        std::cout << std::endl;
    
        std::cout << "alloc(20)" << std::endl;
        Allocator<int> alloc(20);
    
        std::cout << std::endl;
    
        std::cout << "set<int> s1;" << std::endl;
        set<int> s1;
    
        std::cout << std::endl;
    
        std::cout << "set<int> s2(alloc);" << std::endl;
        set<int> s2(alloc);
    }
    
    new Allocator<int>(10)
    default with arg i
    state 10
    
    alloc(20)
    default with arg i
    state 20
    
    set<int> s1;
    default without arg NSt3__111__tree_nodeIiPvEE
    state 10
    
    set<int> s2(alloc);
    conversion NSt3__111__tree_nodeIiPvEE from i
    state 20