Подтвердить что ты не робот

Получение метапрограммирования шаблонов во время выполнения во время выполнения

Фон

Рассмотрим следующее:

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

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

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

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

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

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

Спасибо заранее.

4b9b3361

Ответ 1

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 имеет свои ограничения: вам нужен предварительно вычисляемый максимум, и для получения значений во время выполнения требуется рекурсия (поскольку шаблоны определены рекурсивно).

Ответ 2

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

#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

т.е. он повторяется, пока не найдет значение в "Таблице". (проверяется путем перехода по разборке в отладчике по строкам, также путем замены тестовых ints на случайное число <= 45)

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

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

Ответ 3

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

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

Ответ 4

С С++ 11: вы можете создать std::array и простой 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);
}

Ответ 5

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

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

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

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

Ответ 6

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

Ответ 7

Это должно сделать трюк...

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

Ответ 8

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

Разве это не решает вашу проблему? Я имею в виду, что это, в конце концов, последовательность фибоначчи. Конечно, вы не можете его перечислить. Цель этого "упражнения" (пусть и глупого:)) должна заключаться в том, чтобы указать, что вы можете делать с шаблонами, и в этом случае применить его с помощью рекурсивного алгоритма для генерации чисел фибоначчи. Или я что-то упускаю?