【问题标题】:Function array initialization at compile time with metaprograming使用元编程在编译时初始化函数数组
【发布时间】:2013-12-05 04:02:04
【问题描述】:

在视频游戏中,资源以步进方式加载是很常见的,因此在单个线程中,加载栏可以在每个加载步骤中更新。举例:

1 -> 加载纹理 A

2 -> 将加载条更新为 2%

3 -> 加载纹理 B

4 -> 将加载条更新为 4%

5 ...

这可以通过多种方式完成。其中之一是为每个加载步骤定义一个函数。

void LoadTextureA()
{
    //Loading routine
    ...
}

这具有可读性的优势,不需要太多的嵌套代码,甚至在某些情况下可以在两个游戏状态之间共享加载例程。

现在我的想法是用模板概括这种“功能换步”模型。让我们说。

template <int S>
struct Foo{
    void LoadingStep()
    {
    }
};

template <>
struct Foo<0>
{
    void LoadingStep()
    {
        //First loading step
        ...
    }
};

如果我错了,请纠正我。但似乎我可以使用元编程在编译时迭代 0 .. 到 N 个步骤,并将这个专门的函数分配给函数指针的数组或向量。 N 个步骤在编译时与它各自的功能一起是已知的。 函数指针向量将像这样迭代:

template <int Steps>
class Loader  {
public:
    bool Load() 
    {
        functionArray[m_step]();
        if (++m_step == Steps)
            return false; //End loading
        else
            return true;
    }  
private:
    int m_step;
}

这可能吗?我知道这是更简单的方法。但除了项目要求之外,这是一个有趣的编程挑战

【问题讨论】:

  • 是的,这正是我想要的。谢谢
  • 你为什么不只拥有一个函数指针数组或std::functions,计算它的大小,并对内容进行简单的运行时循环,更新每个函数指针之间的进度条加载?甚至是资源名称数组,只需调用“加载资源”函数?

标签: c++ templates metaprogramming generic-programming


【解决方案1】:

我是根据 Kal 对类似问题的回答实现的 Create N-element constexpr array in C++11

template <int S>
struct Foo{
    static void LoadingStep()
    {
    }
};

template <>
struct Foo<0>
{
    static void LoadingStep()
    {
        //First loading step

    }
};

template<template<int S> class T,int N, int... Rest>
struct Array_impl {
    static constexpr auto& value = Array_impl<T,N - 1, N, Rest...>::value;
};

template<template<int S> class T,int... Rest>
struct Array_impl<T,0, Rest...> {
    static constexpr std::array<void*,sizeof...(Rest)+1> value = {reinterpret_cast<void*>(T<0>::LoadingStep),reinterpret_cast<void*>(T<Rest>::LoadingStep)...};
};

template<template<int S> class T,int... Rest>
constexpr std::array<void*,sizeof...(Rest)+1> Array_impl<T,0, Rest...>::value;

template<template<int S> class T,int N>
struct F_Array {
    static_assert(N >= 0, "N must be at least 0");

    static constexpr auto& value = Array_impl<T,N>::value;

    F_Array() = delete;
    F_Array(const F_Array&) = delete;
    F_Array(F_Array&&) = delete;
};

使用示例:

int main()
{
    auto& value = F_Array< Foo ,4>::value;
    std::cout << value[0] << std::endl;

}

这会产生指向模板函数的 void* 指针数组:

Foo<0>::LoadinStep()
Foo<1>::LoadinStep() 
Foo<2>::LoadinStep()
Foo<3>::LoadinStep()
Foo<4>::LoadinStep()

由于 Foo 不是专门的,它们将落入 Default LoadingStep 函数

【讨论】:

    【解决方案2】:

    是的。这是可能的。而且如果使用模板元编程,就不需要使用运行时循环,而是递归调用模板方法:

    #include <iostream>
    
    // The template numerated methods
    template <int S> struct Foo{static void LoadingStep(){}};
    template <> struct Foo<0> {static void LoadingStep(){std::cout<<0;}};
    template <> struct Foo<1> {static void LoadingStep(){std::cout<<1;}};
    template <> struct Foo<2> {static void LoadingStep(){std::cout<<2;}};
    
    // The loader template method
    template <int Step> 
    void Loader()
    {
        Foo<Step>::LoadingStep();
        Loader<Step-1>();
    }
    
    // Stopping rule
    template <> void Loader<-1>(){}
    
    int main()
    {
        Loader<2>();
    }
    

    【讨论】:

    • 感谢您的回答。现在的问题是我无法在运行时遍历这些步骤,因为 Step 模板参数必须是常量
    【解决方案3】:

    如果你想要一个数组:

     LoadingFunction functionArray[] = {Function0, Function1, Function2};
     .....
    
     for (int i = 0; i < nSteps; ++i)
        RunStep(i, nSteps, Function[i]);
    

    或者用它初始化一个标准容器。

    如果你想要模板,你可以写

     for (int i = 0; i < nSteps; ++i)
        RunStep(i, nSteps, Function<i>);
    

    Function&lt;i&gt; 中的 i 除外,必须是常量。所以你必须用模板递归的东西来做:

     template <int i, int NSteps> struct RunSteps
     {
        void Run() 
        {
          RunStep(i, NSteps, Function<i>);
          RunSteps<i+1, NSteps>::Run();
        }
     };
    
     template <int NSteps> struct RunSteps<NSteps, NSteps>
     {
        void Run() {}
     };
    
     RunSteps<0, NSteps>::Run();
    

    编译时迭代实际上并不存在。 for 循环和模板化递归的东西做的完全一样。编译器能够展开循环,就像内联调用一样。

    似乎从模板化这些东西中获得的收益很少,而损失却很多。

    目前尚不清楚为什么要在编译时将模板化函数放入数组中,但您可以这样做:

     LoadingFunction functionArray[] = {Function<0>, Function<1>, Function<2>};
    

    现在,如果您不想像那样手动枚举函数,那可能会有点挑战。旧版 C 数组或任何 std 容器似乎都不可能。假设您确实需要它,则可以编写一个能够进行此类初始化的自定义容器。

     template <template <int> class FunctionWrappper, int NFunctions>
     class MyOptimizedFunctionArray {
          // filling this space is left as an exercise
     };
    

    【讨论】:

    • for (int i = 0; i ); “i”必须是常量,怎么编译?
    • 是的,你是对的。所以你不能做循环,你需要做递归模板的事情。其实是一样的。我将编辑答案以反映这一点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-22
    • 2018-02-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多