0

The question

I am writing a software in c++17 for which performances are absolutely critical. I would like available in a few key functions constants in arrays themselves in arrays. It matters that both these array are accessible by a integer value in such (or similar) manner :
int main()
{
    for (int i = 0; i < size_of_A ; i++)
    {
        for (int j = 0; j < size_of_B_in_A(i); j++)
        {
            std::cout << A[i][j];
        }
    }
}

This would be the kind of array we would like to create assuming some function int f(a, b)

A
{
    // B1
    {
        f(1, 1),
        f(1, 2),
        f(1, 3),
           ...
        f(1, large number)
    },
    // B2
    {
        f(2, 1),
           ...
        f(2, some other large number)
    },
    ... etc
}

The Twist

Each inner array may be of different size which we have will stored elsewhere, we have to find the size at compile time. I would rather not use std::vector for they are assumed slightly slower . Also an I suppose a std::vector would be stored on the heap which would be a performance issue in my specific case. Furthermore, std::vector cannot be used as "inline constexpr" which would be necessary as I expect to have a large amount of value in those array never going to change. I am fine with recompiling all those values each time but not keeping them in an external file by policy as I am to follow a strict coding style.

What I Have Tried

brace initializer

// A.hh

#pragma once

#include <iostream>

void test1();
void test2();

inline constexpr int B1[1] = {1};
inline constexpr int B2[2] = {2, 3};
inline constexpr int B3[3] = {4, 5, 6};

inline constexpr const int *A[3] = {B1, B2, B3};

// main.cc

#include "A.hh"

int main()
{
    std::cout << "values : ";
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j <= i; j++)
        {
            std::cout << A[i][j];
        }
    }

    std::cout << "\n\naddress test : \n";
    std::cout << &A << '\n';
    test1();
    test2();
}
// somewhere.cc

#include "A.hh"

void test1()
{
    std::cout << &A << '\n';
}

// elsewhere.cc

#include "A.hh"

void test2()
{
    std::cout << &A << '\n';
}

which prints :

./a.out
values : 123456

address test :
0x56180505cd70
0x56180505cd70
0x56180505cd70

Therefore A has not been copied in main.cc, somewhere.cc and elsewhere.cc which is good. I would like to go further and be able to create a huge amount of values.

struct with constexpr

using tips found here , I do this to be able to perform operations during array construction.
// B.hh

#pragma once

#include <iostream>

template <int N>
struct X
{
    int arr[N];
    constexpr X(): arr()
    {
        for (int i = 0; i < N; i++)
        {
            arr[i] = i % 3;
        }
    }
};

inline constexpr auto A = X<500>();
// main.cc

#include "B.hh"

int main()
{
    for (int i = 0; i < 500; i++)
    {
        std::cout << A.arr[i];
    }
}

Which unsuspectingly prints out

012012 (etc)...

Finally an array of array

And this where I am stuck
#pragma once

#include <iostream>

template <int N>
struct sub_array
{
    int arr[N];
    constexpr sub_array() : arr()
    {
        for (int i = 0; i < N; i++)
        {
            arr[i] = i;
        }
    }
};

struct array
{
    sub_array</*what here ?*/> arr[100];

    constexpr array() : arr()
    {
        for (int i = 0; i < 100; i++)
        {
            int size = i * 2; // a very large number

            // the value of 'size' is not usable in a constant expression
            //
            // I see why it is, but I can't think of any other way
            arr[i] = sub_array<size>;
        }
    }
};

inline constexpr array A = array();

How can I build such kind of array ?

Thank you for your time and consideration.

NRagot
  • 113
  • 2
  • 12
  • A raw array cannot have elements of different types (and `array<2>` and `array<4>` are different types). A `std::tuple` could, but it will take some extra work to be able to index it with a runtime index value. – aschepler Mar 09 '21 at 18:16
  • Have you considered a flat array for storage and an second array of offsets+sizes for variable-sized indexing? – alter_igel Mar 09 '21 at 18:19
  • Would a regular 2D array be viable with size `row_count * max_width`? – IlCapitano Mar 09 '21 at 18:23
  • @alterigel I did. I see a few issues though : 1) I would have to store a lot of offsets, enough for it to be a problem as I work on limited memory. 2) the place we retrieve the value will be called thousands of times. If there is another way, I would rather not have to do arithmetic operations to retrieve the value. Thank you for your help though ! – NRagot Mar 09 '21 at 18:24
  • @IlCapitano Sorry but is not an option : the max_width WILL be much bigger than the width of most inner arrays. – NRagot Mar 09 '21 at 18:26
  • @NRagot: Wait: if the array is being accessed with a runtime index, this means the compiler has to put that array in your executable. So if you're working in "limited memory," then you have to spend some of that memory for the array, right? – Nicol Bolas Mar 09 '21 at 18:28
  • @NicolBolas Yes, the arrays is part of the executable and It will already takes most of what I am allowed. I would like to cut my loses but if I fail to find an answer reasonably soon, I'll bite that bullet. – NRagot Mar 09 '21 at 18:32
  • @NRagot: My question is why does it need to be `constexpr` if you're accessing it at runtime? – Nicol Bolas Mar 09 '21 at 19:40
  • @NicolBolas Yes, I will be reading them but I will not never overwrite them, they're constant I need at runtime. If I can, i'd rather the values to not be generated at startup. – NRagot Mar 09 '21 at 19:48
  • Is `int f(a, b)` `constexpr`? – Jarod42 Mar 10 '21 at 08:46
  • @Jarod42 yes `f(a, b)` is a `constexpr` function – NRagot Mar 10 '21 at 15:36

2 Answers2

2

Just use std::array<std::span<int>, N>, which is a fixed size array of spans of different sizes. To generate this, use an std::index_sequence

Header:

