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
Ie, data_t
with vector
is copy-constructible, and with unique_ptr
it is not. But how can this difference affect compilation?
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.