In a prior post, I talked about list initialization, which is differs from initializer_list discussed here. Though personally, I don’t find initializer_list really useful as I never used it in my projects.

  • list initialization is a general syntax using {} for initializing a variety of variables and objects.
  • initializer_list is a template class representing a lightweight, read-only array of elements, typically used in constructors or functions.

initializer_list promotes safety, flexibility and modern tone compared to the raw array. The example below shows the usage of initializer_list.

Code

#include <initializer_list>

void Receiver(std::initializer_list<int> list); //the code cannot be correctly executed due to the missing definition

void Fun() {
    std::initializer_list<int> list{3, 4, 5, 6};
    Receiver(list); // Pass the initializer list
}

int main () {
  Fun();
  return 0;
}

With C++ Insights, we can see the compiler generates the code below. The initializer_list turns out to be a view-like wrapper around a pointer to a backing array and the size of the array.

#include <initializer_list>

void Receiver(std::initializer_list<int> list);

void Fun()
{
  const int __list8_36[4] = {3, 4, 5, 6}; // create an array
  std::initializer_list<int> list = std::initializer_list<int>{__list8_36, 4}; // a pointer to the array and the size
  Receiver(std::initializer_list<int>(list));
}

int main()
{
  Fun();
  return 0;
}

We further investigate the code with the generated assembly code. With g++13, the compiler generates the code below:

Fun():
        sub     rsp, 24 // Stack Allocation
        movdqa  xmm0, XMMWORD PTR .LC0[rip] // Load the constant pool into xmm0
        mov     esi, 4
        mov     rdi, rsp
        movaps  XMMWORD PTR [rsp], xmm0 // Copy the data from xmm0 to the stack
        call    Receiver(std::initializer_list<int>)
        add     rsp, 24
        ret
main:
        sub     rsp, 24
        movdqa  xmm0, XMMWORD PTR .LC0[rip]
        mov     esi, 4   // set as the array size 4
        mov     rdi, rsp  // rdi is set to the stack pointer (address of the copied array)
        movaps  XMMWORD PTR [rsp], xmm0
        call    Receiver(std::initializer_list<int>)
        xor     eax, eax
        add     rsp, 24
        ret
.LC0:   //Constant Pool 
        .long   3
        .long   4
        .long   5
        .long   6

g++14 compiler optimizes the initializer_list by generating a static backing array:

Fun():
        mov     edi, OFFSET FLAT:C.0.0  // edi is loaded with the address of the static array
        mov     esi, 4  // set as the array size 4
        jmp     Receiver(std::initializer_list<int>)
main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:C.0.0
        mov     esi, 4
        call    Receiver(std::initializer_list<int>)
        xor     eax, eax
        add     rsp, 8
        ret
C.0.0:  // Static Data Section
        .long   3
        .long   4
        .long   5
        .long   6
AspectASM with g++14ASM with g++13
Memory AllocationBacking array stored in the static data section (C.0.0)Backing array copied onto the stack during function execution
LifetimePersistent for the lifetime of the programTemporary: exists only during the function’s execution
PerformanceMore efficient: no copying requiredLess efficient: incurs copying overhead to stack memory
Stack UsageNo stack memory used for the arrayConsumes stack space (24 bytes in this case)
Instruction SetSimple memory reference and register loadsInvolves SIMD instructions (e.g., movdqa) and stack operations
ReusabilityData can be reused without reallocationBacking array is unique to each function call

The optimization of g++14 on the initializer_list is actually introduced by C++26, and backported to lower standard version (e.g., C++17, C++20) with compatible compiler. Though this optimization is a bit trivial, learning the benefit of static is still useful.