3

Why is push for std::queue<T, Container> defined as

void push( const value_type& value );
void push( value_type&& value );

instead of a single member function with forwarding reference?

void push( T&& value );

Is backward compatibility the main reason?

4
  • 4
    That would require the function to be a template and probably open a whole can of complication worms. Commented Mar 29 at 14:57
  • 1
    It does have an emplace, in case you want to avoid moving the pr-value. Commented Mar 29 at 15:10
  • 3
    See : en.cppreference.com/w/cpp/container/queue/push, the first implementations of std::queue pre-date rvalue references (introduced with C++11). So the const& version is also kept for backward compatibility. Commented Mar 29 at 15:16
  • @molbdnilo can you provide an example in this case? Commented Mar 31 at 15:02

2 Answers 2

4

Because unfortunately they are not equivalent. One of the biggest reasons is that not all subexpressions have types that can be deduced solely from the subexpressions themselves. In some cases types are context-sensitive i.e. depend on their surroundings.

For example, try pushing 0 or NULL into a queue of pointers via a forwarding reference.

(This is not the only counterexample. Exercise for the reader: find other ones. Harder exercise: find counterexamples where the subexpression type isn't the issue.)

Sign up to request clarification or add additional context in comments.

6 Comments

If a 0 or nullptr is explicitly pushed into a queue<T*> the user would expect a "pointer pointing to an invalid memory address" somewhere in the queue. I do not see the problem there. i have made a partial implementation of Queue here coliru.stacked-crooked.com/a/f80192f1b117b17a
@zza: Push 0 or NULL, not new int(0) or nullptr.
There is no difference. Both 0 and NULL gets converted to nullptr in my example.
@zza: I don't think you're understanding what I mean. See coliru.stacked-crooked.com/a/d09a4948b2807c07
In this case it fails because template deduction blocks conversion of int to int*. I do not think it is a fair comparison because for queue<T>, emplace is defined as emplace(Args&& ...) instead of emplace(T&&)
|
-2

Is backward compatibility the main reason?

No.

The purpose of forwarding reference is to avoid code duplication, and forwarding reference will not bring any performance improvement.

First of all, std::queue<T, Container> is just a wrapper of Container, which can be regarded as a Container that is restricted to using only some functions. Therefore, std::queue does not need to implement the detailed logic of push, it only needs to forward push to Container::push_back.

Since std::queue's push only takes 3 lines, there is no need to use forwarding reference. In addition, forwarding references can lead to other problems:
If push really looks like this:

template <class T>
void push( T&& value );

You can observe that since the forwarding reference uses a template, users can pass arguments of any type that is not value_type without immediately triggering a compilation error.

For a real container like std::deque or std::vector, the implementation of push_back typically also just forwards to a push_back_impl that uses a forwarding reference and does all the heavy lifting there.
PS This push_back_impl is usually emplace_back

Related: Why couldn't push_back be overloaded to do the job of emplace_back?

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.