Say, I have a constexpr
variable that contains all primes less than 216.
constexpr auto primes = [] {
constexpr int N = 1 << 16;
std::array<int, 6542> ret;
bool not_prime[N] = {};
int prime_cnt = 0;
for (int i = 2; i < N; i ) {
if (!not_prime[i]) {
ret[prime_cnt ] = i;
for (int j = 2 * i; j < N; j = i) not_prime[j] = true;
}
}
return ret;
}();
Here I manually specified the ret
's array size. When I want to change N
, I have to re-run this loop to see what the final prime_cnt
value is and manually specify it. Particularly, if I increase N
, I have to first give an upper-bound guess to avoid segfault.
In C 20, we can dynamically allocate memory in compile-time, and thus we have constexpr
vector now. So we no longer need to worry about the upper-bound problem. But such a value cannot be used in run-time, which means, this code is invalid.
constexpr auto primes = [] {
constexpr int N = 1 << 16;
std::vector<int> ret;
bool not_prime[N] = {};
for (int i = 2; i < N; i ) {
if (!not_prime[i]) {
ret.push_back(i);
for (int j = 2 * i; j < N; j = i) not_prime[j] = true;
}
}
return ret; // invalid here
}();
So here comes the problem, how do I store such a vector?
An obvious solution is to split this process into two phases. The first one calculates the array size and the second one does the normal calculation. But this needs extra code and computing resources. Is there an elegant and automatic way to achieve this goal?
CodePudding user response:
Create a lambda that makes a vector
, and use another lambda to create an array
#include <array>
#include <vector>
#include <algorithm>
constexpr auto primes_num_vector = [] {
constexpr int N = 1 << 16;
std::vector<int> ret;
bool not_prime[N] = {};
for (int i = 2; i < N; i ) {
if (!not_prime[i]) {
ret.push_back(i);
for (int j = 2 * i; j < N; j = i) not_prime[j] = true;
}
}
return ret;
};
constexpr auto primes = [] {
std::array<int, primes_num_vector().size()> ret;
std::ranges::copy(primes_num_vector(), ret.begin());
return ret;
}();
CodePudding user response:
A std::vector
uses dynamic allocations, and in a constexpr
any dynamic allocation must be matched by delete
. So you cannot constexpr
a std::vector
like that.
But nothing stops you from using std::array
. The number of primes below N
can be counted in a constexpr
, and then you can use that to give a compile-time size for the std::array
:
#include <vector>
#include <array>
constexpr std::size_t num_primes(std::size_t N) {
std::size_t num = N - 2;
for (std::size_t i = 2; i < N; i) {
for (std::size_t j = 2; j * j <= i; j) {
if (i % j == 0) {
--num;
break;
}
}
}
return num;
}
constexpr auto primes = [] {
constexpr int N = 1 << 16;
std::array<std::size_t, num_primes(N)> ret;
std::size_t pos = 0;
bool not_prime[N] = {};
for (int i = 2; i < N; i ) {
if (!not_prime[i]) {
ret[pos ] = i;
for (int j = 2 * i; j < N; j = i) not_prime[j] = true;
}
}
return ret; // invalid here no more
}();
#include <iostream>
int main() {
for (const auto & x : primes) {
std::cout << " " << x;
}
std::cout << std::endl;
}
The code is pretty stupid as it computes all primes twice, once with the horrible inefficient division test, and once with a sieve. Optimizing this is left to the reader.
Tip: merge the two functions, count primes while you create the sieve. Then create the array and fill it by iterating over the sieve. Or, create a vector during sieve creation, and then copy the vector into the array. As long as the vector is destroyed in the constexpr
, the whole is a constexpr
.