Initializing the function table at compile time with metaprogramming

advertisements

In video-games is common that resources are loaded in a step fashion way, so within a single thread a loading bar can update at each loading step. By example:

1 -> Load texture A

2 -> Update Loading Bar to 2%

3 -> Load texture B

4 -> Update Loading Bar to 4%

5 ...

This can be done in many ways. One of these is define a function for each loading step.

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

This has the advantage of readability, not need too much nested code and even possible in some cases to share loading routines between two game states.

Now what I was thinking was to generalize this "function-for-step" model with templates. Lets say.

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

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

Please correct me if I'm wrong. But it appears possible that I can compile-time iterate through 0 .. to N steps using metaprogramming and assign this specialized functions to an array or vector of function pointers. N steps are known at compile time along with it respective functions. Function pointer vector would be iterated like this:

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

Is this possible? I know that that are easier ways to do it. But besides project requirments it's an interesting programming challenge


I achieved it based on Kal answer of a similar problem 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;
};

Using example:

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

}

This yields of void* array of pointers to template functions:

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

Since Foo<1..3> are not specialized they will fall to Default LoadingStep function