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

3

17

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>&?

Vic answered 15/12, 2020 at 19:49 Comment(0)
I
13

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;
};
Icao answered 15/12, 2020 at 20:3 Comment(1)
1. store() demonstrates that a std::vector should be constructible from any container with elements of the proper type. That it isn't is bad. Still, the interface should not be burdened by that insufficiency of the target, especially as there is a good-enough workaround. 2. foo() demonstrates that not using std::span where appropriate is contagious. It has to be fixed first at the leaves.Seismography
H
4

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.

Heavyduty answered 15/12, 2020 at 20:28 Comment(0)
K
2

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.

Krupp answered 24/6, 2022 at 17:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.