在 C++20 中,std::span 与 constexpr 的结合让我们能够在编译期对数组进行更灵活、更高效的操作。下面通过一个完整示例演示如何利用这两个特性,在编译期计算数组的总和,并将结果用于程序的其他部分。
1. 基础概念回顾
std::span:是一个无所有权的视图类型,提供对连续内存块的轻量级封装。它允许我们在不复制数据的情况下,对数组或容器的子范围进行统一处理。constexpr:从 C++11 开始引入,用于指定在编译期可求值的函数、变量或表达式。C++20 在constexpr方面做了进一步扩展,支持更复杂的控制流、递归调用等。
将两者结合,可以在编译期构建一个对数组范围可读可写的视图,并对其执行复杂操作。
2. 编译期求和函数
下面的 constexpr_sum 函数接受一个 std::span<const T> 并返回其元素之和。函数在 C++20 之前只能做简单累加,而现在可以利用循环和递归实现更通用的版本。
#include <span>
#include <cstddef>
#include <iostream>
#include <array>
template <typename T>
constexpr T constexpr_sum(std::span<const T> s) noexcept {
T sum{0};
for (const auto& val : s) {
sum += val;
}
return sum;
}
该函数满足以下属性:
- constexpr:在编译期可求值。
- 通用性:可以处理任意类型
T,只要满足默认构造、可加和复制语义。 - 无副作用:只读视图,保证安全性。
3. 使用 constexpr 计算编译期常量
下面演示如何在编译期计算一个固定数组的总和,并将结果用作编译期常量。
constexpr std::array<int, 5> arr{1, 2, 3, 4, 5};
constexpr int total = constexpr_sum(std::span<const int>{arr});
static_assert(total == 15, "编译期总和计算错误");
int main() {
std::cout << "编译期总和为: " << total << '\n';
return 0;
}
运行时会输出:
编译期总和为: 15
通过 static_assert 可以在编译阶段就验证计算结果,进一步提升程序安全性。
4. 运行期动态数组的混合使用
虽然 constexpr_sum 在编译期对常量数组有效,但它也可用于运行时传入的动态数组,只要将其包装成 std::span:
#include <vector>
int main() {
std::vector <double> vec{0.1, 0.2, 0.3};
double runtime_sum = constexpr_sum(std::span<const double>{vec});
std::cout << "运行期总和为: " << runtime_sum << '\n';
}
此时 constexpr_sum 会在运行期执行,但其实现与编译期实现保持一致,代码可重用性更高。
5. 高级用法:递归求和(可选)
若想进一步演示 constexpr 的力量,可以写一个递归版本,避免循环:
template <typename T>
constexpr T recursive_sum(std::span<const T> s, std::size_t idx = 0) noexcept {
return idx == s.size() ? T{} : s[idx] + recursive_sum(s, idx + 1);
}
同样适用于编译期和运行期,编译器会在编译期展开递归,生成最优的求和代码。
6. 结论
std::span为我们提供了对任意连续内存块的统一视图,减少了代码重复。- C++20 的
constexpr让复杂操作能够在编译期完成,提升程序的安全性与性能。 - 通过把这两者结合,可轻松实现对数组在编译期的求和、排序、筛选等高级功能,既保持了代码的简洁,又不牺牲可读性。
小贴士:在实际项目中,可将
constexpr_sum封装成头文件模板,供全局使用,或者在需要编译期常量的场景下使用static_assert进行验证,从而避免潜在的运行期错误。