【问题标题】:Avoiding code duplication for runtime-to-compile-time numeric parameter translation避免运行时到编译时数字参数转换的代码重复
【发布时间】:2016-12-01 16:01:49
【问题描述】:

假设我们有这样的功能

template <typename T, unsigned N> void foo();

为简单起见,假设我们知道只有(常数)值 N_1N_2 ... N_kN 有效。

现在,假设我想将编译时参数设为运行时参数,使用 foo() 作为黑盒,即实现:

template <typename T> void foo(unsigned n);

通过拨打foo&lt;,&gt;() 电话。我该怎么做呢?显然,我可以写:

template <typename T> void foo(unsigned n) {
    switch(n) {
    case N_1 :  foo<T, N_1>(); break;
    case N_2 :  foo<T, N_2>(); break;
    // etc. etc.
    case N_k :  foo<T, N_k>(); break;
    }
 }

...但这让我觉得很脏。我想我可以使用 MAP() 元宏来生成这些 k 行;但是我可以做一些更好、更不宏观的事情来实现同样的目标吗?是否可以编写类似于上述通用的东西,并且适用于每个可变参数模板和固定的常量值序列?

注意事项:

  • 显然欢迎针对 C++11/14/17 提出建议。
  • N 不一定是连续的,也不一定是小的,也不是排序的。例如假设 N_2 = 123456789 和 N_5 = 1。

【问题讨论】:

    标签: c++ templates template-meta-programming template-specialization idioms


    【解决方案1】:

    你可以制作一个函数指针表:

    using F = void(*)();
    
    template <class T, class >
    struct Table;
    
    template <class T, size_t... Is>
    struct Table<T, std::index_sequence<Is...> > {
        static constexpr F fns[] = {
            foo<T, Is>...
        };
    };
    
    template <class T, size_t... Is>
    constexpr F Table<T, std::index_sequence<Is...> >::fns[sizeof...(Is)];
    

    然后调用你想要的:

    template <class T, size_t N>
    struct MakeTable : Table<T, std::make_index_sequence<N>> { };
    
    template <typename T>
    void foo(unsigned n) {
        MakeTable<T, MaxN>::fns[n]();
    }
    

    如果N_ks 不连续,那么我们可以使用 lambda 进行内联参数解包:

     template <class T>
     void foo(unsigned n) {
         using seq = std::index_sequence<N_1, N_2, ..., N_k>;
         indexer(seq)([n](auto i){
             if (n == i) {
                 f<T, i>();
             }
         });
    }
    

    如果上面的速度太慢,那么我想只是手动构建一个std::unordered_map&lt;unsigned, void(*)()&gt; 或其他东西。

    【讨论】:

    • @einpoklum 哦,N_ks 不是连续的吗?
    • 不,我从来没有暗示他们是。
    • 实际上,除非 k 很大(假设编译器足够聪明),否则您的索引器可能应该比无序映射更快。 +1。
    【解决方案2】:

    在这种情况下,我喜欢构建一个函数指针的静态表,并使用一个动态参数来决定分派到哪一个。下面是一个实现这一点的实现,在函数foo_dynamic 中。对于此函数,您指定要支持的 N 的最大值,它使用一些递归模板构建函数指针的静态表。然后,您可以使用您的动态参数取消对该表的引用。

    using ftype = void (*)();
    
    template <typename T, unsigned N> void foo()
    {
        std::cout << N << std::endl;
    }
    
    template <typename T, unsigned max>
    struct TablePopulator
    {
        static void populateFTable(ftype* table)
        {
            table[max] = foo<T,max>;
            TablePopulator<T,max-1>::populateFTable(table);
        }
    };
    
    template <typename T>
    struct TablePopulator<T, 0>
    {
        static void populateFTable(ftype* table)
        {
            table[0] = foo<T,0>;
        }
    };
    
    template<typename T, unsigned max_N>
    std::array<ftype, max_N>& initTable()
    {
        static std::array<ftype, max_N> table;
        TablePopulator<T, max_N-1>::populateFTable(table.data());
        return table;
    }
    
    template<typename T, unsigned max_N>
    void foo_dynamic(unsigned actualN)
    {
        static auto ftable = initTable<T, max_N>();
        if(actualN >= max_N)
            throw std::runtime_error("Max param exceeded");
    
        ftable[actualN]();
    }
    
    
    int main()
    {
        foo_dynamic<int, 10>(1);
        foo_dynamic<int, 10>(5);
    
       return 0;
    }
    

    编辑:鉴于问题编辑中的约束,这是一种手动指定有效索引的方法,它使用 unordered_map 而不是数组:

    using ftype = void (*)();
    
    template <typename T, unsigned N> void foo()
    {
        std::cout << N << std::endl;
    }
    
    template<typename T, size_t ... Indices>
    void foo_dynamic_indices(size_t actual_index)
    {
        static std::unordered_map<size_t, ftype> fmap = {{Indices, foo<T,Indices>}...};
        auto fIt = fmap.find(actual_index);
        if(fIt == fmap.end())
            throw std::runtime_error("Index not found");
    
        fIt->second();
    }
    
    int main()
    {
    
        foo_dynamic_indices<int, 0, 3, 400, 1021, 10000000>(10000000);
        foo_dynamic_indices<int, 0, 3, 400, 1021, 10000000>(4); //Exception
    
       return 0;
    }
    

    【讨论】:

    • 假设 N_k = 10000000。
    • 是的,也适用于此(使用适当的-ftemplate-depth=10000000 开关),虽然编译非常很慢
    • unordered_map 也有一个 at(),因此您可以将调用简化为 fmap.at(actual_index)()(抛出 out_of_range 而不是 runtime_error
    猜你喜欢
    • 2019-11-23
    • 1970-01-01
    • 2020-11-22
    • 1970-01-01
    • 2021-02-26
    • 2019-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多