web-dev-qa-db-fra.com

Comment écrire une fonction récursive de modèle variadique?

J'essaie d'écrire un modèle variadique constexpr fonction qui calcule la somme des paramètres du modèle donnés. Voici mon code:

template<int First, int... Rest>
constexpr int f()
{
    return First + f<Rest...>();
}

template<int First>
constexpr int f()
{
    return First;
}

int main()
{
    f<1, 2, 3>();
    return 0;
}

Malheureusement, il ne compile pas le rapport d'un message d'erreur error C2668: 'f': ambiguous call to overloaded function Lors de la tentative de résolution de l'appel f<3,>().

J'ai également essayé de changer mon cas de base de récursivité pour accepter 0 arguments de modèle au lieu de 1:

template<>
constexpr int f()
{
    return 0;
}

Mais ce code ne compile pas non plus (message error C2912: explicit specialization 'int f(void)' is not a specialization of a function template).

Je pourrais extraire les premier et deuxième arguments de modèle pour que cette compilation et ce travail fonctionnent, comme ceci:

template<int First, int Second, int... Rest>
constexpr int f()
{
    return First + f<Second, Rest...>();
}

Mais cela ne semble pas être la meilleure option. Alors, la question est: comment écrire ce calcul de manière élégante?

UP : J'ai aussi essayé d'écrire ceci comme une seule fonction:

template<int First, int... Rest>
constexpr int f()
{
    return sizeof...(Rest) == 0 ? First : (First + f<Rest...>());
}

Et cela ne fonctionne pas non plus: error C2672: 'f': no matching overloaded function found.

17
alexeykuzmin0

Votre scénario de base était incorrect. Vous avez besoin d'un cas pour la liste vide, mais comme le compilateur le suggère, votre deuxième essai n'était pas une spécialisation de modèle valide. Une façon de définir une instanciation valide pour zéro argument est de créer une surcharge qui accepte une liste vide

template<class none = void>
constexpr int f()
{
    return 0;
}
template<int First, int... Rest>
constexpr int f()
{
    return First + f<Rest...>();
}
int main()
{
    f<1, 2, 3>();
    return 0;
}

EDIT: par souci d'exhaustivité aussi ma première réponse, que @ alexeykuzmin0 corrigé en ajoutant le conditionnel:

template<int First=0, int... Rest>
constexpr int f()
{
    return sizeof...(Rest)==0 ? First : First + f<Rest...>();
}
19
example
template<int First, int... Rest>
constexpr int f()
{
    return First + f<Rest...>();
}

template<int First>
constexpr int f()
{
    return First;
}

int main()
{
    f<1, 2, 3>();
    return 0;
}

Vous obtenez cette erreur:

error C2668: 'f': ambiguous call to overloaded function while trying to resolve f<3,>() call.

Cela est dû au fait qu'un paquet de paramètres variadic peut recevoir 0 arguments, donc f<3> pourrait fonctionner avec template<int First, int... Rest> en "développant" en template<3, >. Cependant, vous avez également la spécialisation de template<int First>, donc le compilateur ne sait pas lequel choisir.

Énoncer explicitement les premier et deuxième arguments de modèle est une solution complètement valide et bonne à ce problème.


Lorsque vous essayez de modifier le cas de base en:

template <>
constexpr int f()
{
    return 0;
}

Vous avez un problème car les fonctions ne peuvent pas être spécialisées de cette manière. Les classes et les structures peuvent être, mais pas des fonctions.


Solution n ° 1: expression C++ 17 fois avec constexpr

template <typename... Is>
constexpr int sum(Is... values) {
    return (0 + ... + values);
}

Solution n ° 2: utilisez une fonction constexpr

constexpr int sum() {
    return 0;
}

template <typename I, typename... Is>
constexpr int sum(I first, Is... rest) {
    return first + sum(rest...);
}

Solution n ° 3: utiliser la métaprogrammation de modèle

template <int... Is>
struct sum;

template <>
struct sum<>
    : std::integral_constant<int, 0>
{};

template <int First, int... Rest>
struct sum<First, Rest...>
    : std::integral_constant<int,
        First + sum_impl<Rest...>::value>
{};
11
Justin

Je trouve généralement plus facile de déplacer le code des arguments de modèle vers les arguments de fonction:

constexpr int sum() { return 0; }

template <class T, class... Ts>
constexpr int sum(T value, Ts... rest) {
    return value + sum(rest...);
}

Si vous les voulez vraiment comme arguments de modèle, vous pouvez demander à votre f d'appeler simplement sum en les déplaçant vers le bas:

template <int... Is>
constexpr int f() {
    return sum(Is...);
}

C'est constexpr, donc juste utiliser ints est très bien.

10
Barry

Résumez-le simplement de la manière habituelle.

template<int... Args>
constexpr int f() {
   int sum = 0;
   for(int i : { Args... }) sum += i;
   return sum;
}
7
T.C.

Une solution plus générique utilisant std::initializer_list serait:

template <typename... V>                                                                                                                                                                                         
auto sum_all(V... v)
{
  using rettype = typename std::common_type_t<V...>;
  rettype result{};
  (void)std::initializer_list<int>{(result += v, 0)...};
  return result;
}
3
Patryk

C'est assez similaire à ce que @ T.C a suggéré ci-dessus:

#include<iostream>
#include<numeric>

template<int First, int... Rest>
constexpr int f()
{
    auto types = {Rest...};
    return std::accumulate(std::begin(types), std::end(types),0);   
}

int main()
{
    std::cout <<f<1, 2, 3>();
    return 0;
}
1
Steephen