Search code examples
c++templatesc++20template-argument-deduction

Template argument deduction of custom allocator


There is a custom allocator class:

template<typename T>
class pool_allocator {
public:
    using value_type = T;
    using pointer = value_type *;

    /* Default constructor */
    constexpr pool_allocator( void ) noexcept = default;

    /* Converting constructor used for rebinding */
    template<typename U>
    constexpr pool_allocator( const pool_allocator<U> & ) noexcept {}

    [[nodiscard]] pointer allocate( size_t n, [[maybe_unused]] const pointer hint = nullptr ) const noexcept {
        return get_pool().allocate( n );
    }

    void deallocate( pointer ptr, size_t n ) noexcept {
        get_pool().deallocate( ptr, n );
    }

private:
    template<size_t CAPACITY>
    memory_pool<value_type, CAPACITY> & get_pool( void ) noexcept;
};

The pool_allocator serves as the allocator to a memory_pool implementation:

template<typename T, size_t CAPACITY>
class memory_pool {
public:
    using element_type = T;
    using pointer = element_type *;
    static constexpr size_t capacity { CAPACITY };

    [[nodiscard]] inline pointer allocate( [[maybe_unused]] size_t n = 1 ) noexcept { ... }

    inline void deallocate( pointer ptr, [[maybe_unused]] size_t n = 1 ) noexcept { ... }
};

The problem is there are many instantiations of memory pool for particular type of various sizes. For instance:

struct sample { /* ... */ };

memory_pool<sample, 10> m_samples;  /* Serves for 10 instances of 'sample' type */

Once tried to use the pool_allocator to allocate the instance from the m_samples pool:

allocator_traits<pool_allocator<sample>>::allocate( pool_allocator<sample> {}, 1 );

It is required to "link" concrete memory_pool instance with the allocator. Let's consider, the type T matches every time, but the allocator shall not be aware of the memory_pool capacity. So my thoughts were to declare a getter for particular memory_pool type get_pool() - to declare it within the allocator but to have it defined in a .cpp file to implement the link, something like:

.cpp:

template<> template<>
memory_pool<sample, 10> & pool_allocator<sample>::get_pool( void ) noexcept {
    return m_samples; /* The instance being created before */
}

So my question is: How to make this implemented? Simply to have the option to use type specific pool_allocator<sample> to operate with concrete memory_pool instance for the same type but not to specify the capacity as explicit pool_allocator template parameter... to make it deduced? To use delegate/function pointer instead? Any other way of doing so?


Solution

  • The solution is quite simple (only important parts are listed):

    template<typename T>
    class pool_allocator {
    public:
    
        [[nodiscard]] pointer allocate( size_t n, [[maybe_unused]] const pointer hint = nullptr ) const noexcept {
            return get_pool().allocate( n );
        }
    
        void deallocate( pointer ptr, size_t n ) noexcept {
            get_pool().deallocate( ptr, n );
        }
    
    private:
        static auto & get_pool( void ) noexcept; /* <-- here it comes */
    };
    

    and then somewhere in a .cpp file, specialize the get_pool() static member for particular type:

    template<>
    auto & pool_allocator<sample>::get_pool( void ) noexcept {
        return m_samples;
    }
    

    So simple auto declaration does the trick. Maybe the next step to improve would be to constrain the get_pool() return value by a concept, but it is another story...