Comment avoir une variable const dans une boucle for pour la génération de classes modèles?

15

J'ai un code comme

template <size_t N>
class A
{
    template <size_t N>
    someFunctions() {};
};

Maintenant, je veux créer des instances de la classe et appeler les fonctions dans une boucle for pour un ensemble de nombreuses valeurs comme

// in main()

int main()
{
    for (int i = 1; i <= 100; i++)
    {
        const int N = i;  // dont know how to do this
        A<N> a;
        a.functionCalls();
    }
}

Comment faire ça? En espérant une méthode pour le faire.

nachiappan venkatesh
la source
Pour être utilisé comme paramètre de modèle, il Ndoit l'être, constexprs'il s'agit d'une variable de boucle, ce qui n'est pas le cas
CoryKramer
Vous ne pouvez pas, A doit-il vraiment être un modèle?
Alan Birtles
Oui, il y a un besoin pour la classe A d'être un modèle pour certaines raisons et c'est un modèle de quelque chose, donc ce doit être une classe de modèle
nachiappan venkatesh

Réponses:

11

Cela nécessiterait quelque chose appelé a template forqui est la forme attendue des instructions d'expansion , qui ressemble à une boucle for mais qui est en réalité un modèle de bloc dans une fonction qui est instanciée plusieurs fois.

Bien sûr, il existe une solution de contournement. Nous pouvons abuser de lambdas génériques pour déclarer une sorte de bloc de modèle local et l'instancier nous-mêmes:

template <typename T, T... S, typename F>
constexpr void for_sequence(std::integer_sequence<T, S...>, F f) {
    (static_cast<void>(f(std::integral_constant<T, S>{})), ...);
}

Cette fonction prend une séquence entière et instancie le lambda Fautant de fois que la longueur de la séquence.

Il est utilisé comme ceci:

for_sequence(std::make_index_sequence<100>(), [](auto N) { /* N is from 0 to 99 */
  A<N + 1> a; /* N + 1 is from 1 to 100 */
  a.functionCalls();
});

Ici, Npeut être envoyé en tant que paramètre de modèle car c'est un objet qui a un opérateur de conversion constexpr en un type entier. Plus précisément, c'est un std::integral_constantavec une valeur croissante.

Exemple en direct

Guillaume Racicot
la source
3
Pouah. Quand je vois un modèle amusant comme ça, je sais juste que je vais devoir le déboguer plus tard sans pile d'appels et deviner ce qui se passe ... :)
Michael Dorgan
Quel est le but de static_cast<void>?
Ayxan
2
@Ayxan évite les problèmes lorsque la lambda fretourne un type qui surcharge l'opérateur virgule
Guillaume Racicot
@MichaelDorgan C'est pourquoi nous avons besoin template for. Abuser des constructions linguistiques comme celle-ci est toujours plus douloureux
Guillaume Racicot
@GuillaumeRacicot ou nous avons besoin de meilleures abstractions que les modèles pour la méta-programmation.
Ajay Brahmakshatriya
5

Il Ndoit être constant au moment de la compilation, ce qui est forimpossible avec une boucle normale .

Mais, il existe de nombreuses solutions de contournement. Par exemple, inspiré par cette publication SO , vous pouvez faire quelque chose comme ceci. ( Voir une démo en direct )

template<size_t N>
class A
{
public:
    // make the member function public so that you can call with its instance
    void someFunctions()
    {
        std::cout << N << "\n";
    };
};

template<int N> struct AGenerator
{
    static void generate()
    {
        AGenerator<N - 1>::generate();
        A<N> a;
        a.someFunctions();
    }
};

template<> struct AGenerator<1>
{
    static void generate()
    {
        A<1> a;
        a.someFunctions();
    }
};

int main()
{
    // call the static member for constructing 100 A objects
    AGenerator<100>::generate();
}

Imprime 1à100


En , ce qui précède peut être réduit à une seule AGeneratorclasse de modèle (c'est-à-dire que la spécialisation peut être évitée), en utilisant if constexpr. ( Voir une démo en direct )

template<std::size_t N>
struct AGenerator final
{
    static constexpr void generate() noexcept
    {
        if constexpr (N == 1)
        {
            A<N> a;
            a.someFunctions();
            // .. do something more with `a`
        }
        else
        {
            AGenerator<N - 1>::generate();
            A<N> a;
            a.someFunctions();
            // .. do something more with `a`
        }
    }
};

Sortie :

1
2
3
4
5
6
7
8
9
10

En cas de fournir la plage d'itération, vous pouvez utiliser ce qui suit. ( Voir une démo en direct )

template<std::size_t MAX, std::size_t MIN = 1> // `MIN` is set to 1 by default
struct AGenerator final
{
    static constexpr void generate() noexcept
    {
        if constexpr (MIN == 1)
        {
            A<MIN> a;
            a.someFunctions();
            // .. do something more with `a`
            AGenerator<MAX, MIN + 1>::generate();
        }
        else if constexpr (MIN != 1 && MIN <= MAX)
        {
            A<MIN> a;
            a.someFunctions();
            // .. do something more with `a`
            AGenerator<MAX, MIN + 1>::generate();
        }
    }
};

int main()
{
    // provide the `MAX` count of looping. `MIN` is set to 1 by default
    AGenerator<10>::generate();
}

Produit la même chose que la version ci-dessus.

JeJo
la source
4

À partir de C ++ 20, vous pouvez utiliser des modèles lambdas, vous pouvez donc essayer quelque chose comme suit

[]<int ... Is>(std::integer_sequence<int, Is...>)
 { (A<Is+1>{}.functionCall(), ...); }
   (std::make_integer_sequence<int, 100>{});

Voici un exemple complet de compilation qui imprime tous les nombres de 0 à 99

#include <utility>
#include <iostream>

int main()
 {
  []<int ... Is>(std::integer_sequence<int, Is...>)
   { (std::cout << Is << std::endl, ...); }
     (std::make_integer_sequence<int, 100>{});
 }
max66
la source
1

Une façon de le faire est d'utiliser la méta-programmation de modèle avec quelque chose comme ceci:

#include <iostream>

template <std::size_t N>
struct A {
  void foo() { std::cout << N << '\n'; }
};

template <std::size_t from, std::size_t to>
struct call_foo {
  void operator()() {
    if constexpr (from != to) {
      A<from + 1>{}.foo();
      call_foo<from + 1, to>{}();
    }
  }
};

int main() { call_foo<0, 100>{}(); }
Ayxan
la source
0

Juste pour être complet - est-il vraiment nécessaire que la classe ou la fonction soit modélisée, si le seul usage de la fonction doit être appelé depuis la boucle?

Si c'est le cas et que vous ne voulez pas écrire à la main, regardez boost.hana.

CapSel
la source