简体   繁体   中英

what is the standard way to eliminate code redundancy with template class specialization?

I have a template class which recursively build fibonacci array using template recursion like this:

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

what i want to do is to eliminate code redundancy in the base case specialization (when N == 1 ) Note: if i partitioned the class members into private and public ones and used direct inheritance, it wouldn't be efficient because the private members are actually get inherited but without access to them. i'm thinking of creating a base class to make the general template class and the specialization inherit from it, but i don't know the exact syntax for that and if there's nicer way it would be better. thanks!

How about terminating your recursion at N=0 ? In this case you can throw away the whole body for fibonacci_array<1, T> and replace with this little guy:

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

One pitfall is that this specialization will be empty, but when you aggregate it to the main class as generate_sequence_here , it will consume 1 byte of space, since in C++ every object shall have a unique address. This will waste you 1 byte of space (and will require to update your operator[] ). Don't worry though, it can be easily solved, if you change aggregation of fibonacci_array<N - 1, T> to inheritance from it. This works thanks to the feature called "empty base class optimization".

Also, if you can use C++14, prefer constexpr constructors for this task instead, the code will be much cleaner:

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

See the demo .

Also see how compiler was able to compute it on compile time: https://godbolt.org/g/kJEFvN

Sorry... I'm not a standard expert... but I find your mode to access to the values

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

a little dangerous.

I suggest to avoid the class recursion, to use a simple constexpr function to calculate the values

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

and, using a delegating constructor, initialize a std::array in a simple class

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

This solution use std::integer_sequence and std::make_integer_sequence , so is a C++14 solution; but if you need a C++11 solution, substitute they isn't really difficult.

The full working example

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

-- EDIT --

As pointed by Mikhail, my fibonacci() function could compute with exponenzial complexity.

With the same exponential algorithm, but using static constexpr values in structs, so using memoization (I hope), the following code should avoid exponential complexity.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM