Populate an Array Using Constexpr at Compile-Time

Populate An Array Using Constexpr at Compile-time

Ignoring ALL the issues, indices to the rescue:

template<unsigned... Is> struct seq{};
template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};
template<unsigned... Is>
struct gen_seq<0, Is...> : seq<Is...>{};

template<unsigned... Is>
constexpr Table MagicFunction(seq<Is...>){
return {{ whichCategory(Is)... }};
}

constexpr Table MagicFunction(){
return MagicFunction(gen_seq<128>{});
}

Live example.

Filling an std::array using math formula in compile time

Here's a non-confusing approach: wrap the calculation in a function:

template <int N>
constexpr std::array<double, N> generate()
{
std::array<double, N> arr{};
for (int i = 0; i < N; ++i)
arr[i] = complexFormula(i);
return arr;
}

Usage example:

constexpr std::array<double, 10000> arr = generate<10000>();

(live demo)

This works because, since C++14, loops are allowed in a constexpr function, and variables can be modified as long as their lifetime starts within the evaluation of the constant expression.

How to initialize a constexpr multidimensional array at compile time?

C-array are not copyable, so using function is not really possible, but with std::array, you might create constexpr function (C++11 is more limited though)

constexpr auto generate()
{
std::array<std::array<std::array<std::array<bool, 64>, 64>, 64>, 64> res{};

for (int a = 0; a != 64; ++a) {
for (int b = 0; b != 64; ++b) {
for (int c = 0; c != 64; ++c) {
for (int d = 0; d != 64; ++d) {
res[a][b][c][d] = f(a, b, c, d);
}
}
}
}

return res;
}

constexpr auto test_values = generate();

If you really need C-array, you could wrap it in a struct and use similar code.

How to create a constexpr array with a sequence of string_views at compile time?

I now found a solution that finally satisfies my needs.

Because of many good answers by SO users and additional answers to my question here,

I will now use the following code.

#include <iostream>
#include <algorithm>
#include <iterator>
#include <array>
#include <string>

// Specification for the output that we want to generate
constexpr const char BaseString[]{ "text" }; // Some example text
constexpr size_t StartIndex = 1u; // Start index. So first array element will be "Text1"
constexpr size_t NumberOfElements = 20u; // Number of elements to create. Last array element will be "Text20"

// These templates are used to generate a std::array
namespace ConstexprGenerator {
// To create something like "text123" as constexpr
template <const size_t numberToConvert, const char* Text>
class Converter {
public:
// Some helper variables for calculating sizes
static constexpr size_t TextLength{ std::char_traits<char>::length(Text) };
static constexpr size_t NumberOfDigits{ ([]() constexpr noexcept {size_t result = 0; int temp = numberToConvert; for (; temp != 0; temp /= 10) ++result; return result; }()) };
static constexpr size_t ArrayLength{ (numberToConvert ? 1u : 2u) + NumberOfDigits + TextLength };

// Here we will store the string
std::array<char, ArrayLength> internalString{};

// Constructor: Copy text and Convert number to character digits
constexpr Converter() noexcept {
size_t i{ 0 }; for (; i < TextLength; ++i) internalString[i] = Text[i]; // Copy text
if (numberToConvert == 0) internalString[i] = '0'; // In case that the given number is 0, then simply copy '0' character
else {
i = NumberOfDigits + TextLength - 1; // Convert number to character digits
int number = numberToConvert; for (; number; number /= 10)
internalString[i--] = number % 10 + '0';
}
}
constexpr std::array<char, ArrayLength> get() const { return *this; }; // getter
constexpr operator std::array<char, ArrayLength>() const { return internalString; } // type cast
};

// Templated variable. Will have always a different type, depending on the template parameters
template<const size_t numberToConvert, const char* Text>
constexpr auto Converted = Converter<numberToConvert, Text>{}.get();

// Generate a std::array with n elements that consist of const char *, pointing to Textx...Texty
template <int... ManyIntegers>
constexpr auto generateTextHelper(std::integer_sequence<size_t, ManyIntegers...>) noexcept {
return std::array<const char*, sizeof...(ManyIntegers)>{ {Converted<ManyIntegers + StartIndex, BaseString>.data()...}};
}
// Generate the required number of texts
constexpr auto generateTextArray()noexcept {
return generateTextHelper(std::make_integer_sequence<size_t, NumberOfElements>());
}
}
// This is a constexpr array
constexpr auto text = ConstexprGenerator::generateTextArray();

int main() {
std::copy(text.begin(), text.end(), std::ostream_iterator<const char*>(std::cout, "\n"));
return 0;
}

Tested with MSVC, Clang and gcc

populating a constexpr array using C++17

This is a restriction in C++ until C++20, see Permitting trivial default initialization in constexpr contexts: You have to initialize all variables inside of a constexpr function. The Standard gives the following example:

C++17

constexpr int uninit() {
int a; // error: variable is uninitialized
return a;
}

C++20

constexpr int uninit() {
struct { int a; } s;
return s.a; // error: uninitialized read of s.a
}

Note that the initialization itself is OK in C++20. Reading the indeterminate value (inside the "uninitialized" object) is not OK.


To fix your issue, initialize the array or switch to C++20:

static constexpr std::array<int, N> getArr()
{
std::array<int, N> a{}; // now initialized!
for (auto idx = 0; idx < a.size(); ++idx)
{
a[idx] = idx * idx;
}
return a;
}

Filling a std::array at compile time and possible undefined behaviour with const_cast

As far as I can tell this is not undefined behavior. The proposal that added constexpr to operator[] happened before the changes that removed the implicit const from constexpr member functions. So it looks like they just added on constexpr without reflecting on the need for keeping const or not.

We can see form an earlier version of Relaxing constraints on constexpr functions that it says the following about mutating literals within a constant expression:

Objects created within a constant expression can be modified within the evalution of that constant expression (including the evaluation of any constexpr function calls it makes), until the evaluation of that constant expression ends, or the lifetime of the object ends, whichever happens sooner. They cannot be modified by later constant expression evaluations. [...]

This approach allows arbitrary variable mutations within an evaluation, while still preserving the essential property that constant expression evaluation is independent of the mutable global state of the program. Thus a constant expression evaluates to the same value no matter when it is evaluated, excepting when the value is unspecified (for instance, floating-point calculations can give different results and, with these changes, differing orders of evaluation can also give different results).

and we can see the earlier proposal I referenced points out the const_cast hack and it says:

In C++11, constexpr member functions are implicitly const. This creates problems for literal class types which desire to be usable both within constant expressions and outside them:

[...]

Several alternatives have been suggested to resolve this problem:

  • Accept the status quo, and require users to work around this minor embarrassment with const_cast.

Array Initialisation Compile Time - Constexpr Sequence

1) How to implement that kind of integer_sequence?

template <std::size_t... Is> 
constexpr auto make_sequence_impl(std::index_sequence<Is...>)
{
return std::index_sequence<generate_ith_number(Is)...>{};
}

template <std::size_t N>
constexpr auto make_sequence()
{
return make_sequence_impl(std::make_index_sequence<N>{});
}

2) Is it possible to build an std::array from that integer_sequence at compile time?

template <std::size_t... Is>
constexpr auto make_array_from_sequence_impl(std::index_sequence<Is...>)
{
return std::array<std::size_t, sizeof...(Is)>{Is...};
}

template <typename Seq>
constexpr auto make_array_from_sequence(Seq)
{
return make_array_from_sequence_impl(Seq{});
}

Usage:

int main()
{
constexpr auto arr = make_array_from_sequence(make_sequence<6>());
static_assert(arr[0] == 0);
static_assert(arr[1] == 1);
static_assert(arr[2] == 2);
static_assert(arr[3] == 4);
static_assert(arr[4] == 5);
static_assert(arr[5] == 7);
}

live example on wandbox.org

Why is std::arrayT,N::begin() a constexpr since C++17?

constexpr functions can be called in non-compiletime-constant expressions. Such calls are evaluated at runtime. Only when constexpr function is called in a compiletime-constant expression, is the function evaluated at compile time.

But how can the return of begin be known at compile time?

It can be known at compile time when the array itself is a compile time constant.

The fact that it cannot be known at compile time when the array is not a compile time constant is not a problem because the function is executed at runtime in that case.



Related Topics



Leave a reply



Submit