Search code examples
c++c++11deque

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


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?


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.