0

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?

MathuSum Mut
  • 2,765
  • 3
  • 27
  • 59
  • 4
    Why would you insert it all into `namespace std`? – DeiDei Feb 11 '17 at 14:40
  • 1
    This looks to me like ambiguity between attempting to use either the declared templated copy constructor directly or by using the default copy constructor to construct a new instance of `StackAllocator`, for the copy-construction. The error message suggests the compiler it attempting to do just that. As to why the compilation error occurs only in debug mode, who knows. You need to disambiguate the template, using SFINAE, to only be in effect when `T` and `T2` are different. ... and that's besides that using `namespace std` to declare this is horribly wrong. – Sam Varshavchik Feb 11 '17 at 14:40
  • I added an overload of the copy constructor with `T` as a type parameter instead of `T2`, but it still doesn't compile. I'm not fully understanding what the issue is here. – MathuSum Mut Feb 11 '17 at 14:56
  • As the first comment says, this should not be in namespace `std`. What the hell are you doing adding your own things to namespace `std`?! Get off my lawn, you're trespassing. – Jonathan Wakely Apr 04 '17 at 14:25

1 Answers1

1

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;
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521