web-dev-qa-db-fra.com

Obtention des constantes de compilation à la métaprogrammation du modèle lors de l'exécution

Contexte

Considérer ce qui suit:

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

Ceci est un exemple courant et nous pouvons obtenir la valeur d’un nombre de Fibonacci en tant que constante de compilation:

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

Mais vous ne pouvez évidemment pas obtenir la valeur au moment de l'exécution:

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

Parce que fibb n'est pas une constante de compilation.

Question

Donc ma question est:

Quel est le meilleur moyen de jeter un coup d'œil dans cette table au moment de l'exécution? La solution la plus évidente (et "solution" doit être prise à la légère), consiste à avoir une déclaration de commutateur de grande taille:

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

Mais maintenant, la taille de la table est très difficile à coder et il ne serait pas facile de l’étendre pour dire 40.

Le seul que je propose avec une méthode de requête similaire est la suivante:

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

Ce qui semble bien fonctionner. Les deux seuls problèmes que je vois sont:

  • Potentiel d’appel potentiellement important, car le calcul de Fibonacci <2> nécessite de passer par TableMax jusqu’à 2, et:

  • Si la valeur est en dehors de la table, elle renvoie zéro au lieu de la calculer.

Alors, y a-t-il quelque chose qui me manque? Il semble qu'il devrait y avoir un meilleur moyen de choisir ces valeurs au moment de l'exécution.

Une version de métaprogrammation de modèle d'une instruction switch peut-être, qui génère une instruction switch jusqu'à un certain nombre?

Merci d'avance.

41
GManNickG
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';
}

Après avoir bien réfléchi au problème, j'ai proposé cette solution. Bien sûr, vous devez toujours ajouter les valeurs à un conteneur au moment de l'exécution, mais (surtout) elles ne sont pas calculées au moment de l'exécution.

Par ailleurs, il est important de ne pas définir Fibonacci<1> ci-dessus Fibonacci<0>, sinon votre compilateur obtiendra very confused lorsqu’il résout l’appel en Fibonacci<0>::add_values, car la spécialisation des modèles de Fibonacci<0> n’a pas été spécifiée.

Bien sûr, TMP a ses limites: vous avez besoin d'un maximum précalculé et l'obtention des valeurs au moment de l'exécution nécessite une récursivité (car les modèles sont définis de manière récursive).

27
rlbond

Je sais que cette question est ancienne, mais elle m'a intriguée et je devais essayer de me passer d'un conteneur dynamique rempli au moment de l'exécution:

#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

Cela semble fonctionner et, une fois compilé avec des optimisations (vous ne le savez pas, la pile d'appels ne va pas trop loin - il y a une récursion normale au runtime sur la pile du cours pour les valeurs (arguments) n> N, où N est la taille de la table utilisée dans l'instanciation du modèle. Cependant, une fois en dessous de TableSize, le code généré remplace une constante calculée lors de la compilation, ou au pire une valeur "calculée" en passant par une table de saut (compilée dans gcc avec -c -g -Wa, -adhlns = main. s et vérifié la liste), le même que je pense que votre déclaration de commutateur explicite aurait pour résultat.

Lorsqu'il est utilisé comme ceci:

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

Il n'y a aucun appel à un calcul dans le premier cas (valeur calculée au moment de la compilation), et dans le second cas, la profondeur de la pile d'appels est au pire:

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

C'est à dire. il revient jusqu'à ce qu'il trouve une valeur dans la "Table". (vérifié en procédant pas à pas dans le désassemblage, ligne par ligne dans le débogueur, également en remplaçant les entiers de test par un nombre aléatoire <= 45)

La partie récursive pourrait également être remplacée par la solution itérative linéaire:

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

Si vous avez un compilateur C++ qui prend en charge les modèles variadiques (norme C++ 0x), vous pouvez enregistrer la séquence fibonacii dans un Tuple au moment de la compilation. Au moment de l'exécution, vous pouvez accéder à n'importe quel élément de ce tuple en indexant.

#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
sigidagi

Avec C++ 11: vous pouvez créer un std::array et un 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);
}
3
Jarod42

L'un des contrats de base de C (et pour la plupart C++) est que vous ne payez pas pour ce dont vous n'avez pas besoin.

La génération automatique de tables de recherche n'est tout simplement pas quelque chose que le compilateur doit faire pour vous. Même si vous avez besoin de cette fonctionnalité, tout le monde n’en a pas forcément besoin.

Si vous voulez une table de correspondance, écrivez un programme pour en créer une. Ensuite, utilisez ces données dans votre programme.

N'utilisez pas de métaprogramme de modèle si vous souhaitez que les valeurs soient calculées au moment de l'exécution, utilisez simplement un programme standard pour calculer les valeurs.

0
James Caccese

Vous pouvez générer le commutateur ou un tableau statique à l'aide de techniques de métaprogrammation du préprocesseur. C'est une bonne décision si la complexité ne dépasse pas les limites de cette approche et si vous préférez ne pas étendre votre chaîne d'outils avec des étapes supplémentaires générant du code ou des données.

0
jmihalicza