18

According to the C++ Core Guidelines, I should use a gsl::span to pass a half-open sequence.

I think that means that instead of writing a function like:

void func(const std::vector<int>& data) {
    for (auto v : data) std::cout << v << " ";
}

I should prefer:

void func(gsl::span<const int> data) {
    for (auto v : data) std::cout << v << " ";
}

Which has the advantage that it does not assume the caller has their data in a vector, or force them to construct a temporary vector. They could pass a std::array for example.

But a common use-case is to pass a brace-enclosed initializer list:

func({0,1,2,3})

This works for a function taking a std::vector but for a function taking a gsl::span I get the error message:

error C2664: 'void func(gsl::span)' : cannot convert argument 1 from 'initializer-list' to 'gsl::span'

It looks like gsl::span has a templated constructor designed to take any container.

Is this just something missing from the Microsoft GSL implementation or is there a good reason to prevent this practice?

user3467895
  • 572
  • 4
  • 13

3 Answers3

14

When you call the vector version, the initializer list is used to create a temporary std::vector, which is then passed to the function by const reference. This is possible, because std::vector has a constructor, that takes an std::initializer_list<T> as an argument.
However, gsl::span doesn't have such a constructor and as {0,1,2,3} doesn't have a type, it also can't be accepted by the templated constructor you mentioned (besides the fact, that std::initializer_list<T> wouldn't satisfy the container concept anyway).

One (ugly) workaround would be of course to explicitly create a temporary array:

func(std::array<int,4>{ 0,1,2,3 });

I don't see a particular reason, why gsl::span should not have a constructor that takes a std::initializer_list, but keep in mind that this library is in still pretty new and under active development. So maybe it is something they overlooked, didn't have time to implement, weren't sure how to do properly or there are really some details, that would make that construct dangerous. Its probably best to ask the developers directly on github.


EDIT:
As @Nicol Bolas explains in his comment, this was by design because an initializer list like {0,1,2,3} (and the elements within) is a temporary object and as gsl::span is not a container in its own right (it doesn't take ownership of the elements), they think it would be too easy to accidentally create a gsl::span that contains a dangling reference to those temporary elements.

So, while this would be OK:

func({ 0,1,2,3 });

because the lifetime of the initializer list ends after the completion of the function, something like this would create a dangling reference:

gsl::span<const int> data{ 0,1,2,3 };
func(data);
MikeMB
  • 20,029
  • 9
  • 57
  • 102
  • 1
    @NicolBolas: Thank you, I added this to my answer. Maybe you can have a look, whether the edit is correct. – MikeMB Dec 26 '15 at 22:02
  • Yeah, that's the basic idea. – Nicol Bolas Dec 26 '15 at 22:38
  • Note your "fix" example doesn't work, and also it should be 4 not 3 int the std:::array. See this: https://godbolt.org/z/zj7nGa and https://godbolt.org/z/Tv7eMY – Krupip Aug 07 '20 at 03:22
  • 1
    @whn: Thanks! You are right about the array size of course(fixed that), but `func` takes a `span` for which that code works and not a `span` as in your godbolt links. – MikeMB Aug 08 '20 at 10:39
  • 1
    @MikeMB Ah I didn't realize const would have any different behavior, that indeed fixes it. https://godbolt.org/z/fTqqzn – Krupip Aug 08 '20 at 17:50
  • @MikeMB: Consider https://godbolt.org/z/aozvad61n ; I don't understand why explicitly specifying `initializer_list` really helps, but it does. Additionally, it seems that a few things evolved since your answer was posted: in the godbolt above, we can see that C++ allows for dangling span references (the vector at the bottom). Can you elaborate on these changes? Thanks! – Michaël Apr 30 '21 at 19:47
4

Span is non owning. Does not own storage. It is a replacement for pointer arithmetic, not a storage class.

You need to put your data in a storage class, and then if you want to do clever things with pointer arithmetic, you instead do clever things with spans.

You cannot initialize a span with an initializer list, because there is nowhere to put the data.

studie
  • 149
  • 1
  • 1
  • 6
1

This now works with absl::Span. The following example is copied from https://abseil.io/tips/93:

void TakesSpan(absl::Span<const int> ints);

void PassALiteral() {
  // Span does not need a temporary allocation and copy, so it is faster.
  TakesSpan({1, 2, 3});
}
Bence
  • 313
  • 1
  • 4
  • 1
    It is worth mentioning the [warnings from it's source](https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L254) – Кое Кто Dec 07 '22 at 18:46