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

1

1

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.

Pipsissewa answered 11/4, 2021 at 21:41 Comment(9)
rebind_alloc is a type. It doesn't "do" anything.Uncourtly
Also template<class U> allocator( const allocator<U> & o ) is technically not a "copy constructor", it's just a normal conversion constructor, but that's pedantic.Uncourtly
How are you constructing the rebind_alloc?Uncourtly
@MooingDuck I know rebind_alloc is a type, it is going to be used to create the allocators of the elements. What do you mean "constructing rebind_alloc"? The default value Alloc<T, Args> that mimics the container's allocator type is fine for me. So I don't have a type problem but I would like the conversion constructor (thanks for the remark, I edited my question) to be called by my container instead of the default constructor.Pipsissewa
The container is in charge of constructing an instance of the type. The container can either default construct, or copy construct. This is choice by the container is 100% unrelated to anything rebind_alloc does.Uncourtly
Let me take an example (from en.cppreference.com/w/cpp/memory/allocator): std::list<T, A> allocates nodes of some internal type Node<T> using the allocator std::allocator_traits<A>::rebind_alloc<Node<T>> that I can choose to (and will by default) be A<Node<T>, Args>. Now the allocator A<T> I passed to list is stateful and I want the state to be transfered to the allocator of the internal type.Pipsissewa
Btw, the (default) constructor of A<Node<T>, Args> is called every time I declase a list<T, A>. But I would like it to take the container's instance of A<T> as argument (or any other way that would allow me to share a state).Pipsissewa
did you pass the state you wanted to the std::list constructor? Can you show code that has the problem?Uncourtly
Oh I think I figured it out while coding an example.Pipsissewa
P
0

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
Pipsissewa answered 11/4, 2021 at 23:14 Comment(2)
Using globals for this is a terrible ideaUncourtly
Thanks for the review, it was for the sake of example.Pipsissewa

© 2022 - 2024 — McMap. All rights reserved.