Search code examples
c++c++11nested-listsinitializer-listcompile-time-constant

Using std::initializer_list[] as const list of lists of varying length


I need a static const list of lists of varying length (of QPointF from Qt) that I directly initialize in my code (see below), and which is only used read-only later on.

I first used const std::vector<QPointF>[] for that, but then it ocured to me that I could just use const std::initializer_list<QPointF>[] instead (is the const even necessary?).

static const std::initializer_list<QPointF> points[6] = {
        { { 0.50f, 0.50f } },
        { { 0.25f, 0.25f }, { 0.75f, 0.75f } },
        { { 0.25f, 0.25f }, { 0.50f, 0.50f }, { 0.75f, 0.75f } },
        { { 0.25f, 0.25f }, { 0.75f, 0.75f }, { 0.25f, 0.75f }, { 0.75f, 0.25f } },
        { { 0.25f, 0.25f }, { 0.75f, 0.75f }, { 0.50f, 0.50f }, { 0.25f, 0.75f }, { 0.75f, 0.25f } },
        { { 0.25f, 0.25f }, { 0.75f, 0.75f }, { 0.25f, 0.50f }, { 0.75f, 0.50f }, { 0.25f, 0.75f }, { 0.75f, 0.25f } },
    };

Is this a valid use of std::initializer_list? It looks like a hack to me, probably because the name of std::iniatilizer_list suggests a certain use. Answers to this post also mention that initializer lists are intended to be used for, well, initialization of e.g. containers.

I also considered const std::array<...>[], but then all the inner lists have to be of the same length.

If std::initializer_list is not the right choice here, then how to do this correctly. The alternative std::vector does not feel right because of the heap allocation.


Solution

  • Knowing your compiler output can help you with this decision. std::initializer_list<> is just a thin wrapper around an array of objects.

    If you really need static constant data, std::initializer_list<>[] is perfectly valid (as long as you don't mind using its accessing syntax instead of subscript operators like you would find on std::array<> or std::vector<>). Clang 8 lowers each initializer_list in the array to its own fixed-length array of QPointF structs. It then fills another array with references to the QPointF arrays it created:

    // pseudo-C++ from Clang's generated output
    
    QPointF RT1[1] = { { 0.50f, 0.50f } };
    QPointF RT2[2] = { { 0.25f, 0.25f }, { 0.75f, 0.75f } };
    QPointF RT3[3] = { { 0.25f, 0.25f }, { 0.50f, 0.50f }, { 0.75f, 0.75f } };
    QPointF RT4[4] = { { 0.25f, 0.25f }, { 0.75f, 0.75f }, { 0.25f, 0.75f }, { 0.75f, 0.25f } };
    QPointF RT5[5] = { { 0.25f, 0.25f }, { 0.75f, 0.75f }, { 0.50f, 0.50f }, { 0.25f, 0.75f }, { 0.75f, 0.25f } };
    QPointF RT6[6] = { { 0.25f, 0.25f }, { 0.75f, 0.75f }, { 0.25f, 0.50f }, { 0.75f, 0.50f }, { 0.25f, 0.75f }, { 0.75f, 0.25f } };
    
    // Reference array:
    QPointF* points[6] = { RT1, RT2, RT3, RT4, RT5, RT6 };
    

    Accessing individual values from the list

    (points[5].begin() + 3)->y
    

    is lowered to essentially the equivalent of

    points[5][3].y
    // alternatively:
    (*(*(points + 5) + 3)).y
    // which is actually:
    (*(RT5 + 3)).y
    

    (Implementations of std::initializer_list<>::begin() can differ.)

    So if you don't mind hidden dereferences or different syntax styles, this is fine. (Although you would have dereferenecs occuring with arrays or vectors, too.)

    Try it with different optimization levels and see the results: https://godbolt.org/z/Vwjbp2