17

I'd like to use std::copy to insert elements into a queue like this:

vector<int> v;
v.push_back( 1 );
v.push_back( 2 );

queue<int> q;

copy( v.begin(), v.end(), insert_iterator< queue<int> >( q, q.front() ) );

But this fails to compile, complaining that begin is not a member of std::queue.

Note: I tried it with std::inserter too - this also failed, this time saying that 'reference' is not a member of 'std::queue'. std::back_inserter and std::back_insert_iterator also fail with the same error.

Am I missing something obvious, or do insert_iterators just not work with queues?

JFMR
  • 23,265
  • 4
  • 52
  • 76
Andy Balaam
  • 6,423
  • 6
  • 34
  • 37
  • Although the answers you've been given are good, personally I would just avoid std::queue and any other crippled container adapter. – Kylotan Nov 12 '09 at 17:04
  • Yes, sbi and Naveen's suggestion to use a deque would be a good alternative. – Andy Balaam Nov 13 '09 at 09:23

9 Answers9

23

Unfortunately std::queue 'adapts' the function known as push_back to just push which means that the standard back_insert_iterator doesn't work.

Probably the simplest way (albeit conceptually ugly) is to adapt the container adapter with a short lived container adapter adapter[sic] (eugh!) that lives as long as the back insert iterator.

template<class T>
class QueueAdapter
{
public:
    QueueAdapter(std::queue<T>& q) : _q(q) {}
    void push_back(const T& t) { _q.push(t); }

private:
    std::queue<T>& _q;
};

Used like this:

std::queue<int> qi;

QueueAdapter< std::queue<int> > qiqa( qi );

std::copy( v.begin(), v.end(), std::back_inserter( qiqa ) );
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • 3
    Genius. Neat, beautiful, and exposes the odd decision to use the name push instead of push_back in the first place. – Andy Balaam Nov 12 '09 at 17:00
  • 2
    It's not `push_back`, because the point where the insertions happen don't matter conceptually. Besides, what would `push_back` mean for a `priority_queue`? – UncleBens Nov 12 '09 at 17:24
  • 1
    Conceptually it makes sense, I just used the work 'unfortunately' because it means that it closes off using the `back_insert_iterator` which could be really useful. – CB Bailey Nov 12 '09 at 17:32
  • I agree with Kylotan's comment to the question. If you want to do things beyond what the queue ADT allows (basically pushing and popping), there's little point fighting the queue adaptor. - +1 to your elegant answer, though. – UncleBens Nov 12 '09 at 20:34
8

Queue does not allow iteration through its elements.

From the SGI STL Docs:

A queue is an adaptor that provides a restricted subset of Container functionality A queue is a "first in first out" (FIFO) data structure. 1 That is, elements are added to the back of the queue and may be removed from the front; Q.front() is the element that was added to the queue least recently. Queue does not allow iteration through its elements. [2]

You can make this work, but you can't use insert_iterator. You'll have to write something like queue_inserter that presents an iterator interface.

Update I couldn't help myself and deicded to try to implement the iterator you need. Here are the results:

template< typename T, typename U >
class queue_inserter {
    queue<T, U> &qu;  
public:
    queue_inserter(queue<T,U> &q) : qu(q) { }
    queue_inserter<T,U> operator ++ (int) { return *this; }
    queue_inserter<T,U> operator * () { return *this; }
    void operator = (const T &val) { qu.push(val); }
};

template< typename T, typename U >
queue_inserter<T,U> make_queue_inserter(queue<T,U> &q) {
    return queue_inserter<T,U>(q);
}    

This works great for functions like this:

template<typename II, typename OI>
void mycopy(II b, II e, OI oi) {
    while (b != e) { *oi++ = *b++; }
}

But it doesn't work with the STL copy because the STL is stupid.

