Search code examples
c++constantsconstexpr

How to declare a compile-time constant list of different size lists in a constant header file?


Introduction

Good day,

I want to optimize my application and especially a constant header file. In order to do so, I define inline constexpr constants in order to avoid multiple copies of those variable in files they are included. And also to have compile-time constants.

I want now to add a new inline constexpr variable/arrays and this complexifies the constant header files. I want a list/array of a certain fixed size containing lists/arrays, each of them of different sizes. Finally, I would like an easy way of using it and easy accessibility.


Planned work

In the following, I note CONST to denot that the CONST values are compile-time constants. I wanted to implement my constant lists/arrays, that are later needed to be contained in a parent list/array (all defined in the constant header file), as:

inline constexpr std::array<CONST int, size1> first = { some CONST values };
inline constexpr std::array<CONST int, size2> second= { some other CONST values };
inline constexpr std::array<CONST int, size3> third = { some other CONST values };

Now, the idea is to access each array via a parent list for simplicity, if possible:

CONTAINER parent = { first, second, third };

Problems

Now I see problem arrising when I want to manipulate the arrays. Ideally, I would like the children (first, second and third) to behave as std::vector<int> and the parent as std::vector<std::vector<CONST int>*>. Because I could access first via a std::vector<CONST int>* pointer and I could change the pointed vector like the following:

// Initialization
std::vector<CONST int>* current_vec = nullptr;
...
// First assignation
current_vec = parent[0];
...
// Reassignation
current_vec = parent[1];
...

But this implementation uses vector. With std::array, I think my pointer could not do the thing because it needs to know the size of each child arrays. Also, maybe I could do something with iterators but same problem (auto keyword might be a solution?).

So, what I did for now is to use std::vector and define the child arrays as:

inline const std::vector<int> first = { some CONST values };
inline const std::vector<int> second = { some other CONST values };
inline const std::vector<int> third = { some other CONST values };

And afterwards, I define the parent as:

inline constexpr std::array<const std::vector<CONST int>*, 3> parent = {&first, &second, &third};

I can access the children like expected above:

const std::vector<CONST int>* to_access = parent[0];

It seems to work and I think the vectors are compiled-const (I guess so, if not, I wouldn't be able to compile the inline constexpr parent?).


Question

First, do you find the approach meaningful and is there something I don't see know that actually make my solution wrong? Secondly, would you use another container or another solution with constexpr rather than a const vector, an alternative that I am maybe not aware of (maybe constexpr functions or working with iterators)?

I hope everything is clear. I thank you in advance for your advises and help.

Note

I know that we can define constexpr vector since c++20 but I think it works only with the msvc c++20 compiler so I want to avoid that solution.


Solution

  • The following may give you ideas:

    #include <array>
    const int size1 = 1;
    const int size2 = 2;
    const int size3 = 3;
    
    constexpr std::array<int, size1> first = { 1 };
    constexpr std::array<int, size2> second= { 2 };
    constexpr std::array<int, size3> third = { 3 };
    
    #include <variant>
    // The safest, I guess, annoying to use.
    constexpr std::array<
        std::variant<decltype(first), decltype(second), decltype(third)>,
        3> parent1 = {
            first, second, third
    };
    
    // The simplest - no bounds information.
    constexpr std::array<const int *, 3> parent2 = {
        first.data(), second.data(), third.data()
    };
    
    // Tie bounds together with pointers.
    constexpr std::array<std::pair<const int *, std::size_t>, 3> parent3 = {{
        { first.data(), first.size(), },
        { second.data(), second.size(), },
        { third.data(), third.size(), },
    }};
    
    // In C++20 we now have span
    #include <span>
    constexpr std::array<std::span<const int>, 3> parent4 = {{
        first, second, third
    }};
    
    // Compile-time template access.
    template<int index>
    constexpr auto parent5() {
        if constexpr (index == 0) {
            return first;
        } else if constexpr (index == 1) {
            return second;
        } else if constexpr (index == 2) {
            return third;
        }
    }
    

    I think accessing uniformly 3 arrays with different sizes is asking for out-of-bounds trouble. Remember to check bounds to avoid undefined behavior.