views:

806

answers:

8

Hi, 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?

+2  A: 

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
Or use a for loop to push into the queue. What I'm doing doesn't seem unreasonable, though, does it?
Andy Balaam
Not it isn't. See Frank's answer as to how to achieve what you want.
sbi
No it does not (functionally), but you will have to roll your own inserter unfortunately.
Matthieu M.
+3  A: 

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
Andy isn't trying to iteratoe through the queue, but to append to it.
sbi
Yes, but I need an iterator-like thing to be able to push into it.
Andy Balaam
Otherwise your answer is reasonable, though. `:)` +1
sbi
@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
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.
Charles Bailey
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
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.
Charles Bailey
`char*` does not inherit from `iterator`, but there is a specialization for `iterator_traits<T*>`. 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
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
+2  A: 

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
It's called "back inserter" in the STL and you get one from `back_inserter`. However, this doesn't work on `std::queue`.
sbi
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
@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
@Éric: No, it isn't. Not at all. However, my comment was written before Luc's answer ever had a `push_inserter`. `:)`
sbi
+1  A: 

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
This is also a great idea.
Andy Balaam
+3  A: 

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
A: 

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
Very good point. Unfortunately my example was too simplified and that isn't what I want to do.
Andy Balaam
queue cannot use a vector. The container must support `pop_front`.
UncleBens
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
+3  A: 

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
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
+7  A: 

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 ) );
Charles Bailey
Genius. Neat, beautiful, and exposes the odd decision to use the name push instead of push_back in the first place.
Andy Balaam
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
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.
Charles Bailey
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