Search code examples
c++c++11cpp-core-guidelines

Why can't I construct a gsl::span with a brace-enclosed initializer list


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?


Solution

  • 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);