c++visual-studiovectorallocatordebug-mode

Custom allocator only compiles in Release mode in VS 2015


I wrote a simple dummy allocator for vector<> so that I can use vector<> as a wrapper for stack arrays, like so:

#include <vector>
#include "stdio.h"
#include "stack_allocator.h"

using namespace std;

int main() {
    int buffer[100];
    vector<int, StackAllocator<int>> v((StackAllocator<int>(buffer, 100)));
    v.push_back(2);
    printf("%d", v[0]);
    v.pop_back();
}

However, only in Debug Mode in VS2015, I get the following compiler error:

'std::StackAllocator<T2, std::allocator<T>>::StackAllocator(std::StackAllocator<T, std::allocator<T>> &&)':
   cannot convert argument 1 from
      'std::_Wrap_alloc<std::StackAllocator<int,std::allocator<T>>>'
   to
      'const std::allocator<T>&'
in "c:\program files (x86)\microsoft visual studio 14.0\vc\include\xmemory0" at line 952

Compilation and execution work as intended in Release mode, though.

Here is stack_allocator.h:

#pragma once

#include <functional>

namespace std {
    template <typename T, typename Allocator = allocator<T>>
    class StackAllocator {
    public:
        typedef typename allocator_traits<Allocator>::value_type value_type;
        typedef typename allocator_traits<Allocator>::pointer pointer;
        typedef typename allocator_traits<Allocator>::const_pointer const_pointer;
        typedef typename allocator_traits<Allocator>::size_type size_type;
        typedef typename allocator_traits<Allocator>::difference_type difference_type;
        typedef typename allocator_traits<Allocator>::const_void_pointer const_void_pointer;
        typedef typename Allocator::reference reference;
        typedef typename Allocator::const_reference const_reference;

        template<typename T2>
        struct rebind {
            typedef StackAllocator<T2> other;
        };

    private:
        size_t m_size;
        Allocator m_allocator;
        pointer m_begin;
        pointer m_end;
        pointer m_stack_pointer;

        bool pointer_to_internal_buffer(const_pointer p) const {
            return (!(less<const_pointer>()(p, m_begin)) && (less<const_pointer>()(p, m_end)));
        }

    public:
        StackAllocator(const Allocator& alloc = Allocator()) noexcept :
            m_size(0),
            m_allocator(alloc),
            m_begin(nullptr),
            m_end(nullptr),
            m_stack_pointer(nullptr) {
        }

        StackAllocator(pointer buffer, size_t size, const Allocator& alloc = Allocator()) noexcept :
            m_size(size),
            m_allocator(alloc),
            m_begin(buffer),
            m_end(buffer + size),
            m_stack_pointer(buffer) {
        }

        template <typename T2>
        StackAllocator(const StackAllocator<T2, Allocator>& other) noexcept :
            m_size(other.m_size),
            m_allocator(other.m_allocator),
            m_begin(other.m_begin),
            m_end(other.m_end),
            m_stack_pointer(other.m_stack_pointer) {
        }

        pointer allocate(size_type n, const_void_pointer hint = const_void_pointer()) {
            if (n <= size_type(distance(m_stack_pointer, m_end))) {
                pointer result = m_stack_pointer;
                m_stack_pointer += n;
                return result;
            }
            else
                return m_allocator.allocate(n, hint);
        }

        void deallocate(pointer p, size_type n) {
            if (pointer_to_internal_buffer(p))
                m_stack_pointer -= n;
            else
                m_allocator.deallocate(p, n);
        }

        size_type capacity() const noexcept {
            return m_size;
        }

        size_type max_size() const noexcept {
            return m_size;
        }

        pointer address(reference x) const noexcept {
            if (pointer_to_internal_buffer(addressof(x)))
                return addressof(x);
            else
                return m_allocator.address(x);
        }

        const_pointer address(const_reference x) const noexcept {
            if (pointer_to_internal_buffer(addressof(x)))
                return addressof(x);
            else
                return m_allocator.address(x);
        }

        pointer buffer() const noexcept {
            return m_begin;
        }

        template <typename T2, typename... Args>
        void construct(T2* p, Args&&... args) {
            m_allocator.construct(p, forward<Args>(args)...);
        }

        template <typename T2>
        void destroy(T2* p) {
            m_allocator.destroy(p);
        }

        template <typename T2>
        bool operator==(const StackAllocator<T2, Allocator>& other) const noexcept {
            return buffer() == other.buffer();
        }

        template <typename T2>
        bool operator!=(const StackAllocator<T2, Allocator>& other) const noexcept {
            return buffer() != other.buffer();
        }
    };
}

Anybody has a clue as to why this error is occurring? How do I solve it?


Solution

  • Your rebind is broken, it should be:

    template<typename T2>
        struct rebind {
            using Alloc2
              = typename allocator_traits<Allocator>::rebind_alloc<T2>;
            using other = StackAllocator<T2, Alloc2>;
        };
    

    Otherwise rebinding always creates something using std::allocator<T2> not something related to the current Allocator argument.

    e.g. if you instantiate StackAllocator<int, SomeAlloc<int> and then rebind it to long you get StackAllocator<long, std::allocator<long>> which is a completely different type.

    I think the VC++ debug mode is creating some kind of wrapper allocator by rebinding yours, and it fails because of your broken rebind.

    Also, these lines are a problem:

        typedef typename Allocator::reference reference;
        typedef typename Allocator::const_reference const_reference;
    

    Types meeting the allocator requirements don't have to have reference and const_reference so by adding these typedefs you ensure your allocator can only work with a subset of allocators. If you think you need them, just define them the same way std::allocator does:

        typedef value_type& reference;
        typedef const value_type& const_reference;