Getting template metaprogramming compile-time constants at runtime

Viewed 23969

Background

Consider the following:

template <unsigned N>
struct Fibonacci
{
    enum
    {
        value = Fibonacci<N-1>::value + Fibonacci<N-2>::value
    };
};

template <>
struct Fibonacci<1>
{
    enum
    {
        value = 1
    };
};

template <>
struct Fibonacci<0>
{
    enum
    {
        value = 0
    };
};

This is a common example and we can get the value of a Fibonacci number as a compile-time constant:

int main(void)
{
    std::cout << "Fibonacci(15) = ";
    std::cout << Fibonacci<15>::value;
    std::cout << std::endl;
}

But you obviously cannot get the value at runtime:

int main(void)
{
    std::srand(static_cast<unsigned>(std::time(0)));

    // ensure the table exists up to a certain size
    // (even though the rest of the code won't work)
    static const unsigned fibbMax = 20;
    Fibonacci<fibbMax>::value;

    // get index into sequence
    unsigned fibb = std::rand() % fibbMax;

    std::cout << "Fibonacci(" << fibb << ") = ";
    std::cout << Fibonacci<fibb>::value;
    std::cout << std::endl;
}

Because fibb is not a compile-time constant.

Question

So my question is:

What is the best way to peek into this table at run-time? The most obvious solution (and "solution" should be taken lightly), is to have a large switch statement:

unsigned fibonacci(unsigned index)
{
    switch (index)
    {
    case 0:
        return Fibonacci<0>::value;
    case 1:
        return Fibonacci<1>::value;
    case 2:
        return Fibonacci<2>::value;
    .
    .
    .
    case 20:
        return Fibonacci<20>::value;
    default:
        return fibonacci(index - 1) + fibonacci(index - 2);
    }
}

int main(void)
{
    std::srand(static_cast<unsigned>(std::time(0)));

    static const unsigned fibbMax = 20;    

    // get index into sequence
    unsigned fibb = std::rand() % fibbMax;

    std::cout << "Fibonacci(" << fibb << ") = ";
    std::cout << fibonacci(fibb);
    std::cout << std::endl;
}

But now the size of the table is very hard coded and it wouldn't be easy to expand it to say, 40.

The only one I came up with that has a similiar method of query is this:

template <int TableSize = 40>
class FibonacciTable
{
public:
    enum
    {
        max = TableSize
    };

    static unsigned get(unsigned index)
    {
        if (index == TableSize)
        {
            return Fibonacci<TableSize>::value;
        }
        else
        {
            // too far, pass downwards
            return FibonacciTable<TableSize - 1>::get(index);
        }
    }
};

template <>
class FibonacciTable<0>
{
public:
    enum
    {
        max = 0
    };

    static unsigned get(unsigned)
    {
        // doesn't matter, no where else to go.
        // must be 0, or the original value was
        // not in table
        return 0;
    }
};

int main(void)
{
    std::srand(static_cast<unsigned>(std::time(0)));

    // get index into sequence
    unsigned fibb = std::rand() % FibonacciTable<>::max;

    std::cout << "Fibonacci(" << fibb << ") = ";
    std::cout << FibonacciTable<>::get(fibb);
    std::cout << std::endl;
}

Which seems to work great. The only two problems I see are:

  • Potentially large call stack, since calculating Fibonacci<2> requires we go through TableMax all the way to 2, and:

  • If the value is outside of the table, it returns zero as opposed to calculating it.

So is there something I am missing? It seems there should be a better way to pick out these values at runtime.

A template metaprogramming version of a switch statement perhaps, that generates a switch statement up to a certain number?

Thanks in advance.

7 Answers
template <unsigned long N>
struct Fibonacci
{
    enum
    {
        value = Fibonacci<N-1>::value + Fibonacci<N-2>::value
    };
    static void add_values(vector<unsigned long>& v)
    {
        Fibonacci<N-1>::add_values(v);
        v.push_back(value);
    }
};

template <>
struct Fibonacci<0>
{
    enum
    {
        value = 0
    };
    static void add_values(vector<unsigned long>& v)
    {
        v.push_back(value);
    }

};

template <>
struct Fibonacci<1>
{
    enum
    {
        value = 1
    };
    static void add_values(vector<unsigned long>& v)
    {
        Fibonacci<0>::add_values(v);
        v.push_back(value);
    }
};



int main()
{
    vector<unsigned long> fibonacci_seq;
    Fibonacci<45>::add_values(fibonacci_seq);
    for (int i = 0; i <= 45; ++i)
        cout << "F" << i << " is " << fibonacci_seq[i] << '\n';
}

After much thought into the problem, I came up with this solution. Of course, you still have to add the values to a container at run-time, but (importantly) they are not computed at run-time.

As a side note, it's important not to define Fibonacci<1> above Fibonacci<0>, or your compiler will get very confused when it resolves the call to Fibonacci<0>::add_values, since Fibonacci<0>'s template specialization has not been specified.

Of course, TMP has its limitations: You need a precomputed maximum, and getting the values at run-time requires recursion (since templates are defined recursively).

My idea is to recursively save the fibonacci sequence in the variadic templates then convert it into an array. All of this are done at compile-time. For example with n = 5 we have:

F<5>::array
= F<4, 0>::array
= F<3, 0, 1>::array
= F<2, 0, 1, 1>::array
= F<1, 0, 1, 1, 2>::array
= F<0, 0, 1, 1, 2, 3>::array
= { 0, 1, 1, 2, 3 }

Then we can index the array at runtime.

My C++14 implementation:

#include <cstdint>
#include <array>
#include <iostream>


template<uint64_t n>
struct Helper { static constexpr uint64_t value = Helper<n - 1>::value + Helper<n - 2>::value; };

template<>
struct Helper<0> { static constexpr uint64_t value = 0; };

template<>
struct Helper<1> { static constexpr uint64_t value = 1; };


template<u_int64_t x>
class Fib {
private:
    template<u_int64_t n, u_int64_t...rest>
    struct Get {
        static constexpr std::array<u_int64_t, n + sizeof...(rest)> value = Get<n - 1, rest..., Helper<sizeof...(rest)>::value>::value;
    };

    template<u_int64_t...rest>
    struct Get<0, rest...> {
        static constexpr std::array<u_int64_t, sizeof...(rest)> value{rest...};
    };
public:
    static constexpr std::array<u_int64_t, x> sequence = Get<x>::value;
};

template<u_int64_t x>
constexpr std::array<u_int64_t, x> Fib<x>::sequence;


int main() {
    for (int i = 0; i < 45; i++) std::cout << "F" << i << " = " << Fib<45>::sequence[i] << std::endl;
}

One of the basic tennants of C (and for the most part C++) is that you don't pay for what you don't need.

The automatic generation of look-up tables is just not something that the compiler needs to do for you. Even if you need that functionality, not everyone else necessarly does.

If you want a lookup table, write a program to make one. Then use that data in your program.

Don't use a template metaprogram if you want values to be calculated at runtime, just use a regular program to calculate values.

Related