c++c++20idiomsstd-spanstdinitializerlist

C++ - std::initializer_list vs std::span


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?


Solution

  • 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.