0

I am a little new to C++ and learning the language. Right now I'm using C++17 and G++.

I'm trying to initialize an array of structs at compile time. I want to explicitly specify the array indexes in which my initial data goes which makes the process much less error prone. For the indexes I'm going to use integer-aliasses, but for the sake of simplicitly I left the alliases out in the example.

I found that this approach could work:

#include <array>

struct Spec
{
    int val;
    double otherVal;
};

constexpr static std::array<Spec, 2> make_init_data()
{
    std::array<Spec, 2> array{};
    array[0] = {1, 3.56};
    array[1] = {1, 3.56};
    return array;
};

constexpr static std::array<Spec, 2> myArray = make_init_data();

This does compile, and myArray is populated.

However, I want myArray to be a class member. So I modified my example to be:

#include <array>

struct Spec
{
    int val;
    double otherVal;
};

class Test
{
    constexpr static std::array<Spec, 2> make_init_data()
    {
        std::array<Spec, 2> array{};
        array[0] = {1, 3.56};
        array[1] = {1, 3.56};
        return array;
    };

    constexpr static std::array<Spec, 2> myArray = make_init_data();
};

Which gives me the following error:

Test.h:19:66: error: 'static constexpr std::array<Spec, 2> Test::make_init_data()' called in a constant expression before its definition is complete
 19 |     constexpr static std::array<Spec, 2> myArray = make_init_data();
    |

I understand that the class definition is not yet complete, and that therefore the constexpr make_init_data cannot be evaluated yet. I can simply put it outside the class definition, but I need many classes doing this. To avoid a lot of cluttering, scrolling and searching, now and in the future my aim is to to have code which is only relevant for functioanlity inside one class to also be inside this class.

Is there a way to use the constexpr so that my array gets populated at compile time while the array is also class member?

(Another way to populate the array at compile time in a similar way without constexpr would also be fine).

Stefan
  • 919
  • 2
  • 13
  • 24

3 Answers3

4

You might do it with lambda directly invoked:

class Test
{
    constexpr static std::array<Spec, 2> myArray = [](){
        std::array<Spec, 2> array{};
        array[0] = {1, 3.56};
        array[1] = {1, 3.56};
        return array;
    }();
};

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Thanks! This seems to work and is really concise. Is `myArray` still a compile time constant? I mean, is it populated during compilation and not during runtime? Will the lambda be stored in my binary or just the initialized array? – Stefan Jul 19 '23 at 08:49
  • 1
    `myArray` variable is `constexpr`, so its value is known at compile time. – Jarod42 Jul 19 '23 at 12:09
1

I would do it like this a private lambda to calculate the values, and then initialze from that.

#include <array>

struct Spec
{
    int val;
    double otherVal;
};

class Test
{
private:
    static constexpr auto init_array = []
    {
        std::array<Spec, 2> array{};
        array[0] = { 1, 3.56 };
        array[1] = { 1, 3.56 };
        return array;
    };

public:
    static constexpr auto myArray{ init_array() };
};

int main()
{
    static_assert(Test::myArray[0].val == 1);
    return 0;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
  • Thanks! Same questions as with Jarod42's answer. (I'm still learning C++). This seems to work. Is myArray still a compile time constant? I mean, is it populated during compilation and not during runtime? Will the lambda be stored in my binary or just the initialized array? – Stefan Jul 19 '23 at 08:52
  • 1
    Yes it is see here : https://godbolt.org/z/6fK65baGx the top lines are your compile time constants from : Test::myArray: – Pepijn Kramer Jul 19 '23 at 17:39
0

you can also initialize it outside the class definition.

// Test.hpp
std::array<Spec, 2> Test::myArray = Test::make_init_data();
  • 1
    Thanks! It is a bit short to unambiguously know what you mean, but if I put the line from your answer after the class definition AND I remove the `constexpr` from the `myArray` member definition (thus it becomes `static std::array myArray;`) it compiles. But is it now still a compile time constant? Or will it be initialized at program start as the `constexpr` is missing? – Stefan Jul 19 '23 at 08:33
  • 1
    yeah my bad - sorry. It will be initialized at runtime. Since a class is not complete until you get to it's ```;``` you can't use its own functions in the definition process. Since a lambda is not a function - it's a type - than it is known before you complete the class definition, thus it's callable. The code that initializes the array must be accessible at the time of initialization - member functions might use member variables! so you can't use them, how would you know the right order of initialization/definitions? – Eyal Kamitchi Jul 19 '23 at 17:35
  • Thanks! Put this comment in your anwer, it hold a pretty good explanation of what is going on helping to understand the problem. Thanks again. – Stefan Jul 20 '23 at 14:29
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 25 '23 at 11:03