使用元编程在编译时初始化函数数组

时间:2013-12-05 04:02:04

标签: c++ templates metaprogramming generic-programming

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

  

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

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

3 个答案:

答案 0 :(得分: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&lt; 1..3&gt;不专业,他们将落入Default LoadingStep函数

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

如果你想要一个数组:

 LoadingFunction functionArray[] = {Function0, Function1, Function2};
 .....

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

或用它初始化一个std容器。

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

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

i中的Function<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
 };