Reputation: 8401
Why can the following program not be compiled?
NB: something_t's move constructor is not noexcept.
#include <memory>
#include <vector>
class something_t {
public:
constexpr something_t() = default;
constexpr something_t(const something_t& other)
: field_(other.field_) {
}
constexpr something_t(something_t&& other)
: field_(other.field_) {
}
private:
unsigned int field_{ 0 };
};
struct data_t {
something_t something;
std::vector<std::unique_ptr<int>> move_only; // <-- this line
};
int main() {
std::vector<data_t> result;
data_t data;
result.push_back(std::move(data));
return 0;
}
Error is (within g++):
/usr/include/c++/9/bits/stl_uninitialized.h:127:72: error: static assertion failed: result type must be constructible from value type of input range
127 | static_assert(is_constructible<_ValueType2, decltype(*__first)>::value,
| ^~~~~
(nearly the same with clang and MSVC).
If I replace the line with the "this line" comment by std::unique_ptr<int> move_only
then the code compiles fine:
struct data_t {
something_t something;
std::unique_ptr<int> move_only;
};
Why does removing std::vector
help? It also compiles with or without std::vector
if I make the something_t
move constructor noexcept.
NB: adding noexcept
to something_t
's move constructor helps, but that's not the question.
Question is:
Why with this:
struct data_t {
something_t something;
std::unique_ptr<int> move_only;
};
does the program compile?
But with
struct data_t {
something_t something;
std::vector<std::unique_ptr<int>> move_only; // <-- this line
};
does the program NOT compile?
In fact, both std::unique_ptr<int>
and std::vector<std::unique_ptr<int>>
:
So they have the same properties.
Update: I've tried to compare the type_traits of both variants:
data_t(vector) data_t(unique_ptr):
is_constructible: true true
is_trivially_constructible: false false
is_nothrow_constructible: true true
is_default_constructible: true true
is_trivially_default_constructible: false false
is_nothrow_default_constructible: true true
is_copy_constructible: true false
is_trivially_copy_constructible: false false
is_nothrow_copy_constructible: false false
is_move_constructible: true true
is_trivially_move_constructible: false false
is_nothrow_move_constructible: false false
is_assignable: false false
is_trivially_assignable: false false
is_nothrow_assignable: false false
is_copy_assignable: false false
is_trivially_copy_assignable: false false
is_nothrow_copy_assignable: false false
is_move_assignable: false false
is_trivially_move_assignable: false false
is_nothrow_move_assignable: false false
is_destructible: true true
is_trivially_destructible: false false
is_nothrow_destructible: true true
is_swappable: false false
is_nothrow_swappable: false false
The only difference is:
is_copy_constructible: true false
I.e., data_t
with vector
is copy-constructible, and with unique_ptr
it is not. But how can this difference affect compilation?
Upvotes: 4
Views: 1451
Reputation: 72431
The important difference here is:
std::is_copy_constructible<std::vector<std::unique_ptr<int>>>::value == true
std::is_copy_constructible<std::unique_ptr<int>>::value == false
That first one is maybe surprising. But note that is_copy_constructible
and most similar type traits only require that the operation they test is declared, not that it would be valid to actually use. std::vector
unfortunately lacks some "SFINAE correctness" here, but that might be intentional for backwards compatibility.
The Standard's description of template <class T, class Allocator> class vector
in [vector.overview]/2 simply says that it declares a member vector(const vector& x);
. The following sections say nothing else about the copy constructor. In particular, std::vector
doesn't have a piece similar to this sentence from [optional.ctor]/6 about the copy constructor of std::optional<T>
:
constexpr optional(const optional& rhs);
Remarks: This constructor shall be defined as deleted unless
is_copy_constructible_v<T>
is true.
Because of the various requirements on std::vector<T>
, its functions like push_back
, insert
, and emplace
which need to deal with the possibility of reallocating and populating new memory with elements already in the vector are forced to be implemented like this:
std::is_nothrow_move_constructible<T>::value
is true, uses the move constructor of T
, and the functions provide the strong exception guarantee.std::is_nothrow_move_constructible<T>::value
is false and std::is_copy_constructible<T>::value
is true, uses the copy constructor of T
, and the functions provide the strong exception guarantee.std::is_nothrow_move_constructible<T>::value
and std::is_copy_constructible<T>::value
are both false, uses the move constructor of T
, but the functions cannot provide the strong exception guarantee.(T
must be move constructible, which might actually mean using a copy constructor, as a general requirement of these container functions.)
So when data_t
has a std::vector<std::unique_ptr<int>>
member, it "incorrectly" has an implicitly declared copy constructor which is not deleted. This leads to std::vector<data_t>::push_back
choosing the second option from the list above, but the actual use of the copy constructor leads to errors.
When data_t
has a std::unique_ptr<int>
member, its deleted copy constructor means that the implicitly declared copy constructor of data_t
is also deleted. So in this case, std::vector<data_t>::push_back
chooses the third option from the list above, using the move constructor, but if it does throw, the vector is left in an unspecified state.
Upvotes: 5