views:

818

answers:

3

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.

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

Doesn't this solve your problem? I mean this is after all a fibonacci sequence. Of course you can't list it. The purpose with this "exercise" (albeit a stupid one :)) must have to be to point what you can do with templates and in this case apply it with a recursive algorithm to generate fibonacci numbers. Or am I missing something?

Magnus Skog
This misses the point. With this, the value is calculated at runtime. consider the first code segment. Fibonacci<15>::value will not be calculated at runtime, it will simply be replaced with 610. With your function here, it will be recalculated, and every time, too.
GMan
Also, in your code here, just replace Fibonacci<0>::value with 0, and Fibonacci<1>::value with 1, and you have the complete non-templated, non-dynamic programming version.
GMan
+6  A: 
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).

rlbond
Thanks, this explains exactly what I was wondering.
GMan
+1 nice, general way of solving that. With regard to the out-flipping of VS: It's because of 14.7.3/6 :) "If a template, a member template or the member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required.". Such a "no diagnostic required" thingy will allow an implementation to do anything it wants.
Johannes Schaub - litb
A: 

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.

James Caccese