如何存储编译时动态分配的内存以便它可以在 运行 时间内使用?
How do I store the compile-time dynamically allocated memory so that it can be used in run-time?
比如说,我有一个 constexpr
变量,它包含所有小于 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;
}();
这里我手动指定了ret
的数组大小。当我想改变N
时,我不得不重新运行这个循环,看看最后的prime_cnt
值是多少,手动指定。特别是,如果我增加N
,我必须首先给出一个上限猜测以避免段错误。
在 C++20 中,我们可以在编译时动态分配内存,因此我们现在有了 constexpr
向量。所以我们不再需要担心上界问题。但是这样的值不能在运行时间内使用,也就是说,这个代码是无效的。
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
}();
那么问题来了,这样一个vector怎么存储呢?
一个明显的解决方案是将这个过程分成两个阶段。第一个计算数组大小,第二个进行正常计算。但这需要额外的代码和计算资源。有没有一种优雅而自动的方式来实现这个目标?
创建一个生成 vector
的 lambda,并使用另一个 lambda 生成 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;
}();
A std::vector
使用动态分配,在 constexpr
中任何动态分配都必须由 delete
匹配。所以你不能 constexpr
那样 std::vector
。
但没有什么能阻止您使用 std::array
。 N
以下的素数可以在constexpr
中计算出来,然后你可以用它来给std::array
一个compile-time的大小:
#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;
}
该代码非常愚蠢,因为它对所有素数进行了两次计算,一次是通过可怕的低效除法测试,一次是通过筛子。优化这个留给 reader.
提示:合并这两个函数,在创建筛子的同时计算素数。然后创建数组并通过遍历筛子来填充它。或者,在创建筛子期间创建一个向量,然后将该向量复制到数组中。只要vector在constexpr
中被破坏,整体就是一个constexpr
.
比如说,我有一个 constexpr
变量,它包含所有小于 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;
}();
这里我手动指定了ret
的数组大小。当我想改变N
时,我不得不重新运行这个循环,看看最后的prime_cnt
值是多少,手动指定。特别是,如果我增加N
,我必须首先给出一个上限猜测以避免段错误。
在 C++20 中,我们可以在编译时动态分配内存,因此我们现在有了 constexpr
向量。所以我们不再需要担心上界问题。但是这样的值不能在运行时间内使用,也就是说,这个代码是无效的。
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
}();
那么问题来了,这样一个vector怎么存储呢?
一个明显的解决方案是将这个过程分成两个阶段。第一个计算数组大小,第二个进行正常计算。但这需要额外的代码和计算资源。有没有一种优雅而自动的方式来实现这个目标?
创建一个生成 vector
的 lambda,并使用另一个 lambda 生成 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;
}();
A std::vector
使用动态分配,在 constexpr
中任何动态分配都必须由 delete
匹配。所以你不能 constexpr
那样 std::vector
。
但没有什么能阻止您使用 std::array
。 N
以下的素数可以在constexpr
中计算出来,然后你可以用它来给std::array
一个compile-time的大小:
#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;
}
该代码非常愚蠢,因为它对所有素数进行了两次计算,一次是通过可怕的低效除法测试,一次是通过筛子。优化这个留给 reader.
提示:合并这两个函数,在创建筛子的同时计算素数。然后创建数组并通过遍历筛子来填充它。或者,在创建筛子期间创建一个向量,然后将该向量复制到数组中。只要vector在constexpr
中被破坏,整体就是一个constexpr
.