You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 22:45:27