Getting template metaprogramming compile-time constants at runtime
Asked Answered
K

7

47

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.

Krummhorn answered 25/5, 2009 at 22:24 Comment(7)
May I ask why you are doing this? :)Stoneware
Curiosity. Nobody ever got better at programming or thinking by not programming or not thinking.Krummhorn
Wait... You want your compile time algorithm calculate the result with input you get at runtime? Isn't that something in need of a time machine?Lachman
Anyway, just so you know. With C++1x, you can write "constexpr int fib(int n) { return (n < 2) ? n : (fib(n-2) + fib(n-1)); } fib(2) is calculated at compile time while fib(rand() % 10); is calculated at runtime, AFAIKLachman
No, I want the compiler to do the work of creating a look-up table for me, which is done and easy, but now I need a way of peeking into that table. I can say Fibonacci<40>, and now the compiler will generate all the Fibonacci numbers from 0 to 40. So if I want to know fibb(25), I don't need to calculate it. I can just look it up. The switch statement is the basic idea. I want the value, so I either look it up or recalculate it. We used templates to generate the values rather than hardcode it. Now what's the most elegant way of generate the switch function, rather than hardcode it?Krummhorn
Instead of using templates I may suggest to use dynamic programing. At least it seems more easily generalizable to other problems.Perspicuity
great example! but some people just tend to do oversmart things with templates. Certainly this case is interesting and instructive, but for a real project I'd rather see this coded as a memoization or as a look-up table created at initialization. Think about it, this example does not reduce code size, and adds some obscurity about what is happening. Of course is clear since the word "fibonacci" readily explains what it does. But picture a more complicated scenario. It's not trivial to see it's a lookup table.Klepht
U
30
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).

Upstream answered 25/5, 2009 at 23:16 Comment(1)
+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.Lachman
L
18

I know this question is old, but it intrigued me and I had to have a go at doing without a dynamic container filled at runtime:

#ifndef _FIBONACCI_HPP
#define _FIBONACCI_HPP


template <unsigned long N>
struct Fibonacci
{
    static const unsigned long long value = Fibonacci<N-1>::value + Fibonacci<N-2>::value;
    
    static unsigned long long get_value(unsigned long n)
    {
        switch (n) {
            case N:
                return value;
            default:
                return n < N    ? Fibonacci<N-1>::get_value(n)
                                : get_value(n-2) + get_value(n-1);
        }
    }
};

template <>
struct Fibonacci<0>
{
    static const unsigned long long value = 0;
        
    static unsigned long long get_value(unsigned long n)
    {
        return value;
    }
};

template <>
struct Fibonacci<1>
{
    static const unsigned long long value = 1;

    static unsigned long get_value(unsigned long n)
    {
        if(n == N){
            return value;
        }else{
            return 0; // For `Fibonacci<N>::get(0);`
        }
    }
};

#endif

This seems to work, and when compiled with optimizations (not sure if you were going to allow that), the call stack does not get to deep - there is normal runtime recursion on the stack of course for values (arguments) n > N, where N is the TableSize used in the template instantiation. However, once you go below the TableSize the generated code substitutes a constant computed at compile time, or at worst a value "computed" by dropping through a jump table (compiled in gcc with -c -g -Wa,-adhlns=main.s and checked the listing), the same as I reckon your explicit switch statement would result in.

When used like this:

int main()
{
    std::cout << "F" << 39 << " is " << Fibonacci<40>::get_value(39) << '\n';
    std::cout << "F" << 45 << " is " << Fibonacci<40>::get_value(45) << '\n';
}

There is no call to a computation at all in the first case (value computed at compile time), and in the second case the call stack depth is at worst:

fibtest.exe!Fibonacci<40>::get_value(unsigned long n=41)  Line 18 + 0xe bytes    C++
fibtest.exe!Fibonacci<40>::get_value(unsigned long n=42)  Line 18 + 0x2c bytes    C++
fibtest.exe!Fibonacci<40>::get_value(unsigned long n=43)  Line 18 + 0x2c bytes    C++
fibtest.exe!Fibonacci<40>::get_value(unsigned long n=45)  Line 18 + 0xe bytes    C++
fibtest.exe!main()  Line 9 + 0x7 bytes    C++
fibtest.exe!__tmainCRTStartup()  Line 597 + 0x17 bytes    C

I.e. it recurses until it finds a value in the "Table". (verified by stepping through Disassembly in the debugger line by line, also by replacing the test ints by a random number <= 45)

The recursive part could also be replaced by the linear iterative solution:

