如何实现依参数是否为constexpr切换std::array/std::vector的Range类
实现支持自动选择存储的Range类
要实现根据构造参数是否为编译期常量自动选择std::array或std::vector的Range类,核心难点在于编译期和运行期参数的类型区分——编译期参数需要作为模板参数来实例化固定大小的std::array,而运行期参数只能动态分配std::vector。以下是两种可行方案:
方案一:辅助函数自动推导(推荐,写法统一)
通过make_range辅助函数,利用C++20的std::is_constant_evaluated()判断当前是否为编译期求值,自动选择对应的模板实例:
#include <array> #include <vector> #include <type_traits> #include <cassert> template <std::size_t N = std::dynamic_extent> class Range { private: using Storage = std::conditional_t<N != std::dynamic_extent, std::array<int, N>, std::vector<int>>; Storage data_; constexpr void init_data() { for (std::size_t i = 0; i < size(); ++i) { data_[i] = i; } } public: // 编译期构造:N为具体大小 template <std::size_t M = N, std::enable_if_t<M != std::dynamic_extent, bool> = true> constexpr Range() { init_data(); } // 运行期构造:N为动态扩展 template <std::size_t M = N, std::enable_if_t<M == std::dynamic_extent, bool> = true> explicit Range(std::size_t n) : data_(n) { init_data(); } constexpr int operator[](std::size_t idx) const { return data_[idx]; } constexpr std::size_t size() const { if constexpr (N != std::dynamic_extent) { return N; } else { return data_.size(); } } }; constexpr auto make_range(std::size_t n) { if constexpr (std::is_constant_evaluated()) { return Range<n>(); } else { return Range<>(n); } } // 使用示例 int main() { // 编译期参数:自动用std::array存储 constexpr auto range_1 = make_range(10); static_assert(range_1.size() == 10); static_assert(range_1[5] == 5); // 运行期参数:自动用std::vector存储 int n = 5; auto range_2 = make_range(n); assert(range_2.size() == 5); assert(range_2[3] == 3); }
优点
- 写法统一:无论编译期还是运行期参数,都用
make_range(n)创建对象 - 编译期完全 constexpr 兼容,支持静态断言
- 无虚函数开销,性能接近原生容器
方案二:类型擦除实现统一类名
如果必须使用Range作为统一类名,可通过类型擦除封装两种存储方式,用虚函数提供统一接口:
#include <array> #include <vector> #include <memory> #include <cassert> class Range { private: struct StorageInterface { virtual int operator[](std::size_t idx) const = 0; virtual std::size_t size() const = 0; virtual ~StorageInterface() = default; }; template <std::size_t N> struct ArrayStorage : StorageInterface { std::array<int, N> data; constexpr ArrayStorage() { for (std::size_t i = 0; i < N; ++i) { data[i] = i; } } int operator[](std::size_t idx) const override { return data[idx]; } std::size_t size() const override { return N; } }; struct VectorStorage : StorageInterface { std::vector<int> data; explicit VectorStorage(std::size_t n) : data(n) { for (std::size_t i = 0; i < n; ++i) { data[i] = i; } } int operator[](std::size_t idx) const override { return data[idx]; } std::size_t size() const override { return data.size(); } }; std::unique_ptr<StorageInterface> storage_; public: // 编译期参数构造:需显式传递std::integral_constant template <std::size_t N> constexpr Range(std::integral_constant<std::size_t, N>) : storage_(std::make_unique<ArrayStorage<N>>()) {} // 运行期参数构造 explicit Range(std::size_t n) : storage_(std::make_unique<VectorStorage>(n)) {} int operator[](std::size_t idx) const { return (*storage_)[idx]; } std::size_t size() const { return storage_->size(); } }; // 推导指引简化编译期参数写法 template <std::size_t N> Range(std::integral_constant<std::size_t, N>) -> Range; // 使用示例 int main() { // 编译期参数:用std::array存储 constexpr Range range_1(std::integral_constant<std::size_t, 10>{}); static_assert(range_1.size() == 10); static_assert(range_1[5] == 5); // 运行期参数:用std::vector存储 int n = 5; Range range_2(n); assert(range_2.size() == 5); assert(range_2[3] == 3); }
优点
- 类名统一:始终使用
Range类型 - 编译期仍支持 constexpr 操作
缺点
- 编译期参数写法稍繁琐,需用
std::integral_constant包装 - 存在虚函数调用开销
内容的提问来源于stack exchange,提问作者Moeda Chaos
相关产品推荐
相关产品推荐

