【问题标题】:Automatically generating switch statements at compile time for sparse array indexing在编译时为稀疏数组索引自动生成 switch 语句
【发布时间】:2019-10-23 22:49:02
【问题描述】:

有没有办法生成编译时 switch 语句以匹配索引?例如,如果我有一个序列1,5,8 并且我想将它与0,1,2 匹配,编译器有没有办法在编译时生成一个函数,当给定 1、5、8 时分别返回 0、1、2 .为了更好地说明我想要什么,我准备了这个稀疏数组结构:https://godbolt.org/z/QpWVST

#include <tuple>
#include <limits>

template<size_t depth, size_t Idx, size_t I, size_t...Is>
size_t get_idx()
{
    static_assert(Idx==I || sizeof...(Is)>0, "Index not found.");
    if constexpr(Idx==I) return depth;
    else return get_idx<depth+1, Idx,Is...>();
}

template<typename T, size_t... Is>
struct sparse_array
{
    constexpr static size_t N = sizeof...(Is);
    constexpr static size_t size() { return N; }

    T e[N];

    constexpr sparse_array() : e{} {}
    template<typename...type_pack>
    constexpr sparse_array(const type_pack&... pack) : e{ pack... }
    {
        static_assert(sizeof...(type_pack) == size(), "Argument count must mach array size.");
    }

    template<size_t I>
    constexpr size_t idx()
    {
        return get_idx<0, I, Is...>();
    }

    size_t idx(const size_t i)
    {
        // how to achieve somethig like this?
        return idx<i>();
    }

    constexpr T& at(size_t idx)
    {
        return e[idx];
    }
};

template<typename T, size_t...Is>
auto make_dense_array(std::index_sequence<Is...>&& seq)
{
    return sparse_array<T, Is...>{};
}

template<typename T, size_t N>
using dense_array = decltype(make_dense_array<T>(std::declval<std::make_index_sequence<N>>()));

size_t test()
{
    dense_array<int, 3> a;
    sparse_array<int,1,5,8> b;
    return b.idx<8>();
}

我还希望能够传入运行时变量,这些变量将通过带有索引的开关并返回相应的相应索引。我解决这个问题的唯一想法是生成一个Is... 序列的数组,然后使用 if 语句进行 for 循环以返回正确的索引。另一种选择是使用地图(但这也不是编译时)。 sparse_array 通常会非常小,我希望能够在编译时间内完成大多数事情。

【问题讨论】:

    标签: c++ templates c++17 sparse-matrix index-sequence


    【解决方案1】:

    这样的?

    static constexpr size_t idx(size_t i)
    {
        size_t j = 0;
        if(!(... || (j++, i == Is))) {
            throw "Index out of range!";
        }
        return j-1;
    }
    

    阅读起来可能有点棘手,但如果我理解正确的话,应该做你想做的事。在实例化之后,这基本上相当于一系列if else 从左到右通过Is 中的索引。

    您可以通过将折叠表达式的主体分成一个 lambda 来使其更具可读性。您还应该将 throw 表达式替换为您认为合理的任何内容。

    使用constexpr 限定符,它也可以用于模板版本:

    template<size_t I, auto s = idx(I)>
    static constexpr size_t idx() {
        return s;
    }
    

    (将结果放在模板默认参数中可以保证编译时评估。)

    这将不是性能最好的代码,与手动编写的switch 语句存在相同的问题。根据输入的可预测性,许多分支可能经常被错误预测,在这种情况下,(大部分)无分支版本会更可取。这可以通过适当修改折叠表达式来实现。

    如果索引的数量不小,则通过正确构造的静态数组的循环将更适合指令缓存局部性:

    static constexpr size_t idx(size_t i)
    {
        static constexpr std::array ind{Is...};
        size_t j = 0;
        for(; j < ind.size() && i != ind[j]; j++);
        if(j == ind.size())
            throw "Index out of range!";
        return j;
    }
    

    同样,最好替换循环中的提前退出。

    如果数组更大,那么不仅使用带有循环的声明数组,而且在该数组上正确实现二进制搜索可能很有用。

    可以使用std::any_ofstd::findstd::binary_search 等标准算法来代替手动搜索实现。但是,这些算法将仅在 C++20 中为 constexpr,因此这将限制此处的使用。

    【讨论】:

    • 似乎正是我想要的。我有一个类似的版本,但由于我的一些函数需要编译时表达式,所以无法让它工作。不过这看起来不错,也不难阅读(至少对我而言)。
    • @lightxbulb 我添加了更多信息,因为除了极少数索引之外,您确实应该使用不同的方法。
    • 我意识到对于较大的数组,可以使用 map 和 unordered_map,但是它们都是运行时的。我认为完美的散列也可以在编译时实现,但这远远超出了我的需要和目标。我的数组通常不会超过 10 项,这也是我使用静态数组进行数据存储的原因。
    猜你喜欢
    • 2021-10-20
    • 2014-02-06
    • 2011-08-25
    • 1970-01-01
    • 2017-05-06
    • 1970-01-01
    • 1970-01-01
    • 2011-06-09
    • 1970-01-01
    相关资源
    最近更新 更多