Tom Gebel
Tom Gebel

Reputation: 805

Why in particular should I rather pass a std::span than a std::vector& to a function?

I know this might overlap with the question What is a “span” and when should I use one?, but I think the answer to this specific part of the question is pretty confusing. On one hand, there are quotes like this:

Don't use it if you have a standard library container (or a Boost container etc.) which you know is the right fit for your code. It's not intended to supplant any of them.

But in the same answer, this statement occurs:

is the reasonable alternative to passing const vector& to functions when you expect your data to be contiguous in memory. No more getting scolded by high-and-mighty C++ gurus!

So what part am I not getting here? When would I do this:

void foo(const std::vector<int>& vec) {}

And when this?

void foo(std::span<int> sp) {}

Also, would this

void foo(const std::span<int> sp) {}

make any sense? I figured that it shouldn't, because a std::span is just a struct, containing a pointer and the length. But if it doesn't prevent you from changing the values of the std::vector you passed as an argument, how can it replace a const std::vector<T>&?

Upvotes: 18

Views: 12653

Answers (3)

dave_k_smith
dave_k_smith

Reputation: 693

Another differentiator between the two: In order to modify size of the owning vector inside your function (via std::vector::assign or std::vector::clear, for instance), you would rather pass a std::vector& than a std::span, since span doesn't provide those features.

You can modify the contents of a std::span, but you can't change its size.

Upvotes: 2

Guillaume Racicot
Guillaume Racicot

Reputation: 41770

The equivalent of passing a std::vector<int> const& is not std::span<int> const, but rather std::span<int const>. The span itself being const or not won't really change anything, but more const is certainly good practice.

So when should you use it?

I would say that it entirely depends on the body of the function, which you omitted from your examples.

For example, I would still pass a vector around for this kind of functions:

std::vector<int> stored_vec;

void store(std::vector<int> vec) {
    stored_vec = std::move(vec);
}

This function does store the vector, so it needs a vector. Here's another example:

void needs_vector(std::vector<int> const&);

void foo(std::vector<int> const& vec) {
    needs_vector(vec);
}

As you can see, we need a vector. With a span you would have to create a new vector and therefore allocate.


For this kind of functions, I would pass a span:

auto array_sum(std::span<int const> const values) -> int {
    auto total = int{0};

    for (auto const v : values) {
        total += v;
    }

    return total;
}

As you can see, this function don't need a vector.

Even if you need to mutate the values in the range, you can still use span:

void increment(std::span<int> const values) {
    for (auto& v : values) {
        ++v;
    }
}

For things like getter, I will tend to use a span too, in order to not expose direct references to members from the class:

struct Bar {
    auto get_vec() const -> std::span<int const> {
        return vec;
    }

private:
    std::vector<int> vec;
};

Upvotes: 14

tlongeri
tlongeri

Reputation: 132

Regarding the difference between passing a &std::vector and passing a std::span, I can think of two important things:

  • std::span allows you to pass only the data you want the function to see or modify, as opposed to the whole vector (and you don't have to pass a start index and an end index). I've found this was much needed to keep code clean. After all, why would you give a function access to any more data than it needs?
  • std::span can take data from multiple types of containers (e.g. std::array, std::vector, C-style arrays).

This can of course be also done by passing C-style arrays - std::span is just a wrapper around C-style arrays with some added safety and convenience.

Upvotes: 5

Related Questions