c++c++17

How do you capture the elements of a vector with specified variables elegantly?


I have a function foo(), it returns a vector with three elements. I want specify three variables to capture the elements at index 0, 1, 2. Obviously I know the normal code, like

auto vec = foo();
a = vec[0]; b = vec[1]; c = vec[2];

But I want an elegant format, just like Python, as follows:

auto& [a,b,c] = foo();

Is there any way to implement it?


Solution

  • auto [a,b,c] = foo(); (structured binding) would be possible for a tuple-like type such as std::tuple or std::array, but it's not possible for std::vector. However, you can use structured bindings syntax with a slight workaround:

    template <typename T>
    std::array<T, 3> first_three(const std::vector<T>& vec) {
        if (vec.size() < 3) {
            throw std::invalid_argument("vec must contain at least three elements");
        }
        return { vec[0], vec[1], vec[2] };
    }
    
    auto [a, b, c] = first_three(foo());
    

    That being said, if you're only interested in the first three elements, perhaps foo() should return a std::array<T, 3> or some other tuple-like type. Perhaps std::vector<T> was the wrong type to use in the first place.

    Note on move semantics

    first_three only works for copyable types in its current state. You can add an rvalue overload to avoid copying:

    template <typename T>
    std::array<T, 3> first_three(std::vector<T>&& vec) {
        if (vec.size() < 3) {
            throw std::invalid_argument("vec must contain at least three elements");
        }
        return { std::move(vec[0]), std::move(vec[1]), std::move(vec[2]) };
    }
    

    There are ways to have only one function as well, but it would be quite annoying to implement. For starters, std::vector::operator[] has no rvalue overloads and you don't have std::forward_like in C++17, so forwarding the elements out of the vector is a bit tricky.