我遇到了模板问题,我不确定如何以消除对(容易出错的)样板代码的需要的方式修复它。
想象一下以下情况,我想存储在编译时已知的一定量的数据。用于访问正确 mip 的 LOD 值,仅在 runtime 时才知道。 (请注意,这是一个简化的示例):
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;
//...
}
}
};
我想要这样的东西,但是这是不可能的,因为具体类型不一样。
constexpr auto& GetImageStorage(byte LOD) {
switch (LOD) {
case 0:
return _lod0;
case 1:
return _lod1;
case 2:
return _lod2;
case 3:
return _lod3;
//...
}
}
是否可以更新
SetValueAt()
功能,这样就不需要开关了?我正在使用 C++20。
编辑:一个要求是我需要避免虚拟函数调用和堆存储,因为这对性能影响太大
std::variant
:
constexpr auto GetImageStorage(byte LOD)
-> std::variant<ImageStorage<0>*, ImageStorage<1>*, ImageStorage<2>*, ImageStorage<3>*, ImageStorage<4>*>
{
switch (LOD) {
case 0:
return &_lod0;
case 1:
return &_lod1;
case 2:
return &_lod2;
case 3:
return &_lod3;
//...
}
}
std::visit
:
constexpr void SetValueAt(int x, int y, byte value, byte LOD)
{
x >>= LOD;
y >>= LOD;
std::visit([&](auto* lod){ lod->SetValueAt(x, y, value); }, GetImageStorage(LOD));
}
如果您可以接受用
std::tuple
替换普通数据成员,您可以考虑此解决方案。
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
std::tuple<ImageStorage<0>,
ImageStorage<1>,
ImageStorage<2>,
ImageStorage<3>,
ImageStorage<4>,
ImageStorage<5>> _lods;
template <byte LOD>
ImageStorage<LOD>& GetLod() {
return std::get<ImageStorage<LOD>>(_lods);
}
};
使用 std::index_sequence 和 C++20 中引入的通用 lambda,
template <auto value, typename F>
bool call_at(decltype(value) v, F&& f) {
if (value == v) {
f.template operator()<value>();
return true;
}
return false;
}
class MipLevels {
constexpr static std::size_t lod_num = 5;
template <typename F>
constexpr void Call(int v, F&& f) {
CallImpl(v, std::make_index_sequence<lod_num>{}, std::forward<F>(f));
}
template <auto... Is, typename F>
bool CallImpl(auto v, std::index_sequence<Is...>, F&& f) {
return (call_at<Is>(v, std::forward<F>(f)) || ...);
}
constexpr void SetValueAt(int x, int y, byte value, byte LOD) {
x >>= LOD;
y >>= LOD;
Call(LOD, [=, this]<auto V> () {
this->GetLod<V>().SetValueAt(x, y, value);
});
}
};