Simplified code:
#include <queue>
#include <memory>
#include <vector>
class Foo {
public:
Foo() {};
virtual ~Foo() {}
};
int main()
{
std::queue<std::unique_ptr<Foo>> queue;
auto element = std::make_unique<Foo>();
queue.push(std::move(element));
std::vector<std::queue<std::unique_ptr<Foo>>> vector;
// Error 1
vector.push_back(queue);
// Error 2
vector.push_back(std::move(queue));
// Error 3
vector.push_back({});
return 0;
}
Error:
'std::unique_ptr>::unique_ptr(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)': attempting to reference a deleted function
Obviously copying c~tor of unique_ptr is removed but I'm not trying to copy it. Am I?
This is a bit tricky. All std::vector<T>
functions that can increase the size of the vector have to do it in an exception-safe way if either of these two things are true:
T
has a move constructor that guarantees it will never throw any exceptions; or,
T
has a copy constructor.
So in most implementations, if T
has a move constructor declared nothrow
or equivalent, vector
will use the move constructor of T
for those operations. If not, and T
has a copy constructor, vector
will use the copy constructor, even if T
has a move constructor.
And the problem here is that std::queue
always declares it has a copy constructor, even if that copy constructor can't actually be instantiated, and always declares it has a move constructor that might throw, even if the container member's move constructor guarantees it won't throw.
The Standard specifies these in [queue.defn] as:
namespace std {
template<class T, class Container = deque<T>>
class queue {
// ...
public:
explicit queue(const Container&);
explicit queue(Container&& = Container());
// ...
};
}
This class template definition could be improved in a couple of ways to be more "SFINAE-friendly" and avoid issues like the one you ran into. (Maybe somebody could check for other classes with similar issues and submit a proposal to the Library Working Group.)
Change the move constructor to promise not to throw if the Container
type makes the same promise, typically done with language like:
explicit queue(Container&& rhs = Container()) nothrow(see below);
Remarks: The expression inside noexcept
is equivalent to is_nothrow_move_constructible_v<Container>
.
Change the copy constructor to be deleted if the Container
type is not copyable, typically done with language like:
explicit queue(const Container&);
Remarks: This constructor shall be defined as deleted unless is_copy_constructible_v<Container>
is true
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With