Получение шаблонных метапрограммирующих констант времени компиляции во время выполнения - PullRequest
42 голосов
/ 26 мая 2009

Фон

Обратите внимание на следующее:

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

Это общий пример, и мы можем получить значение числа Фибоначчи как константу времени компиляции:

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

Но вы, очевидно, не можете получить значение во время выполнения:

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

Поскольку fibb не является константой времени компиляции.

Вопрос

Итак, мой вопрос:

Каков наилучший способ заглянуть в эту таблицу во время выполнения? Наиболее очевидное решение (и «решение» должно быть принято слегка), это иметь большой оператор switch:

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

Но теперь размер таблицы очень жестко запрограммирован, и было бы нелегко его расширить, скажем, 40.

Единственное, что я придумал, имеет похожий метод запроса:

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

Что, кажется, отлично работает. Я вижу только две проблемы:

  • Потенциально большой стек вызовов, поскольку для вычисления Фибоначчи <2> требуется пройти через TableMax до 2, и:

  • Если значение находится за пределами таблицы, оно возвращает ноль, а не вычисляет его.

Так что-то мне не хватает? Кажется, должен быть лучший способ выбрать эти значения во время выполнения.

Возможно, шаблонная метапрограммирующая версия оператора switch, которая генерирует оператор switch до определенного числа?

Заранее спасибо.

Ответы [ 7 ]

27 голосов
/ 26 мая 2009
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';
}

После долгих раздумий над проблемой я пришел к этому решению. Конечно, вам все равно нужно добавить значения в контейнер во время выполнения, но (что важно) они не вычисляются во время выполнения.

В качестве примечания важно не определять Fibonacci<1> выше Fibonacci<0>, иначе ваш компилятор будет очень сбит с толку, когда он разрешит вызов Fibonacci<0>::add_values, поскольку Fibonacci<0> специализация шаблона не указана.

Конечно, у TMP есть свои ограничения: вам нужен предварительно вычисленный максимум, а получение значений во время выполнения требует рекурсии (поскольку шаблоны определяются рекурсивно).

17 голосов
/ 18 мая 2011

Я знаю, что этот вопрос старый, но он заинтриговал меня, и мне пришлось попытаться обойтись без динамического контейнера, заполненного во время выполнения:

#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)
    {
        return value;
    }
};

#endif

Похоже, это работает, и при компиляции с оптимизацией (не уверен, если вы собираетесь это разрешить), стек вызовов не доходит до глубины - в стеке есть нормальная рекурсия времени выполнения для значений (аргументов) n > N, где N - это TableSize, используемый в создании экземпляра шаблона. Однако, как только вы перейдете ниже TableSize, сгенерированный код заменяет константу, вычисленную во время компиляции, или, в худшем случае, значение, «вычисленное» путем сброса таблицы переходов (скомпилированной в gcc с -c -g -Wa, -adhlns = main. s и проверил список), так же, как я полагаю, ваш явный оператор switch приведет к.

При использовании так:

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

В первом случае вообще нет вызова для вычислений (значение вычисляется во время компиляции), а во втором случае глубина стека вызовов наихудшая:

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

т.е. он повторяется до тех пор, пока не найдет значение в «Таблице». (проверяется пошагово через Disassembly в отладчике, а также путем замены тестовых значений случайным числом <= 45) </p>

Рекурсивную часть также можно заменить линейным итерационным решением:

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;
            }
    }
}
4 голосов
/ 19 июня 2011

Если у вас есть компилятор C ++, который поддерживает шаблоны переменных (стандарт C ++ 0x), вы можете сохранить последовательность fibonacii в кортеже во время компиляции. Во время выполнения вы можете получить доступ к любому элементу из этого кортежа путем индексации.

#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;
}
3 голосов
/ 20 марта 2014

С C ++ 11: вы можете создать std::array и простой метод получения: 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);
}
0 голосов
/ 18 ноября 2014

Это должно сработать ...

template <int N> class EXPAND {
public:
        static const string value;
};
template <> class EXPAND<0> {
public:
        static const string value;
};

template <int N> const string EXPAND<N>::value = EXPAND<N-1>::value+"t";
const string EXPAND<0>::value = "t";

int main() {
        cout << EXPAND<5>::value << endl;
}
0 голосов
/ 05 мая 2013

Вы можете сгенерировать коммутатор или статический массив, используя методы метапрограммирования препроцессора. Это хорошее решение, если сложность не превышает ограничений этого подхода, и вы предпочитаете не расширять свою цепочку инструментов дополнительными шагами, которые генерируют код или данные.

0 голосов
/ 26 мая 2009

Один из основных принципов C (и по большей части C ++) заключается в том, что вы не платите за то, что вам не нужно.

Автоматическая генерация справочных таблиц - это не то, что компилятор должен сделать для вас. Даже если вам нужна эта функциональность, не всем остальным это необходимо.

Если вы хотите таблицу поиска, напишите программу для ее создания. Затем используйте эти данные в вашей программе.

Не используйте шаблонную метапрограмму, если вы хотите, чтобы значения рассчитывались во время выполнения, просто используйте обычную программу для вычисления значений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...