18

Consider the following insert and emplace member functions of std::vector<T>:

template <class... Args> iterator emplace(const_iterator position, Args&&... args);
iterator insert(const_iterator position, const T& x);
iterator insert(const_iterator position, T&& x);
iterator insert(const_iterator position, size_type n, const T& x);

What if one of them is invoked with a reference to an element of the vector itself as an argument? Normally, each of them invalidates references to all elements starting from position, which might include the argument, or if a reallocation happens, references to all elements, which definitely include it, but does this mean such an invocation is invalid or does the insertion (seem to) happen first?

Looking at some common implementations gives curious results:

  • libstdc++ copies the argument before moving any elements, but only in the const T& overloads of insert. It contains this comment:

    The order of the three operations is dictated by the C++0x case, where the moves could alter a new element belonging to the existing vector. This is an issue only for callers taking the element by const lvalue ref (see 23.1/13).

    But C++11 §23.1 is just a brief summary of the containers library, and even if we assume this refers to §23.2.1 (which used to be §23.1 in C++03), §23.2.1/13 only gives a definition of allocator-aware containers, which seems to have nothing to do with this. I’ve looked through chapter 23, but I haven’t found anything relevant anywhere.

  • libc++ creates a temporary before moving any elements in emplace, while in insert it moves elements first but converts the argument reference to a pointer and adjusts it to ensure it points to the original element—but again, it does all this only in the const T& overload.

  • Visual C++ creates a copy/temporary before moving any elements in all cases.

Did I miss the place where the standard defines this behaviour? Why do the three C++ libraries I looked at disagree with each other? Why does the libstdc++ comment say it’s only an issue for insert(const_iterator, const T&)? If the standard doesn’t require this to work, why do the libraries ever bother to make it work at all? (Surely this costs some copies and/or moves that could otherwise be avoided.) Finally, if I’m implementing a container that should resemble std::vector, should I make this work?

Chortos-2
  • 995
  • 7
  • 20
  • I think you're looking at an oddity resulting from the history of c++. If vector were being re-engineered today (after c++11) I think you'd find that the insert method would take its argument by value, relying of the availability of move-awareness for performance. – Richard Hodges Mar 27 '15 at 12:31
  • See also [DR2164](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2164). I believe the sentiment is, this should be well-defined. Pre-conditions specified in the standard are met, so the burden is on the implementation to do things in just the right order to satisfy post-conditions. – Igor Tandetnik Mar 27 '15 at 12:36
  • @RichardHodges Part of me agrees with you, but do note that moves aren’t always cheap. Consider `std::array`, for example. – Chortos-2 Mar 27 '15 at 13:24
  • @IgorTandetnik Interesting… and makes sense. But, at least theoretically, do you think an implementation could detect when `emplace` does merely copy- or move-construction or when the constructor is `noexcept` and avoid the temporary, constructing the new element after the move/reallocation? – Chortos-2 Mar 27 '15 at 13:41
  • @IgorTandetnik And (similarly) it still leaves the question of `insert(const_iterator, const T &)` open. Since it only invokes the copy-constructor, which is allowed to throw and leave the container in a modified state, can it avoid the temporary and do the construction after the move/reallocation? – Chortos-2 Mar 27 '15 at 13:43
  • Finally found two past questions that are this is almost a duplicate of! https://stackoverflow.com/questions/18788780/is-it-safe-to-push-back-an-element-from-the-same-vector https://stackoverflow.com/questions/24908718/can-stdvector-emplace-back-copy-construct-from-an-element-of-the-vector-itself It doesn’t seem like anyone is able to come up with an unambiguous explanation based purely on the standard though… Perhaps a clarification request with LWG might be in order. – Chortos-2 Mar 27 '15 at 13:51
  • @Chortos-2 I *knew* I had answered something like this already, but I couldn't remember exactly and search didn't bring it up either. I don't think it's a duplicate, though. Your question is the only one which mentions the rvalue reference overloads. – Angew is no longer proud of SO Mar 27 '15 at 13:54
  • 1
    @Chortos-2 moves may not always be cheap, but copies are _never_ cheaper. – Richard Hodges Mar 27 '15 at 13:54

1 Answers1

8

To answer the second question first: the standard explicitly says that the standard library is allowed to assume that when passing something by rvalue reference, that rvalue reference is the only reference to that object. This means that it cannot legally be an element of the vector. The relevant part of C++11 17.6.4.9/1:

  • If a function argument binds to an rvalue reference parameter, the implementation may assume that this parameter is a unique reference to this argument. ... [ Note: If a program casts an lvalue to an xvalue while passing that lvalue to a library function (e.g. by calling the function with the argument move(x)), the program is effectively asking that function to treat that lvalue as a temporary. The implementation is free to optimize away aliasing checks which might be needed if the argument was an lvalue. —end note ]

This leaves us just to handle the const T & case. And even though libstdc++ and libc++ differ in this case, their net result is the same—they will correctly copy from the object passed in. And the standard only prescribes behaviour, not implementation. As long as they achieve the correct behaviour, they're fine.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Ah, that’s a good point! Okay, the situation with the rvalue reference overload is clear. Another comment notes that `emplace` needs to construct the element first to comply with the strong exception guarantee because it may invoke an arbitrary constructor. But what about the const-lval-ref `insert`? The standard doesn’t seem to define it either way. It says the function “inserts a copy” of the argument, but does this actually imply the copy is made before the reference is invalidated? – Chortos-2 Mar 27 '15 at 13:00
  • @Chortos-2 Unless I misunderstood your question, you've shown that all 3 implementations make sure they copy from a valid source. So what's the question? – Angew is no longer proud of SO Mar 27 '15 at 13:10
  • @Chortos-2: Also note that these are usually documented in the standard. Different 'insert' overloads have different requirements with respect to aliasing. For example, those taking ranges of elements (two iterators) require that the iterators don't refer to the container itself. – David Rodríguez - dribeas Mar 27 '15 at 13:11
  • @Angew They do, but do they _have_ to? And why? – Chortos-2 Mar 27 '15 at 13:15
  • 2
    @Chortos-2: They **have to**, the behavior is defined in the standard as inserting a copy of the original object at the right location. There are no restrictions on whether the object may or may not alias elements inside the vector and thus the implementation needs to make sure that the *required* behavior is followed even in the event of aliasing. Compare with `insert(p,i,j)` that has this precondition: *i and j are not iterators into a.* --this is BTW another reason why you should not try to reimplement standard containers, chances are that the implementation won't be as good. – David Rodríguez - dribeas Mar 27 '15 at 13:49
  • Related info: part of the rationale for this rule is so that move operations do not have to perform a self-move check – M.M Mar 28 '15 at 01:12