Why is std::is_copy_constructible_v<std::vector<MoveOnlyType>> true?
Asked Answered
T

1

22

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.

Toughen answered 26/3, 2022 at 21:50 Comment(1)
quuxplusone.github.io/blog/2020/02/05/…Earthwork
B
29

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.

Balata answered 26/3, 2022 at 23:4 Comment(3)
It would be interesting to replace "I don't know yet, and thus I pretend X" with "I don't know yet, lets defer though I prefer X", and let the compiler only complain if it cannot resolve it after collecting all the code. Might make the compiler a bit more complex though.Hoad
Is it a feature or bug in the standard though? I get the chicken and egg problem, but currently it seems some type traits are broken due to that...Samira
@Samira For the three listed containers the choice has already been made. Even changing the type trait behavior of the other containers would be breaking backwards-compatibility, so I guess that is unlikely to happen. I don't know whether this was unintentional or with what reasoning this decision was made when C++11 introduced the possibility to use the containers with non-copyable types. Fundamentally if both incomplete types and "accurate" type traits should be supported, how templates work would probably need significant rework far beyond a defect report.Balata

© 2022 - 2024 — McMap. All rights reserved.