Michael Kuritzky
Michael Kuritzky

Reputation: 123

Virtual wrapper of std::queue<T> does not compile when T has no copy constructor

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

Answers (2)

Walter
Walter

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

Marius
Marius

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

Related Questions