Reputation: 805
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
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
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
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