2

What is the difference between std::initializer_list and std::span? Both are contiguous sequences of values of some type. Both are non-owning. So when do we use the first and when do we use the latter?

  • Where did you hear that `std::initializer_list` is non-owning? It's "owning" in the sense that the lifetime of the underlying array basically copies the lifetime of the initializer list object (simply said). – Daniel Langr Dec 30 '20 at 13:27
  • This implementation of span aims to follow the C++20 proposal as closely as possible, in order to make it easy for people to switch to `std::span` when they upgrade to '20 (the `make_span()` functions are a temporary workaround for the lack of CTAD before '17). C++20's span doesn't provide a separate constructor or deduction guide for `std::initializer_list.` – Devi Khositashvili Dec 30 '20 at 13:27
  • You can use `span` to "view" for example some subsequence of vector's elements. How would you do this with `initilizer_list`? – Daniel Langr Dec 30 '20 at 13:35

3 Answers3

7

The short answer is that std::initializer_list<T> is used to create a new range, for the purposes of initialization. While std::span<T> is used to refer to existing ranges, for better APIs.


std::initializer_list<T> is a language feature that actually constructs a new array and owns it. It solves the problem of how to conveniently initialize containers:

template <typename T>
struct vector {
    vector(std::initializer_list<T>);
};

vector<int> v = {1, 2, 3, 4};

That creates a std::initializer_list<int> on the fly containing the four integers there, and passes it into vector so that it can construct itself properly.

This is really the only place std::initializer_list<T> should be used: either constructors or function parameters to pass a range in on the fly, for convenience (unit tests are a common place that desires such convenience).


std::span<T> on the other hand is used to refer to an existing range. Its job is to replace functions of the form:

void do_something(int*, size_t);

with

void do_something(std::span<int>);

Which makes such functions generally easier to use and safer. std::span is constructible from any appropriate contiguous range, so taking our example from earlier:

std::vector<int> v = {1, 2, 3, 4};
do_something(v); // ok

It can also be used to replace functions of the form:

void do_something_else(std::vector<unsigned char> const&);

Which can only be called with, specifically, a vector, with the more general:

void do_something_else(std::span<unsigned char const>);

Which can be called with any backing contiguous storage over unsigned char.

With span you have to be careful, since it's basically a reference that just doesn't get spelled with a &, but it is an extremely useful type.

Barry
  • 286,269
  • 29
  • 621
  • 977
3

The principle differences between the two are how the language treats them. Or more specifically, how it doesn't in the case of a span.

You cannot create an initializer_list from an existing container object or array. They can only be created by copying from other initializer_lists or from a braced-init-list (ie: { stuff }). That is, the compiler governs the creation of an initializer_list and the array it points into.

Similarly, a constructor that takes only an initializer_list has special meaning to the compiler. When performing list initialization on that type, such constructors are given priority in overload resolution. span is given no special meaning by the compiler.

initializer_list also has lifetime rules that are different from span. The lifetime of the array pointed to by an initializer_list is the lifetime of the initializer_list object that was created from a braced-init-list. This is not true of span; the lifetime of a span created from a container is whatever you say it is based on how you use it in code.

Broadly speaking, you should only use initializer_list when you're initializing an object.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Can you provide a reliable sources for "only use initializer_list when you're initializing an object."? I looked through Stroustrup and CoreGuidelines and didn't find any. – Кое Кто Dec 07 '22 at 18:23
  • 1
    @КоеКто: The source is me. It's my rule; you can decide for yourself if you agree or not. – Nicol Bolas Dec 07 '22 at 20:21
-1

One difference is that std::span can be used dynamically, unlike std::initializer_list.

Adna Kateg
  • 93
  • 1
  • 6