scx
scx

Reputation: 3937

Reference to vector element in allocator aware class calls copy constructor

I have a class that contains a vector of vector. It is allocator aware. When trying to call the operator[] to store elements in a reference, Visual Studio 2015 fails to compile, AppleClang (latest) is fine with it.

I am unsure whether this is a bug or not, which compiler is right, or if there is some undefined behaviour somewhere in my code.

Here is a concise example, with everything as simple as possible.

#include <cstdlib>
#include <memory>
#include <new>
#include <vector>

/* Allocator */
template <class T>
struct my_allocator {
    typedef T value_type;
    my_allocator() = default;

    template <class U>
    constexpr my_allocator(const my_allocator<U>&) noexcept {
    }

    T* allocate(std::size_t n) {
        if (n > std::size_t(-1) / sizeof(T))
            throw std::bad_alloc();
        if (auto p = static_cast<T*>(std::malloc(n * sizeof(T))))
            return p;
        throw std::bad_alloc();
    }

    void deallocate(T* p, std::size_t) noexcept {
        std::free(p);
    }
};

template <class T, class U>
bool operator==(const my_allocator<T>&, const my_allocator<U>&) {
    return true;
}
template <class T, class U>
bool operator!=(const my_allocator<T>&, const my_allocator<U>&) {
    return false;
}

/* Example Element */
struct X {
    X() = default;
    X(X&&) = default;
    X& operator=(X&&) = default;

    X(const X&) = delete;
    X& operator=(const X&) = delete;

    int test = 42;
};

/* Example Container Class */
template <class T, class Allocator = std::allocator<T>>
struct vec_of_vec {
    using OuterAlloc = typename std::allocator_traits<
            Allocator>::template rebind_alloc<std::vector<T, Allocator>>;

    vec_of_vec(const Allocator& alloc = Allocator{})
            : data(10, std::vector<T, Allocator>{ alloc },
                      OuterAlloc{ alloc }) {

        for (int i = 0; i < 10; ++i) {
            data[i].resize(42);
        }
    }

    std::vector<T, Allocator>& operator[](size_t i) {
        return data[i];
    }

    std::vector<std::vector<T, Allocator>, OuterAlloc> data;
};

/* Trigger Error */
int main(int, char**) {
    my_allocator<X> alloc;
    vec_of_vec<X, my_allocator<X>> test(alloc);

    X& ref_test = test[0][0]; // <-- Error Here!
    printf("%d\n", ref_test.test);

    return 0;
}

VS tries to use the copy constructor of X.

error C2280: 'X::X(const X &)': attempting to reference a deleted function

function main.cpp(42): note: see declaration of 'X::X'

Is there something I am missing with the use of allocators and allocator_traits?

Upvotes: 3

Views: 1210

Answers (1)

Andriy Tylychko
Andriy Tylychko

Reputation: 16256

GCC error sheds light on what's going on, potentially same in VS2015 case.

In file included from memory:65,
                 from prog.cc:2:
bits/stl_uninitialized.h: In instantiation of '_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, _Allocator&) [with _InputIterator = __gnu_cxx::__normal_iterator<const X*, std::vector<X, my_allocator<X> > >; _ForwardIterator = X*; _Allocator = my_allocator<X>]':
bits/stl_vector.h:454:31:   required from 'std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = X; _Alloc = my_allocator<X>]'
bits/alloc_traits.h:250:4:   required from 'static std::_Require<std::__and_<std::__not_<typename std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::type>, std::is_constructible<_Tp, _Args ...> > > std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = std::vector<X, my_allocator<X> >; _Args = {const std::vector<X, my_allocator<X> >&}; _Alloc = my_allocator<std::vector<X, my_allocator<X> > >; std::_Require<std::__and_<std::__not_<typename std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::type>, std::is_constructible<_Tp, _Args ...> > > = void]'
bits/alloc_traits.h:344:16:   required from 'static decltype (std::allocator_traits<_Alloc>::_S_construct(__a, __p, (forward<_Args>)(std::allocator_traits::construct::__args)...)) std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = std::vector<X, my_allocator<X> >; _Args = {const std::vector<X, my_allocator<X> >&}; _Alloc = my_allocator<std::vector<X, my_allocator<X> > >; decltype (std::allocator_traits<_Alloc>::_S_construct(__a, __p, (forward<_Args>)(std::allocator_traits::construct::__args)...)) = void]'
bits/stl_uninitialized.h:351:25:   required from '_ForwardIterator std::__uninitialized_fill_n_a(_ForwardIterator, _Size, const _Tp&, _Allocator&) [with _ForwardIterator = std::vector<X, my_allocator<X> >*; _Size = long unsigned int; _Tp = std::vector<X, my_allocator<X> >; _Allocator = my_allocator<std::vector<X, my_allocator<X> > >]'
bits/stl_vector.h:1466:33:   required from 'void std::vector<_Tp, _Alloc>::_M_fill_initialize(std::vector<_Tp, _Alloc>::size_type, const value_type&) [with _Tp = std::vector<X, my_allocator<X> >; _Alloc = my_allocator<std::vector<X, my_allocator<X> > >; std::vector<_Tp, _Alloc>::size_type = long unsigned int; std::vector<_Tp, _Alloc>::value_type = std::vector<X, my_allocator<X> >]'
bits/stl_vector.h:421:9:   required from 'std::vector<_Tp, _Alloc>::vector(std::vector<_Tp, _Alloc>::size_type, const value_type&, const allocator_type&) [with _Tp = std::vector<X, my_allocator<X> >; _Alloc = my_allocator<std::vector<X, my_allocator<X> > >; std::vector<_Tp, _Alloc>::size_type = long unsigned int; std::vector<_Tp, _Alloc>::value_type = std::vector<X, my_allocator<X> >; std::vector<_Tp, _Alloc>::allocator_type = my_allocator<std::vector<X, my_allocator<X> > >]'
prog.cc:59:42:   required from 'vec_of_vec<T, Allocator>::vec_of_vec(const Allocator&) [with T = X; Allocator = my_allocator<X>]'
prog.cc:76:46:   required from here
bits/stl_uninitialized.h:275:25: error: no matching function for call to '__gnu_cxx::__alloc_traits<my_allocator<X>, X>::construct(my_allocator<X>&, X*, const X&)'
      __traits::construct(__alloc, std::__addressof(*__cur), *__first);```

Wandbox

if we go from bottom to top we see:

  • vec_of_vec constructor
  • filling vector<vector> by 10 copies of empty inner vector
  • construction of copies

Despite of the fact that inner vector is empty, its copy constructor requires that the type it contains is copy-constructible.

P.S.

I don't know how clang overcomes this. Potentially it recognises that vector<vector> if filled with default value (if constructor with passed allocator instance still qualifies as default) and so instead of copying uses default construction

EDIT:

To fix the error replace

vec_of_vec(const Allocator& alloc = Allocator{})
        : data(10, std::vector<T, Allocator>{ alloc },
                  OuterAlloc{ alloc }) {

    for (int i = 0; i < 10; ++i) {
        data[i].resize(42);
    }
}

by

vec_of_vec(const Allocator& alloc = Allocator{})
{
    data.resize(10); // here we don't `fill` it by copies but default-construct 10 instances
    for (int i = 0; i < 10; ++i) {
        data[i].resize(42);
    }
}

or version for stateful allocator:

vec_of_vec(const Allocator& alloc = Allocator{}):
    data(OuterAlloc(alloc))
{
    for (int i = 0; i < 10; ++i) {
        data.emplace_back(alloc);
        data.back().resize(42);
    }
}

Upvotes: 1

Related Questions