Search code examples
c++c++17language-lawyertype-traitslibc++

Why is std::is_copy_constructible_v<std::vector<MoveOnlyType>> true?


In my version of clang and libc++ (near HEAD), this static_assert passes:

static_assert(std::is_copy_constructible_v<std::vector<std::unique_ptr<int>>>)

Of course if you actually try to copy-construct a vector of unique pointers it fails to compile:

../include/c++/v1/__memory/allocator.h:151:28: error: call to implicitly-deleted copy constructor of 'std::unique_ptr<int>'
        ::new ((void*)__p) _Up(_VSTD::forward<_Args>(__args)...);
[...]
note: in instantiation of member function 'std::vector<std::unique_ptr<int>>::vector' requested here
    const std::vector<std::unique_ptr<int>> bar(foo);
                                            ^
../include/c++/v1/__memory/unique_ptr.h:215:3: note: copy constructor is implicitly deleted because 'unique_ptr<int>' has a user-declared move constructor
  unique_ptr(unique_ptr&& __u) _NOEXCEPT

I assume that this situation is because the std::vector<T> implementation doesn't use SFINAE to disable the copy constructor when T isn't copy-constructible. But why not? Is there something in the standard that says it must work this way? It's unfortunate because it means my own SFINAE around copy-constructibility doesn't do the right thing around vectors.


Solution

  • std::vector and other containers (except std::array) are specified to have a copy constructor. This is not specified to be conditional on whether or not the element type is copyable. Only instantiation of the copy constructor's definition is forbidden if the element type is not copyable.

    As a result std::is_copy_constructible_v on the container will always be true. There is no way to test whether an instantiation of a definition would be well-formed with a type trait.

    It would be possible to specify that the copy constructor is not declared or excluded from overload resolution if the element type is not copyable. However, that would come with a trade-off which is explained in detail in this blog post: https://quuxplusone.github.io/blog/2020/02/05/vector-is-copyable-except-when-its-not/.

    In short, if we want to be able to use the container with an incomplete type, e.g. recursively like

    struct X {
        std::vector<X> x;
    };
    

    then we cannot determine whether X is copyable when the container class is instantiated. Therefore the declaration of the copy constructor cannot be made dependent on this property.

    Since C++17 the standard requires std::vector, std::list and std::forward_list, but not the other containers, to work like this with incomplete types.