根据索引选择模板变量

问题描述 投票:0回答:2

我遇到了模板问题,我不确定如何以消除对(容易出错的)样板代码的需要的方式修复它。

想象一下以下情况,我想存储在编译时已知的一定量的数据。用于访问正确 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。

编辑:一个要求是我需要避免虚拟函数调用和堆存储,因为这对性能影响太大

c++ templates c++20 metaprogramming
2个回答
2
投票

一种可能的方法是使用

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));
}

0
投票

如果您可以接受用

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);
        });
    }
};

演示

© www.soinside.com 2019 - 2024. All rights reserved.