Frank Krueger
  • 69,552
  • 46
  • 163
  • 208
  • 1
    Andy isn't trying to iteratoe through the queue, but to append to it. – sbi Nov 12 '09 at 16:42
  • Yes, but I need an iterator-like thing to be able to push into it. – Andy Balaam Nov 12 '09 at 16:43
  • 1
    Otherwise your answer is reasonable, though. `:)` +1 – sbi Nov 12 '09 at 16:44
  • @sbi, It's important that you understand "iterators" in C++ do a heck of a lot more than just "iterate". When they say it doesn't allow iteration, they're saying a lot more than "you can't enumerate the elements" – Frank Krueger Nov 12 '09 at 16:49
  • 2
    Why not implement the queue_inserter to correspond to the back_insert_iterator interface? `std::copy` isn't 'stupid' (IMHO!), it just requires an output iterator which isn't an onerous interface. – CB Bailey Nov 12 '09 at 17:48
  • 1
    onerous? Seriously? My class perfectly implements OutputIterator. The fact that `copy` requires that I inherit `std::iterator` is just a leak in the abstraction. Does `char*` inherit from `std::iterator`? – Frank Krueger Nov 12 '09 at 19:48
  • 4
    Your queue_inserter isn't assignable, but apart from that you don't _have_ to derive from `iterator`, you only have to provide a specialization for `iterator_traits` if you want to able to use your iterator with standard algorithms. – CB Bailey Nov 12 '09 at 20:28
  • 1
    `char*` does not inherit from `iterator`, but there is a specialization for `iterator_traits`. Inheriting from `iterator` is just the simplest way of getting `iterator_traits` to recognize your iterator. - May-be you'd realize the value if you tried to implement some algorithm that needs to know the `value_type` (C++0x's `auto` probably makes things a lot simpler in this department), or handle random access iterators and bidirectional iterators differently. – UncleBens Nov 12 '09 at 22:52
  • 2
    BTW, `std::copy` in particular commonly needs to know more about the iterators, so it can pick an optimal way to do it: e.g pointers + PODs can be memmoved. – UncleBens Nov 12 '09 at 23:05
3

