13

I used to work with gsl::span from the Guidelines Support Library for some time. With gsl::span, is was possible to define a function taking a static extent and calling it with a gsl::span of dynamic extent, e.g.

void f(gsl::span<int, 5> s) { }
...
std::vector<int> v(100);
f(gsl::make_span(v).subspan(42, 5));    // Works

After porting the code to std::span, I noticed that this is no longer possible:

void f(std::span<int, 5> s) { }
...
std::vector<int> v(100);
f(std::span(v).subspan(42, 5));    // Does not work

Is there any reason for this difference? Is there any recommended way to convert a std::span with dynamic extent to a std::span with fixed extent? Of course, it is possible to create a new span with fixed extent when calling the function:

f(std::span<int, 5>(&v[42], 5));

However, I think that the "subspan" variant is much better readable, it expresses the intention in a better way, and it allows appropriate range checking in a debug build.

Barry
  • 286,269
  • 29
  • 621
  • 977
Holger Strauss
  • 225
  • 2
  • 10

1 Answers1

14

Is there any reason for this difference?

P1976 goes through this at length. Fixed-extent spans have explicit constructors on purpose to try to alleviate undefined behavior, so std::span<int> (the type of std::span(v).subspan(42, 5)) is not convertible to std::span<int, 5>.

Is there any recommended way to convert a std::span with dynamic extent to a std::span with fixed extent?

If both the offset and the count are constant expressions (which is true in this case), you can use the other overload of subspan():

f(std::span(v).subspan<42, 5>());

This overload exists the way it does so that you can take fixed-extent subspans of fixed-extent spans safely, but is less than ideally useful when wanting to take fixed-extent subspans of a dynamic-extent span, where ideally we'd just have wanted std::span(v).subspan<5>(42).

For that case, you can instead use first() in conjunction with subspan:

f(std::span(v).subspan(42).first<5>());

Slightly longer, but gets the job done.

Or you can go full out and use the constructor:

f(std::span<int, 5>(std::span(v).subspan(42, 5)));
f(std::span<int, 5>(v.data() + 42, 5));
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    Many thanks for explaining all the details. In my real-world application, the offset is actually not a constant. However "f(std::span(v).subspan(42).first<5>());" does the job. "f(std::span(std::span(v).subspan(42, 5)));" is something which I've already tried before, but the compiler (MS VS 2019 16.6.5) does not accept it (it says that it can not convert the argument). So is this is bug in the compiler/library implementation? – Holger Strauss Jul 23 '20 at 13:21
  • 1
    @HolgerStrauss Yeah, library bug. gcc [gets it right](https://godbolt.org/z/14njqb). – Barry Jul 23 '20 at 13:45
  • Link to the MS bug report for reference purposes: [link](https://developercommunity.visualstudio.com/content/problem/1123813/conversion-of-stdspan-with-dynamic-extent-to-stdsp.html) – Holger Strauss Jul 23 '20 at 15:20
  • 1
    Given the option I would've liked to up-vote his answer twice since the `f(std::span(v).subspan(42).first<5>());` gem is *exactly* what I was looking for! – YePhIcK Feb 06 '22 at 13:27
  • Thanks for all the explanations, I was trying to create a helper function to get the slice but did not manage to compile it with the first() version any idea why it wouldn't work? `code` template constexpr std::span slice(std::span buf, unsigned int offset) { //return buf.subspan(offset).first(); //doesn't compile on gcc return std::span(buf.subspan(offset, Size)); //works perfectly } and the Type T cannot be deduced but it's still easy to call `code` const auto& part = slice(buffer, i); – Bertrand Thelen Dec 16 '22 at 10:40