static unsigned long long get_value(unsigned long n)
{
    switch (n) {
        case N:
            return value;    
        default:
            if (n < N) {
                return Fibonacci<N-1>::get_value(n);
            } else {
                // n > N
                unsigned long long i = Fibonacci<N-1>::value, j = value, t;
                for (unsigned long k = N; k < n; k++) {
                    t = i + j;
                    i = j;
                    j = t;
                }
                return j;
            }
    }
}
Leveroni answered 18/5, 2011 at 6:11 Comment(4)
Very nice, thanks for the answer. Actually handles my original question: "A template metaprogramming version of a switch statement perhaps, that generates a switch statement up to a certain number?" This is, effectively, that.Krummhorn
It looks nice, but what I personally do not like is a fact you you have second implementation of fibbonacci computation hidden in the "default" case. Would it be possible to such solution for a general case (perhaps using c++0x constexpr functions to define the shared functionality)?Doable
Another obvious way would be to define interface such that user is not allowed to call Fibonacci<N>::get_value(n) with n>N (exception thrown, assertion ...). For practical use this would often be reasonable, and the function duplicity would disappear immediately.Doable
Yes, that was my first cut, and the code ends up more compact. But I wanted to try to address the issue "If the value is outside of the table, it returns zero as opposed to calculating it" (where you replace returning zero with an exception) in the original question.Leveroni
H
4

If you have C++ compiler which supports variadic templates (C++0x standard ) you can save fibonacii sequence in a tuple at the compile time. At runtime you can access any element from that tuple by indexing.

#include <tuple>   
#include <iostream>

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

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

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

// ----------------------
template<int N, typename Tuple, typename ... Types>
struct make_fibtuple_impl;

template<int N, typename ... Types>
struct make_fibtuple_impl<N, std::tuple<Types...> >
{
    typedef typename make_fibtuple_impl<N-1, std::tuple<Fib<N>, Types... > >::type type;
};

template<typename ... Types>
struct make_fibtuple_impl<0, std::tuple<Types...> >
{
    typedef std::tuple<Fib<0>, Types... > type;
};

template<int N>
struct make_fibtuple : make_fibtuple_impl<N, std::tuple<> >
{};

int main()
{
   auto tup = typename make_fibtuple<25>::type();
   std::cout << std::get<20>(tup).value;  
   std::cout << std::endl; 

   return 0;
}
Hexyl answered 18/6, 2011 at 21:4 Comment(2)
Can "20" come from user input, as OP requires?Dicentra
@KubaWyrostek yes if user can input it in source file and feed it to compiler.Historian
F
4

With C++11: you may create a std::array and a simple getter: https://ideone.com/F0b4D3

namespace detail
{

template <std::size_t N>
struct Fibo :
    std::integral_constant<size_t, Fibo<N - 1>::value + Fibo<N - 2>::value>
{
    static_assert(Fibo<N - 1>::value + Fibo<N - 2>::value >= Fibo<N - 1>::value,
                  "overflow");
};

template <> struct Fibo<0u> : std::integral_constant<size_t, 0u> {};
template <> struct Fibo<1u> : std::integral_constant<size_t, 1u> {};

template <std::size_t ... Is>
constexpr std::size_t fibo(std::size_t n, index_sequence<Is...>)
{
    return const_cast<const std::array<std::size_t, sizeof...(Is)>&&>(
        std::array<std::size_t, sizeof...(Is)>{{Fibo<Is>::value...}})[n];
}

template <std::size_t N>
constexpr std::size_t fibo(std::size_t n)
{
    return n < N ?
        fibo(n, make_index_sequence<N>()) :
        throw std::runtime_error("out of bound");
}
} // namespace detail

constexpr std::size_t fibo(std::size_t n)
{
    // 48u is the highest
    return detail::fibo<48u>(n);
}

In C++14, you can simplify some function:

template <std::size_t ... Is>
constexpr std::size_t fibo(std::size_t n, index_sequence<Is...>)
{
    constexpr std::array<std::size_t, sizeof...(Is)> fibos{{Fibo<Is>::value...}};
    return fibos[n];
}
Form answered 20/3, 2014 at 14:35 Comment(0)
E
1

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;
}
Entice answered 1/5, 2021 at 7:48 Comment(0)
W
0

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.

Willingham answered 25/5, 2009 at 23:29 Comment(0)
A
0

You can generate the switch or a static array using preprocessor metaprogramming techniques. It is a good decision if the complexity does not exceed the limitations of that approach, and you prefer not extending your toolchain with extra steps that generate code or data.

Anaheim answered 5/5, 2013 at 2:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.