Reputation: 123
I'm using C++11. I encountered a compilation error when writing a simple wrapper for std::queue<T>
, and using it with a class that does not have a copy constructor.
Following is a snippet to illustrate the problem.
Basically, I have a wrapper for std::queue<T>
, which has two virtual overloads for push
.
#include <queue>
#include <utility>
#include <future>
template <typename T>
class myqueue {
public:
myqueue() : q() {}
virtual ~myqueue() {}
// pushes a copy
virtual void push(const T& item) {
q.push(item);
}
// pushes the object itself (move)
virtual void push(T&& item) {
q.push(std::move(item));
}
private:
std::queue<T> q;
};
int main() {
// Thanks to Yakk for pointing out that I can reduce clutter by using one of std's non-copyable classes!
myqueue<std::packaged_task<int()>> q;
std::packaged_task<int()> t([]{return 42;});
q.push(std::move(t));
}
When I try to compile this (ICC 13.2, g++ 4.7.3 on my Linux machine here, and also g++ 4.7.2 on Ideone: http://ideone.com/HwBhIX ), the compiler complains that it cannot instantiate myqueue::push(const nocopy&) because nocopy's copy constructor is deleted.
If I remove the virtual
modifiers from push
, this compiles fine (both on my machine and on Ideone).
Can anyone help me understand why this is happening?
P.S.: here is the error stack from Ideone:
In file included from /usr/include/c++/4.7/i486-linux-gnu/bits/c++allocator.h:34:0,
from /usr/include/c++/4.7/bits/allocator.h:48,
from /usr/include/c++/4.7/deque:62,
from /usr/include/c++/4.7/queue:61,
from prog.cpp:1:
/usr/include/c++/4.7/ext/new_allocator.h: In instantiation of ‘void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = std::packaged_task<int()>; _Args = {const std::packaged_task<int()>&}; _Tp = std::packaged_task<int()>]’:
/usr/include/c++/4.7/bits/stl_deque.h:1376:6: required from ‘void std::deque<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::packaged_task<int()>; _Alloc = std::allocator<std::packaged_task<int()> >; std::deque<_Tp, _Alloc>::value_type = std::packaged_task<int()>]’
/usr/include/c++/4.7/bits/stl_queue.h:212:9: required from ‘void std::queue<_Tp, _Sequence>::push(const value_type&) [with _Tp = std::packaged_task<int()>; _Sequence = std::deque<std::packaged_task<int()>, std::allocator<std::packaged_task<int()> > >; std::queue<_Tp, _Sequence>::value_type = std::packaged_task<int()>]’
prog.cpp:13:9: required from ‘void myqueue<T>::push(const T&) [with T = std::packaged_task<int()>]’
prog.cpp:28:1: required from here
/usr/include/c++/4.7/ext/new_allocator.h:110:4: error: use of deleted function ‘std::packaged_task<_Res(_ArgTypes ...)>::packaged_task(const std::packaged_task<_Res(_ArgTypes ...)>&) [with _Res = int; _ArgTypes = {}; std::packaged_task<_Res(_ArgTypes ...)> = std::packaged_task<int()>]’
In file included from prog.cpp:3:0:
/usr/include/c++/4.7/future:1337:7: error: declared here
Upvotes: 4
Views: 1239
Reputation: 45444
This occurs because the c++11 implementation provided by gcc 4.7 is incomplete. When using c++11 you must always use the most up-to-date compiler versions. clang is usually most complete, followed by gcc and, some distance behind, icpc.
Following your edit, the code is not compiling any more (clang 3.2 or gcc 4.8), because it's simply wrong. When the template class myqueue<std::packaged_task<int()>>
is instantiated, the non-template member virtual void push(const T&)
is instantiated too. However this member calls the deleted copy constructor of T
, so is illegal, error.
(That it compiles for non-virtual push(const T&)
is dangerous, as you will get an error as soon as you try to use that function.)
To make you code work, you must avoid that. A virtual
member cannot be a template (which would have allowed you to avoid the problem via SFINAE). But you can specialise class myqueue<T>
depending on whether T
is copyable or not. The following code compiles with gcc 4.8 (but not with icpc 13 or clang 3.2, which has a faulty implementation of std::is_copy_constructible
).
#include <queue>
#include <type_traits>
#include <utility>
#include <future>
template <typename T, typename E=void> class myqueue;
template <typename T>
class myqueue<T, typename std::enable_if<std::is_copy_constructible<T>::value>::type>
{
std::queue<T> q;
public:
virtual ~myqueue() {}
// pushes a copy
virtual void push(const T& item) { q.push(item); }
// pushes the object itself (move)
virtual void push(T&& item) { q.push(std::move(item)); }
};
template <typename T>
class myqueue<T,typename std::enable_if<!std::is_copy_constructible<T>::value>::type>
{
std::queue<T> q;
public:
virtual ~myqueue() {}
// pushes the object itself (move)
virtual void push(T&& item) { q.push(std::move(item)); }
};
int main() {
std::packaged_task<int()> t([]{return 42;});
myqueue<std::packaged_task<int()>> q;
q.push(std::move(t));
}
Of course, this is not quite the same as your original code, as there will be no virtual void push(const&T)
for non-copyable T
, but I think this is exactly what you want. Alternatively, you could make the offending method pure virtual in the specialisation for non-copyable T
.
Sorry, that I cannot fix icpc for you, but at least this code seems to be legal C++11.
Upvotes: 1
Reputation: 2273
myqueue
's standard copy constructor calls the copy constructor of nocopy
. Simply overwrite or =delete
the copy-constructor and the operator=
of myqueue
and you should be fine.
Upvotes: 2