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.

11
  • 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. Commented Mar 9, 2021 at 18:16
  • Have you considered a flat array for storage and an second array of offsets+sizes for variable-sized indexing? Commented Mar 9, 2021 at 18:19
  • Would a regular 2D array be viable with size row_count * max_width? Commented Mar 9, 2021 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 ! Commented Mar 9, 2021 at 18:24
  • @IlCapitano Sorry but is not an option : the max_width WILL be much bigger than the width of most inner arrays. Commented Mar 9, 2021 at 18:26

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

11 Comments

I would add a note that there exist libraries that backports std::span from C++20 to C++14/11
This is basically what OP tried with their brace initializer method.
@IlCapitano nearly, but now you can know the size of each inner array
@alterigel Sure, but that wasn't a problem for them, at least I don't see that in the question.
@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.
|
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}};

3 Comments

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 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.
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?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.