У меня есть шаблонный класс, который рекурсивно строит массив Фибоначчи, используя рекурсию шаблона следующим образом:

#include <iostream>
#include "C++ Helpers/static_check.hpp"
using namespace std;

template <typename T>
struct valid_type : std::integral_constant<bool, std::is_integral<T>::value  > {};

template <size_t N, typename = void>
struct fibonacci {
    static const int value = fibonacci<N - 1>::value + fibonacci<N - 2>::value;
};
template <size_t N>
struct fibonacci <N, typename std::enable_if<(N < 2)>::type > {
    static const int value = 1;
};
template <size_t N, typename T = int>
struct fibonacci_array {
    static_assert(valid_type<T>::value, "invalid storage type for fibonacci sequence");
// the base case specialization should has all general case code except for the next line
    fibonacci_array<N - 1, T> generate_sequence_here;
    int value;
    fibonacci_array() : value(fibonacci<N - 1>::value) {}
    int operator [] (size_t pos) {
        return *((int*)this + pos);
    }
};

template <typename T>
struct fibonacci_array<1, T> {
    static_assert(valid_type<T>::value, "invalid storage type for fibonacci sequence");
    int value;
    fibonacci_array() : value(fibonacci<0>::value) {}
    int operator [] (size_t pos) {
        return *((int*)this + pos);
    }
};

int main () {
    const size_t n = 10;
    fibonacci_array<n, int> arr;
    for(size_t i = 0; i < n; ++i)
        cout << arr[i] << endl;
    return 0;
}

Что я хочу сделать, это устранить избыточность кода в специализации базового случая (когда N == 1) Примечание: если бы я разделил членов класса на частные и публичные и использовал прямое наследование, это было бы неэффективно, потому что частные члены фактически наследуются, но без доступа к ним. Я думаю о создании базового класса, чтобы наследовать от него общий шаблонный класс и специализацию, но я не знаю точного синтаксиса для этого, и если есть более хороший способ, он будет лучше. Спасибо!

0
Abdelrahman Ramadan 27 Май 2017 в 19:36

2 ответа

Лучший ответ

Как насчет завершения вашей рекурсии в N=0? В этом случае вы можете выбросить все тело fibonacci_array<1, T> и заменить его следующим маленьким парнем:

template <typename T>
struct fibonacci_array<0, T> 
{
};

Одна ловушка заключается в том, что эта специализация будет пустой, но когда вы агрегируете ее в основной класс как generate_sequence_here, она будет занимать 1 байт пространства, поскольку в C ++ каждый объект должен иметь уникальный адрес. Это израсходует на вас 1 байт (и потребует обновления вашего operator[]). Не беспокойтесь, это может быть легко решено, если вы измените агрегацию fibonacci_array<N - 1, T> на наследование от нее. Это работает благодаря функции под названием «пустая оптимизация базового класса».

Также, если вы можете использовать C ++ 14, вместо этого предпочитайте конструкторы constexpr для этой задачи, код будет намного чище:

template <int N, typename T = int>
struct fibonacci_array 
{
    int values[N];
    constexpr fibonacci_array() : values()
    {
        if (N > 1)
            values[1] = 1;
        for (int i = 2; i < N; ++i)
            values[i] = values[i - 1] + values[i - 2];
    }
    constexpr int operator [] (size_t pos) const
    {
        return values[pos];
    }
};

См. демонстрационную версию.

Также посмотрите, как компилятор смог вычислить его во время компиляции: https://godbolt.org/g/kJEFvN

3
Mikhail 27 Май 2017 в 17:37

Извините ... Я не стандартный эксперт ... но я нахожу ваш режим для доступа к значениям

    return *((int*)this + pos);

Немного опасно

Я предлагаю избежать рекурсии класса, использовать простую функцию constexpr для вычисления значений

template <typename T>
constexpr T fibonacci (T const & val)
 { return val < T{2} ? T{1} : (fibonacci(val-T{1})+fibonacci(val-T{2})); }

И, используя делегирующий конструктор, инициализируем std::array в простом классе

template <size_t N, typename T = std::size_t>
struct fibonacci_array
 {
   static_assert(std::is_integral<T>::value,
                 "invalid storage type for fibonacci sequence");

   std::array<T, N> values;

   template <T ... Is>
   constexpr fibonacci_array (std::integer_sequence<T, Is...> const &)
      : values{ { fibonacci(Is)... } }
    { }

   constexpr fibonacci_array ()
      : fibonacci_array{std::make_integer_sequence<T, N>{}}
    { }

   T operator [] (size_t pos) const
    { return values[pos]; }

   T & operator [] (size_t pos)
    { return values[pos]; }
 };

Это решение использует std::integer_sequence и std::make_integer_sequence, как и решение C ++ 14; но если вам нужно решение C ++ 11, заменить его на самом деле не сложно.

Полный рабочий пример

#include <array>
#include <utility>
#include <iostream>
#include <type_traits>

template <typename T>
constexpr T fibonacci (T const & val)
 { return val < T{2} ? T{1} : (fibonacci(val-T{1})+fibonacci(val-T{2})); }

template <std::size_t N, typename T = std::size_t>
struct fibonacci_array
 {
   static_assert(std::is_integral<T>::value,
                 "invalid storage type for fibonacci sequence");

   std::array<T, N> values;

   template <T ... Is>
   constexpr fibonacci_array (std::integer_sequence<T, Is...> const &)
      : values{ { fibonacci(Is)... } }
    { }

   constexpr fibonacci_array ()
      : fibonacci_array{std::make_integer_sequence<T, N>{}}
    { }

   T operator [] (std::size_t pos) const
    { return values[pos]; }

   T & operator [] (std::size_t pos)
    { return values[pos]; }
 };

int main ()
 {
   constexpr std::size_t n { 10U };

   fibonacci_array<n, int> arr;

   for (auto i = 0U; i < n ; ++i )
      std::cout << arr[i] << std::endl;
 }

- РЕДАКТИРОВАТЬ -

Как указал Михаил, моя функция fibonacci() может вычисляться с экспоненциальной сложностью.

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

#include <array>
#include <utility>
#include <iostream>
#include <type_traits>

template <typename T, T N, bool = (N < T{2})>
struct fibonacci;

template <typename T, T N>
struct fibonacci<T, N, false>
   : std::integral_constant<T,   fibonacci<T, N-1U>::value
                               + fibonacci<T, N-2U>::value>
 { };

template <typename T, T N>
struct fibonacci<T, N, true>
   : std::integral_constant<T, 1U>
 { };

template <std::size_t N, typename T = std::size_t>
struct fibonacci_array
 {
   static_assert(std::is_integral<T>::value,
                 "invalid storage type for fibonacci sequence");

   std::array<T, N> values;

   template <T ... Is>
   constexpr fibonacci_array (std::integer_sequence<T, Is...> const &)
      : values{ { fibonacci<T, Is>::value... } }
    { }

   constexpr fibonacci_array ()
      : fibonacci_array{std::make_integer_sequence<T, N>{}}
    { }

   T operator [] (std::size_t pos) const
    { return values[pos]; }

   T & operator [] (std::size_t pos)
    { return values[pos]; }
 };

int main ()
 {
   constexpr std::size_t n { 10U };

   constexpr fibonacci_array<n, int> const arr;

   for (auto i = 0U; i < n ; ++i )
      std::cout << arr[i] << std::endl;
 }
1
max66 27 Май 2017 в 21:00