Is it possible to simultaneously remove and get a copy of an object from C++ std::vector or std::deque?

StackOverflow https://stackoverflow.com/questions/19723614

  •  02-07-2022
  •  | 
  •  

Question

In Java, the Deque class has removal methods for the ends that actually return the element returned. In C++, it seems like the only way to achieve the same behavior is to first explicitly copy the element and then pop it.

std::deque<int> myDeque;
myDeque.push_back(5);

int element = myDeque.back();
myDeque.pop_back();

Is there a mechanism to do both simultaneously?

Was it helpful?

Solution

You can write your own wrapper function template:

// Precondition: !container.empty()
// Exception safety: If there is an exception during the construction of val,
//                   the container is not changed.
//                   If there is an exception during the return of the value,
//                   the value is lost.
template <typename C>
auto back_popper(C & container) -> decltype(container.back())
{
    auto val(std::move(container.back()));
    container.pop_back();
    return val;
}

Usage:

auto element = back_popper(myDeque);

You cannot get around the fundamental problem that motivates the separation of back() and pop_back() in the first place, which is that the element's copy or move constructor may throw exceptions and that you may lose the popped element when this happens. You could mitigate it on a case-by-case basis by returning a non-throwing object, e.g. a unique_ptr, which would trade off risk of losing the element for a dynamic allocation, but obviously that's a personal choice you have to make which the standard doesn't make for you.

For example:

// Guarantees that you either get the last element or that the container
// is not changed.
//
template <typename C>
auto expensive_but_lossless_popper(C & container)
-> typename std::unique_ptr<decltype(container.back())>
{
    using T = decltype(container.back());

    std::unique_ptr<T> p(new T(std::move(container.back())));
    container.pop_back();
    return p;                // noexcept-guaranteed
}

Edit: I added std::move around the back() calls, as @Simple suggested. This is legitimate since the moved-from element is no longer needed, and many real-world classes come with noexcept move constructors so that this covers a large number of cases, and the "lossless" workaround only offers an advantage for a small number of "weird" types which have no noexcept moves.

OTHER TIPS

By design, C++ does not offer such a mechanism out of the box to ensure exception safety:

When you try to implement it, you first make a copy of the element, then pop the container and finally you want to return the object to the caller. But what happens when the copy constructor for the last operation throws an exception? The object is no longer in the container and you, as a caller, did not received a copy of it. To prevent that and to offer the strong exception guarantee on the container's operations, an operation that returns the element that is simultaneously popped is not supperted directly.

If you don't want to loose the exception safety, you can restrict this method using SFINAE:

// 
// Precondition: !container.empty()
//               
template <typename C>
auto back_popper(C & container)
-> typename std::enable_if<std::is_nothrow_move_constructible<
                             decltype(container.back())>::value,
                           decltype(container.back())>::type
{
    auto val = std::move(container.back());
    container.pop_back();
    return val;
}

or static_assert (work that out for yourself). (note: edited to avoid any copies in case there is a move constructor, follow Simple's comment.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top