constexpr std::size_t size_of_A = 500;
extern const std::array<const std::span<const int>, size_of_A>& A;

Implementation:

constexpr std::size_t size_of_B_in_A(std::size_t i) { return i%10+1;}
constexpr int f(std::size_t i, std::size_t j) {return static_cast<int>(i%(j+1));}

template <int I, int N>
struct B
{
    std::array<int,N> arr;
    explicit constexpr B()
    {
        for (int j = 0; j < N; j++)
            arr[j] = f(I, j);
    }
    constexpr operator const std::span<const int>() const {return {arr};}
};

template<class index_sequence>
class BGen;
template<std::size_t... I>
struct BGen<std::integer_sequence<std::size_t,I...>> {
    static constexpr std::tuple<B<I, size_of_B_in_A(I)>...> bs{};
    static constexpr std::array<const std::span<const int>, sizeof...(I)> A {std::get<I>(bs)...};
};
const std::array<const std::span<const int>, size_of_A>& A 
    = BGen<decltype(std::make_index_sequence<size_of_A>{})>::A;

Usage:

int main()
{
    for (unsigned i = 0; i < A.size() ; i++)
    {
        for (unsigned j = 0; j < A[i].size(); j++)
        {
            std::cout << A[i][j];
        }
    }
}

http://coliru.stacked-crooked.com/a/d68b0e9fd6142f86


However, stepping back: This solution is NOT the normal way to go about solving this problem. Since it's all constexpr, this is all data not code. Ergo, the most performant solution is two programs. One generates the data and saves it to a file that ships with (inside?) your program. Then your program simply maps the file into memory, and uses the data directly.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • 1
    I would add a note that there exist libraries that backports `std::span` from C++20 to C++14/11 – Guillaume Racicot Mar 09 '21 at 18:32
  • 1
    This is basically what OP tried with their _brace initializer_ method. – IlCapitano Mar 09 '21 at 18:35
  • 1
    @IlCapitano nearly, but now you can know the size of each inner array – alter_igel Mar 09 '21 at 18:36
  • @alterigel Sure, but that wasn't a problem for them, at least I don't see that in the question. – IlCapitano Mar 09 '21 at 18:37
  • @Mooing Duck I'm sorry but it doesn't answer my question as it comes with multiple majors contradiction with what I asked : 1) span is c++20 and I must use c++17 2) the sizes of inner arrays are supposed to be calculated at compile time : I do not know how big the inner array are exactly before I make my calculation 3) There will be more than 3 inner array, a very large number of them in fact. Thank you for your help though. – NRagot Mar 09 '21 at 18:46
  • @NRagot: I misunderstood the question at first, so I've fixed the inner generations. Span is trivially implemented yourself in any version of C++. All the data and sizes were already calculated at compile time. – Mooing Duck Mar 09 '21 at 18:49
  • @MooingDuck Some problems still holds, for instance : you write `b<300> B1`, which would imply that I wrote 300 myself. As stated in my question : I would like to figure out that 300 during compilation. Furthermore, that would mean I'd have to write B1, B2, B3 ... B500. Is it really the only way ? – NRagot Mar 09 '21 at 19:03
  • @NRagot: No, what you want is also possible. We just need more details. How is the width of each row calculated? Oh that's probably what `size_of_B_in_A` was for? Is that `constexpr`? – Mooing Duck Mar 09 '21 at 19:45
  • @MooingDuck The size of the rows are calculated by counting the number of successful match between the numbers between 1..2^12 and some proprietary test. I assume to not need to remember how many match actually happens as long the array is just as big to contain all the values I'm going to generate. I wrote `size_of_B_in_A` in my example to make the example with `std::cout` but it has no practical use in my situation. – NRagot Mar 09 '21 at 19:57
  • @NRagot Done! B lengths are now generated from `constexpr size_of_B_in_A` – Mooing Duck Mar 09 '21 at 21:23
  • the updated solution works for me, thank you dearly for your time and help. @MooingDuck – NRagot Mar 12 '21 at 11:19
0

Here's a way of implementing a constexpr jagged array which can be initialized without intermediates. It does require listing the row sizes as template arguments, but there are ways to make that easier too, depending on how the row sizes can be known at compile time.

#include <tuple>
#include <array>
#include <utility>

template <std::size_t ...Sizes>
struct jagged_array
{
    const std::tuple<std::array<int,Sizes>...> data;

    static constexpr std::size_t num_rows = sizeof...(Sizes);
    static constexpr std::size_t length[num_rows]{Sizes...};
    int const* const row_ptr[num_rows];

    template <std::size_t ...I>
    constexpr jagged_array(std::index_sequence<I...>,
                           const std::array<int, Sizes>& ...arrs)
    : data{arrs...}, row_ptr{&std::get<I>(data)[0]...} {}

    constexpr jagged_array(const std::array<int, Sizes>& ...arrs)
    : jagged_array(std::make_index_sequence<num_rows>{}, arrs...)
    {}

    constexpr int const* operator[](std::size_t idx) const
    { return row_ptr[idx]; }
};

inline constexpr jagged_array<2,4> jarr = {{2,3}, {4,5,6,7}};
aschepler
  • 70,891
  • 9
  • 107
  • 161
  • thank you for your help but I see a problem : I need to put the 2 and 4 myself. As stated in my question, I want that 2 and 4 to be values that I calculated at compile time. furthermore, I won't have 2 of them but hundreds instead. – NRagot Mar 09 '21 at 19:07
  • @NRagot There is no mention in your question that the lengths of the inner arrays should be calculated at compile time. You may want to clarify that in the question. – Mooing Duck Mar 09 '21 at 21:46
  • This can probably be improved to fit your data source, but that part is not very well described in the question. How can the list of row sizes be determined? – aschepler Mar 09 '21 at 21:54