32

Why doesn't vector::push_back take a forwarding reference instead of having two overloads? I've read that the only reason you'd want to overload on lvalues and rvalues is if your functions do something differently for them, so how do both overloads of vector::push_back differ other than moving/copying?

Jeffrey Bosboom
  • 13,313
  • 16
  • 79
  • 92
template boy
  • 10,230
  • 8
  • 61
  • 97
  • I think the forwarding reference version of `push_back` is just called `emplace_back`. – Barry Jan 24 '15 at 21:43
  • 1
    @Barry `emplace_back` takes forwarding references to the arguments of the *constructor* of the element type. – template boy Jan 24 '15 at 21:44
  • @templateboy I don't understand the emphasis. `push_back(T&&)` moves its argument into the constructor of the element type too. – Barry Jan 24 '15 at 21:51
  • 1
    @Barry Nope. `T` is the element type of the vector but `emplace_back` takes `Args&&... args` and forwards `args...` to the constructor as if by `element_type(std::forward(args)...)`. It does not do that in `push_back` – template boy Jan 24 '15 at 21:54
  • 1
    But if the `value_type` has copy constructors then `emplace_back` can be used as a more general version of `push_back`. – juanchopanza Jan 24 '15 at 22:00

1 Answers1

53

I did this largely just because of how the situation evolved. Prior to C++11, there was only:

vector<T>::push_back(const T&);

With the introduction of rvalue references I recommended the addition of the overload:

vector<T>::push_back(T&&);

instead of changing the original signature to:

template <class U> vector<T>::push_back(U&&);

Part of this decision was made because of some concerns about backward compatibility (whether warranted or not), and to ease concerns of both vendors and others on the committee that this was a simple addition of functionality, and not a change to existing functionality.

If I were redesigning vector from scratch today, I would seriously consider just having:

template <class U> vector<T>::push_back(U&&);

or perhaps just:

template <class ...Args> vector<T>::emplace_back(Args&& ...);

More details than you probably want to know about are in N1858.

Why not push_back by value?

This question has been marked as a duplicate of:

Why do C++11 std containers have pass-by-ref and pass-by-rvalue insert/push methods?

which asks this question. So I figured it would be the polite thing to do to actually address that aspect in this answer...

For lvalues and xvalues, a push_back(T) would cost an extra move construction compared to the by-reference solutions. xvalues would require 2 move constructions and lvalues would require 1 copy construction and 1 move construction.

In contrast, with the current design, lvalues cost 1 copy construction and xvalues cost 1 move construction.

For some types T, move construction is not cheap. It would be a poor design choice for vector<T> to assume that T is always cheaply movable. For example what if T is std::array<double, 100>? Changing to the by-value design would require 2 copy constructions instead of 1 to push_back (except for prvalues).

The by-value solution does have advantages, and times when it should be used. It is just that vector<T>::push_back() is not one of those times.

Community
  • 1
  • 1
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • So is there code duplication in those two functions? – template boy Jan 24 '15 at 21:58
  • 1
    @templateboy: In the libc++ implementation there is a very small amount of code duplication in the inlined "fast path" (where no reallocation is needed), but the "slow path" is a non-inlined private function which both overloads forward to. – Howard Hinnant Jan 24 '15 at 22:01
  • 6
    `v.push_back({aggregate_initialization});` breaks under your "if it where designed today". Perfect forwarding is not perfect. – Yakk - Adam Nevraumont Jan 25 '15 at 01:51
  • @HowardHinnant You say that if you were designing it from scratch today you consider having only the U&& version. Wouldn't that require two moves for lvalues? Couldn't it be worse for types which move construction is not cheap? – amfcosta Feb 13 '16 at 21:56
  • 2
    @amfcosta: Point one: I said _consider_. I haven't invested time in testing and implementing these considerations. Point two: No, lvalues would bind to the forwarding reference `U&&` with no moves or copies. Then internally the code would `forward(u)` to construct a `T`. Assuming `U` is a `T`, that's 1 copy construction. I would probably also restrict `U` such that `is_constructible{}`. – Howard Hinnant Feb 13 '16 at 22:10