14

The following code does not compile with Visual Studio 2013:

#include <vector>

struct X {
    X() = default;
    X(const X&) = delete;
    X& operator=(const X&) = delete;
    X(X&&) = delete;
    X& operator=(X&&) = delete;
    ~X() = default;
};

void foo()
{
    std::vector<X> v;
    std::vector<X> w;
    w = std::move(v);
}

The error message says

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

That makes no sense to me. You should not need the move constructor for X in order to move a vector<X>. Is this a compiler bug, or am I missing something?

Here is the complete error message:

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\xmemory0(600): error C2280: 'X::X(X &&)' : attempting to reference a deleted function
    Test.cpp(9) : see declaration of 'X::X'
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\xmemory0(723) : see reference to function template instantiation 'void std::allocator<_Ty>::construct<_Objty,_Ty>(_Objty *,_Ty &&)' being compiled
    with
    [
        _Ty=X
    ,   _Objty=X
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\xmemory0(723) : see reference to function template instantiation 'void std::allocator<_Ty>::construct<_Objty,_Ty>(_Objty *,_Ty &&)' being compiled
    with
    [
        _Ty=X
    ,   _Objty=X
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\xmemory0(872) : see reference to function template instantiation 'void std::allocator_traits<_Alloc>::construct<_Ty,_Ty>(std::allocator<_Ty> &,_Objty *,_Ty &&)' being compiled
    with
    [
        _Alloc=std::allocator<X>
    ,   _Ty=X
    ,   _Objty=X
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\xmemory0(872) : see reference to function template instantiation 'void std::allocator_traits<_Alloc>::construct<_Ty,_Ty>(std::allocator<_Ty> &,_Objty *,_Ty &&)' being compiled
    with
    [
        _Alloc=std::allocator<X>
    ,   _Ty=X
    ,   _Objty=X
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\xmemory(378) : see reference to function template instantiation 'void std::_Wrap_alloc<std::allocator<_Ty>>::construct<X,X>(_Ty *,X &&)' being compiled
    with
    [
        _Ty=X
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\xmemory(378) : see reference to function template instantiation 'void std::_Wrap_alloc<std::allocator<_Ty>>::construct<X,X>(_Ty *,X &&)' being compiled
    with
    [
        _Ty=X
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\xmemory(416) : see reference to function template instantiation '_FwdIt std::_Uninit_copy<_InIt,_FwdIt,std::allocator<_Ty>>(_InIt,_InIt,_FwdIt,std::_Wrap_alloc<std::allocator<_Ty>> &,std::_Nonscalar_ptr_iterator_tag)' being compiled
    with
    [
        _FwdIt=X *
    ,   _InIt=std::move_iterator<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<X>>>>
    ,   _Ty=X
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\xmemory(427) : see reference to function template instantiation '_FwdIt std::_Uninit_copy<_Iter,X,_Alloc>(_InIt,_InIt,_FwdIt,_Alloc &)' being compiled
    with
    [
        _FwdIt=X *
    ,   _Iter=std::move_iterator<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<X>>>>
    ,   _Alloc=std::_Wrap_alloc<std::allocator<X>>
    ,   _InIt=std::move_iterator<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<X>>>>
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\vector(1640) : see reference to function template instantiation '_FwdIt std::_Uninitialized_copy<_Iter,X*,std::_Wrap_alloc<std::allocator<_Ty>>>(_InIt,_InIt,_FwdIt,_Alloc &)' being compiled
    with
    [
        _FwdIt=X *
    ,   _Iter=std::move_iterator<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<X>>>>
    ,   _Ty=X
    ,   _InIt=std::move_iterator<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<X>>>>
    ,   _Alloc=std::_Wrap_alloc<std::allocator<X>>
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\vector(789) : see reference to function template instantiation 'X *std::vector<X,std::allocator<_Ty>>::_Ucopy<_Iter>(_Iter,_Iter,X *)' being compiled
    with
    [
        _Ty=X
    ,   _Iter=std::move_iterator<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<X>>>>
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\vector(789) : see reference to function template instantiation 'X *std::vector<X,std::allocator<_Ty>>::_Ucopy<_Iter>(_Iter,_Iter,X *)' being compiled
    with
    [
        _Ty=X
    ,   _Iter=std::move_iterator<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<X>>>>
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\vector(766) : see reference to function template instantiation 'void std::vector<X,std::allocator<_Ty>>::_Construct<_Iter>(_Iter,_Iter,std::forward_iterator_tag)' being compiled
    with
    [
        _Ty=X
    ,   _Iter=std::move_iterator<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<X>>>>
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\vector(766) : see reference to function template instantiation 'void std::vector<X,std::allocator<_Ty>>::_Construct<_Iter>(_Iter,_Iter,std::forward_iterator_tag)' being compiled
    with
    [
        _Ty=X
    ,   _Iter=std::move_iterator<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<X>>>>
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\vector(854) : see reference to function template instantiation 'void std::vector<X,std::allocator<_Ty>>::_Construct<std::move_iterator<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<X>>>>>(_Iter,_Iter)' being compiled
    with
    [
        _Ty=X
    ,   _Iter=std::move_iterator<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<X>>>>
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\vector(854) : see reference to function template instantiation 'void std::vector<X,std::allocator<_Ty>>::_Construct<std::move_iterator<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<X>>>>>(_Iter,_Iter)' being compiled
    with
    [
        _Ty=X
    ,   _Iter=std::move_iterator<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<X>>>>
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\vector(849) : while compiling class template member function 'void std::vector<X,std::allocator<_Ty>>::_Assign_rv(std::vector<_Ty,std::allocator<_Ty>> &&,std::false_type)'
    with
    [
        _Ty=X
    ]
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\vector(860) : see reference to function template instantiation 'void std::vector<X,std::allocator<_Ty>>::_Assign_rv(std::vector<_Ty,std::allocator<_Ty>> &&,std::false_type)' being compiled
    with
    [
        _Ty=X
    ]
    Test.cpp(16) : see reference to class template instantiation 'std::vector<X,std::allocator<_Ty>>' being compiled
    with
    [
        _Ty=X
    ]
Columbo
  • 60,038
  • 8
  • 155
  • 203
Johan Råde
  • 20,480
  • 21
  • 73
  • 110
  • 2
    Isn't this a defect in the default allocator? http://open-std.org/JTC1/SC22/WG21/docs/lwg-defects.html#2103 http://rextester.com/LCFA56035 – dyp Oct 20 '14 at 18:29
  • 2
    You may as well update this to include your speculation for **why** you *don't* need object-level move construction to perform this. Some readers seeing this may not understand why you think it isn't needed without clarification. My suspicion is @dyp is correct; the allocator either isn't configured for `propagate_on_container_move_assignment` (C++14, btw) or is ignoring said-same. – WhozCraig Oct 20 '14 at 18:33
  • As written actually compiles for me on gcc 4.7.2. But then, won't you never be able to add anything to your `vector`? – Barry Oct 20 '14 at 18:34
  • Why would you try to put non-copyable, non-moveable objects into an `std::vector<>`? It seems pretty useless to me - you won't even be able to insert anything into it with `emplace_back()`. I would understand if you used either a `vector >` or a `vector`... – cmaster - reinstate monica Oct 20 '14 at 20:02
  • @cmaster: This snippet is based on production code where I need a container with exactly `1 << 16` instances of a class. The number `1 << 16` is fixed and will never change. – Johan Råde Oct 20 '14 at 20:21
  • Then, why don't you just use `X* array = new X[1 << 16];`? Gives you `1 << 16` default constructed objects, and no nightmares about missing constructors. `std::vector<>` is designed for flexible arrays, it's not necessarily the best choice for statically sized arrays. – cmaster - reinstate monica Oct 20 '14 at 20:27
  • @cmaster: Yes I could do that. But I asked the question mainly to improve my theoretical understanding of C++11. And the answers I received were very interesting and enlightening. – Johan Råde Oct 21 '14 at 14:51
  • 1
    @dyp somewhat related to [why does deleting move constructor cause vector to stop working](http://stackoverflow.com/q/15181424/1708801). – Shafik Yaghmour Oct 22 '14 at 01:30

2 Answers2

12

As mentioned by dyp in the comments, this is a reported bug in C++11*. The expression

a = rv

(where a is a Container of type X with element type T and rv is a non-const rvalue of type X)
had the following requirement in Table 99, "Allocator-aware container requirements":

If allocator_traits<allocator_type>::propagate_on_container_move_assignment ::value is false, T is MoveInsertable into X and MoveAssignable. All existing elements of a are either move assigned to or destroyed.

allocator_traits had the following definition of propagate_on_container_move_assignment:

typedef see below propagate_on_container_move_assignment;

Type: Alloc::propagate_on_container_move_assignment if such a type exists,
otherwise false_type.

The problem was that one forgot to put the corresponding typedef into std::allocator, so propagate_on_container_move_assignment was always false. This was resolved for C++14 by simply adding the typedef.

* Note that [default.allocator] and [allocator.traits.types] are actually in §20.6 in N3337, not §20.7.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • 5
    The reason why `propagate_on_container_move_assignment` is so important here has to do with deallocation: if the allocator is not propagated, the allocator on the lhs of the assignment must be able to deallocate the memory allocated with the allocator of the rhs. This is only allowed if the allocator objects compare equal - a run-time decision. If they do not compare equal, you cannot reuse the memory and have to move the individual elements. Since it's a run-time decision, move-assignment must be supported. – dyp Oct 20 '14 at 22:04
4

Answer for C++11: VS is compliant with the original spec, because according to this defect report, the specification of std::allocator

leads to the unneeded requirements (MoveInsertable and MoveAssignable of the value type) on the move assignment operator of containers with the default allocator.

This was however fixed in C++14. So now std::allocator does not make this code illegal anymore and according to Table 96 in N3797, ([20.2.1,container.requirements.general]), the requirement for the template argument T of std::vector<T> =: X is

Requires: T is Erasable from X

which is true and a = rv for a value a of type X and an non-cpnst r-value rv of type X has the requirement

a shall be equal to the value that rv had before this assignment,

so no further requirement to T. I did not find any additional requirements to T in [23.3.6,vector], so this should be legal code in C++14 (like the defect report suggests).

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
  • Defect resolutions are considered to apply retroactively to the document they were filed against, so we should instead say that VS followed the original spec , and is wrong as a result. – M.M Mar 18 '16 at 01:10