C++20下如何移除模板LOD访问的switch冗余代码?
问题
需存储编译时确定的数据量,但访问对应MIP的LOD值仅在运行时可知,现有代码存在冗余的switch语句,希望在C++20中去除switch,同时避免虚函数调用和堆存储(防止性能损耗)。现有简化代码如下:
template <byte LOD> struct ImageStorage { constexpr static byte GridSize = 32 >> LOD; void SetValueAt(int x, int y, byte value) { _data[x + y * GridSize] = value; } std::array<byte, GridSize * GridSize> _data{}; }; struct MipLevels { ImageStorage<0> _lod0; // 32x32 ImageStorage<1> _lod1; // 16x16 ImageStorage<2> _lod2; // 8x8 ImageStorage<3> _lod3; // 4x4 ImageStorage<4> _lod4; // 2x2 ImageStorage<5> _lod5; // 1x1 constexpr void SetValueAt(int x, int y, byte value, byte LOD) { x >>= LOD; y >>= LOD; switch (LOD) { case 0: _lod0.SetValueAt(x, y, value); break; case 1: _lod1.SetValueAt(x, y, value); break; case 2: _lod2.SetValueAt(x, y, value); break; case 3: _lod3.SetValueAt(x, y, value); break; //... 剩余LOD分支 } } };
期望实现类似GetImageStorage的功能,但因各ImageStorage<LOD>类型不同无法直接返回统一引用。
解决方案
利用C++20的编译期特性,可通过编译时生成的分发逻辑彻底消除switch语句,同时满足无虚函数、无堆存储的性能要求,以下是几种可行方案:
方案1:编译期生成成员指针数组
直接构建存储各ImageStorage成员指针的constexpr数组,运行时通过LOD值索引后调用方法:
#include <array> #include <cstddef> using byte = unsigned char; template <byte LOD> struct ImageStorage { constexpr static byte GridSize = 32 >> LOD; void SetValueAt(int x, int y, byte value) { _data[x + y * GridSize] = value; } std::array<byte, GridSize * GridSize> _data{}; }; struct MipLevels { ImageStorage<0> _lod0; ImageStorage<1> _lod1; ImageStorage<2> _lod2; ImageStorage<3> _lod3; ImageStorage<4> _lod4; ImageStorage<5> _lod5; private: // 编译期生成成员指针数组 static constexpr std::array<void*, 6> _lod_ptrs = []{ std::array<void*, 6> arr{}; arr[0] = &_lod0; arr[1] = &_lod1; arr[2] = &_lod2; arr[3] = &_lod3; arr[4] = &_lod4; arr[5] = &_lod5; return arr; }(); // 模板方法处理类型转换与调用 template <byte TargetLOD> static void call_set(void* ptr, int x, int y, byte value) { static_cast<ImageStorage<TargetLOD>*>(ptr)->SetValueAt(x, y, value); } // 编译期生成调用函数数组 static constexpr std::array<void(*)(void*, int, int, byte), 6> _call_funcs = { call_set<0>, call_set<1>, call_set<2>, call_set<3>, call_set<4>, call_set<5> }; public: constexpr void SetValueAt(int x, int y, byte value, byte LOD) { x >>= LOD; y >>= LOD; _call_funcs[LOD](_lod_ptrs[LOD], x, y, value); } };
方案2:使用std::tie与std::get编译期索引
通过std::tie将所有LOD成员绑定为元组,利用编译期生成的索引直接访问对应成员:
#include <array> #include <tuple> #include <cstddef> using byte = unsigned char; template <byte LOD> struct ImageStorage { constexpr static byte GridSize = 32 >> LOD; void SetValueAt(int x, int y, byte value) { _data[x + y * GridSize] = value; } std::array<byte, GridSize * GridSize> _data{}; }; struct MipLevels { ImageStorage<0> _lod0; ImageStorage<1> _lod1; ImageStorage<2> _lod2; ImageStorage<3> _lod3; ImageStorage<4> _lod4; ImageStorage<5> _lod5; private: // 编译期生成索引对应的调用逻辑 template <std::size_t... Is> constexpr void dispatch_set(int x, int y, byte value, byte LOD, std::index_sequence<Is...>) { ((LOD == Is ? (std::get<Is>(std::tie(_lod0, _lod1, _lod2, _lod3, _lod4, _lod5)).SetValueAt(x, y, value), true) : false) || ...); } public: constexpr void SetValueAt(int x, int y, byte value, byte LOD) { x >>= LOD; y >>= LOD; dispatch_set(x, y, value, LOD, std::make_index_sequence<6>{}); } };
方案3:std::variant与编译期std::visit
将所有ImageStorage实例放入std::variant数组,通过std::visit结合编译期索引完成分发,无运行时开销:
#include <array> #include <variant> #include <cstddef> using byte = unsigned char; template <byte LOD> struct ImageStorage { constexpr static byte GridSize = 32 >> LOD; void SetValueAt(int x, int y, byte value) { _data[x + y * GridSize] = value; } std::array<byte, GridSize * GridSize> _data{}; }; // 定义包含所有LOD类型的variant using ImageVariant = std::variant< ImageStorage<0>, ImageStorage<1>, ImageStorage<2>, ImageStorage<3>, ImageStorage<4>, ImageStorage<5> >; struct MipLevels { std::array<ImageVariant, 6> _lods; constexpr MipLevels() : _lods{ ImageStorage<0>{}, ImageStorage<1>{}, ImageStorage<2>{}, ImageStorage<3>{}, ImageStorage<4>{}, ImageStorage<5>{} } {} public: constexpr void SetValueAt(int x, int y, byte value, byte LOD) { x >>= LOD; y >>= LOD; std::visit([x, y, value](auto& storage) { storage.SetValueAt(x, y, value); }, _lods[LOD]); } };
说明
以上三种方案均基于C++20编译期特性实现,完全消除了switch语句,且无虚函数调用、无堆内存分配,性能与原switch版本一致甚至更优(编译期可进一步优化索引逻辑)。方案1和方案2贴近原有代码结构,方案3通过variant简化了成员管理逻辑,可根据实际需求选择。
内容的提问来源于stack exchange,提问作者Duckdoom5
相关产品推荐
相关产品推荐