std::queue isn't a container in the STL sense, it's a container adapter with very limited functionality. For what you seem to need either std::vector or std::deque ("double-ended queue, which is a "real container"), seems the right choice.

sbi
  • 219,715
  • 46
  • 258
  • 445
3

I'm pretty sure it just won't work -- a queue provides push, but an insert iterator expects to use push_front or push_back. There's no real reason you couldn't write your own push_insert_iterator (or whatever name you prefer) but it is a bit of a pain...

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
3

insert_iterator and back_insert_iterator only work on containers (or adaptors) with (respectively) insert and push_back methods - queue doesn't have these. You could write your own iterator modelled on these, something like this:

template <typename Container> 
class push_iterator : public iterator<output_iterator_tag,void,void,void,void>
{
public:
    explicit push_iterator(Container &c) : container(c) {}

    push_iterator &operator*() {return *this;}
    push_iterator &operator++() {return *this;}
    push_iterator &operator++(int) {return *this;}

    push_iterator &operator=(typename Container::const_reference value)
    {
         container.push(value);
         return *this;
    }
private:
    Container &container;
};

Unless such a thing already exists, but I'm pretty sure it doesn't.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • I was just about to accept this answer, which looks excellent (although I haven't tried it) but Charles pipped you at the post. – Andy Balaam Nov 12 '09 at 17:01
  • It would be nice to have such an iterator in the Standard Library. However, regarding your implementation, I think it would be safer to have the dereferencing operator return a proxy object instead of the current object. Indeed, the code as it is allows you to do `push_iterator< queue< int > > it; it = 42;`, which is wrong (it also allows you to do `******it`, which is not more correct). The `operator*` should return an object defining the `operator=`. – Luc Touraille Nov 17 '11 at 14:30
  • You could also add to your answer the usual helper function allowing one to omit the container type: `template < typename Container > push_iterator< Container > pusher( Container & c ) { return push_iterator< Container >( c ); }`. – Luc Touraille Nov 17 '11 at 14:32
2

What you need is a push_inserter (i.e. an inserter that performs pushes into the queue). As far as I know, there is no such iterator in the STL. What I usually do is sadly fall back to the good old for loop.

If you have the courage, you can roll your own iterator, something along these lines:

template <typename Container>
class push_insert_iterator
{
  public:
    typedef Container                      container_type;
    typedef typename Container::value_type value_type;

    explicit push_insert_iterator(container_type & c)
        : container(c)
    {}    // construct with container

    push_insert_iterator<container_type> & operator=(const value_type & v)
    {
        //push value into the queue
        container.push(v);
        return (*this);
    }

    push_insert_iterator<container_type> & operator*()
    {
        return (*this);
    }

    push_insert_iterator<container_type> & operator++()
    {
        // Do nothing
        return (*this);
    }

    push_insert_iterator<container_type> operator++(int)
    {
        // Do nothing
        return (*this);
    }

  protected:
    container_type & container;    // reference to container
};

template <typename Container>
inline push_insert_iterator<Container> push_inserter(Container & c)
{
    return push_insert_iterator<Container>(c);
}

This is just a draft but you got the idea. Works with any container (or, well, container adapters) with a push method (e.g. queue, stack).

Luc Touraille
  • 79,925
  • 15
  • 92
  • 137
  • It's called "back inserter" in the STL and you get one from `back_inserter`. However, this doesn't work on `std::queue`. – sbi Nov 12 '09 at 16:41
  • 1
    I know what a back_insert_iterator is: an iterator that performs push_back into a container. Queue does not have a push_back method, that is why back_insert_iterator doesn't work. The method for adding elements in a queue is push, hence the push_insert_iterator idea... – Luc Touraille Nov 12 '09 at 16:44
  • @sbi: Considering that "inserting" an element into a queue is made via the queue::push() function, `push_inserter` is not a bad name, IMHO – Éric Malenfant Nov 12 '09 at 16:50
  • @Éric: No, it isn't. Not at all. However, my comment was written before Luc's answer ever had a `push_inserter`. `:)` – sbi Nov 12 '09 at 23:36
2

std::queue is not one of the basic containers in STL. It is a container adaptor which is built using one of the basic STL containers ( in this case one of the sequential container either std::vector std::deque or std::list). It is designed specifically for FIFO behaviour and does not provide random insertion at the given iterator which you want for the insert_iterator to work. Hence it will not be possible to use queue like this.

The easiest way I could think of to do this is to:

class PushFunctor
{
public:
    PushFunctor(std::queue<int>& q) : myQ(q)
    {
    }
    void operator()(int n)
    {
        myQ.push(n);
    }

private:
    std::queue<int>& myQ;
};

And use it like:

queue<int> q;
PushFunctor p(q);
std::for_each(v.begin(), v.end(), p);
Naveen
  • 74,600
  • 47
  • 176
  • 233
1

for c++11

std::for_each( v.begin(), v.end(), [&q1](int data) {  q1.push(data); }  );

and c++14

std::for_each( v.begin(), v.end(), [&q1](auto data) {  q1.push(data); }  );
E_g
  • 63
  • 6
0

In this simple case, you can write:

vector<int> v;
v.push_back( 1 );
v.push_back( 2 );

queue<int, vector<int> > q(v);

This will make a copy of the vector and use it as the underlying container of the queue.

Of course, this approach won't work if you need to enqueue things after the queue has been constructed.

Thomas
  • 174,939
  • 50
  • 355
  • 478
  • Very good point. Unfortunately my example was too simplified and that isn't what I want to do. – Andy Balaam Nov 12 '09 at 16:54
  • 2
    queue cannot use a vector. The container must support `pop_front`. – UncleBens Nov 12 '09 at 17:06
  • Argh! You're right. Due to templates, my test didn't show this. Well, I guess you can still create a queue that allows you to inspect `front()` and `back()`... :P – Thomas Nov 12 '09 at 20:24