Chromium’s span-over-initializer-list success story

Previously: span should have a converting constructor from initializer_list (2021-10-03). This converting constructor was added by P2447 for C++26. Way back in 2024, Peter Kasting added the same constructor to Chromium’s base::span — he emailed me about it at the time — but I was only recently reminded that in the /r/cpp thread about the feature he’d written:

Yup, this change was so useful it led to me doing a ton of reworking of Chromium’s base::span just so I could implement it there.

Speaking of ambiguity: out of context that comment could be taken as sarcasm. What programmer enjoys “doing a ton of reworking just” to implement a single new constructor? Did he mean the change was so useful, or, like, “so useful”? :) So it’s worthwhile to track down pkasting’s actual commit from November 2024 and see all the places he sincerely did clean up as a result.

What follows is a “close reading” of all the client call sites changed in Chromium commit 7a129f92f5.


std::vector<scoped_refptr<const Cert>> certs(
    {kcer_cert_0, kcer_cert_1, kcer_cert_2, kcer_cert_3, kcer_cert_3,
     kcer_cert_2, kcer_cert_1, kcer_cert_0, kcer_cert_0, kcer_cert_2,
     kcer_cert_3, kcer_cert_1});
CertCache cache(certs);

becomes simply

CertCache cache({kcer_cert_0, kcer_cert_1, kcer_cert_2, kcer_cert_3,
                 kcer_cert_3, kcer_cert_2, kcer_cert_1, kcer_cert_0,
                 kcer_cert_0, kcer_cert_2, kcer_cert_3, kcer_cert_1});

This is the poster-child use-case: the new code directly views a stack-allocated initializer_list, where the old code had wasted time and memory copying the contents of that initializer_list into a heap-allocated vector. This being test code, we don’t really care about the new code’s improved efficiency, but we do care about its improved readability and convenience.


ASSERT_TRUE(ConfigureAppContainerSandbox(
    std::array<const base::FilePath*, 2>{&pathA, &pathB}));

becomes simply

ASSERT_TRUE(ConfigureAppContainerSandbox({&pathA, &pathB}));

EXPECT_THAT(MapThenFilterStrings(
                {{"en", "de"}},
                base::BindRepeating(~~~~)),
            IsEmpty());

replaces its double-braces with single-braces.


FetchImagesForURLs(base::span_from_ref(card_art_url),
                   base::span({AutofillImageFetcherBase::ImageSize::kSmall,
                               AutofillImageFetcherBase::ImageSize::kLarge}));

becomes simply

FetchImagesForURLs(base::span_from_ref(card_art_url),
                   {AutofillImageFetcherBase::ImageSize::kSmall,
                    AutofillImageFetcherBase::ImageSize::kLarge});

Notice that these preceding three examples all had the same intent — to view a fixed list of two items — but in the absence of natural syntax they invented three different workarounds to imperfectly express their intent. (Temporary std::array; doubled curly braces; explicit cast to base::span.) All three converged on the natural syntax as soon as it became available. Two of them benefit from P2752, too.


There were two “failure stories” in Peter’s commit, both due to the new constructor’s lack of CTAD. (I still don’t think anyone should ever use CTAD, and LEWG was a little scared of adding it here anyway.) For example Peter rewrote

if (base::span(box.type) == base::span({'f', 't', 'y', 'p'}))

into

if (base::span(box.type) == base::span<const char>({'f', 't', 'y', 'p'}))

Now, you might think after P2447 this could have become simply

if (base::span(box.type) == {'f', 't', 'y', 'p'})

but sadly no, for historical reasons a braced initializer list is grammatically disallowed after most C++ operators (the exceptions being co_yield and the assignment operators). I myself would probably have written one of

if (std::string_view(box.type, 4) == "ftyp")

if (memcmp(box.type, "ftyp", 4) == 0)

In the other “failure case,” Peter rewrote

hosts[DnsHostsKey("localhost", ADDRESS_FAMILY_IPV4)] =
    IPAddress({192, 168, 1, 1});

into

hosts[DnsHostsKey("localhost", ADDRESS_FAMILY_IPV4)] =
    IPAddress(base::span<const uint8_t>({192, 168, 1, 1}));

The trick here is that the old code (Godbolt) wasn’t actually constructing a span at all; it was calling IPAddress’s four-argument converting constructor followed by a redundant explicit cast to IPAddress. Personally I would have preserved the old behavior and improved readability at the same time by simply removing the curly braces:

hosts[DnsHostsKey("localhost", ADDRESS_FAMILY_IPV4)] =
    IPAddress(192, 168, 1, 1);
Posted 2026-03-19